diff options
Diffstat (limited to 't')
335 files changed, 9213 insertions, 2334 deletions
diff --git a/t/Makefile b/t/Makefile index 7f56e52f76..882782a519 100644 --- a/t/Makefile +++ b/t/Makefile @@ -36,14 +36,21 @@ CHAINLINTTMP_SQ = $(subst ','\'',$(CHAINLINTTMP)) T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh))) +TLIBS = $(sort $(wildcard lib-*.sh)) annotate-tests.sh TPERF = $(sort $(wildcard perf/p[0-9][0-9][0-9][0-9]-*.sh)) +TINTEROP = $(sort $(wildcard interop/i[0-9][0-9][0-9][0-9]-*.sh)) CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.test))) -CHAINLINT = sed -f chainlint.sed +CHAINLINT = '$(PERL_PATH_SQ)' chainlint.pl + +# `test-chainlint` (which is a dependency of `test-lint`, `test` and `prove`) +# checks all tests in all scripts via a single invocation, so tell individual +# scripts not to "chainlint" themselves +CHAINLINTSUPPRESS = GIT_TEST_CHAIN_LINT=0 && export GIT_TEST_CHAIN_LINT && all: $(DEFAULT_TEST_TARGET) test: pre-clean check-chainlint $(TEST_LINT) - $(MAKE) aggregate-results-and-cleanup + $(CHAINLINTSUPPRESS) $(MAKE) aggregate-results-and-cleanup failed: @failed=$$(cd '$(TEST_RESULTS_DIRECTORY_SQ)' && \ @@ -52,7 +59,7 @@ failed: test -z "$$failed" || $(MAKE) $$failed prove: pre-clean check-chainlint $(TEST_LINT) - @echo "*** prove ***"; $(PROVE) --exec '$(TEST_SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) + @echo "*** prove ***"; $(CHAINLINTSUPPRESS) $(PROVE) --exec '$(TEST_SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) $(MAKE) clean-except-prove-cache $(T): @@ -62,10 +69,11 @@ pre-clean: $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' clean-except-prove-cache: clean-chainlint - $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)' + $(RM) -r 'trash directory'.* $(RM) -r valgrind/bin clean: clean-except-prove-cache + $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' $(RM) .prove clean-chainlint: @@ -73,13 +81,35 @@ clean-chainlint: check-chainlint: @mkdir -p '$(CHAINLINTTMP_SQ)' && \ - sed -e '/^# LINT: /d' $(patsubst %,chainlint/%.test,$(CHAINLINTTESTS)) >'$(CHAINLINTTMP_SQ)'/tests && \ - sed -e '/^[ ]*$$/d' $(patsubst %,chainlint/%.expect,$(CHAINLINTTESTS)) >'$(CHAINLINTTMP_SQ)'/expect && \ - $(CHAINLINT) '$(CHAINLINTTMP_SQ)'/tests | grep -v '^[ ]*$$' >'$(CHAINLINTTMP_SQ)'/actual && \ - diff -u '$(CHAINLINTTMP_SQ)'/expect '$(CHAINLINTTMP_SQ)'/actual + for i in $(CHAINLINTTESTS); do \ + echo "test_expect_success '$$i' '" && \ + sed -e '/^# LINT: /d' chainlint/$$i.test && \ + echo "'"; \ + done >'$(CHAINLINTTMP_SQ)'/tests && \ + { \ + echo "# chainlint: $(CHAINLINTTMP_SQ)/tests" && \ + for i in $(CHAINLINTTESTS); do \ + echo "# chainlint: $$i" && \ + sed -e '/^[ ]*$$/d' chainlint/$$i.expect; \ + done \ + } >'$(CHAINLINTTMP_SQ)'/expect && \ + $(CHAINLINT) --emit-all '$(CHAINLINTTMP_SQ)'/tests | \ + grep -v '^[ ]*$$' >'$(CHAINLINTTMP_SQ)'/actual && \ + if test -f ../GIT-BUILD-OPTIONS; then \ + . ../GIT-BUILD-OPTIONS; \ + fi && \ + if test -x ../git$$X; then \ + DIFFW="../git$$X --no-pager diff -w --no-index"; \ + else \ + DIFFW="diff -w -u"; \ + fi && \ + $$DIFFW '$(CHAINLINTTMP_SQ)'/expect '$(CHAINLINTTMP_SQ)'/actual test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax \ test-lint-filenames +ifneq ($(GIT_TEST_CHAIN_LINT),0) +test-lint: test-chainlint +endif test-lint-duplicates: @dups=`echo $(T) $(TPERF) | tr ' ' '\n' | sed 's/-.*//' | sort | uniq -d` && \ @@ -102,6 +132,9 @@ test-lint-filenames: test -z "$$bad" || { \ echo >&2 "non-portable file name(s): $$bad"; exit 1; } +test-chainlint: + @$(CHAINLINT) $(T) $(TLIBS) $(TPERF) $(TINTEROP) + aggregate-results-and-cleanup: $(T) $(MAKE) aggregate-results $(MAKE) clean @@ -117,4 +150,5 @@ valgrind: perf: $(MAKE) -C perf/ all -.PHONY: pre-clean $(T) aggregate-results clean valgrind perf check-chainlint clean-chainlint +.PHONY: pre-clean $(T) aggregate-results clean valgrind perf \ + check-chainlint clean-chainlint test-chainlint @@ -196,11 +196,6 @@ appropriately before running "make". Short options can be bundled, i.e. this feature by setting the GIT_TEST_CHAIN_LINT environment variable to "1" or "0", respectively. - A few test scripts disable some of the more advanced - chain-linting detection in the name of efficiency. You can - override this by setting the GIT_TEST_CHAIN_LINT_HARDER - environment variable to "1". - --stress:: Run the test script repeatedly in multiple parallel jobs until one of them fails. Useful for reproducing rare failures in @@ -366,12 +361,47 @@ excluded as so much relies on it, but this might change in the future. GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole test suite. Accept any boolean values that are accepted by git-config. -GIT_TEST_PASSING_SANITIZE_LEAK=<boolean> when compiled with -SANITIZE=leak will run only those tests that have whitelisted -themselves as passing with no memory leaks. Tests can be whitelisted -by setting "TEST_PASSES_SANITIZE_LEAK=true" before sourcing -"test-lib.sh" itself at the top of the test script. This test mode is -used by the "linux-leaks" CI target. +GIT_TEST_PASSING_SANITIZE_LEAK=true skips those tests that haven't +declared themselves as leak-free by setting +"TEST_PASSES_SANITIZE_LEAK=true" before sourcing "test-lib.sh". This +test mode is used by the "linux-leaks" CI target. + +GIT_TEST_PASSING_SANITIZE_LEAK=check checks that our +"TEST_PASSES_SANITIZE_LEAK=true" markings are current. Rather than +skipping those tests that haven't set "TEST_PASSES_SANITIZE_LEAK=true" +before sourcing "test-lib.sh" this mode runs them with +"--invert-exit-code". This is used to check that there's a one-to-one +mapping between "TEST_PASSES_SANITIZE_LEAK=true" and those tests that +pass under "SANITIZE=leak". This is especially useful when testing a +series that fixes various memory leaks with "git rebase -x". + +GIT_TEST_SANITIZE_LEAK_LOG=true will log memory leaks to +"test-results/$TEST_NAME.leak/trace.*" files. The logs include a +"dedup_token" (see +"ASAN_OPTIONS=help=1 ./git") and other options to +make logs +machine-readable. + +With GIT_TEST_SANITIZE_LEAK_LOG=true we'll look at the leak logs +before exiting and exit on failure if the logs showed that we had a +memory leak, even if the test itself would have otherwise passed. This +allows us to catch e.g. missing &&-chaining. This is especially useful +when combined with "GIT_TEST_PASSING_SANITIZE_LEAK", see below. + +GIT_TEST_PASSING_SANITIZE_LEAK=check when combined with "--immediate" +will run to completion faster, and result in the same failing +tests. The only practical reason to run +GIT_TEST_PASSING_SANITIZE_LEAK=check without "--immediate" is to +combine it with "GIT_TEST_SANITIZE_LEAK_LOG=true". If we stop at the +first failing test case our leak logs won't show subsequent leaks we +might have run into. + +GIT_TEST_PASSING_SANITIZE_LEAK=(true|check) will not catch all memory +leaks unless combined with GIT_TEST_SANITIZE_LEAK_LOG=true. Some tests +run "git" (or "test-tool" etc.) without properly checking the exit +code, or git will invoke itself and fail to ferry the abort() exit +code to the original caller. When the two modes are combined we'll +look at the "test-results/$TEST_NAME.leak/trace.*" files at the end of +the test run to see if had memory leaks which the test itself didn't +catch. GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version' default to n. @@ -935,32 +965,6 @@ see test-lib-functions.sh for the full list and their options. test_done fi - - test_external [<prereq>] <message> <external> <script> - - Execute a <script> with an <external> interpreter (like perl). This - was added for tests like t9700-perl-git.sh which do most of their - work in an external test script. - - test_external \ - 'GitwebCache::*FileCache*' \ - perl "$TEST_DIRECTORY"/t9503/test_cache_interface.pl - - If the test is outputting its own TAP you should set the - test_external_has_tap variable somewhere before calling the first - test_external* function. See t9700-perl-git.sh for an example. - - # The external test will outputs its own plan - test_external_has_tap=1 - - - test_external_without_stderr [<prereq>] <message> <external> <script> - - Like test_external but fail if there's any output on stderr, - instead of checking the exit code. - - test_external_without_stderr \ - 'Perl API' \ - perl "$TEST_DIRECTORY"/t9700/test.pl - - test_expect_code <exit-code> <command> Run a command and ensure that it exits with the given exit code. diff --git a/t/chainlint.pl b/t/chainlint.pl new file mode 100755 index 0000000000..976db4b8a0 --- /dev/null +++ b/t/chainlint.pl @@ -0,0 +1,770 @@ +#!/usr/bin/env perl +# +# Copyright (c) 2021-2022 Eric Sunshine <sunshine@sunshineco.com> +# +# This tool scans shell scripts for test definitions and checks those tests for +# problems, such as broken &&-chains, which might hide bugs in the tests +# themselves or in behaviors being exercised by the tests. +# +# Input arguments are pathnames of shell scripts containing test definitions, +# or globs referencing a collection of scripts. For each problem discovered, +# the pathname of the script containing the test is printed along with the test +# name and the test body with a `?!FOO?!` annotation at the location of each +# detected problem, where "FOO" is a tag such as "AMP" which indicates a broken +# &&-chain. Returns zero if no problems are discovered, otherwise non-zero. + +use warnings; +use strict; +use Config; +use File::Glob; +use Getopt::Long; + +my $jobs = -1; +my $show_stats; +my $emit_all; + +# Lexer tokenizes POSIX shell scripts. It is roughly modeled after section 2.3 +# "Token Recognition" of POSIX chapter 2 "Shell Command Language". Although +# similar to lexical analyzers for other languages, this one differs in a few +# substantial ways due to quirks of the shell command language. +# +# For instance, in many languages, newline is just whitespace like space or +# TAB, but in shell a newline is a command separator, thus a distinct lexical +# token. A newline is significant and returned as a distinct token even at the +# end of a shell comment. +# +# In other languages, `1+2` would typically be scanned as three tokens +# (`1`, `+`, and `2`), but in shell it is a single token. However, the similar +# `1 + 2`, which embeds whitepace, is scanned as three token in shell, as well. +# In shell, several characters with special meaning lose that meaning when not +# surrounded by whitespace. For instance, the negation operator `!` is special +# when standing alone surrounded by whitespace; whereas in `foo!uucp` it is +# just a plain character in the longer token "foo!uucp". In many other +# languages, `"string"/foo:'string'` might be scanned as five tokens ("string", +# `/`, `foo`, `:`, and 'string'), but in shell, it is just a single token. +# +# The lexical analyzer for the shell command language is also somewhat unusual +# in that it recursively invokes the parser to handle the body of `$(...)` +# expressions which can contain arbitrary shell code. Such expressions may be +# encountered both inside and outside of double-quoted strings. +# +# The lexical analyzer is responsible for consuming shell here-doc bodies which +# extend from the line following a `<<TAG` operator until a line consisting +# solely of `TAG`. Here-doc consumption begins when a newline is encountered. +# It is legal for multiple here-doc `<<TAG` operators to be present on a single +# line, in which case their bodies must be present one following the next, and +# are consumed in the (left-to-right) order the `<<TAG` operators appear on the +# line. A special complication is that the bodies of all here-docs must be +# consumed when the newline is encountered even if the parse context depth has +# changed. For instance, in `cat <<A && x=$(cat <<B &&\n`, bodies of here-docs +# "A" and "B" must be consumed even though "A" was introduced outside the +# recursive parse context in which "B" was introduced and in which the newline +# is encountered. +package Lexer; + +sub new { + my ($class, $parser, $s) = @_; + bless { + parser => $parser, + buff => $s, + heretags => [] + } => $class; +} + +sub scan_heredoc_tag { + my $self = shift @_; + ${$self->{buff}} =~ /\G(-?)/gc; + my $indented = $1; + my $tag = $self->scan_token(); + $tag =~ s/['"\\]//g; + push(@{$self->{heretags}}, $indented ? "\t$tag" : "$tag"); + return "<<$indented$tag"; +} + +sub scan_op { + my ($self, $c) = @_; + my $b = $self->{buff}; + return $c unless $$b =~ /\G(.)/sgc; + my $cc = $c . $1; + return scan_heredoc_tag($self) if $cc eq '<<'; + return $cc if $cc =~ /^(?:&&|\|\||>>|;;|<&|>&|<>|>\|)$/; + pos($$b)--; + return $c; +} + +sub scan_sqstring { + my $self = shift @_; + ${$self->{buff}} =~ /\G([^']*'|.*\z)/sgc; + return "'" . $1; +} + +sub scan_dqstring { + my $self = shift @_; + my $b = $self->{buff}; + my $s = '"'; + while (1) { + # slurp up non-special characters + $s .= $1 if $$b =~ /\G([^"\$\\]+)/gc; + # handle special characters + last unless $$b =~ /\G(.)/sgc; + my $c = $1; + $s .= '"', last if $c eq '"'; + $s .= '$' . $self->scan_dollar(), next if $c eq '$'; + if ($c eq '\\') { + $s .= '\\', last unless $$b =~ /\G(.)/sgc; + $c = $1; + next if $c eq "\n"; # line splice + # backslash escapes only $, `, ", \ in dq-string + $s .= '\\' unless $c =~ /^[\$`"\\]$/; + $s .= $c; + next; + } + die("internal error scanning dq-string '$c'\n"); + } + return $s; +} + +sub scan_balanced { + my ($self, $c1, $c2) = @_; + my $b = $self->{buff}; + my $depth = 1; + my $s = $c1; + while ($$b =~ /\G([^\Q$c1$c2\E]*(?:[\Q$c1$c2\E]|\z))/gc) { + $s .= $1; + $depth++, next if $s =~ /\Q$c1\E$/; + $depth--; + last if $depth == 0; + } + return $s; +} + +sub scan_subst { + my $self = shift @_; + my @tokens = $self->{parser}->parse(qr/^\)$/); + $self->{parser}->next_token(); # closing ")" + return @tokens; +} + +sub scan_dollar { + my $self = shift @_; + my $b = $self->{buff}; + return $self->scan_balanced('(', ')') if $$b =~ /\G\((?=\()/gc; # $((...)) + return '(' . join(' ', $self->scan_subst()) . ')' if $$b =~ /\G\(/gc; # $(...) + return $self->scan_balanced('{', '}') if $$b =~ /\G\{/gc; # ${...} + return $1 if $$b =~ /\G(\w+)/gc; # $var + return $1 if $$b =~ /\G([@*#?$!0-9-])/gc; # $*, $1, $$, etc. + return ''; +} + +sub swallow_heredocs { + my $self = shift @_; + my $b = $self->{buff}; + my $tags = $self->{heretags}; + while (my $tag = shift @$tags) { + my $indent = $tag =~ s/^\t// ? '\\s*' : ''; + $$b =~ /(?:\G|\n)$indent\Q$tag\E(?:\n|\z)/gc; + } +} + +sub scan_token { + my $self = shift @_; + my $b = $self->{buff}; + my $token = ''; +RESTART: + $$b =~ /\G[ \t]+/gc; # skip whitespace (but not newline) + return "\n" if $$b =~ /\G#[^\n]*(?:\n|\z)/gc; # comment + while (1) { + # slurp up non-special characters + $token .= $1 if $$b =~ /\G([^\\;&|<>(){}'"\$\s]+)/gc; + # handle special characters + last unless $$b =~ /\G(.)/sgc; + my $c = $1; + last if $c =~ /^[ \t]$/; # whitespace ends token + pos($$b)--, last if length($token) && $c =~ /^[;&|<>(){}\n]$/; + $token .= $self->scan_sqstring(), next if $c eq "'"; + $token .= $self->scan_dqstring(), next if $c eq '"'; + $token .= $c . $self->scan_dollar(), next if $c eq '$'; + $self->swallow_heredocs(), $token = $c, last if $c eq "\n"; + $token = $self->scan_op($c), last if $c =~ /^[;&|<>]$/; + $token = $c, last if $c =~ /^[(){}]$/; + if ($c eq '\\') { + $token .= '\\', last unless $$b =~ /\G(.)/sgc; + $c = $1; + next if $c eq "\n" && length($token); # line splice + goto RESTART if $c eq "\n"; # line splice + $token .= '\\' . $c; + next; + } + die("internal error scanning character '$c'\n"); + } + return length($token) ? $token : undef; +} + +# ShellParser parses POSIX shell scripts (with minor extensions for Bash). It +# is a recursive descent parser very roughly modeled after section 2.10 "Shell +# Grammar" of POSIX chapter 2 "Shell Command Language". +package ShellParser; + +sub new { + my ($class, $s) = @_; + my $self = bless { + buff => [], + stop => [], + output => [] + } => $class; + $self->{lexer} = Lexer->new($self, $s); + return $self; +} + +sub next_token { + my $self = shift @_; + return pop(@{$self->{buff}}) if @{$self->{buff}}; + return $self->{lexer}->scan_token(); +} + +sub untoken { + my $self = shift @_; + push(@{$self->{buff}}, @_); +} + +sub peek { + my $self = shift @_; + my $token = $self->next_token(); + return undef unless defined($token); + $self->untoken($token); + return $token; +} + +sub stop_at { + my ($self, $token) = @_; + return 1 unless defined($token); + my $stop = ${$self->{stop}}[-1] if @{$self->{stop}}; + return defined($stop) && $token =~ $stop; +} + +sub expect { + my ($self, $expect) = @_; + my $token = $self->next_token(); + return $token if defined($token) && $token eq $expect; + push(@{$self->{output}}, "?!ERR?! expected '$expect' but found '" . (defined($token) ? $token : "<end-of-input>") . "'\n"); + $self->untoken($token) if defined($token); + return (); +} + +sub optional_newlines { + my $self = shift @_; + my @tokens; + while (my $token = $self->peek()) { + last unless $token eq "\n"; + push(@tokens, $self->next_token()); + } + return @tokens; +} + +sub parse_group { + my $self = shift @_; + return ($self->parse(qr/^}$/), + $self->expect('}')); +} + +sub parse_subshell { + my $self = shift @_; + return ($self->parse(qr/^\)$/), + $self->expect(')')); +} + +sub parse_case_pattern { + my $self = shift @_; + my @tokens; + while (defined(my $token = $self->next_token())) { + push(@tokens, $token); + last if $token eq ')'; + } + return @tokens; +} + +sub parse_case { + my $self = shift @_; + my @tokens; + push(@tokens, + $self->next_token(), # subject + $self->optional_newlines(), + $self->expect('in'), + $self->optional_newlines()); + while (1) { + my $token = $self->peek(); + last unless defined($token) && $token ne 'esac'; + push(@tokens, + $self->parse_case_pattern(), + $self->optional_newlines(), + $self->parse(qr/^(?:;;|esac)$/)); # item body + $token = $self->peek(); + last unless defined($token) && $token ne 'esac'; + push(@tokens, + $self->expect(';;'), + $self->optional_newlines()); + } + push(@tokens, $self->expect('esac')); + return @tokens; +} + +sub parse_for { + my $self = shift @_; + my @tokens; + push(@tokens, + $self->next_token(), # variable + $self->optional_newlines()); + my $token = $self->peek(); + if (defined($token) && $token eq 'in') { + push(@tokens, + $self->expect('in'), + $self->optional_newlines()); + } + push(@tokens, + $self->parse(qr/^do$/), # items + $self->expect('do'), + $self->optional_newlines(), + $self->parse_loop_body(), + $self->expect('done')); + return @tokens; +} + +sub parse_if { + my $self = shift @_; + my @tokens; + while (1) { + push(@tokens, + $self->parse(qr/^then$/), # if/elif condition + $self->expect('then'), + $self->optional_newlines(), + $self->parse(qr/^(?:elif|else|fi)$/)); # if/elif body + my $token = $self->peek(); + last unless defined($token) && $token eq 'elif'; + push(@tokens, $self->expect('elif')); + } + my $token = $self->peek(); + if (defined($token) && $token eq 'else') { + push(@tokens, + $self->expect('else'), + $self->optional_newlines(), + $self->parse(qr/^fi$/)); # else body + } + push(@tokens, $self->expect('fi')); + return @tokens; +} + +sub parse_loop_body { + my $self = shift @_; + return $self->parse(qr/^done$/); +} + +sub parse_loop { + my $self = shift @_; + return ($self->parse(qr/^do$/), # condition + $self->expect('do'), + $self->optional_newlines(), + $self->parse_loop_body(), + $self->expect('done')); +} + +sub parse_func { + my $self = shift @_; + return ($self->expect('('), + $self->expect(')'), + $self->optional_newlines(), + $self->parse_cmd()); # body +} + +sub parse_bash_array_assignment { + my $self = shift @_; + my @tokens = $self->expect('('); + while (defined(my $token = $self->next_token())) { + push(@tokens, $token); + last if $token eq ')'; + } + return @tokens; +} + +my %compound = ( + '{' => \&parse_group, + '(' => \&parse_subshell, + 'case' => \&parse_case, + 'for' => \&parse_for, + 'if' => \&parse_if, + 'until' => \&parse_loop, + 'while' => \&parse_loop); + +sub parse_cmd { + my $self = shift @_; + my $cmd = $self->next_token(); + return () unless defined($cmd); + return $cmd if $cmd eq "\n"; + + my $token; + my @tokens = $cmd; + if ($cmd eq '!') { + push(@tokens, $self->parse_cmd()); + return @tokens; + } elsif (my $f = $compound{$cmd}) { + push(@tokens, $self->$f()); + } elsif (defined($token = $self->peek()) && $token eq '(') { + if ($cmd !~ /\w=$/) { + push(@tokens, $self->parse_func()); + return @tokens; + } + $tokens[-1] .= join(' ', $self->parse_bash_array_assignment()); + } + + while (defined(my $token = $self->next_token())) { + $self->untoken($token), last if $self->stop_at($token); + push(@tokens, $token); + last if $token =~ /^(?:[;&\n|]|&&|\|\|)$/; + } + push(@tokens, $self->next_token()) if $tokens[-1] ne "\n" && defined($token = $self->peek()) && $token eq "\n"; + return @tokens; +} + +sub accumulate { + my ($self, $tokens, $cmd) = @_; + push(@$tokens, @$cmd); +} + +sub parse { + my ($self, $stop) = @_; + push(@{$self->{stop}}, $stop); + goto DONE if $self->stop_at($self->peek()); + my @tokens; + while (my @cmd = $self->parse_cmd()) { + $self->accumulate(\@tokens, \@cmd); + last if $self->stop_at($self->peek()); + } +DONE: + pop(@{$self->{stop}}); + return @tokens; +} + +# TestParser is a subclass of ShellParser which, beyond parsing shell script +# code, is also imbued with semantic knowledge of test construction, and checks +# tests for common problems (such as broken &&-chains) which might hide bugs in +# the tests themselves or in behaviors being exercised by the tests. As such, +# TestParser is only called upon to parse test bodies, not the top-level +# scripts in which the tests are defined. +package TestParser; + +use base 'ShellParser'; + +sub find_non_nl { + my $tokens = shift @_; + my $n = shift @_; + $n = $#$tokens if !defined($n); + $n-- while $n >= 0 && $$tokens[$n] eq "\n"; + return $n; +} + +sub ends_with { + my ($tokens, $needles) = @_; + my $n = find_non_nl($tokens); + for my $needle (reverse(@$needles)) { + return undef if $n < 0; + $n = find_non_nl($tokens, $n), next if $needle eq "\n"; + return undef if $$tokens[$n] !~ $needle; + $n--; + } + return 1; +} + +sub match_ending { + my ($tokens, $endings) = @_; + for my $needles (@$endings) { + next if @$tokens < scalar(grep {$_ ne "\n"} @$needles); + return 1 if ends_with($tokens, $needles); + } + return undef; +} + +sub parse_loop_body { + my $self = shift @_; + my @tokens = $self->SUPER::parse_loop_body(@_); + # did loop signal failure via "|| return" or "|| exit"? + return @tokens if !@tokens || grep(/^(?:return|exit|\$\?)$/, @tokens); + # did loop upstream of a pipe signal failure via "|| echo 'impossible + # text'" as the final command in the loop body? + return @tokens if ends_with(\@tokens, [qr/^\|\|$/, "\n", qr/^echo$/, qr/^.+$/]); + # flag missing "return/exit" handling explicit failure in loop body + my $n = find_non_nl(\@tokens); + splice(@tokens, $n + 1, 0, '?!LOOP?!'); + return @tokens; +} + +my @safe_endings = ( + [qr/^(?:&&|\|\||\||&)$/], + [qr/^(?:exit|return)$/, qr/^(?:\d+|\$\?)$/], + [qr/^(?:exit|return)$/, qr/^(?:\d+|\$\?)$/, qr/^;$/], + [qr/^(?:exit|return|continue)$/], + [qr/^(?:exit|return|continue)$/, qr/^;$/]); + +sub accumulate { + my ($self, $tokens, $cmd) = @_; + goto DONE unless @$tokens; + goto DONE if @$cmd == 1 && $$cmd[0] eq "\n"; + + # did previous command end with "&&", "|", "|| return" or similar? + goto DONE if match_ending($tokens, \@safe_endings); + + # if this command handles "$?" specially, then okay for previous + # command to be missing "&&" + for my $token (@$cmd) { + goto DONE if $token =~ /\$\?/; + } + + # if this command is "false", "return 1", or "exit 1" (which signal + # failure explicitly), then okay for all preceding commands to be + # missing "&&" + if ($$cmd[0] =~ /^(?:false|return|exit)$/) { + @$tokens = grep(!/^\?!AMP\?!$/, @$tokens); + goto DONE; + } + + # flag missing "&&" at end of previous command + my $n = find_non_nl($tokens); + splice(@$tokens, $n + 1, 0, '?!AMP?!') unless $n < 0; + +DONE: + $self->SUPER::accumulate($tokens, $cmd); +} + +# ScriptParser is a subclass of ShellParser which identifies individual test +# definitions within test scripts, and passes each test body through TestParser +# to identify possible problems. ShellParser detects test definitions not only +# at the top-level of test scripts but also within compound commands such as +# loops and function definitions. +package ScriptParser; + +use base 'ShellParser'; + +sub new { + my $class = shift @_; + my $self = $class->SUPER::new(@_); + $self->{ntests} = 0; + return $self; +} + +# extract the raw content of a token, which may be a single string or a +# composition of multiple strings and non-string character runs; for instance, +# `"test body"` unwraps to `test body`; `word"a b"42'c d'` to `worda b42c d` +sub unwrap { + my $token = @_ ? shift @_ : $_; + # simple case: 'sqstring' or "dqstring" + return $token if $token =~ s/^'([^']*)'$/$1/; + return $token if $token =~ s/^"([^"]*)"$/$1/; + + # composite case + my ($s, $q, $escaped); + while (1) { + # slurp up non-special characters + $s .= $1 if $token =~ /\G([^\\'"]*)/gc; + # handle special characters + last unless $token =~ /\G(.)/sgc; + my $c = $1; + $q = undef, next if defined($q) && $c eq $q; + $q = $c, next if !defined($q) && $c =~ /^['"]$/; + if ($c eq '\\') { + last unless $token =~ /\G(.)/sgc; + $c = $1; + $s .= '\\' if $c eq "\n"; # preserve line splice + } + $s .= $c; + } + return $s +} + +sub check_test { + my $self = shift @_; + my ($title, $body) = map(unwrap, @_); + $self->{ntests}++; + my $parser = TestParser->new(\$body); + my @tokens = $parser->parse(); + return unless $emit_all || grep(/\?![^?]+\?!/, @tokens); + my $c = main::fd_colors(1); + my $checked = join(' ', @tokens); + $checked =~ s/^\n//; + $checked =~ s/^ //mg; + $checked =~ s/ $//mg; + $checked =~ s/(\?![^?]+\?!)/$c->{rev}$c->{red}$1$c->{reset}/mg; + $checked .= "\n" unless $checked =~ /\n$/; + push(@{$self->{output}}, "$c->{blue}# chainlint: $title$c->{reset}\n$checked"); +} + +sub parse_cmd { + my $self = shift @_; + my @tokens = $self->SUPER::parse_cmd(); + return @tokens unless @tokens && $tokens[0] =~ /^test_expect_(?:success|failure)$/; + my $n = $#tokens; + $n-- while $n >= 0 && $tokens[$n] =~ /^(?:[;&\n|]|&&|\|\|)$/; + $self->check_test($tokens[1], $tokens[2]) if $n == 2; # title body + $self->check_test($tokens[2], $tokens[3]) if $n > 2; # prereq title body + return @tokens; +} + +# main contains high-level functionality for processing command-line switches, +# feeding input test scripts to ScriptParser, and reporting results. +package main; + +my $getnow = sub { return time(); }; +my $interval = sub { return time() - shift; }; +if (eval {require Time::HiRes; Time::HiRes->import(); 1;}) { + $getnow = sub { return [Time::HiRes::gettimeofday()]; }; + $interval = sub { return Time::HiRes::tv_interval(shift); }; +} + +# Restore TERM if test framework set it to "dumb" so 'tput' will work; do this +# outside of get_colors() since under 'ithreads' all threads use %ENV of main +# thread and ignore %ENV changes in subthreads. +$ENV{TERM} = $ENV{USER_TERM} if $ENV{USER_TERM}; + +my @NOCOLORS = (bold => '', rev => '', reset => '', blue => '', green => '', red => ''); +my %COLORS = (); +sub get_colors { + return \%COLORS if %COLORS; + if (exists($ENV{NO_COLOR}) || + system("tput sgr0 >/dev/null 2>&1") != 0 || + system("tput bold >/dev/null 2>&1") != 0 || + system("tput rev >/dev/null 2>&1") != 0 || + system("tput setaf 1 >/dev/null 2>&1") != 0) { + %COLORS = @NOCOLORS; + return \%COLORS; + } + %COLORS = (bold => `tput bold`, + rev => `tput rev`, + reset => `tput sgr0`, + blue => `tput setaf 4`, + green => `tput setaf 2`, + red => `tput setaf 1`); + chomp(%COLORS); + return \%COLORS; +} + +my %FD_COLORS = (); +sub fd_colors { + my $fd = shift; + return $FD_COLORS{$fd} if exists($FD_COLORS{$fd}); + $FD_COLORS{$fd} = -t $fd ? get_colors() : {@NOCOLORS}; + return $FD_COLORS{$fd}; +} + +sub ncores { + # Windows + return $ENV{NUMBER_OF_PROCESSORS} if exists($ENV{NUMBER_OF_PROCESSORS}); + # Linux / MSYS2 / Cygwin / WSL + do { local @ARGV='/proc/cpuinfo'; return scalar(grep(/^processor\s*:/, <>)); } if -r '/proc/cpuinfo'; + # macOS & BSD + return qx/sysctl -n hw.ncpu/ if $^O =~ /(?:^darwin$|bsd)/; + return 1; +} + +sub show_stats { + my ($start_time, $stats) = @_; + my $walltime = $interval->($start_time); + my ($usertime) = times(); + my ($total_workers, $total_scripts, $total_tests, $total_errs) = (0, 0, 0, 0); + my $c = fd_colors(2); + print(STDERR $c->{green}); + for (@$stats) { + my ($worker, $nscripts, $ntests, $nerrs) = @$_; + print(STDERR "worker $worker: $nscripts scripts, $ntests tests, $nerrs errors\n"); + $total_workers++; + $total_scripts += $nscripts; + $total_tests += $ntests; + $total_errs += $nerrs; + } + printf(STDERR "total: %d workers, %d scripts, %d tests, %d errors, %.2fs/%.2fs (wall/user)$c->{reset}\n", $total_workers, $total_scripts, $total_tests, $total_errs, $walltime, $usertime); +} + +sub check_script { + my ($id, $next_script, $emit) = @_; + my ($nscripts, $ntests, $nerrs) = (0, 0, 0); + while (my $path = $next_script->()) { + $nscripts++; + my $fh; + unless (open($fh, "<", $path)) { + $emit->("?!ERR?! $path: $!\n"); + next; + } + my $s = do { local $/; <$fh> }; + close($fh); + my $parser = ScriptParser->new(\$s); + 1 while $parser->parse_cmd(); + if (@{$parser->{output}}) { + my $c = fd_colors(1); + my $s = join('', @{$parser->{output}}); + $emit->("$c->{bold}$c->{blue}# chainlint: $path$c->{reset}\n" . $s); + $nerrs += () = $s =~ /\?![^?]+\?!/g; + } + $ntests += $parser->{ntests}; + } + return [$id, $nscripts, $ntests, $nerrs]; +} + +sub exit_code { + my $stats = shift @_; + for (@$stats) { + my ($worker, $nscripts, $ntests, $nerrs) = @$_; + return 1 if $nerrs; + } + return 0; +} + +Getopt::Long::Configure(qw{bundling}); +GetOptions( + "emit-all!" => \$emit_all, + "jobs|j=i" => \$jobs, + "stats|show-stats!" => \$show_stats) or die("option error\n"); +$jobs = ncores() if $jobs < 1; + +my $start_time = $getnow->(); +my @stats; + +my @scripts; +push(@scripts, File::Glob::bsd_glob($_)) for (@ARGV); +unless (@scripts) { + show_stats($start_time, \@stats) if $show_stats; + exit; +} + +unless ($Config{useithreads} && eval { + require threads; threads->import(); + require Thread::Queue; Thread::Queue->import(); + 1; + }) { + push(@stats, check_script(1, sub { shift(@scripts); }, sub { print(@_); })); + show_stats($start_time, \@stats) if $show_stats; + exit(exit_code(\@stats)); +} + +my $script_queue = Thread::Queue->new(); +my $output_queue = Thread::Queue->new(); + +sub next_script { return $script_queue->dequeue(); } +sub emit { $output_queue->enqueue(@_); } + +sub monitor { + while (my $s = $output_queue->dequeue()) { + print($s); + } +} + +my $mon = threads->create({'context' => 'void'}, \&monitor); +threads->create({'context' => 'list'}, \&check_script, $_, \&next_script, \&emit) for 1..$jobs; + +$script_queue->enqueue(@scripts); +$script_queue->end(); + +for (threads->list()) { + push(@stats, $_->join()) unless $_ == $mon; +} + +$output_queue->end(); +$mon->join(); + +show_stats($start_time, \@stats) if $show_stats; +exit(exit_code(\@stats)); diff --git a/t/chainlint.sed b/t/chainlint.sed deleted file mode 100644 index dc4ce37cb5..0000000000 --- a/t/chainlint.sed +++ /dev/null @@ -1,399 +0,0 @@ -#------------------------------------------------------------------------------ -# Detect broken &&-chains in tests. -# -# At present, only &&-chains in subshells are examined by this linter; -# top-level &&-chains are instead checked directly by the test framework. Like -# the top-level &&-chain linter, the subshell linter (intentionally) does not -# check &&-chains within {...} blocks. -# -# Checking for &&-chain breakage is done line-by-line by pure textual -# inspection. -# -# Incomplete lines (those ending with "\") are stitched together with following -# lines to simplify processing, particularly of "one-liner" statements. -# Top-level here-docs are swallowed to avoid false positives within the -# here-doc body, although the statement to which the here-doc is attached is -# retained. -# -# Heuristics are used to detect end-of-subshell when the closing ")" is cuddled -# with the final subshell statement on the same line: -# -# (cd foo && -# bar) -# -# in order to avoid misinterpreting the ")" in constructs such as "x=$(...)" -# and "case $x in *)" as ending the subshell. -# -# Lines missing a final "&&" are flagged with "?!AMP?!", as are lines which -# chain commands with ";" internally rather than "&&". A line may be flagged -# for both violations. -# -# Detection of a missing &&-link in a multi-line subshell is complicated by the -# fact that the last statement before the closing ")" must not end with "&&". -# Since processing is line-by-line, it is not known whether a missing "&&" is -# legitimate or not until the _next_ line is seen. To accommodate this, within -# multi-line subshells, each line is stored in sed's "hold" area until after -# the next line is seen and processed. If the next line is a stand-alone ")", -# then a missing "&&" on the previous line is legitimate; otherwise a missing -# "&&" is a break in the &&-chain. -# -# ( -# cd foo && -# bar -# ) -# -# In practical terms, when "bar" is encountered, it is flagged with "?!AMP?!", -# but when the stand-alone ")" line is seen which closes the subshell, the -# "?!AMP?!" violation is removed from the "bar" line (retrieved from the "hold" -# area) since the final statement of a subshell must not end with "&&". The -# final line of a subshell may still break the &&-chain by using ";" internally -# to chain commands together rather than "&&", but an internal "?!AMP?!" is -# never removed from a line even though a line-ending "?!AMP?!" might be. -# -# Care is taken to recognize the last _statement_ of a multi-line subshell, not -# necessarily the last textual _line_ within the subshell, since &&-chaining -# applies to statements, not to lines. Consequently, blank lines, comment -# lines, and here-docs are swallowed (but not the command to which the here-doc -# is attached), leaving the last statement in the "hold" area, not the last -# line, thus simplifying &&-link checking. -# -# The final statement before "done" in for- and while-loops, and before "elif", -# "else", and "fi" in if-then-else likewise must not end with "&&", thus -# receives similar treatment. -# -# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a -# line such as "cat <<EOF" is seen, the here-doc tag is copied to the front of -# the line enclosed in angle brackets as a sentinel, giving "<EOF>cat <<EOF". -# As each subsequent line is read, it is appended to the target line and a -# (whitespace-loose) back-reference match /^<(.*)>\n\1$/ is attempted to see if -# the content inside "<...>" matches the entirety of the newly-read line. For -# instance, if the next line read is "some data", when concatenated with the -# target line, it becomes "<EOF>cat <<EOF\nsome data", and a match is attempted -# to see if "EOF" matches "some data". Since it doesn't, the next line is -# attempted. When a line consisting of only "EOF" (and possible whitespace) is -# encountered, it is appended to the target line giving "<EOF>cat <<EOF\nEOF", -# in which case the "EOF" inside "<...>" does match the text following the -# newline, thus the closing here-doc tag has been found. The closing tag line -# and the "<...>" prefix on the target line are then discarded, leaving just -# the target line "cat <<EOF". -#------------------------------------------------------------------------------ - -# incomplete line -- slurp up next line -:squash -/\\$/ { - N - s/\\\n// - bsquash -} - -# here-doc -- swallow it to avoid false hits within its body (but keep the -# command to which it was attached) -/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/ { - /"[^"]*<<[^"]*"/bnotdoc - s/^\(.*<<-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1\2/ - :hered - N - /^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{ - s/\n.*$// - bhered - } - s/^<[^>]*>// - s/\n.*$// -} -:notdoc - -# one-liner "(...) &&" -/^[ ]*!*[ ]*(..*)[ ]*&&[ ]*$/boneline - -# same as above but without trailing "&&" -/^[ ]*!*[ ]*(..*)[ ]*$/boneline - -# one-liner "(...) >x" (or "2>x" or "<x" or "|x" or "&" -/^[ ]*!*[ ]*(..*)[ ]*[0-9]*[<>|&]/boneline - -# multi-line "(...\n...)" -/^[ ]*(/bsubsh - -# innocuous line -- print it and advance to next line -b - -# found one-liner "(...)" -- mark suspect if it uses ";" internally rather than -# "&&" (but not ";" in a string) -:oneline -/;/{ - /"[^"]*;[^"]*"/!s/;/; ?!AMP?!/ -} -b - -:subsh -# bare "(" line? -- stash for later printing -/^[ ]*([ ]*$/ { - h - bnextln -} -# "(..." line -- "(" opening subshell cuddled with command; temporarily replace -# "(" with sentinel "^" and process the line as if "(" had been seen solo on -# the preceding line; this temporary replacement prevents several rules from -# accidentally thinking "(" introduces a nested subshell; "^" is changed back -# to "(" at output time -x -s/.*// -x -s/(/^/ -bslurp - -:nextln -N -s/.*\n// - -:slurp -# incomplete line "...\" -/\\$/bicmplte -# multi-line quoted string "...\n..."? -/"/bdqstr -# multi-line quoted string '...\n...'? (but not contraction in string "it's") -/'/{ - /"[^'"]*'[^'"]*"/!bsqstr -} -:folded -# here-doc -- swallow it (but not "<<" in a string) -/<<-*[ ]*[\\'"]*[A-Za-z0-9_]/{ - /"[^"]*<<[^"]*"/!bheredoc -} -# comment or empty line -- discard since final non-comment, non-empty line -# before closing ")", "done", "elsif", "else", or "fi" will need to be -# re-visited to drop "suspect" marking since final line of those constructs -# legitimately lacks "&&", so "suspect" mark must be removed -/^[ ]*#/bnextln -/^[ ]*$/bnextln -# in-line comment -- strip it (but not "#" in a string, Bash ${#...} array -# length, or Perforce "//depot/path#42" revision in filespec) -/[ ]#/{ - /"[^"]*#[^"]*"/!s/[ ]#.*$// -} -# one-liner "case ... esac" -/^[ ^]*case[ ]*..*esac/bchkchn -# multi-line "case ... esac" -/^[ ^]*case[ ]..*[ ]in/bcase -# multi-line "for ... done" or "while ... done" -/^[ ^]*for[ ]..*[ ]in/bcont -/^[ ^]*while[ ]/bcont -/^[ ]*do[ ]/bcont -/^[ ]*do[ ]*$/bcont -/;[ ]*do/bcont -/^[ ]*done[ ]*&&[ ]*$/bdone -/^[ ]*done[ ]*$/bdone -/^[ ]*done[ ]*[<>|]/bdone -/^[ ]*done[ ]*)/bdone -/||[ ]*exit[ ]/bcont -/||[ ]*exit[ ]*$/bcont -# multi-line "if...elsif...else...fi" -/^[ ^]*if[ ]/bcont -/^[ ]*then[ ]/bcont -/^[ ]*then[ ]*$/bcont -/;[ ]*then/bcont -/^[ ]*elif[ ]/belse -/^[ ]*elif[ ]*$/belse -/^[ ]*else[ ]/belse -/^[ ]*else[ ]*$/belse -/^[ ]*fi[ ]*&&[ ]*$/bdone -/^[ ]*fi[ ]*$/bdone -/^[ ]*fi[ ]*[<>|]/bdone -/^[ ]*fi[ ]*)/bdone -# nested one-liner "(...) &&" -/^[ ^]*(.*)[ ]*&&[ ]*$/bchkchn -# nested one-liner "(...)" -/^[ ^]*(.*)[ ]*$/bchkchn -# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x") -/^[ ^]*(.*)[ ]*[0-9]*[<>|]/bchkchn -# nested multi-line "(...\n...)" -/^[ ^]*(/bnest -# multi-line "{...\n...}" -/^[ ^]*{/bblock -# closing ")" on own line -- exit subshell -/^[ ]*)/bclssolo -# "$((...))" -- arithmetic expansion; not closing ")" -/\$(([^)][^)]*))[^)]*$/bchkchn -# "$(...)" -- command substitution; not closing ")" -/\$([^)][^)]*)[^)]*$/bchkchn -# multi-line "$(...\n...)" -- command substitution; treat as nested subshell -/\$([^)]*$/bnest -# "=(...)" -- Bash array assignment; not closing ")" -/=(/bchkchn -# closing "...) &&" -/)[ ]*&&[ ]*$/bclose -# closing "...)" -/)[ ]*$/bclose -# closing "...) >x" (or "2>x" or "<x" or "|x") -/)[ ]*[<>|]/bclose -:chkchn -# mark suspect if line uses ";" internally rather than "&&" (but not ";" in a -# string and not ";;" in one-liner "case...esac") -/;/{ - /;;/!{ - /"[^"]*;[^"]*"/!s/;/; ?!AMP?!/ - } -} -# line ends with pipe "...|" -- valid; not missing "&&" -/|[ ]*$/bcont -# missing end-of-line "&&" -- mark suspect -/&&[ ]*$/!s/$/ ?!AMP?!/ -:cont -# retrieve and print previous line -x -s/^\([ ]*\)^/\1(/ -s/?!HERE?!/<</g -n -bslurp - -# found incomplete line "...\" -- slurp up next line -:icmplte -N -s/\\\n// -bslurp - -# check for multi-line double-quoted string "...\n..." -- fold to one line -:dqstr -# remove all quote pairs -s/"\([^"]*\)"/@!\1@!/g -# done if no dangling quote -/"/!bdqdone -# otherwise, slurp next line and try again -N -s/\n// -bdqstr -:dqdone -s/@!/"/g -bfolded - -# check for multi-line single-quoted string '...\n...' -- fold to one line -:sqstr -# remove all quote pairs -s/'\([^']*\)'/@!\1@!/g -# done if no dangling quote -/'/!bsqdone -# otherwise, slurp next line and try again -N -s/\n// -bsqstr -:sqdone -s/@!/'/g -bfolded - -# found here-doc -- swallow it to avoid false hits within its body (but keep -# the command to which it was attached) -:heredoc -s/^\(.*\)<<\(-*[ ]*\)[\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\3>\1?!HERE?!\2\3/ -:hdocsub -N -/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{ - s/\n.*$// - bhdocsub -} -s/^<[^>]*>// -s/\n.*$// -bfolded - -# found "case ... in" -- pass through untouched -:case -x -s/^\([ ]*\)^/\1(/ -s/?!HERE?!/<</g -n -:cascom -/^[ ]*#/{ - N - s/.*\n// - bcascom -} -/^[ ]*esac/bslurp -bcase - -# found "else" or "elif" -- drop "suspect" from final line before "else" since -# that line legitimately lacks "&&" -:else -x -s/\( ?!AMP?!\)* ?!AMP?!$// -x -bcont - -# found "done" closing for-loop or while-loop, or "fi" closing if-then -- drop -# "suspect" from final contained line since that line legitimately lacks "&&" -:done -x -s/\( ?!AMP?!\)* ?!AMP?!$// -x -# is 'done' or 'fi' cuddled with ")" to close subshell? -/done.*)/bclose -/fi.*)/bclose -bchkchn - -# found nested multi-line "(...\n...)" -- pass through untouched -:nest -x -:nstslrp -s/^\([ ]*\)^/\1(/ -s/?!HERE?!/<</g -n -:nstcom -# comment -- not closing ")" if in comment -/^[ ]*#/{ - N - s/.*\n// - bnstcom -} -# closing ")" on own line -- stop nested slurp -/^[ ]*)/bnstcl -# "$((...))" -- arithmetic expansion; not closing ")" -/\$(([^)][^)]*))[^)]*$/bnstcnt -# "$(...)" -- command substitution; not closing ")" -/\$([^)][^)]*)[^)]*$/bnstcnt -# closing "...)" -- stop nested slurp -/)/bnstcl -:nstcnt -x -bnstslrp -:nstcl -# is it "))" which closes nested and parent subshells? -/)[ ]*)/bslurp -bchkchn - -# found multi-line "{...\n...}" block -- pass through untouched -:block -x -s/^\([ ]*\)^/\1(/ -s/?!HERE?!/<</g -n -:blkcom -/^[ ]*#/{ - N - s/.*\n// - bblkcom -} -# closing "}" -- stop block slurp -/}/bchkchn -bblock - -# found closing ")" on own line -- drop "suspect" from final line of subshell -# since that line legitimately lacks "&&" and exit subshell loop -:clssolo -x -s/\( ?!AMP?!\)* ?!AMP?!$// -s/^\([ ]*\)^/\1(/ -s/?!HERE?!/<</g -p -x -s/^\([ ]*\)^/\1(/ -s/?!HERE?!/<</g -b - -# found closing "...)" -- exit subshell loop -:close -x -s/^\([ ]*\)^/\1(/ -s/?!HERE?!/<</g -p -x -s/^\([ ]*\)^/\1(/ -s/?!HERE?!/<</g -b diff --git a/t/chainlint/blank-line-before-esac.expect b/t/chainlint/blank-line-before-esac.expect new file mode 100644 index 0000000000..48ed4eb124 --- /dev/null +++ b/t/chainlint/blank-line-before-esac.expect @@ -0,0 +1,18 @@ +test_done ( ) { + case "$test_failure" in + 0 ) + test_at_end_hook_ + + exit 0 ;; + + * ) + if test $test_external_has_tap -eq 0 + then + say_color error "# failed $test_failure among $msg" + say "1..$test_count" + fi + + exit 1 ;; + + esac +} diff --git a/t/chainlint/blank-line-before-esac.test b/t/chainlint/blank-line-before-esac.test new file mode 100644 index 0000000000..cecccad19f --- /dev/null +++ b/t/chainlint/blank-line-before-esac.test @@ -0,0 +1,19 @@ +# LINT: blank line before "esac" +test_done () { + case "$test_failure" in + 0) + test_at_end_hook_ + + exit 0 ;; + + *) + if test $test_external_has_tap -eq 0 + then + say_color error "# failed $test_failure among $msg" + say "1..$test_count" + fi + + exit 1 ;; + + esac +} diff --git a/t/chainlint/block.expect b/t/chainlint/block.expect index da60257ebc..a3bcea492a 100644 --- a/t/chainlint/block.expect +++ b/t/chainlint/block.expect @@ -1,7 +1,7 @@ ( foo && { - echo a + echo a ?!AMP?! echo b } && bar && @@ -9,4 +9,15 @@ echo c } ?!AMP?! baz -) +) && + +{ + echo a ; ?!AMP?! echo b +} && +{ echo a ; ?!AMP?! echo b ; } && + +{ + echo "${var}9" && + echo "done" +} && +finis diff --git a/t/chainlint/block.test b/t/chainlint/block.test index 0a82fd579f..4ab69a4afc 100644 --- a/t/chainlint/block.test +++ b/t/chainlint/block.test @@ -11,4 +11,17 @@ echo c } baz -) +) && + +# LINT: ";" not allowed in place of "&&" +{ + echo a; echo b +} && +{ echo a; echo b; } && + +# LINT: "}" inside string not mistaken as end of block +{ + echo "${var}9" && + echo "done" +} && +finis diff --git a/t/chainlint/chain-break-background.expect b/t/chainlint/chain-break-background.expect new file mode 100644 index 0000000000..28f9114f42 --- /dev/null +++ b/t/chainlint/chain-break-background.expect @@ -0,0 +1,9 @@ +JGIT_DAEMON_PID= && +git init --bare empty.git && +> empty.git/git-daemon-export-ok && +mkfifo jgit_daemon_output && +{ + jgit daemon --port="$JGIT_DAEMON_PORT" . > jgit_daemon_output & + JGIT_DAEMON_PID=$! +} && +test_expect_code 2 git ls-remote --exit-code git://localhost:$JGIT_DAEMON_PORT/empty.git diff --git a/t/chainlint/chain-break-background.test b/t/chainlint/chain-break-background.test new file mode 100644 index 0000000000..e10f656b05 --- /dev/null +++ b/t/chainlint/chain-break-background.test @@ -0,0 +1,10 @@ +JGIT_DAEMON_PID= && +git init --bare empty.git && +>empty.git/git-daemon-export-ok && +mkfifo jgit_daemon_output && +{ +# LINT: exit status of "&" is always 0 so &&-chaining immaterial + jgit daemon --port="$JGIT_DAEMON_PORT" . >jgit_daemon_output & + JGIT_DAEMON_PID=$! +} && +test_expect_code 2 git ls-remote --exit-code git://localhost:$JGIT_DAEMON_PORT/empty.git diff --git a/t/chainlint/chain-break-continue.expect b/t/chainlint/chain-break-continue.expect new file mode 100644 index 0000000000..47a3457710 --- /dev/null +++ b/t/chainlint/chain-break-continue.expect @@ -0,0 +1,12 @@ +git ls-tree --name-only -r refs/notes/many_notes | +while read path +do + test "$path" = "foobar/non-note.txt" && continue + test "$path" = "deadbeef" && continue + test "$path" = "de/adbeef" && continue + + if test $(expr length "$path") -ne $hexsz + then + return 1 + fi +done diff --git a/t/chainlint/chain-break-continue.test b/t/chainlint/chain-break-continue.test new file mode 100644 index 0000000000..f0af71d8bd --- /dev/null +++ b/t/chainlint/chain-break-continue.test @@ -0,0 +1,13 @@ +git ls-tree --name-only -r refs/notes/many_notes | +while read path +do +# LINT: broken &&-chain okay if explicit "continue" + test "$path" = "foobar/non-note.txt" && continue + test "$path" = "deadbeef" && continue + test "$path" = "de/adbeef" && continue + + if test $(expr length "$path") -ne $hexsz + then + return 1 + fi +done diff --git a/t/chainlint/chain-break-false.expect b/t/chainlint/chain-break-false.expect new file mode 100644 index 0000000000..989766fb85 --- /dev/null +++ b/t/chainlint/chain-break-false.expect @@ -0,0 +1,9 @@ +if condition not satisified +then + echo it did not work... + echo failed! + false +else + echo it went okay ?!AMP?! + congratulate user +fi diff --git a/t/chainlint/chain-break-false.test b/t/chainlint/chain-break-false.test new file mode 100644 index 0000000000..a5aaff8c8a --- /dev/null +++ b/t/chainlint/chain-break-false.test @@ -0,0 +1,10 @@ +# LINT: broken &&-chain okay if explicit "false" signals failure +if condition not satisified +then + echo it did not work... + echo failed! + false +else + echo it went okay + congratulate user +fi diff --git a/t/chainlint/chain-break-return-exit.expect b/t/chainlint/chain-break-return-exit.expect new file mode 100644 index 0000000000..1732d221c3 --- /dev/null +++ b/t/chainlint/chain-break-return-exit.expect @@ -0,0 +1,19 @@ +case "$(git ls-files)" in +one ) echo pass one ;; +* ) echo bad one ; return 1 ;; +esac && +( + case "$(git ls-files)" in + two ) echo pass two ;; + * ) echo bad two ; exit 1 ;; +esac +) && +case "$(git ls-files)" in +dir/two"$LF"one ) echo pass both ;; +* ) echo bad ; return 1 ;; +esac && + +for i in 1 2 3 4 ; do + git checkout main -b $i || return $? + test_commit $i $i $i tag$i || return $? +done diff --git a/t/chainlint/chain-break-return-exit.test b/t/chainlint/chain-break-return-exit.test new file mode 100644 index 0000000000..46542edf88 --- /dev/null +++ b/t/chainlint/chain-break-return-exit.test @@ -0,0 +1,23 @@ +case "$(git ls-files)" in +one) echo pass one ;; +# LINT: broken &&-chain okay if explicit "return 1" signals failuire +*) echo bad one; return 1 ;; +esac && +( + case "$(git ls-files)" in + two) echo pass two ;; +# LINT: broken &&-chain okay if explicit "exit 1" signals failuire + *) echo bad two; exit 1 ;; + esac +) && +case "$(git ls-files)" in +dir/two"$LF"one) echo pass both ;; +# LINT: broken &&-chain okay if explicit "return 1" signals failuire +*) echo bad; return 1 ;; +esac && + +for i in 1 2 3 4 ; do +# LINT: broken &&-chain okay if explicit "return $?" signals failure + git checkout main -b $i || return $? + test_commit $i $i $i tag$i || return $? +done diff --git a/t/chainlint/chain-break-status.expect b/t/chainlint/chain-break-status.expect new file mode 100644 index 0000000000..f4bada9463 --- /dev/null +++ b/t/chainlint/chain-break-status.expect @@ -0,0 +1,9 @@ +OUT=$(( ( large_git ; echo $? 1 >& 3 ) | : ) 3 >& 1) && +test_match_signal 13 "$OUT" && + +{ test-tool sigchain > actual ; ret=$? ; } && +{ + test_match_signal 15 "$ret" || + test "$ret" = 3 +} && +test_cmp expect actual diff --git a/t/chainlint/chain-break-status.test b/t/chainlint/chain-break-status.test new file mode 100644 index 0000000000..a6602a7b99 --- /dev/null +++ b/t/chainlint/chain-break-status.test @@ -0,0 +1,11 @@ +# LINT: broken &&-chain okay if next command handles "$?" explicitly +OUT=$( ((large_git; echo $? 1>&3) | :) 3>&1 ) && +test_match_signal 13 "$OUT" && + +# LINT: broken &&-chain okay if next command handles "$?" explicitly +{ test-tool sigchain >actual; ret=$?; } && +{ + test_match_signal 15 "$ret" || + test "$ret" = 3 +} && +test_cmp expect actual diff --git a/t/chainlint/chained-block.expect b/t/chainlint/chained-block.expect new file mode 100644 index 0000000000..574cdceb07 --- /dev/null +++ b/t/chainlint/chained-block.expect @@ -0,0 +1,9 @@ +echo nobody home && { + test the doohicky ?!AMP?! + right now +} && + +GIT_EXTERNAL_DIFF=echo git diff | { + read path oldfile oldhex oldmode newfile newhex newmode && + test "z$oh" = "z$oldhex" +} diff --git a/t/chainlint/chained-block.test b/t/chainlint/chained-block.test new file mode 100644 index 0000000000..86f81ece63 --- /dev/null +++ b/t/chainlint/chained-block.test @@ -0,0 +1,11 @@ +# LINT: start of block chained to preceding command +echo nobody home && { + test the doohicky + right now +} && + +# LINT: preceding command pipes to block on same line +GIT_EXTERNAL_DIFF=echo git diff | { + read path oldfile oldhex oldmode newfile newhex newmode && + test "z$oh" = "z$oldhex" +} diff --git a/t/chainlint/chained-subshell.expect b/t/chainlint/chained-subshell.expect new file mode 100644 index 0000000000..af0369d328 --- /dev/null +++ b/t/chainlint/chained-subshell.expect @@ -0,0 +1,10 @@ +mkdir sub && ( + cd sub && + foo the bar ?!AMP?! + nuff said +) && + +cut "-d " -f actual | ( read s1 s2 s3 && +test -f $s1 ?!AMP?! +test $(cat $s2) = tree2path1 && +test $(cat $s3) = tree3path1 ) diff --git a/t/chainlint/chained-subshell.test b/t/chainlint/chained-subshell.test new file mode 100644 index 0000000000..4ff6ddd8cb --- /dev/null +++ b/t/chainlint/chained-subshell.test @@ -0,0 +1,13 @@ +# LINT: start of subshell chained to preceding command +mkdir sub && ( + cd sub && + foo the bar + nuff said +) && + +# LINT: preceding command pipes to subshell on same line +cut "-d " -f actual | (read s1 s2 s3 && +test -f $s1 +test $(cat $s2) = tree2path1 && +# LINT: closing subshell ")" correctly detected on same line as "$(...)" +test $(cat $s3) = tree3path1) diff --git a/t/chainlint/command-substitution-subsubshell.expect b/t/chainlint/command-substitution-subsubshell.expect new file mode 100644 index 0000000000..ab2f79e845 --- /dev/null +++ b/t/chainlint/command-substitution-subsubshell.expect @@ -0,0 +1,2 @@ +OUT=$(( ( large_git 1 >& 3 ) | : ) 3 >& 1) && +test_match_signal 13 "$OUT" diff --git a/t/chainlint/command-substitution-subsubshell.test b/t/chainlint/command-substitution-subsubshell.test new file mode 100644 index 0000000000..321de2951c --- /dev/null +++ b/t/chainlint/command-substitution-subsubshell.test @@ -0,0 +1,3 @@ +# LINT: subshell nested in subshell nested in command substitution +OUT=$( ((large_git 1>&3) | :) 3>&1 ) && +test_match_signal 13 "$OUT" diff --git a/t/chainlint/complex-if-in-cuddled-loop.expect b/t/chainlint/complex-if-in-cuddled-loop.expect index 2fca183409..dac2d0fd1d 100644 --- a/t/chainlint/complex-if-in-cuddled-loop.expect +++ b/t/chainlint/complex-if-in-cuddled-loop.expect @@ -4,6 +4,6 @@ : else echo >file - fi + fi ?!LOOP?! done) && test ! -f file diff --git a/t/chainlint/double-here-doc.expect b/t/chainlint/double-here-doc.expect new file mode 100644 index 0000000000..75477bb1ad --- /dev/null +++ b/t/chainlint/double-here-doc.expect @@ -0,0 +1,2 @@ +run_sub_test_lib_test_err run-inv-range-start "--run invalid range start" --run="a-5" <<-EOF && +check_sub_test_lib_test_err run-inv-range-start <<-EOF_OUT 3 <<-EOF_ERR diff --git a/t/chainlint/double-here-doc.test b/t/chainlint/double-here-doc.test new file mode 100644 index 0000000000..cd584a4357 --- /dev/null +++ b/t/chainlint/double-here-doc.test @@ -0,0 +1,12 @@ +run_sub_test_lib_test_err run-inv-range-start \ + "--run invalid range start" \ + --run="a-5" <<-\EOF && +test_expect_success "passing test #1" "true" +test_done +EOF +check_sub_test_lib_test_err run-inv-range-start \ + <<-\EOF_OUT 3<<-EOF_ERR +> FATAL: Unexpected exit with code 1 +EOF_OUT +> error: --run: invalid non-numeric in range start: ${SQ}a-5${SQ} +EOF_ERR diff --git a/t/chainlint/dqstring-line-splice.expect b/t/chainlint/dqstring-line-splice.expect new file mode 100644 index 0000000000..bf9ced60d4 --- /dev/null +++ b/t/chainlint/dqstring-line-splice.expect @@ -0,0 +1,3 @@ +echo 'fatal: reword option of --fixup is mutually exclusive with' '--patch/--interactive/--all/--include/--only' > expect && +test_must_fail git commit --fixup=reword:HEAD~ $1 2 > actual && +test_cmp expect actual diff --git a/t/chainlint/dqstring-line-splice.test b/t/chainlint/dqstring-line-splice.test new file mode 100644 index 0000000000..b40714439f --- /dev/null +++ b/t/chainlint/dqstring-line-splice.test @@ -0,0 +1,7 @@ +# LINT: line-splice within DQ-string +'" +echo 'fatal: reword option of --fixup is mutually exclusive with'\ + '--patch/--interactive/--all/--include/--only' >expect && +test_must_fail git commit --fixup=reword:HEAD~ $1 2>actual && +test_cmp expect actual +"' diff --git a/t/chainlint/dqstring-no-interpolate.expect b/t/chainlint/dqstring-no-interpolate.expect new file mode 100644 index 0000000000..10724987a5 --- /dev/null +++ b/t/chainlint/dqstring-no-interpolate.expect @@ -0,0 +1,11 @@ +grep "^ ! [rejected][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" out && + +grep "^\.git$" output.txt && + + +( + cd client$version && + GIT_TEST_PROTOCOL_VERSION=$version git fetch-pack --no-progress .. $(cat ../input) +) > output && + cut -d ' ' -f 2 < output | sort > actual && + test_cmp expect actual diff --git a/t/chainlint/dqstring-no-interpolate.test b/t/chainlint/dqstring-no-interpolate.test new file mode 100644 index 0000000000..d2f4219cbb --- /dev/null +++ b/t/chainlint/dqstring-no-interpolate.test @@ -0,0 +1,15 @@ +# LINT: regex dollar-sign eol anchor in double-quoted string not special +grep "^ ! \[rejected\][ ]*$BRANCH -> $BRANCH (non-fast-forward)$" out && + +# LINT: escaped "$" not mistaken for variable expansion +grep "^\\.git\$" output.txt && + +'" +( + cd client$version && +# LINT: escaped dollar-sign in double-quoted test body + GIT_TEST_PROTOCOL_VERSION=$version git fetch-pack --no-progress .. \$(cat ../input) +) >output && + cut -d ' ' -f 2 <output | sort >actual && + test_cmp expect actual +"' diff --git a/t/chainlint/empty-here-doc.expect b/t/chainlint/empty-here-doc.expect new file mode 100644 index 0000000000..f42f2d41ba --- /dev/null +++ b/t/chainlint/empty-here-doc.expect @@ -0,0 +1,3 @@ +git ls-tree $tree path > current && +cat > expected <<EOF && +test_output diff --git a/t/chainlint/empty-here-doc.test b/t/chainlint/empty-here-doc.test new file mode 100644 index 0000000000..24fc165de3 --- /dev/null +++ b/t/chainlint/empty-here-doc.test @@ -0,0 +1,5 @@ +git ls-tree $tree path >current && +# LINT: empty here-doc +cat >expected <<\EOF && +EOF +test_output diff --git a/t/chainlint/exclamation.expect b/t/chainlint/exclamation.expect new file mode 100644 index 0000000000..2d961a58c6 --- /dev/null +++ b/t/chainlint/exclamation.expect @@ -0,0 +1,4 @@ +if ! condition ; then echo nope ; else yep ; fi && +test_prerequisite !MINGW && +mail uucp!address && +echo !whatever! diff --git a/t/chainlint/exclamation.test b/t/chainlint/exclamation.test new file mode 100644 index 0000000000..323595b5bd --- /dev/null +++ b/t/chainlint/exclamation.test @@ -0,0 +1,8 @@ +# LINT: "! word" is two tokens +if ! condition; then echo nope; else yep; fi && +# LINT: "!word" is single token, not two tokens "!" and "word" +test_prerequisite !MINGW && +# LINT: "word!word" is single token, not three tokens "word", "!", and "word" +mail uucp!address && +# LINT: "!word!" is single token, not three tokens "!", "word", and "!" +echo !whatever! diff --git a/t/chainlint/for-loop-abbreviated.expect b/t/chainlint/for-loop-abbreviated.expect new file mode 100644 index 0000000000..a21007a63f --- /dev/null +++ b/t/chainlint/for-loop-abbreviated.expect @@ -0,0 +1,5 @@ +for it +do + path=$(expr "$it" : ( [^:]*) ) && + git update-index --add "$path" || exit +done diff --git a/t/chainlint/for-loop-abbreviated.test b/t/chainlint/for-loop-abbreviated.test new file mode 100644 index 0000000000..1084eccb89 --- /dev/null +++ b/t/chainlint/for-loop-abbreviated.test @@ -0,0 +1,6 @@ +# LINT: for-loop lacking optional "in [word...]" before "do" +for it +do + path=$(expr "$it" : '\([^:]*\)') && + git update-index --add "$path" || exit +done diff --git a/t/chainlint/for-loop.expect b/t/chainlint/for-loop.expect index 6671b8cd84..a5810c9bdd 100644 --- a/t/chainlint/for-loop.expect +++ b/t/chainlint/for-loop.expect @@ -2,10 +2,10 @@ for i in a b c do echo $i ?!AMP?! - cat <<-EOF + cat <<-EOF ?!LOOP?! done ?!AMP?! for i in a b c; do echo $i && - cat $i + cat $i ?!LOOP?! done ) diff --git a/t/chainlint/function.expect b/t/chainlint/function.expect new file mode 100644 index 0000000000..a14388e6b9 --- /dev/null +++ b/t/chainlint/function.expect @@ -0,0 +1,11 @@ +sha1_file ( ) { + echo "$*" | sed "s#..#.git/objects/&/#" +} && + +remove_object ( ) { + file=$(sha1_file "$*") && + test -e "$file" ?!AMP?! + rm -f "$file" +} ?!AMP?! + +sha1_file arg && remove_object arg diff --git a/t/chainlint/function.test b/t/chainlint/function.test new file mode 100644 index 0000000000..5ee59562c9 --- /dev/null +++ b/t/chainlint/function.test @@ -0,0 +1,13 @@ +# LINT: "()" in function definition not mistaken for subshell +sha1_file() { + echo "$*" | sed "s#..#.git/objects/&/#" +} && + +# LINT: broken &&-chain in function and after function +remove_object() { + file=$(sha1_file "$*") && + test -e "$file" + rm -f "$file" +} + +sha1_file arg && remove_object arg diff --git a/t/chainlint/here-doc-indent-operator.expect b/t/chainlint/here-doc-indent-operator.expect new file mode 100644 index 0000000000..fb6cf7285d --- /dev/null +++ b/t/chainlint/here-doc-indent-operator.expect @@ -0,0 +1,5 @@ +cat > expect <<-EOF && + +cat > expect <<-EOF ?!AMP?! + +cleanup diff --git a/t/chainlint/here-doc-indent-operator.test b/t/chainlint/here-doc-indent-operator.test new file mode 100644 index 0000000000..c8a6f18eb4 --- /dev/null +++ b/t/chainlint/here-doc-indent-operator.test @@ -0,0 +1,13 @@ +# LINT: whitespace between operator "<<-" and tag legal +cat >expect <<- EOF && +header: 43475048 1 $(test_oid oid_version) $NUM_CHUNKS 0 +num_commits: $1 +chunks: oid_fanout oid_lookup commit_metadata generation_data bloom_indexes bloom_data +EOF + +# LINT: not an indented here-doc; just a plain here-doc with tag named "-EOF" +cat >expect << -EOF +this is not indented +-EOF + +cleanup diff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect index 2578191ca8..be64b26869 100644 --- a/t/chainlint/here-doc-multi-line-string.expect +++ b/t/chainlint/here-doc-multi-line-string.expect @@ -1,4 +1,5 @@ ( - cat <<-TXT && echo "multi-line string" ?!AMP?! + cat <<-TXT && echo "multi-line + string" ?!AMP?! bap ) diff --git a/t/chainlint/if-condition-split.expect b/t/chainlint/if-condition-split.expect new file mode 100644 index 0000000000..ee745ef8d7 --- /dev/null +++ b/t/chainlint/if-condition-split.expect @@ -0,0 +1,7 @@ +if bob && + marcia || + kevin +then + echo "nomads" ?!AMP?! + echo "for sure" +fi diff --git a/t/chainlint/if-condition-split.test b/t/chainlint/if-condition-split.test new file mode 100644 index 0000000000..240daa9fd5 --- /dev/null +++ b/t/chainlint/if-condition-split.test @@ -0,0 +1,8 @@ +# LINT: "if" condition split across multiple lines at "&&" or "||" +if bob && + marcia || + kevin +then + echo "nomads" + echo "for sure" +fi diff --git a/t/chainlint/if-in-loop.expect b/t/chainlint/if-in-loop.expect index 03b82a3e58..d6514ae749 100644 --- a/t/chainlint/if-in-loop.expect +++ b/t/chainlint/if-in-loop.expect @@ -3,7 +3,7 @@ do if false then - echo "err" ?!AMP?! + echo "err" exit 1 fi ?!AMP?! foo diff --git a/t/chainlint/if-in-loop.test b/t/chainlint/if-in-loop.test index f0cf19cfad..90c23976fe 100644 --- a/t/chainlint/if-in-loop.test +++ b/t/chainlint/if-in-loop.test @@ -3,7 +3,7 @@ do if false then -# LINT: missing "&&" on "echo" +# LINT: missing "&&" on "echo" okay since "exit 1" signals error explicitly echo "err" exit 1 # LINT: missing "&&" on "fi" diff --git a/t/chainlint/loop-detect-failure.expect b/t/chainlint/loop-detect-failure.expect new file mode 100644 index 0000000000..a66025c39d --- /dev/null +++ b/t/chainlint/loop-detect-failure.expect @@ -0,0 +1,15 @@ +git init r1 && +for n in 1 2 3 4 5 +do + echo "This is file: $n" > r1/file.$n && + git -C r1 add file.$n && + git -C r1 commit -m "$n" || return 1 +done && + +git init r2 && +for n in 1000 10000 +do + printf "%"$n"s" X > r2/large.$n && + git -C r2 add large.$n && + git -C r2 commit -m "$n" ?!LOOP?! +done diff --git a/t/chainlint/loop-detect-failure.test b/t/chainlint/loop-detect-failure.test new file mode 100644 index 0000000000..b9791cc802 --- /dev/null +++ b/t/chainlint/loop-detect-failure.test @@ -0,0 +1,17 @@ +git init r1 && +# LINT: loop handles failure explicitly with "|| return 1" +for n in 1 2 3 4 5 +do + echo "This is file: $n" > r1/file.$n && + git -C r1 add file.$n && + git -C r1 commit -m "$n" || return 1 +done && + +git init r2 && +# LINT: loop fails to handle failure explicitly with "|| return 1" +for n in 1000 10000 +do + printf "%"$n"s" X > r2/large.$n && + git -C r2 add large.$n && + git -C r2 commit -m "$n" +done diff --git a/t/chainlint/loop-detect-status.expect b/t/chainlint/loop-detect-status.expect new file mode 100644 index 0000000000..0ad23bb35e --- /dev/null +++ b/t/chainlint/loop-detect-status.expect @@ -0,0 +1,18 @@ +( while test $i -le $blobcount +do + printf "Generating blob $i/$blobcount\r" >& 2 && + printf "blob\nmark :$i\ndata $blobsize\n" && + + printf "%-${blobsize}s" $i && + echo "M 100644 :$i $i" >> commit && + i=$(($i+1)) || + echo $? > exit-status +done && +echo "commit refs/heads/main" && +echo "author A U Thor <author@email.com> 123456789 +0000" && +echo "committer C O Mitter <committer@email.com> 123456789 +0000" && +echo "data 5" && +echo ">2gb" && +cat commit ) | +git fast-import --big-file-threshold=2 && +test ! -f exit-status diff --git a/t/chainlint/loop-detect-status.test b/t/chainlint/loop-detect-status.test new file mode 100644 index 0000000000..1c6c23cfc9 --- /dev/null +++ b/t/chainlint/loop-detect-status.test @@ -0,0 +1,19 @@ +# LINT: "$?" handled explicitly within loop body +(while test $i -le $blobcount + do + printf "Generating blob $i/$blobcount\r" >&2 && + printf "blob\nmark :$i\ndata $blobsize\n" && + #test-tool genrandom $i $blobsize && + printf "%-${blobsize}s" $i && + echo "M 100644 :$i $i" >> commit && + i=$(($i+1)) || + echo $? > exit-status + done && + echo "commit refs/heads/main" && + echo "author A U Thor <author@email.com> 123456789 +0000" && + echo "committer C O Mitter <committer@email.com> 123456789 +0000" && + echo "data 5" && + echo ">2gb" && + cat commit) | +git fast-import --big-file-threshold=2 && +test ! -f exit-status diff --git a/t/chainlint/loop-in-if.expect b/t/chainlint/loop-in-if.expect index e1be42376c..6c5d6e5b24 100644 --- a/t/chainlint/loop-in-if.expect +++ b/t/chainlint/loop-in-if.expect @@ -4,7 +4,7 @@ while true do echo "pop" ?!AMP?! - echo "glup" + echo "glup" ?!LOOP?! done ?!AMP?! foo fi ?!AMP?! diff --git a/t/chainlint/loop-upstream-pipe.expect b/t/chainlint/loop-upstream-pipe.expect new file mode 100644 index 0000000000..0b82ecc4b9 --- /dev/null +++ b/t/chainlint/loop-upstream-pipe.expect @@ -0,0 +1,10 @@ +( + git rev-list --objects --no-object-names base..loose | + while read oid + do + path="$objdir/$(test_oid_to_path "$oid")" && + printf "%s %d\n" "$oid" "$(test-tool chmtime --get "$path")" || + echo "object list generation failed for $oid" + done | + sort -k1 +) >expect && diff --git a/t/chainlint/loop-upstream-pipe.test b/t/chainlint/loop-upstream-pipe.test new file mode 100644 index 0000000000..efb77da897 --- /dev/null +++ b/t/chainlint/loop-upstream-pipe.test @@ -0,0 +1,11 @@ +( + git rev-list --objects --no-object-names base..loose | + while read oid + do +# LINT: "|| echo" signals failure in loop upstream of a pipe + path="$objdir/$(test_oid_to_path "$oid")" && + printf "%s %d\n" "$oid" "$(test-tool chmtime --get "$path")" || + echo "object list generation failed for $oid" + done | + sort -k1 +) >expect && diff --git a/t/chainlint/multi-line-string.expect b/t/chainlint/multi-line-string.expect index ab0dadf748..27ff95218e 100644 --- a/t/chainlint/multi-line-string.expect +++ b/t/chainlint/multi-line-string.expect @@ -1,9 +1,14 @@ ( - x="line 1 line 2 line 3" && - y="line 1 line2" ?!AMP?! + x="line 1 + line 2 + line 3" && + y="line 1 + line2" ?!AMP?! foobar ) && ( - echo "xyz" "abc def ghi" && + echo "xyz" "abc + def + ghi" && barfoo ) diff --git a/t/chainlint/nested-loop-detect-failure.expect b/t/chainlint/nested-loop-detect-failure.expect new file mode 100644 index 0000000000..4793a0e8e1 --- /dev/null +++ b/t/chainlint/nested-loop-detect-failure.expect @@ -0,0 +1,31 @@ +for i in 0 1 2 3 4 5 6 7 8 9 ; +do + for j in 0 1 2 3 4 5 6 7 8 9 ; + do + echo "$i$j" > "path$i$j" ?!LOOP?! + done ?!LOOP?! +done && + +for i in 0 1 2 3 4 5 6 7 8 9 ; +do + for j in 0 1 2 3 4 5 6 7 8 9 ; + do + echo "$i$j" > "path$i$j" || return 1 + done +done && + +for i in 0 1 2 3 4 5 6 7 8 9 ; +do + for j in 0 1 2 3 4 5 6 7 8 9 ; + do + echo "$i$j" > "path$i$j" ?!LOOP?! + done || return 1 +done && + +for i in 0 1 2 3 4 5 6 7 8 9 ; +do + for j in 0 1 2 3 4 5 6 7 8 9 ; + do + echo "$i$j" > "path$i$j" || return 1 + done || return 1 +done diff --git a/t/chainlint/nested-loop-detect-failure.test b/t/chainlint/nested-loop-detect-failure.test new file mode 100644 index 0000000000..e6f0c1acfb --- /dev/null +++ b/t/chainlint/nested-loop-detect-failure.test @@ -0,0 +1,35 @@ +# LINT: neither loop handles failure explicitly with "|| return 1" +for i in 0 1 2 3 4 5 6 7 8 9; +do + for j in 0 1 2 3 4 5 6 7 8 9; + do + echo "$i$j" >"path$i$j" + done +done && + +# LINT: inner loop handles failure explicitly with "|| return 1" +for i in 0 1 2 3 4 5 6 7 8 9; +do + for j in 0 1 2 3 4 5 6 7 8 9; + do + echo "$i$j" >"path$i$j" || return 1 + done +done && + +# LINT: outer loop handles failure explicitly with "|| return 1" +for i in 0 1 2 3 4 5 6 7 8 9; +do + for j in 0 1 2 3 4 5 6 7 8 9; + do + echo "$i$j" >"path$i$j" + done || return 1 +done && + +# LINT: inner & outer loops handles failure explicitly with "|| return 1" +for i in 0 1 2 3 4 5 6 7 8 9; +do + for j in 0 1 2 3 4 5 6 7 8 9; + do + echo "$i$j" >"path$i$j" || return 1 + done || return 1 +done diff --git a/t/chainlint/nested-subshell.expect b/t/chainlint/nested-subshell.expect index 41a48adaa2..02e0a9f1bb 100644 --- a/t/chainlint/nested-subshell.expect +++ b/t/chainlint/nested-subshell.expect @@ -6,7 +6,7 @@ ) >file && cd foo && ( - echo a + echo a ?!AMP?! echo b ) >file ) diff --git a/t/chainlint/one-liner-for-loop.expect b/t/chainlint/one-liner-for-loop.expect new file mode 100644 index 0000000000..51a3dc7c54 --- /dev/null +++ b/t/chainlint/one-liner-for-loop.expect @@ -0,0 +1,9 @@ +git init dir-rename-and-content && +( + cd dir-rename-and-content && + test_write_lines 1 2 3 4 5 >foo && + mkdir olddir && + for i in a b c; do echo $i >olddir/$i; ?!LOOP?! done ?!AMP?! + git add foo olddir && + git commit -m "original" && +) diff --git a/t/chainlint/one-liner-for-loop.test b/t/chainlint/one-liner-for-loop.test new file mode 100644 index 0000000000..4bd8c066c7 --- /dev/null +++ b/t/chainlint/one-liner-for-loop.test @@ -0,0 +1,10 @@ +git init dir-rename-and-content && +( + cd dir-rename-and-content && + test_write_lines 1 2 3 4 5 >foo && + mkdir olddir && +# LINT: one-liner for-loop missing "|| exit"; also broken &&-chain + for i in a b c; do echo $i >olddir/$i; done + git add foo olddir && + git commit -m "original" && +) diff --git a/t/chainlint/return-loop.expect b/t/chainlint/return-loop.expect new file mode 100644 index 0000000000..cfc0549bef --- /dev/null +++ b/t/chainlint/return-loop.expect @@ -0,0 +1,5 @@ +while test $i -lt $((num - 5)) +do + git notes add -m "notes for commit$i" HEAD~$i || return 1 + i=$((i + 1)) +done diff --git a/t/chainlint/return-loop.test b/t/chainlint/return-loop.test new file mode 100644 index 0000000000..f90b171300 --- /dev/null +++ b/t/chainlint/return-loop.test @@ -0,0 +1,6 @@ +while test $i -lt $((num - 5)) +do +# LINT: "|| return {n}" valid loop escape outside subshell; no "&&" needed + git notes add -m "notes for commit$i" HEAD~$i || return 1 + i=$((i + 1)) +done diff --git a/t/chainlint/semicolon.expect b/t/chainlint/semicolon.expect index ed0b3707ae..3aa2259f36 100644 --- a/t/chainlint/semicolon.expect +++ b/t/chainlint/semicolon.expect @@ -15,5 +15,5 @@ ) && (cd foo && for i in a b c; do - echo; + echo; ?!LOOP?! done) diff --git a/t/chainlint/sqstring-in-sqstring.expect b/t/chainlint/sqstring-in-sqstring.expect new file mode 100644 index 0000000000..cf0b591cf7 --- /dev/null +++ b/t/chainlint/sqstring-in-sqstring.expect @@ -0,0 +1,4 @@ +perl -e ' + defined($_ = -s $_) or die for @ARGV; + exit 1 if $ARGV[0] <= $ARGV[1]; +' test-2-$packname_2.pack test-3-$packname_3.pack diff --git a/t/chainlint/sqstring-in-sqstring.test b/t/chainlint/sqstring-in-sqstring.test new file mode 100644 index 0000000000..77a425e0c7 --- /dev/null +++ b/t/chainlint/sqstring-in-sqstring.test @@ -0,0 +1,5 @@ +# LINT: SQ-string Perl code fragment within SQ-string +perl -e '\'' + defined($_ = -s $_) or die for @ARGV; + exit 1 if $ARGV[0] <= $ARGV[1]; +'\'' test-2-$packname_2.pack test-3-$packname_3.pack diff --git a/t/chainlint/t7900-subtree.expect b/t/chainlint/t7900-subtree.expect index 1cccc7bf7e..69167da2f2 100644 --- a/t/chainlint/t7900-subtree.expect +++ b/t/chainlint/t7900-subtree.expect @@ -1,10 +1,17 @@ ( - chks="sub1sub2sub3sub4" && + chks="sub1 +sub2 +sub3 +sub4" && chks_sub=$(cat <<TXT | sed "s,^,sub dir/," ) && - chkms="main-sub1main-sub2main-sub3main-sub4" && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && chkms_sub=$(cat <<TXT | sed "s,^,sub dir/," ) && subfiles=$(git ls-files) && - check_equal "$subfiles" "$chkms$chks" + check_equal "$subfiles" "$chkms +$chks" ) diff --git a/t/chainlint/token-pasting.expect b/t/chainlint/token-pasting.expect new file mode 100644 index 0000000000..342360bcd0 --- /dev/null +++ b/t/chainlint/token-pasting.expect @@ -0,0 +1,27 @@ +git config filter.rot13.smudge ./rot13.sh && +git config filter.rot13.clean ./rot13.sh && + +{ + echo "*.t filter=rot13" ?!AMP?! + echo "*.i ident" +} > .gitattributes && + +{ + echo a b c d e f g h i j k l m ?!AMP?! + echo n o p q r s t u v w x y z ?!AMP?! + echo '$Id$' +} > test && +cat test > test.t && +cat test > test.o && +cat test > test.i && +git add test test.t test.i && +rm -f test test.t test.i && +git checkout -- test test.t test.i && + +echo "content-test2" > test2.o && +echo "content-test3 - filename with special characters" > "test3 'sq',$x=.o" ?!AMP?! + +downstream_url_for_sed=$( + printf "%sn" "$downstream_url" | + sed -e 's/\/\\/g' -e 's/[[/.*^$]/\&/g' +) diff --git a/t/chainlint/token-pasting.test b/t/chainlint/token-pasting.test new file mode 100644 index 0000000000..b4610ce815 --- /dev/null +++ b/t/chainlint/token-pasting.test @@ -0,0 +1,32 @@ +# LINT: single token; composite of multiple strings +git config filter.rot13.smudge ./rot13.sh && +git config filter.rot13.clean ./rot13.sh && + +{ + echo "*.t filter=rot13" + echo "*.i ident" +} >.gitattributes && + +{ + echo a b c d e f g h i j k l m + echo n o p q r s t u v w x y z +# LINT: exit/enter string context and escaped-quote outside of string + echo '\''$Id$'\'' +} >test && +cat test >test.t && +cat test >test.o && +cat test >test.i && +git add test test.t test.i && +rm -f test test.t test.i && +git checkout -- test test.t test.i && + +echo "content-test2" >test2.o && +# LINT: exit/enter string context and escaped-quote outside of string +echo "content-test3 - filename with special characters" >"test3 '\''sq'\'',\$x=.o" + +# LINT: single token; composite of multiple strings +downstream_url_for_sed=$( + printf "%s\n" "$downstream_url" | +# LINT: exit/enter string context; "&" inside string not command terminator + sed -e '\''s/\\/\\\\/g'\'' -e '\''s/[[/.*^$]/\\&/g'\'' +) diff --git a/t/chainlint/while-loop.expect b/t/chainlint/while-loop.expect index 0d3a9b3d12..f272aa21fe 100644 --- a/t/chainlint/while-loop.expect +++ b/t/chainlint/while-loop.expect @@ -2,10 +2,10 @@ while true do echo foo ?!AMP?! - cat <<-EOF + cat <<-EOF ?!LOOP?! done ?!AMP?! while true; do echo foo && - cat bar + cat bar ?!LOOP?! done ) diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index fd3303552b..dd8107cd7d 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -45,6 +45,7 @@ while (<>) { /\bhead\s+-c\b/ and err 'head -c is not portable (use test_copy_bytes BYTES <file >out)'; /(?:\$\(seq|^\s*seq\b)/ and err 'seq is not portable (use test_seq)'; /\bgrep\b.*--file\b/ and err 'grep --file FILE is not portable (use grep -f FILE)'; + /\b[ef]grep\b/ and err 'egrep/fgrep obsolescent (use grep -E/-F)'; /\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (use FOO=bar && export FOO)'; /^\s*([A-Z0-9_]+=(\w*|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and err '"FOO=bar shell_func" assignment extends beyond "shell_func"'; diff --git a/t/helper/test-bundle-uri.c b/t/helper/test-bundle-uri.c new file mode 100644 index 0000000000..25afd39342 --- /dev/null +++ b/t/helper/test-bundle-uri.c @@ -0,0 +1,95 @@ +#include "test-tool.h" +#include "parse-options.h" +#include "bundle-uri.h" +#include "strbuf.h" +#include "string-list.h" + +enum input_mode { + KEY_VALUE_PAIRS, + CONFIG_FILE, +}; + +static int cmd__bundle_uri_parse(int argc, const char **argv, enum input_mode mode) +{ + const char *key_value_usage[] = { + "test-tool bundle-uri parse-key-values <input>", + NULL + }; + const char *config_usage[] = { + "test-tool bundle-uri parse-config <input>", + NULL + }; + const char **usage = key_value_usage; + struct option options[] = { + OPT_END(), + }; + struct strbuf sb = STRBUF_INIT; + struct bundle_list list; + int err = 0; + FILE *fp; + + if (mode == CONFIG_FILE) + usage = config_usage; + + argc = parse_options(argc, argv, NULL, options, usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + init_bundle_list(&list); + + switch (mode) { + case KEY_VALUE_PAIRS: + if (argc != 1) + goto usage; + fp = fopen(argv[0], "r"); + if (!fp) + die("failed to open '%s'", argv[0]); + while (strbuf_getline(&sb, fp) != EOF) { + if (bundle_uri_parse_line(&list, sb.buf)) + err = error("bad line: '%s'", sb.buf); + } + fclose(fp); + break; + + case CONFIG_FILE: + if (argc != 1) + goto usage; + err = bundle_uri_parse_config_format("<uri>", argv[0], &list); + break; + } + strbuf_release(&sb); + + print_bundle_list(stdout, &list); + + clear_bundle_list(&list); + + return !!err; + +usage: + usage_with_options(usage, options); +} + +int cmd__bundle_uri(int argc, const char **argv) +{ + const char *usage[] = { + "test-tool bundle-uri <subcommand> [<options>]", + NULL + }; + struct option options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, options, usage, + PARSE_OPT_STOP_AT_NON_OPTION | + PARSE_OPT_KEEP_ARGV0); + if (argc == 1) + goto usage; + + if (!strcmp(argv[1], "parse-key-values")) + return cmd__bundle_uri_parse(argc - 1, argv + 1, KEY_VALUE_PAIRS); + if (!strcmp(argv[1], "parse-config")) + return cmd__bundle_uri_parse(argc - 1, argv + 1, CONFIG_FILE); + error("there is no test-tool bundle-uri tool '%s'", argv[1]); + +usage: + usage_with_options(usage, options); +} diff --git a/t/helper/test-config.c b/t/helper/test-config.c index a6e936721f..4ba9eb6560 100644 --- a/t/helper/test-config.c +++ b/t/helper/test-config.c @@ -37,7 +37,7 @@ * */ -static int iterate_cb(const char *var, const char *value, void *data) +static int iterate_cb(const char *var, const char *value, void *data UNUSED) { static int nr; diff --git a/t/helper/test-crontab.c b/t/helper/test-crontab.c index e7c0137a47..e6c1b1e22b 100644 --- a/t/helper/test-crontab.c +++ b/t/helper/test-crontab.c @@ -2,33 +2,34 @@ #include "cache.h" /* - * Usage: test-tool cron <file> [-l] + * Usage: test-tool crontab <file> -l|<input> * * If -l is specified, then write the contents of <file> to stdout. - * Otherwise, write from stdin into <file>. + * Otherwise, copy the contents of <input> into <file>. */ int cmd__crontab(int argc, const char **argv) { int a; FILE *from, *to; - if (argc == 3 && !strcmp(argv[2], "-l")) { + if (argc != 3) + usage("test-tool crontab <file> -l|<input>"); + + if (!strcmp(argv[2], "-l")) { from = fopen(argv[1], "r"); if (!from) return 0; to = stdout; - } else if (argc == 2) { - from = stdin; - to = fopen(argv[1], "w"); - } else - return error("unknown arguments"); + } else { + from = xfopen(argv[2], "r"); + to = xfopen(argv[1], "w"); + } while ((a = fgetc(from)) != EOF) fputc(a, to); - if (argc == 3) - fclose(from); - else + fclose(from); + if (to != stdout) fclose(to); return 0; diff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c index 4e5553e202..45665ec19a 100644 --- a/t/helper/test-fast-rebase.c +++ b/t/helper/test-fast-rebase.c @@ -184,8 +184,6 @@ int cmd__fast_rebase(int argc, const char **argv) last_picked_commit = commit; last_commit = create_commit(result.tree, commit, last_commit); } - /* TODO: There should be some kind of rev_info_free(&revs) call... */ - memset(&revs, 0, sizeof(revs)); merge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean); diff --git a/t/helper/test-mergesort.c b/t/helper/test-mergesort.c index 202e54a7ff..335e5bb3a9 100644 --- a/t/helper/test-mergesort.c +++ b/t/helper/test-mergesort.c @@ -22,21 +22,35 @@ static int compare_strings(const struct line *x, const struct line *y) static int sort_stdin(void) { - struct line *line, *p = NULL, *lines = NULL; + struct line *lines; + struct line **tail = &lines; struct strbuf sb = STRBUF_INIT; - - while (!strbuf_getline(&sb, stdin)) { - line = xmalloc(sizeof(struct line)); - line->text = strbuf_detach(&sb, NULL); - if (p) { - line->next = p->next; - p->next = line; - } else { - line->next = NULL; - lines = line; - } - p = line; + struct mem_pool lines_pool; + char *p; + + strbuf_read(&sb, 0, 0); + + /* + * Split by newline, but don't create an item + * for the empty string after the last separator. + */ + if (sb.len && sb.buf[sb.len - 1] == '\n') + strbuf_setlen(&sb, sb.len - 1); + + mem_pool_init(&lines_pool, 0); + p = sb.buf; + for (;;) { + char *eol = strchr(p, '\n'); + struct line *line = mem_pool_alloc(&lines_pool, sizeof(*line)); + line->text = p; + *tail = line; + tail = &line->next; + if (!eol) + break; + *eol = '\0'; + p = eol + 1; } + *tail = NULL; sort_lines(&lines, compare_strings); diff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c index 48d3cf6692..506835521a 100644 --- a/t/helper/test-parse-options.c +++ b/t/helper/test-parse-options.c @@ -192,3 +192,131 @@ int cmd__parse_options(int argc, const char **argv) return ret; } + +static void print_args(int argc, const char **argv) +{ + int i; + for (i = 0; i < argc; i++) + printf("arg %02d: %s\n", i, argv[i]); +} + +static int parse_options_flags__cmd(int argc, const char **argv, + enum parse_opt_flags test_flags) +{ + const char *usage[] = { + "<...> cmd [options]", + NULL + }; + int opt = 0; + const struct option options[] = { + OPT_INTEGER('o', "opt", &opt, "an integer option"), + OPT_END() + }; + + argc = parse_options(argc, argv, NULL, options, usage, test_flags); + + printf("opt: %d\n", opt); + print_args(argc, argv); + + return 0; +} + +static enum parse_opt_flags test_flags = 0; +static const struct option test_flag_options[] = { + OPT_GROUP("flag-options:"), + OPT_BIT(0, "keep-dashdash", &test_flags, + "pass PARSE_OPT_KEEP_DASHDASH to parse_options()", + PARSE_OPT_KEEP_DASHDASH), + OPT_BIT(0, "stop-at-non-option", &test_flags, + "pass PARSE_OPT_STOP_AT_NON_OPTION to parse_options()", + PARSE_OPT_STOP_AT_NON_OPTION), + OPT_BIT(0, "keep-argv0", &test_flags, + "pass PARSE_OPT_KEEP_ARGV0 to parse_options()", + PARSE_OPT_KEEP_ARGV0), + OPT_BIT(0, "keep-unknown-opt", &test_flags, + "pass PARSE_OPT_KEEP_UNKNOWN_OPT to parse_options()", + PARSE_OPT_KEEP_UNKNOWN_OPT), + OPT_BIT(0, "no-internal-help", &test_flags, + "pass PARSE_OPT_NO_INTERNAL_HELP to parse_options()", + PARSE_OPT_NO_INTERNAL_HELP), + OPT_BIT(0, "subcommand-optional", &test_flags, + "pass PARSE_OPT_SUBCOMMAND_OPTIONAL to parse_options()", + PARSE_OPT_SUBCOMMAND_OPTIONAL), + OPT_END() +}; + +int cmd__parse_options_flags(int argc, const char **argv) +{ + const char *usage[] = { + "test-tool parse-options-flags [flag-options] cmd [options]", + NULL + }; + + argc = parse_options(argc, argv, NULL, test_flag_options, usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (!argc || strcmp(argv[0], "cmd")) { + error("'cmd' is mandatory"); + usage_with_options(usage, test_flag_options); + } + + return parse_options_flags__cmd(argc, argv, test_flags); +} + +static int subcmd_one(int argc, const char **argv, const char *prefix) +{ + printf("fn: subcmd_one\n"); + print_args(argc, argv); + return 0; +} + +static int subcmd_two(int argc, const char **argv, const char *prefix) +{ + printf("fn: subcmd_two\n"); + print_args(argc, argv); + return 0; +} + +static int parse_subcommand__cmd(int argc, const char **argv, + enum parse_opt_flags test_flags) +{ + const char *usage[] = { + "<...> cmd subcmd-one", + "<...> cmd subcmd-two", + NULL + }; + parse_opt_subcommand_fn *fn = NULL; + int opt = 0; + struct option options[] = { + OPT_SUBCOMMAND("subcmd-one", &fn, subcmd_one), + OPT_SUBCOMMAND("subcmd-two", &fn, subcmd_two), + OPT_INTEGER('o', "opt", &opt, "an integer option"), + OPT_END() + }; + + if (test_flags & PARSE_OPT_SUBCOMMAND_OPTIONAL) + fn = subcmd_one; + argc = parse_options(argc, argv, NULL, options, usage, test_flags); + + printf("opt: %d\n", opt); + + return fn(argc, argv, NULL); +} + +int cmd__parse_subcommand(int argc, const char **argv) +{ + const char *usage[] = { + "test-tool parse-subcommand [flag-options] cmd <subcommand>", + NULL + }; + + argc = parse_options(argc, argv, NULL, test_flag_options, usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (!argc || strcmp(argv[0], "cmd")) { + error("'cmd' is mandatory"); + usage_with_options(usage, test_flag_options); + } + + return parse_subcommand__cmd(argc, argv, test_flags); +} diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index d20e1b7a18..f69709d674 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -8,7 +8,8 @@ * GIT_CEILING_DIRECTORIES. If the path is unusable for some reason, * die with an explanation. */ -static int normalize_ceiling_entry(struct string_list_item *item, void *unused) +static int normalize_ceiling_entry(struct string_list_item *item, + void *data UNUSED) { char *ceil = item->string; diff --git a/t/helper/test-proc-receive.c b/t/helper/test-proc-receive.c index cc08506cf0..a4b305f494 100644 --- a/t/helper/test-proc-receive.c +++ b/t/helper/test-proc-receive.c @@ -6,7 +6,7 @@ #include "test-tool.h" static const char *proc_receive_usage[] = { - "test-tool proc-receive [<options>...]", + "test-tool proc-receive [<options>]", NULL }; diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 4d18bfb1ca..ae8a5648da 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -161,7 +161,7 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv) } static int each_ref(const char *refname, const struct object_id *oid, - int flags, void *cb_data) + int flags, void *cb_data UNUSED) { printf("%s %s 0x%x\n", oid_to_hex(oid), refname, flags); return 0; @@ -207,7 +207,7 @@ static int cmd_for_each_reflog(struct ref_store *refs, const char **argv) static int each_reflog(struct object_id *old_oid, struct object_id *new_oid, const char *committer, timestamp_t timestamp, - int tz, const char *msg, void *cb_data) + int tz, const char *msg, void *cb_data UNUSED) { printf("%s %s %s %" PRItime " %+05d%s%s", oid_to_hex(old_oid), oid_to_hex(new_oid), committer, timestamp, tz, diff --git a/t/helper/test-rot13-filter.c b/t/helper/test-rot13-filter.c new file mode 100644 index 0000000000..f8d564c622 --- /dev/null +++ b/t/helper/test-rot13-filter.c @@ -0,0 +1,382 @@ +/* + * Example implementation for the Git filter protocol version 2 + * See Documentation/gitattributes.txt, section "Filter Protocol" + * + * Usage: test-tool rot13-filter [--always-delay] --log=<path> <capabilities> + * + * Log path defines a debug log file that the script writes to. The + * subsequent arguments define a list of supported protocol capabilities + * ("clean", "smudge", etc). + * + * When --always-delay is given all pathnames with the "can-delay" flag + * that don't appear on the list bellow are delayed with a count of 1 + * (see more below). + * + * This implementation supports special test cases: + * (1) If data with the pathname "clean-write-fail.r" is processed with + * a "clean" operation then the write operation will die. + * (2) If data with the pathname "smudge-write-fail.r" is processed with + * a "smudge" operation then the write operation will die. + * (3) If data with the pathname "error.r" is processed with any + * operation then the filter signals that it cannot or does not want + * to process the file. + * (4) If data with the pathname "abort.r" is processed with any + * operation then the filter signals that it cannot or does not want + * to process the file and any file after that is processed with the + * same command. + * (5) If data with a pathname that is a key in the delay hash is + * requested (e.g. "test-delay10.a") then the filter responds with + * a "delay" status and sets the "requested" field in the delay hash. + * The filter will signal the availability of this object after + * "count" (field in delay hash) "list_available_blobs" commands. + * (6) If data with the pathname "missing-delay.a" is processed that the + * filter will drop the path from the "list_available_blobs" response. + * (7) If data with the pathname "invalid-delay.a" is processed that the + * filter will add the path "unfiltered" which was not delayed before + * to the "list_available_blobs" response. + */ + +#include "test-tool.h" +#include "pkt-line.h" +#include "string-list.h" +#include "strmap.h" +#include "parse-options.h" + +static FILE *logfile; +static int always_delay, has_clean_cap, has_smudge_cap; +static struct strmap delay = STRMAP_INIT; + +static inline const char *str_or_null(const char *str) +{ + return str ? str : "(null)"; +} + +static char *rot13(char *str) +{ + char *c; + for (c = str; *c; c++) + if (isalpha(*c)) + *c += tolower(*c) < 'n' ? 13 : -13; + return str; +} + +static char *get_value(char *buf, const char *key) +{ + const char *orig_buf = buf; + if (!buf || + !skip_prefix((const char *)buf, key, (const char **)&buf) || + !skip_prefix((const char *)buf, "=", (const char **)&buf) || + !*buf) + die("expected key '%s', got '%s'", key, str_or_null(orig_buf)); + return buf; +} + +/* + * Read a text packet, expecting that it is in the form "key=value" for + * the given key. An EOF does not trigger any error and is reported + * back to the caller with NULL. Die if the "key" part of "key=value" does + * not match the given key, or the value part is empty. + */ +static char *packet_key_val_read(const char *key) +{ + char *buf; + if (packet_read_line_gently(0, NULL, &buf) < 0) + return NULL; + return xstrdup(get_value(buf, key)); +} + +static inline void assert_remote_capability(struct strset *caps, const char *cap) +{ + if (!strset_contains(caps, cap)) + die("required '%s' capability not available from remote", cap); +} + +static void read_capabilities(struct strset *remote_caps) +{ + for (;;) { + char *buf = packet_read_line(0, NULL); + if (!buf) + break; + strset_add(remote_caps, get_value(buf, "capability")); + } + + assert_remote_capability(remote_caps, "clean"); + assert_remote_capability(remote_caps, "smudge"); + assert_remote_capability(remote_caps, "delay"); +} + +static void check_and_write_capabilities(struct strset *remote_caps, + const char **caps, int nr_caps) +{ + int i; + for (i = 0; i < nr_caps; i++) { + if (!strset_contains(remote_caps, caps[i])) + die("our capability '%s' is not available from remote", + caps[i]); + packet_write_fmt(1, "capability=%s\n", caps[i]); + } + packet_flush(1); +} + +struct delay_entry { + int requested, count; + char *output; +}; + +static void free_delay_entries(void) +{ + struct hashmap_iter iter; + struct strmap_entry *ent; + + strmap_for_each_entry(&delay, &iter, ent) { + struct delay_entry *delay_entry = ent->value; + free(delay_entry->output); + free(delay_entry); + } + strmap_clear(&delay, 0); +} + +static void add_delay_entry(char *pathname, int count, int requested) +{ + struct delay_entry *entry = xcalloc(1, sizeof(*entry)); + entry->count = count; + entry->requested = requested; + if (strmap_put(&delay, pathname, entry)) + BUG("adding the same path twice to delay hash?"); +} + +static void reply_list_available_blobs_cmd(void) +{ + struct hashmap_iter iter; + struct strmap_entry *ent; + struct string_list_item *str_item; + struct string_list paths = STRING_LIST_INIT_NODUP; + + /* flush */ + if (packet_read_line(0, NULL)) + die("bad list_available_blobs end"); + + strmap_for_each_entry(&delay, &iter, ent) { + struct delay_entry *delay_entry = ent->value; + if (!delay_entry->requested) + continue; + delay_entry->count--; + if (!strcmp(ent->key, "invalid-delay.a")) { + /* Send Git a pathname that was not delayed earlier */ + packet_write_fmt(1, "pathname=unfiltered"); + } + if (!strcmp(ent->key, "missing-delay.a")) { + /* Do not signal Git that this file is available */ + } else if (!delay_entry->count) { + string_list_append(&paths, ent->key); + packet_write_fmt(1, "pathname=%s", ent->key); + } + } + + /* Print paths in sorted order. */ + string_list_sort(&paths); + for_each_string_list_item(str_item, &paths) + fprintf(logfile, " %s", str_item->string); + string_list_clear(&paths, 0); + + packet_flush(1); + + fprintf(logfile, " [OK]\n"); + packet_write_fmt(1, "status=success"); + packet_flush(1); +} + +static void command_loop(void) +{ + for (;;) { + char *buf, *output; + char *pathname; + struct delay_entry *entry; + struct strbuf input = STRBUF_INIT; + char *command = packet_key_val_read("command"); + + if (!command) { + fprintf(logfile, "STOP\n"); + break; + } + fprintf(logfile, "IN: %s", command); + + if (!strcmp(command, "list_available_blobs")) { + reply_list_available_blobs_cmd(); + free(command); + continue; + } + + pathname = packet_key_val_read("pathname"); + if (!pathname) + die("unexpected EOF while expecting pathname"); + fprintf(logfile, " %s", pathname); + + /* Read until flush */ + while ((buf = packet_read_line(0, NULL))) { + if (!strcmp(buf, "can-delay=1")) { + entry = strmap_get(&delay, pathname); + if (entry && !entry->requested) + entry->requested = 1; + else if (!entry && always_delay) + add_delay_entry(pathname, 1, 1); + } else if (starts_with(buf, "ref=") || + starts_with(buf, "treeish=") || + starts_with(buf, "blob=")) { + fprintf(logfile, " %s", buf); + } else { + /* + * In general, filters need to be graceful about + * new metadata, since it's documented that we + * can pass any key-value pairs, but for tests, + * let's be a little stricter. + */ + die("Unknown message '%s'", buf); + } + } + + read_packetized_to_strbuf(0, &input, 0); + fprintf(logfile, " %"PRIuMAX" [OK] -- ", (uintmax_t)input.len); + + entry = strmap_get(&delay, pathname); + if (entry && entry->output) { + output = entry->output; + } else if (!strcmp(pathname, "error.r") || !strcmp(pathname, "abort.r")) { + output = ""; + } else if (!strcmp(command, "clean") && has_clean_cap) { + output = rot13(input.buf); + } else if (!strcmp(command, "smudge") && has_smudge_cap) { + output = rot13(input.buf); + } else { + die("bad command '%s'", command); + } + + if (!strcmp(pathname, "error.r")) { + fprintf(logfile, "[ERROR]\n"); + packet_write_fmt(1, "status=error"); + packet_flush(1); + } else if (!strcmp(pathname, "abort.r")) { + fprintf(logfile, "[ABORT]\n"); + packet_write_fmt(1, "status=abort"); + packet_flush(1); + } else if (!strcmp(command, "smudge") && + (entry = strmap_get(&delay, pathname)) && + entry->requested == 1) { + fprintf(logfile, "[DELAYED]\n"); + packet_write_fmt(1, "status=delayed"); + packet_flush(1); + entry->requested = 2; + if (entry->output != output) { + free(entry->output); + entry->output = xstrdup(output); + } + } else { + int i, nr_packets = 0; + size_t output_len; + const char *p; + packet_write_fmt(1, "status=success"); + packet_flush(1); + + if (skip_prefix(pathname, command, &p) && + !strcmp(p, "-write-fail.r")) { + fprintf(logfile, "[WRITE FAIL]\n"); + die("%s write error", command); + } + + output_len = strlen(output); + fprintf(logfile, "OUT: %"PRIuMAX" ", (uintmax_t)output_len); + + if (write_packetized_from_buf_no_flush_count(output, + output_len, 1, &nr_packets)) + die("failed to write buffer to stdout"); + packet_flush(1); + + for (i = 0; i < nr_packets; i++) + fprintf(logfile, "."); + fprintf(logfile, " [OK]\n"); + + packet_flush(1); + } + free(pathname); + strbuf_release(&input); + free(command); + } +} + +static void packet_initialize(void) +{ + char *pkt_buf = packet_read_line(0, NULL); + + if (!pkt_buf || strcmp(pkt_buf, "git-filter-client")) + die("bad initialize: '%s'", str_or_null(pkt_buf)); + + pkt_buf = packet_read_line(0, NULL); + if (!pkt_buf || strcmp(pkt_buf, "version=2")) + die("bad version: '%s'", str_or_null(pkt_buf)); + + pkt_buf = packet_read_line(0, NULL); + if (pkt_buf) + die("bad version end: '%s'", pkt_buf); + + packet_write_fmt(1, "git-filter-server"); + packet_write_fmt(1, "version=2"); + packet_flush(1); +} + +static const char *rot13_usage[] = { + "test-tool rot13-filter [--always-delay] --log=<path> <capabilities>", + NULL +}; + +int cmd__rot13_filter(int argc, const char **argv) +{ + int i, nr_caps; + struct strset remote_caps = STRSET_INIT; + const char *log_path = NULL; + + struct option options[] = { + OPT_BOOL(0, "always-delay", &always_delay, + "delay all paths with the can-delay flag"), + OPT_STRING(0, "log", &log_path, "path", + "path to the debug log file"), + OPT_END() + }; + nr_caps = parse_options(argc, argv, NULL, options, rot13_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (!log_path || !nr_caps) + usage_with_options(rot13_usage, options); + + logfile = fopen(log_path, "a"); + if (!logfile) + die_errno("failed to open log file"); + + for (i = 0; i < nr_caps; i++) { + if (!strcmp(argv[i], "smudge")) + has_smudge_cap = 1; + if (!strcmp(argv[i], "clean")) + has_clean_cap = 1; + } + + add_delay_entry("test-delay10.a", 1, 0); + add_delay_entry("test-delay11.a", 1, 0); + add_delay_entry("test-delay20.a", 2, 0); + add_delay_entry("test-delay10.b", 1, 0); + add_delay_entry("missing-delay.a", 1, 0); + add_delay_entry("invalid-delay.a", 1, 0); + + fprintf(logfile, "START\n"); + packet_initialize(); + + read_capabilities(&remote_caps); + check_and_write_capabilities(&remote_caps, argv, nr_caps); + fprintf(logfile, "init handshake complete\n"); + strset_clear(&remote_caps); + + command_loop(); + + if (fclose(logfile)) + die_errno("error closing logfile"); + free_delay_entries(); + return 0; +} diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index c9283b47af..3ecb830f4a 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c @@ -136,7 +136,7 @@ static const char * const testsuite_usage[] = { static int testsuite(int argc, const char **argv) { struct testsuite suite = TESTSUITE_INIT; - int max_jobs = 1, i, ret; + int max_jobs = 1, i, ret = 0; DIR *dir; struct dirent *d; struct option options[] = { @@ -152,6 +152,12 @@ static int testsuite(int argc, const char **argv) "write JUnit-style XML files"), OPT_END() }; + struct run_process_parallel_opts opts = { + .get_next_task = next_test, + .start_failure = test_failed, + .task_finished = test_finished, + .data = &suite, + }; argc = parse_options(argc, argv, NULL, options, testsuite_usage, PARSE_OPT_STOP_AT_NON_OPTION); @@ -192,8 +198,8 @@ static int testsuite(int argc, const char **argv) fprintf(stderr, "Running %"PRIuMAX" tests (%d at a time)\n", (uintmax_t)suite.tests.nr, max_jobs); - ret = run_processes_parallel(max_jobs, next_test, test_failed, - test_finished, &suite); + opts.processes = max_jobs; + run_processes_parallel(&opts); if (suite.failed.nr > 0) { ret = 1; @@ -206,7 +212,7 @@ static int testsuite(int argc, const char **argv) string_list_clear(&suite.tests, 0); string_list_clear(&suite.failed, 0); - return !!ret; + return ret; } static uint64_t my_random_next = 1234; @@ -381,13 +387,17 @@ int cmd__run_command(int argc, const char **argv) { struct child_process proc = CHILD_PROCESS_INIT; int jobs; + int ret; + struct run_process_parallel_opts opts = { + .data = &proc, + }; if (argc > 1 && !strcmp(argv[1], "testsuite")) - exit(testsuite(argc - 1, argv + 1)); + return testsuite(argc - 1, argv + 1); if (!strcmp(argv[1], "inherited-handle")) - exit(inherit_handle(argv[0])); + return inherit_handle(argv[0]); if (!strcmp(argv[1], "inherited-handle-child")) - exit(inherit_handle_child()); + return inherit_handle_child(); if (argc >= 2 && !strcmp(argv[1], "quote-stress-test")) return !!quote_stress_test(argc - 1, argv + 1); @@ -404,41 +414,52 @@ int cmd__run_command(int argc, const char **argv) argv += 2; argc -= 2; } - if (argc < 3) - return 1; + if (argc < 3) { + ret = 1; + goto cleanup; + } strvec_pushv(&proc.args, (const char **)argv + 2); if (!strcmp(argv[1], "start-command-ENOENT")) { - if (start_command(&proc) < 0 && errno == ENOENT) - return 0; + if (start_command(&proc) < 0 && errno == ENOENT) { + ret = 0; + goto cleanup; + } fprintf(stderr, "FAIL %s\n", argv[1]); return 1; } - if (!strcmp(argv[1], "run-command")) - exit(run_command(&proc)); + if (!strcmp(argv[1], "run-command")) { + ret = run_command(&proc); + goto cleanup; + } if (!strcmp(argv[1], "--ungroup")) { argv += 1; argc -= 1; - run_processes_parallel_ungroup = 1; + opts.ungroup = 1; } jobs = atoi(argv[2]); strvec_clear(&proc.args); strvec_pushv(&proc.args, (const char **)argv + 3); - if (!strcmp(argv[1], "run-command-parallel")) - exit(run_processes_parallel(jobs, parallel_next, - NULL, NULL, &proc)); - - if (!strcmp(argv[1], "run-command-abort")) - exit(run_processes_parallel(jobs, parallel_next, - NULL, task_finished, &proc)); - - if (!strcmp(argv[1], "run-command-no-jobs")) - exit(run_processes_parallel(jobs, no_job, - NULL, task_finished, &proc)); - - fprintf(stderr, "check usage\n"); - return 1; + if (!strcmp(argv[1], "run-command-parallel")) { + opts.get_next_task = parallel_next; + } else if (!strcmp(argv[1], "run-command-abort")) { + opts.get_next_task = parallel_next; + opts.task_finished = task_finished; + } else if (!strcmp(argv[1], "run-command-no-jobs")) { + opts.get_next_task = no_job; + opts.task_finished = task_finished; + } else { + ret = 1; + fprintf(stderr, "check usage\n"); + goto cleanup; + } + opts.processes = jobs; + run_processes_parallel(&opts); + ret = 0; +cleanup: + child_process_clear(&proc); + return ret; } diff --git a/t/helper/test-serve-v2.c b/t/helper/test-serve-v2.c index 28e905afc3..824e5c0a95 100644 --- a/t/helper/test-serve-v2.c +++ b/t/helper/test-serve-v2.c @@ -24,7 +24,7 @@ int cmd__serve_v2(int argc, const char **argv) /* ignore all unknown cmdline switches for now */ argc = parse_options(argc, argv, prefix, options, serve_usage, PARSE_OPT_KEEP_DASHDASH | - PARSE_OPT_KEEP_UNKNOWN); + PARSE_OPT_KEEP_UNKNOWN_OPT); if (advertise_capabilities) protocol_v2_advertise_capabilities(); diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c index e2692746df..22a41c4092 100644 --- a/t/helper/test-submodule-config.c +++ b/t/helper/test-submodule-config.c @@ -15,14 +15,11 @@ int cmd__submodule_config(int argc, const char **argv) { const char **arg = argv; int my_argc = argc; - int output_url = 0; int lookup_name = 0; arg++; my_argc--; while (arg[0] && starts_with(arg[0], "--")) { - if (!strcmp(arg[0], "--url")) - output_url = 1; if (!strcmp(arg[0], "--name")) lookup_name = 1; arg++; @@ -57,12 +54,8 @@ int cmd__submodule_config(int argc, const char **argv) if (!submodule) die_usage(argc, argv, "Submodule not found."); - if (output_url) - printf("Submodule url: '%s' for path '%s'\n", - submodule->url, submodule->path); - else - printf("Submodule name: '%s' for path '%s'\n", - submodule->name, submodule->path); + printf("Submodule name: '%s' for path '%s'\n", submodule->name, + submodule->path); arg += 2; } diff --git a/t/helper/test-submodule.c b/t/helper/test-submodule.c new file mode 100644 index 0000000000..b7d117cd55 --- /dev/null +++ b/t/helper/test-submodule.c @@ -0,0 +1,140 @@ +#include "test-tool.h" +#include "test-tool-utils.h" +#include "cache.h" +#include "parse-options.h" +#include "remote.h" +#include "submodule-config.h" +#include "submodule.h" + +#define TEST_TOOL_CHECK_NAME_USAGE \ + "test-tool submodule check-name <name>" +static const char *submodule_check_name_usage[] = { + TEST_TOOL_CHECK_NAME_USAGE, + NULL +}; + +#define TEST_TOOL_IS_ACTIVE_USAGE \ + "test-tool submodule is-active <name>" +static const char *submodule_is_active_usage[] = { + TEST_TOOL_IS_ACTIVE_USAGE, + NULL +}; + +#define TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE \ + "test-tool submodule resolve-relative-url <up_path> <remoteurl> <url>" +static const char *submodule_resolve_relative_url_usage[] = { + TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE, + NULL, +}; + +static const char *submodule_usage[] = { + TEST_TOOL_CHECK_NAME_USAGE, + TEST_TOOL_IS_ACTIVE_USAGE, + TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE, + NULL +}; + +/* + * Exit non-zero if any of the submodule names given on the command line is + * invalid. If no names are given, filter stdin to print only valid names + * (which is primarily intended for testing). + */ +static int check_name(int argc, const char **argv) +{ + if (argc > 1) { + while (*++argv) { + if (check_submodule_name(*argv) < 0) + return 1; + } + } else { + struct strbuf buf = STRBUF_INIT; + while (strbuf_getline(&buf, stdin) != EOF) { + if (!check_submodule_name(buf.buf)) + printf("%s\n", buf.buf); + } + strbuf_release(&buf); + } + return 0; +} + +static int cmd__submodule_check_name(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, "test-tools", options, + submodule_check_name_usage, 0); + if (argc) + usage_with_options(submodule_check_name_usage, options); + + return check_name(argc, argv); +} + +static int cmd__submodule_is_active(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, "test-tools", options, + submodule_is_active_usage, 0); + if (argc != 1) + usage_with_options(submodule_is_active_usage, options); + + setup_git_directory(); + + return !is_submodule_active(the_repository, argv[0]); +} + +static int cmd__submodule_resolve_relative_url(int argc, const char **argv) +{ + char *remoteurl, *res; + const char *up_path, *url; + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, "test-tools", options, + submodule_resolve_relative_url_usage, 0); + if (argc != 3) + usage_with_options(submodule_resolve_relative_url_usage, options); + + up_path = argv[0]; + remoteurl = xstrdup(argv[1]); + url = argv[2]; + + if (!strcmp(up_path, "(null)")) + up_path = NULL; + + res = relative_url(remoteurl, url, up_path); + puts(res); + free(res); + free(remoteurl); + return 0; +} + +static struct test_cmd cmds[] = { + { "check-name", cmd__submodule_check_name }, + { "is-active", cmd__submodule_is_active }, + { "resolve-relative-url", cmd__submodule_resolve_relative_url}, +}; + +int cmd__submodule(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + size_t i; + + argc = parse_options(argc, argv, "test-tools", options, submodule_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + if (argc < 1) + usage_with_options(submodule_usage, options); + + for (i = 0; i < ARRAY_SIZE(cmds); i++) + if (!strcmp(cmds[i].name, argv[0])) + return cmds[i].fn(argc, argv); + + usage_msg_optf("unknown subcommand '%s'", submodule_usage, options, + argv[0]); + + return 0; +} diff --git a/t/helper/test-tool-utils.h b/t/helper/test-tool-utils.h new file mode 100644 index 0000000000..6a0e5e0074 --- /dev/null +++ b/t/helper/test-tool-utils.h @@ -0,0 +1,9 @@ +#ifndef TEST_TOOL_UTILS_H +#define TEST_TOOL_UTILS_H + +struct test_cmd { + const char *name; + int (*fn)(int argc, const char **argv); +}; + +#endif diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 318fdbab0c..01cda9358d 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -1,5 +1,6 @@ #include "git-compat-util.h" #include "test-tool.h" +#include "test-tool-utils.h" #include "trace2.h" #include "parse-options.h" @@ -8,15 +9,11 @@ static const char * const test_tool_usage[] = { NULL }; -struct test_cmd { - const char *name; - int (*fn)(int argc, const char **argv); -}; - static struct test_cmd cmds[] = { { "advise", cmd__advise_if_enabled }, { "bitmap", cmd__bitmap }, { "bloom", cmd__bloom }, + { "bundle-uri", cmd__bundle_uri }, { "chmtime", cmd__chmtime }, { "config", cmd__config }, { "crontab", cmd__crontab }, @@ -51,7 +48,9 @@ static struct test_cmd cmds[] = { { "online-cpus", cmd__online_cpus }, { "pack-mtimes", cmd__pack_mtimes }, { "parse-options", cmd__parse_options }, + { "parse-options-flags", cmd__parse_options_flags }, { "parse-pathspec-file", cmd__parse_pathspec_file }, + { "parse-subcommand", cmd__parse_subcommand }, { "partial-clone", cmd__partial_clone }, { "path-utils", cmd__path_utils }, { "pcre2-config", cmd__pcre2_config }, @@ -65,6 +64,7 @@ static struct test_cmd cmds[] = { { "read-midx", cmd__read_midx }, { "ref-store", cmd__ref_store }, { "reftable", cmd__reftable }, + { "rot13-filter", cmd__rot13_filter }, { "dump-reftable", cmd__dump_reftable }, { "regex", cmd__regex }, { "repository", cmd__repository }, @@ -78,6 +78,7 @@ static struct test_cmd cmds[] = { { "simple-ipc", cmd__simple_ipc }, { "strcmp-offset", cmd__strcmp_offset }, { "string-list", cmd__string_list }, + { "submodule", cmd__submodule }, { "submodule-config", cmd__submodule_config }, { "submodule-nested-repo-config", cmd__submodule_nested_repo_config }, { "subprocess", cmd__subprocess }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index bb79927163..ca2948066f 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -7,6 +7,7 @@ int cmd__advise_if_enabled(int argc, const char **argv); int cmd__bitmap(int argc, const char **argv); int cmd__bloom(int argc, const char **argv); +int cmd__bundle_uri(int argc, const char **argv); int cmd__chmtime(int argc, const char **argv); int cmd__config(int argc, const char **argv); int cmd__crontab(int argc, const char **argv); @@ -41,7 +42,9 @@ int cmd__oidtree(int argc, const char **argv); int cmd__online_cpus(int argc, const char **argv); int cmd__pack_mtimes(int argc, const char **argv); int cmd__parse_options(int argc, const char **argv); +int cmd__parse_options_flags(int argc, const char **argv); int cmd__parse_pathspec_file(int argc, const char** argv); +int cmd__parse_subcommand(int argc, const char **argv); int cmd__partial_clone(int argc, const char **argv); int cmd__path_utils(int argc, const char **argv); int cmd__pcre2_config(int argc, const char **argv); @@ -54,6 +57,7 @@ int cmd__read_cache(int argc, const char **argv); int cmd__read_graph(int argc, const char **argv); int cmd__read_midx(int argc, const char **argv); int cmd__ref_store(int argc, const char **argv); +int cmd__rot13_filter(int argc, const char **argv); int cmd__reftable(int argc, const char **argv); int cmd__regex(int argc, const char **argv); int cmd__repository(int argc, const char **argv); @@ -68,6 +72,7 @@ int cmd__sigchain(int argc, const char **argv); int cmd__simple_ipc(int argc, const char **argv); int cmd__strcmp_offset(int argc, const char **argv); int cmd__string_list(int argc, const char **argv); +int cmd__submodule(int argc, const char **argv); int cmd__submodule_config(int argc, const char **argv); int cmd__submodule_nested_repo_config(int argc, const char **argv); int cmd__subprocess(int argc, const char **argv); diff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c index a714130ece..1b092c6071 100644 --- a/t/helper/test-trace2.c +++ b/t/helper/test-trace2.c @@ -229,6 +229,187 @@ static int ut_010bug_BUG(int argc, const char **argv) } /* + * Single-threaded timer test. Create several intervals using the + * TEST1 timer. The test script can verify that an aggregate Trace2 + * "timer" event is emitted indicating that we started+stopped the + * timer the requested number of times. + */ +static int ut_100timer(int argc, const char **argv) +{ + const char *usage_error = + "expect <count> <ms_delay>"; + + int count = 0; + int delay = 0; + int k; + + if (argc != 2) + die("%s", usage_error); + if (get_i(&count, argv[0])) + die("%s", usage_error); + if (get_i(&delay, argv[1])) + die("%s", usage_error); + + for (k = 0; k < count; k++) { + trace2_timer_start(TRACE2_TIMER_ID_TEST1); + sleep_millisec(delay); + trace2_timer_stop(TRACE2_TIMER_ID_TEST1); + } + + return 0; +} + +struct ut_101_data { + int count; + int delay; +}; + +static void *ut_101timer_thread_proc(void *_ut_101_data) +{ + struct ut_101_data *data = _ut_101_data; + int k; + + trace2_thread_start("ut_101"); + + for (k = 0; k < data->count; k++) { + trace2_timer_start(TRACE2_TIMER_ID_TEST2); + sleep_millisec(data->delay); + trace2_timer_stop(TRACE2_TIMER_ID_TEST2); + } + + trace2_thread_exit(); + return NULL; +} + +/* + * Multi-threaded timer test. Create several threads that each create + * several intervals using the TEST2 timer. The test script can verify + * that an individual Trace2 "th_timer" events for each thread and an + * aggregate "timer" event are generated. + */ +static int ut_101timer(int argc, const char **argv) +{ + const char *usage_error = + "expect <count> <ms_delay> <threads>"; + + struct ut_101_data data = { 0, 0 }; + int nr_threads = 0; + int k; + pthread_t *pids = NULL; + + if (argc != 3) + die("%s", usage_error); + if (get_i(&data.count, argv[0])) + die("%s", usage_error); + if (get_i(&data.delay, argv[1])) + die("%s", usage_error); + if (get_i(&nr_threads, argv[2])) + die("%s", usage_error); + + CALLOC_ARRAY(pids, nr_threads); + + for (k = 0; k < nr_threads; k++) { + if (pthread_create(&pids[k], NULL, ut_101timer_thread_proc, &data)) + die("failed to create thread[%d]", k); + } + + for (k = 0; k < nr_threads; k++) { + if (pthread_join(pids[k], NULL)) + die("failed to join thread[%d]", k); + } + + free(pids); + + return 0; +} + +/* + * Single-threaded counter test. Add several values to the TEST1 counter. + * The test script can verify that the final sum is reported in the "counter" + * event. + */ +static int ut_200counter(int argc, const char **argv) +{ + const char *usage_error = + "expect <v1> [<v2> [...]]"; + int value; + int k; + + if (argc < 1) + die("%s", usage_error); + + for (k = 0; k < argc; k++) { + if (get_i(&value, argv[k])) + die("invalid value[%s] -- %s", + argv[k], usage_error); + trace2_counter_add(TRACE2_COUNTER_ID_TEST1, value); + } + + return 0; +} + +/* + * Multi-threaded counter test. Create seveal threads that each increment + * the TEST2 global counter. The test script can verify that an individual + * "th_counter" event is generated with a partial sum for each thread and + * that a final aggregate "counter" event is generated. + */ + +struct ut_201_data { + int v1; + int v2; +}; + +static void *ut_201counter_thread_proc(void *_ut_201_data) +{ + struct ut_201_data *data = _ut_201_data; + + trace2_thread_start("ut_201"); + + trace2_counter_add(TRACE2_COUNTER_ID_TEST2, data->v1); + trace2_counter_add(TRACE2_COUNTER_ID_TEST2, data->v2); + + trace2_thread_exit(); + return NULL; +} + +static int ut_201counter(int argc, const char **argv) +{ + const char *usage_error = + "expect <v1> <v2> <threads>"; + + struct ut_201_data data = { 0, 0 }; + int nr_threads = 0; + int k; + pthread_t *pids = NULL; + + if (argc != 3) + die("%s", usage_error); + if (get_i(&data.v1, argv[0])) + die("%s", usage_error); + if (get_i(&data.v2, argv[1])) + die("%s", usage_error); + if (get_i(&nr_threads, argv[2])) + die("%s", usage_error); + + CALLOC_ARRAY(pids, nr_threads); + + for (k = 0; k < nr_threads; k++) { + if (pthread_create(&pids[k], NULL, ut_201counter_thread_proc, &data)) + die("failed to create thread[%d]", k); + } + + for (k = 0; k < nr_threads; k++) { + if (pthread_join(pids[k], NULL)) + die("failed to join thread[%d]", k); + } + + free(pids); + + return 0; +} + +/* * Usage: * test-tool trace2 <ut_name_1> <ut_usage_1> * test-tool trace2 <ut_name_2> <ut_usage_2> @@ -248,6 +429,12 @@ static struct unit_test ut_table[] = { { ut_008bug, "008bug", "" }, { ut_009bug_BUG, "009bug_BUG","" }, { ut_010bug_BUG, "010bug_BUG","" }, + + { ut_100timer, "100timer", "<count> <ms_delay>" }, + { ut_101timer, "101timer", "<count> <ms_delay> <threads>" }, + + { ut_200counter, "200counter", "<v1> [<v2> [<v3> [...]]]" }, + { ut_201counter, "201counter", "<v1> <v2> <threads>" }, }; /* clang-format on */ diff --git a/t/helper/test-userdiff.c b/t/helper/test-userdiff.c index f013f8a31e..a2b56b9cae 100644 --- a/t/helper/test-userdiff.c +++ b/t/helper/test-userdiff.c @@ -12,7 +12,7 @@ static int driver_cb(struct userdiff_driver *driver, return 0; } -static int cmd__userdiff_config(const char *var, const char *value, void *cb) +static int cmd__userdiff_config(const char *var, const char *value, void *cb UNUSED) { if (userdiff_config(var, value) < 0) return -1; diff --git a/t/lib-bitmap.sh b/t/lib-bitmap.sh index a95537e759..f595937094 100644 --- a/t/lib-bitmap.sh +++ b/t/lib-bitmap.sh @@ -440,7 +440,7 @@ midx_bitmap_partial_tests () { test_commit packed && git repack && test_commit loose && - git multi-pack-index write --bitmap 2>err && + git multi-pack-index write --bitmap && test_path_is_file $midx && test_path_is_file $midx-$(midx_checksum $objdir).bitmap ' diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 497b9b9d92..706799391b 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -80,6 +80,8 @@ PassEnv LSAN_OPTIONS PassEnv GIT_TRACE PassEnv GIT_CONFIG_NOSYSTEM PassEnv GIT_TEST_SIDEBAND_ALL +PassEnv LANG +PassEnv LC_ALL Alias /dumb/ www/ Alias /auth/dumb/ www/auth/dumb/ diff --git a/t/lib-perl.sh b/t/lib-perl.sh new file mode 100644 index 0000000000..d0bf509a16 --- /dev/null +++ b/t/lib-perl.sh @@ -0,0 +1,19 @@ +# Copyright (c) 2022 Ævar Arnfjörð Bjarmason + +test_lazy_prereq PERL_TEST_MORE ' + perl -MTest::More -e 0 +' + +skip_all_if_no_Test_More () { + if ! test_have_prereq PERL + then + skip_all='skipping perl interface tests, perl not available' + test_done + fi + + if ! test_have_prereq PERL_TEST_MORE + then + skip_all="Perl Test::More unavailable, skipping test" + test_done + fi +} diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 03e0abbdb8..2d31fcfda1 100644 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -197,6 +197,7 @@ test_git_directory_exists () { # the submodule repo if it doesn't exist and configures the most problematic # settings for diff.ignoreSubmodules. prolog () { + test_config_global protocol.file.allow always && (test -d submodule_update_repo || create_lib_submodule_repo) && test_config_global diff.ignoreSubmodules all && test_config diff.ignoreSubmodules all diff --git a/t/perf/README b/t/perf/README index fb9127a66f..8f217d7be7 100644 --- a/t/perf/README +++ b/t/perf/README @@ -95,6 +95,10 @@ You can set the following variables (also in your config.mak): Git (e.g., performance of index-pack as the number of threads changes). These can be enabled with GIT_PERF_EXTRA. + GIT_PERF_USE_SCALAR + Boolean indicating whether to register test repo(s) with Scalar + before executing tests. + You can also pass the options taken by ordinary git tests; the most useful one is: diff --git a/t/perf/lib-bitmap.sh b/t/perf/lib-bitmap.sh index 63d3bc7cec..55a8feb1dc 100644 --- a/t/perf/lib-bitmap.sh +++ b/t/perf/lib-bitmap.sh @@ -67,3 +67,34 @@ test_partial_bitmap () { --filter=tree:0 >/dev/null ' } + +test_pack_bitmap () { + test_perf "repack to disk" ' + git repack -ad + ' + + test_full_bitmap + + test_expect_success "create partial bitmap state" ' + # pick a commit to represent the repo tip in the past + cutoff=$(git rev-list HEAD~100 -1) && + orig_tip=$(git rev-parse HEAD) && + + # now kill off all of the refs and pretend we had + # just the one tip + rm -rf .git/logs .git/refs/* .git/packed-refs && + git update-ref HEAD $cutoff && + + # and then repack, which will leave us with a nice + # big bitmap pack of the "old" history, and all of + # the new history will be loose, as if it had been pushed + # up incrementally and exploded via unpack-objects + git repack -Ad && + + # and now restore our original tip, as if the pushes + # had happened + git update-ref HEAD $orig_tip + ' + + test_partial_bitmap +} diff --git a/t/perf/p0004-lazy-init-name-hash.sh b/t/perf/p0004-lazy-init-name-hash.sh index 1afc08fe7f..85be14e4dd 100755 --- a/t/perf/p0004-lazy-init-name-hash.sh +++ b/t/perf/p0004-lazy-init-name-hash.sh @@ -49,7 +49,7 @@ test_perf "single-threaded, $desc" " test-tool lazy-init-name-hash --single --count=$count " -test_perf REPO_BIG_ENOUGH_FOR_MULTI "multi-threaded, $desc" " +test_perf "multi-threaded, $desc" --prereq REPO_BIG_ENOUGH_FOR_MULTI " test-tool lazy-init-name-hash --multi --count=$count " diff --git a/t/perf/p0006-read-tree-checkout.sh b/t/perf/p0006-read-tree-checkout.sh index 900b385c4b..c481c012d2 100755 --- a/t/perf/p0006-read-tree-checkout.sh +++ b/t/perf/p0006-read-tree-checkout.sh @@ -46,7 +46,7 @@ test_expect_success "setup repo" ' ' test_perf "read-tree br_base br_ballast ($nr_files)" ' - git read-tree -m br_base br_ballast -n + git read-tree -n -m br_base br_ballast ' test_perf "switch between br_base br_ballast ($nr_files)" ' diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh index c181110a43..3242cfe91a 100755 --- a/t/perf/p2000-sparse-operations.sh +++ b/t/perf/p2000-sparse-operations.sh @@ -123,5 +123,7 @@ test_perf_on_all git blame $SPARSE_CONE/f3/a test_perf_on_all git read-tree -mu HEAD test_perf_on_all git checkout-index -f --all test_perf_on_all git update-index --add --remove $SPARSE_CONE/a +test_perf_on_all "git rm -f $SPARSE_CONE/a && git checkout HEAD -- $SPARSE_CONE/a" +test_perf_on_all git grep --cached --sparse bogus -- "f2/f1/f1/*" test_done diff --git a/t/perf/p5310-pack-bitmaps.sh b/t/perf/p5310-pack-bitmaps.sh index 7ad4f237bc..b1399f1007 100755 --- a/t/perf/p5310-pack-bitmaps.sh +++ b/t/perf/p5310-pack-bitmaps.sh @@ -4,51 +4,37 @@ test_description='Tests pack performance using bitmaps' . ./perf-lib.sh . "${TEST_DIRECTORY}/perf/lib-bitmap.sh" -test_perf_large_repo - -# note that we do everything through config, -# since we want to be able to compare bitmap-aware -# git versus non-bitmap git -# -# We intentionally use the deprecated pack.writebitmaps -# config so that we can test against older versions of git. -test_expect_success 'setup bitmap config' ' - git config pack.writebitmaps true -' - -# we need to create the tag up front such that it is covered by the repack and -# thus by generated bitmaps. -test_expect_success 'create tags' ' - git tag --message="tag pointing to HEAD" perf-tag HEAD -' - -test_perf 'repack to disk' ' - git repack -ad -' - -test_full_bitmap - -test_expect_success 'create partial bitmap state' ' - # pick a commit to represent the repo tip in the past - cutoff=$(git rev-list HEAD~100 -1) && - orig_tip=$(git rev-parse HEAD) && - - # now kill off all of the refs and pretend we had - # just the one tip - rm -rf .git/logs .git/refs/* .git/packed-refs && - git update-ref HEAD $cutoff && - - # and then repack, which will leave us with a nice - # big bitmap pack of the "old" history, and all of - # the new history will be loose, as if it had been pushed - # up incrementally and exploded via unpack-objects - git repack -Ad && - - # and now restore our original tip, as if the pushes - # had happened - git update-ref HEAD $orig_tip -' - -test_partial_bitmap +test_lookup_pack_bitmap () { + test_expect_success 'start the test from scratch' ' + rm -rf * .git + ' + + test_perf_large_repo + + # note that we do everything through config, + # since we want to be able to compare bitmap-aware + # git versus non-bitmap git + # + # We intentionally use the deprecated pack.writebitmaps + # config so that we can test against older versions of git. + test_expect_success 'setup bitmap config' ' + git config pack.writebitmaps true + ' + + # we need to create the tag up front such that it is covered by the repack and + # thus by generated bitmaps. + test_expect_success 'create tags' ' + git tag --message="tag pointing to HEAD" perf-tag HEAD + ' + + test_perf "enable lookup table: $1" ' + git config pack.writeBitmapLookupTable '"$1"' + ' + + test_pack_bitmap +} + +test_lookup_pack_bitmap false +test_lookup_pack_bitmap true test_done diff --git a/t/perf/p5311-pack-bitmaps-fetch.sh b/t/perf/p5311-pack-bitmaps-fetch.sh index 47c3fd7581..426fab87e3 100755 --- a/t/perf/p5311-pack-bitmaps-fetch.sh +++ b/t/perf/p5311-pack-bitmaps-fetch.sh @@ -3,42 +3,52 @@ test_description='performance of fetches from bitmapped packs' . ./perf-lib.sh -test_perf_default_repo - -test_expect_success 'create bitmapped server repo' ' - git config pack.writebitmaps true && - git repack -ad -' - -# simulate a fetch from a repository that last fetched N days ago, for -# various values of N. We do so by following the first-parent chain, -# and assume the first entry in the chain that is N days older than the current -# HEAD is where the HEAD would have been then. -for days in 1 2 4 8 16 32 64 128; do - title=$(printf '%10s' "($days days)") - test_expect_success "setup revs from $days days ago" ' - now=$(git log -1 --format=%ct HEAD) && - then=$(($now - ($days * 86400))) && - tip=$(git rev-list -1 --first-parent --until=$then HEAD) && - { - echo HEAD && - echo ^$tip - } >revs +test_fetch_bitmaps () { + test_expect_success 'setup test directory' ' + rm -fr * .git ' - test_perf "server $title" ' - git pack-objects --stdout --revs \ - --thin --delta-base-offset \ - <revs >tmp.pack - ' + test_perf_default_repo - test_size "size $title" ' - wc -c <tmp.pack + test_expect_success 'create bitmapped server repo' ' + git config pack.writebitmaps true && + git config pack.writeBitmapLookupTable '"$1"' && + git repack -ad ' - test_perf "client $title" ' - git index-pack --stdin --fix-thin <tmp.pack - ' -done + # simulate a fetch from a repository that last fetched N days ago, for + # various values of N. We do so by following the first-parent chain, + # and assume the first entry in the chain that is N days older than the current + # HEAD is where the HEAD would have been then. + for days in 1 2 4 8 16 32 64 128; do + title=$(printf '%10s' "($days days)") + test_expect_success "setup revs from $days days ago" ' + now=$(git log -1 --format=%ct HEAD) && + then=$(($now - ($days * 86400))) && + tip=$(git rev-list -1 --first-parent --until=$then HEAD) && + { + echo HEAD && + echo ^$tip + } >revs + ' + + test_perf "server $title (lookup=$1)" ' + git pack-objects --stdout --revs \ + --thin --delta-base-offset \ + <revs >tmp.pack + ' + + test_size "size $title" ' + wc -c <tmp.pack + ' + + test_perf "client $title (lookup=$1)" ' + git index-pack --stdin --fix-thin <tmp.pack + ' + done +} + +test_fetch_bitmaps true +test_fetch_bitmaps false test_done diff --git a/t/perf/p5312-pack-bitmaps-revs.sh b/t/perf/p5312-pack-bitmaps-revs.sh new file mode 100755 index 0000000000..0684b690af --- /dev/null +++ b/t/perf/p5312-pack-bitmaps-revs.sh @@ -0,0 +1,35 @@ +#!/bin/sh + +test_description='Tests pack performance using bitmaps (rev index enabled)' +. ./perf-lib.sh +. "${TEST_DIRECTORY}/perf/lib-bitmap.sh" + +test_lookup_pack_bitmap () { + test_expect_success 'start the test from scratch' ' + rm -rf * .git + ' + + test_perf_large_repo + + test_expect_success 'setup bitmap config' ' + git config pack.writebitmaps true && + git config pack.writeReverseIndex true + ' + + # we need to create the tag up front such that it is covered by the repack and + # thus by generated bitmaps. + test_expect_success 'create tags' ' + git tag --message="tag pointing to HEAD" perf-tag HEAD + ' + + test_perf "enable lookup table: $1" ' + git config pack.writeBitmapLookupTable '"$1"' + ' + + test_pack_bitmap +} + +test_lookup_pack_bitmap false +test_lookup_pack_bitmap true + +test_done diff --git a/t/perf/p5326-multi-pack-bitmaps.sh b/t/perf/p5326-multi-pack-bitmaps.sh index f2fa228f16..d082e6cacb 100755 --- a/t/perf/p5326-multi-pack-bitmaps.sh +++ b/t/perf/p5326-multi-pack-bitmaps.sh @@ -4,49 +4,64 @@ test_description='Tests performance using midx bitmaps' . ./perf-lib.sh . "${TEST_DIRECTORY}/perf/lib-bitmap.sh" -test_perf_large_repo - -# we need to create the tag up front such that it is covered by the repack and -# thus by generated bitmaps. -test_expect_success 'create tags' ' - git tag --message="tag pointing to HEAD" perf-tag HEAD -' - -test_expect_success 'start with bitmapped pack' ' - git repack -adb -' - -test_perf 'setup multi-pack index' ' - git multi-pack-index write --bitmap -' - -test_expect_success 'drop pack bitmap' ' - rm -f .git/objects/pack/pack-*.bitmap -' - -test_full_bitmap - -test_expect_success 'create partial bitmap state' ' - # pick a commit to represent the repo tip in the past - cutoff=$(git rev-list HEAD~100 -1) && - orig_tip=$(git rev-parse HEAD) && - - # now pretend we have just one tip - rm -rf .git/logs .git/refs/* .git/packed-refs && - git update-ref HEAD $cutoff && - - # and then repack, which will leave us with a nice - # big bitmap pack of the "old" history, and all of - # the new history will be loose, as if it had been pushed - # up incrementally and exploded via unpack-objects - git repack -Ad && - git multi-pack-index write --bitmap && - - # and now restore our original tip, as if the pushes - # had happened - git update-ref HEAD $orig_tip -' - -test_partial_bitmap +test_bitmap () { + local enabled="$1" + + test_expect_success "remove existing repo (lookup=$enabled)" ' + rm -fr * .git + ' + + test_perf_large_repo + + # we need to create the tag up front such that it is covered by the repack and + # thus by generated bitmaps. + test_expect_success 'create tags' ' + git tag --message="tag pointing to HEAD" perf-tag HEAD + ' + + test_expect_success "use lookup table: $enabled" ' + git config pack.writeBitmapLookupTable '"$enabled"' + ' + + test_expect_success "start with bitmapped pack (lookup=$enabled)" ' + git repack -adb + ' + + test_perf "setup multi-pack index (lookup=$enabled)" ' + git multi-pack-index write --bitmap + ' + + test_expect_success "drop pack bitmap (lookup=$enabled)" ' + rm -f .git/objects/pack/pack-*.bitmap + ' + + test_full_bitmap + + test_expect_success "create partial bitmap state (lookup=$enabled)" ' + # pick a commit to represent the repo tip in the past + cutoff=$(git rev-list HEAD~100 -1) && + orig_tip=$(git rev-parse HEAD) && + + # now pretend we have just one tip + rm -rf .git/logs .git/refs/* .git/packed-refs && + git update-ref HEAD $cutoff && + + # and then repack, which will leave us with a nice + # big bitmap pack of the "old" history, and all of + # the new history will be loose, as if it had been pushed + # up incrementally and exploded via unpack-objects + git repack -Ad && + git multi-pack-index write --bitmap && + + # and now restore our original tip, as if the pushes + # had happened + git update-ref HEAD $orig_tip + ' + + test_partial_bitmap +} + +test_bitmap false +test_bitmap true test_done diff --git a/t/perf/p7527-builtin-fsmonitor.sh b/t/perf/p7527-builtin-fsmonitor.sh index 9338b9ea00..c3f9a4caa4 100755 --- a/t/perf/p7527-builtin-fsmonitor.sh +++ b/t/perf/p7527-builtin-fsmonitor.sh @@ -249,7 +249,7 @@ test_expect_success "Cleanup temp and matrix branches" " do for fsm_val in $fsm_values do - cleanup $uc_val $fsm_val + cleanup $uc_val $fsm_val || return 1 done done " diff --git a/t/perf/p9210-scalar.sh b/t/perf/p9210-scalar.sh new file mode 100755 index 0000000000..265f7cd1fe --- /dev/null +++ b/t/perf/p9210-scalar.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +test_description='test scalar performance' +. ./perf-lib.sh + +test_perf_large_repo "$TRASH_DIRECTORY/to-clone" + +test_expect_success 'enable server-side partial clone' ' + git -C to-clone config uploadpack.allowFilter true && + git -C to-clone config uploadpack.allowAnySHA1InWant true +' + +test_perf 'scalar clone' ' + rm -rf scalar-clone && + scalar clone "file://$(pwd)/to-clone" scalar-clone +' + +test_perf 'git clone' ' + rm -rf git-clone && + git clone "file://$(pwd)/to-clone" git-clone +' + +test_compare_perf () { + command=$1 + shift + args=$* + test_perf "$command $args (scalar)" " + $command -C scalar-clone/src $args + " + + test_perf "$command $args (non-scalar)" " + $command -C git-clone $args + " +} + +test_compare_perf git status +test_compare_perf test_commit --append --no-tag A + +test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index 27c2801792..e7786775a9 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -49,6 +49,9 @@ export TEST_DIRECTORY TRASH_DIRECTORY GIT_BUILD_DIR GIT_TEST_CMP MODERN_GIT=$GIT_BUILD_DIR/bin-wrappers/git export MODERN_GIT +MODERN_SCALAR=$GIT_BUILD_DIR/bin-wrappers/scalar +export MODERN_SCALAR + perf_results_dir=$TEST_RESULTS_DIR test -n "$GIT_PERF_SUBSECTION" && perf_results_dir="$perf_results_dir/$GIT_PERF_SUBSECTION" mkdir -p "$perf_results_dir" @@ -120,6 +123,10 @@ test_perf_create_repo_from () { # status" due to a locked index. Since we have # a copy it's fine to remove the lock. rm .git/index.lock + fi && + if test_bool_env GIT_PERF_USE_SCALAR false + then + "$MODERN_SCALAR" register fi ) || error "failed to copy repository '$source' to '$repo'" } @@ -130,7 +137,11 @@ test_perf_fresh_repo () { "$MODERN_GIT" init -q "$repo" && ( cd "$repo" && - test_perf_do_repo_symlink_config_ + test_perf_do_repo_symlink_config_ && + if test_bool_env GIT_PERF_USE_SCALAR false + then + "$MODERN_SCALAR" register + fi ) } diff --git a/t/perf/run b/t/perf/run index 55219aa405..34115edec3 100755 --- a/t/perf/run +++ b/t/perf/run @@ -171,6 +171,9 @@ run_subsection () { get_var_from_env_or_config "GIT_PERF_MAKE_COMMAND" "perf" "makeCommand" get_var_from_env_or_config "GIT_PERF_MAKE_OPTS" "perf" "makeOpts" + get_var_from_env_or_config "GIT_PERF_USE_SCALAR" "perf" "useScalar" "--bool" + export GIT_PERF_USE_SCALAR + get_var_from_env_or_config "GIT_PERF_REPO_NAME" "perf" "repoName" export GIT_PERF_REPO_NAME @@ -229,10 +232,10 @@ then ) elif test -n "$GIT_PERF_SUBSECTION" then - egrep "^$GIT_PERF_SUBSECTION\$" "$TEST_RESULTS_DIR"/run_subsections.names >/dev/null || + grep -E "^$GIT_PERF_SUBSECTION\$" "$TEST_RESULTS_DIR"/run_subsections.names >/dev/null || die "subsection '$GIT_PERF_SUBSECTION' not found in '$GIT_PERF_CONFIG_FILE'" - egrep "^$GIT_PERF_SUBSECTION\$" "$TEST_RESULTS_DIR"/run_subsections.names | while read -r subsec + grep -E "^$GIT_PERF_SUBSECTION\$" "$TEST_RESULTS_DIR"/run_subsections.names | while read -r subsec do ( GIT_PERF_SUBSECTION="$subsec" diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 17a268ccd1..502b4bcf9e 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -578,6 +578,78 @@ test_expect_success 'subtest: --run invalid range end' ' EOF_ERR ' +test_expect_success 'subtest: --invert-exit-code without --immediate' ' + run_sub_test_lib_test_err full-pass \ + --invert-exit-code && + check_sub_test_lib_test_err full-pass \ + <<-\EOF_OUT 3<<-EOF_ERR + ok 1 - passing test #1 + ok 2 - passing test #2 + ok 3 - passing test #3 + # passed all 3 test(s) + 1..3 + # faking up non-zero exit with --invert-exit-code + EOF_OUT + EOF_ERR +' + +test_expect_success 'subtest: --invert-exit-code with --immediate: all passed' ' + run_sub_test_lib_test_err full-pass \ + --invert-exit-code --immediate && + check_sub_test_lib_test_err full-pass \ + <<-\EOF_OUT 3<<-EOF_ERR + ok 1 - passing test #1 + ok 2 - passing test #2 + ok 3 - passing test #3 + # passed all 3 test(s) + 1..3 + # faking up non-zero exit with --invert-exit-code + EOF_OUT + EOF_ERR +' + +test_expect_success 'subtest: --invert-exit-code without --immediate: partial pass' ' + run_sub_test_lib_test partial-pass \ + --invert-exit-code && + check_sub_test_lib_test partial-pass <<-\EOF + ok 1 - passing test #1 + not ok 2 - # TODO induced breakage (--invert-exit-code): failing test #2 + # false + ok 3 - passing test #3 + # failed 1 among 3 test(s) + 1..3 + # faked up failures as TODO & now exiting with 0 due to --invert-exit-code + EOF +' + +test_expect_success 'subtest: --invert-exit-code with --immediate: partial pass' ' + run_sub_test_lib_test partial-pass \ + --invert-exit-code --immediate && + check_sub_test_lib_test partial-pass \ + <<-\EOF_OUT 3<<-EOF_ERR + ok 1 - passing test #1 + not ok 2 - # TODO induced breakage (--invert-exit-code): failing test #2 + # false + 1..2 + # faked up failures as TODO & now exiting with 0 due to --invert-exit-code + EOF_OUT + EOF_ERR +' + +test_expect_success 'subtest: --invert-exit-code --immediate: got a failure' ' + run_sub_test_lib_test partial-pass \ + --invert-exit-code --immediate && + check_sub_test_lib_test_err partial-pass \ + <<-\EOF_OUT 3<<-EOF_ERR + ok 1 - passing test #1 + not ok 2 - # TODO induced breakage (--invert-exit-code): failing test #2 + # false + 1..2 + # faked up failures as TODO & now exiting with 0 due to --invert-exit-code + EOF_OUT + EOF_ERR +' + test_expect_success 'subtest: tests respect prerequisites' ' write_and_run_sub_test_lib_test prereqs <<-\EOF && diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index f6356db183..26eaca095a 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -65,7 +65,7 @@ test_expect_success 'check commit-tree' ' test_path_is_file "$REAL/objects/$(objpath $SHA)" ' -test_expect_success !SANITIZE_LEAK 'check rev-list' ' +test_expect_success 'check rev-list' ' git update-ref "HEAD" "$SHA" && git rev-list HEAD >actual && echo $SHA >expected && diff --git a/t/t0004-unwritable.sh b/t/t0004-unwritable.sh index 2e9d652d82..8114fac73b 100755 --- a/t/t0004-unwritable.sh +++ b/t/t0004-unwritable.sh @@ -31,7 +31,7 @@ test_expect_success WRITE_TREE_OUT 'write-tree output on unwritable repository' test_cmp expect out.write-tree ' -test_expect_success POSIXPERM,SANITY,!SANITIZE_LEAK 'commit should notice unwritable repository' ' +test_expect_success POSIXPERM,SANITY 'commit should notice unwritable repository' ' test_when_finished "chmod 775 .git/objects .git/objects/??" && chmod a-w .git/objects .git/objects/?? && test_must_fail git commit -m second 2>out.commit diff --git a/t/t0012-help.sh b/t/t0012-help.sh index 6c33a43690..dbfc5c8267 100755 --- a/t/t0012-help.sh +++ b/t/t0012-help.sh @@ -44,6 +44,8 @@ test_expect_success 'invalid usage' ' test_expect_code 129 git help -g add && test_expect_code 129 git help -a -g && + test_expect_code 129 git help --user-interfaces add && + test_expect_code 129 git help -g -c && test_expect_code 129 git help --config-for-completion add && test_expect_code 129 git help --config-sections-for-completion add @@ -104,9 +106,9 @@ test_expect_success 'git help' ' test_i18ngrep "^ commit " help.output && test_i18ngrep "^ fetch " help.output ' + test_expect_success 'git help -g' ' git help -g >help.output && - test_i18ngrep "^ attributes " help.output && test_i18ngrep "^ everyday " help.output && test_i18ngrep "^ tutorial " help.output ' @@ -127,6 +129,12 @@ test_expect_success 'git help succeeds without git.html' ' test_cmp expect test-browser.log ' +test_expect_success 'git help --user-interfaces' ' + git help --user-interfaces >help.output && + grep "^ attributes " help.output && + grep "^ mailmap " help.output +' + test_expect_success 'git help -c' ' git help -c >help.output && cat >expect <<-\EOF && @@ -220,6 +228,10 @@ test_expect_success "'git help -a' section spacing" ' Low-level Commands / Syncing Repositories Low-level Commands / Internal Helpers + + User-facing repository, command and file interfaces + + Developer-facing file formats, protocols and other interfaces EOF test_cmp expect actual ' diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 1c840348bd..abecd75e4e 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -17,9 +17,6 @@ tr \ 'nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM' EOF -write_script rot13-filter.pl "$PERL_PATH" \ - <"$TEST_DIRECTORY"/t0021/rot13-filter.pl - generate_random_characters () { LEN=$1 NAME=$2 @@ -365,8 +362,8 @@ test_expect_success 'diff does not reuse worktree files that need cleaning' ' test_line_count = 0 count ' -test_expect_success PERL 'required process filter should filter data' ' - test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && +test_expect_success 'required process filter should filter data' ' + test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" && test_config_global filter.protocol.required true && rm -rf repo && mkdir repo && @@ -450,8 +447,8 @@ test_expect_success PERL 'required process filter should filter data' ' ) ' -test_expect_success PERL 'required process filter should filter data for various subcommands' ' - test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && +test_expect_success 'required process filter should filter data for various subcommands' ' + test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" && test_config_global filter.protocol.required true && ( cd repo && @@ -561,9 +558,9 @@ test_expect_success PERL 'required process filter should filter data for various ) ' -test_expect_success PERL 'required process filter takes precedence' ' +test_expect_success 'required process filter takes precedence' ' test_config_global filter.protocol.clean false && - test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" && + test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean" && test_config_global filter.protocol.required true && rm -rf repo && mkdir repo && @@ -587,8 +584,8 @@ test_expect_success PERL 'required process filter takes precedence' ' ) ' -test_expect_success PERL 'required process filter should be used only for "clean" operation only' ' - test_config_global filter.protocol.process "rot13-filter.pl debug.log clean" && +test_expect_success 'required process filter should be used only for "clean" operation only' ' + test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean" && rm -rf repo && mkdir repo && ( @@ -622,8 +619,8 @@ test_expect_success PERL 'required process filter should be used only for "clean ) ' -test_expect_success PERL 'required process filter should process multiple packets' ' - test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && +test_expect_success 'required process filter should process multiple packets' ' + test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" && test_config_global filter.protocol.required true && rm -rf repo && @@ -687,8 +684,8 @@ test_expect_success PERL 'required process filter should process multiple packet ) ' -test_expect_success PERL 'required process filter with clean error should fail' ' - test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && +test_expect_success 'required process filter with clean error should fail' ' + test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" && test_config_global filter.protocol.required true && rm -rf repo && mkdir repo && @@ -706,8 +703,8 @@ test_expect_success PERL 'required process filter with clean error should fail' ) ' -test_expect_success PERL 'process filter should restart after unexpected write failure' ' - test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && +test_expect_success 'process filter should restart after unexpected write failure' ' + test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" && rm -rf repo && mkdir repo && ( @@ -735,7 +732,7 @@ test_expect_success PERL 'process filter should restart after unexpected write f rm -f debug.log && git checkout --quiet --no-progress . 2>git-stderr.log && - grep "smudge write error at" git-stderr.log && + grep "smudge write error" git-stderr.log && test_i18ngrep "error: external filter" git-stderr.log && cat >expected.log <<-EOF && @@ -761,8 +758,8 @@ test_expect_success PERL 'process filter should restart after unexpected write f ) ' -test_expect_success PERL 'process filter should not be restarted if it signals an error' ' - test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && +test_expect_success 'process filter should not be restarted if it signals an error' ' + test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" && rm -rf repo && mkdir repo && ( @@ -804,8 +801,8 @@ test_expect_success PERL 'process filter should not be restarted if it signals a ) ' -test_expect_success PERL 'process filter abort stops processing of all further files' ' - test_config_global filter.protocol.process "rot13-filter.pl debug.log clean smudge" && +test_expect_success 'process filter abort stops processing of all further files' ' + test_config_global filter.protocol.process "test-tool rot13-filter --log=debug.log clean smudge" && rm -rf repo && mkdir repo && ( @@ -861,10 +858,10 @@ test_expect_success PERL 'invalid process filter must fail (and not hang!)' ' ) ' -test_expect_success PERL 'delayed checkout in process filter' ' - test_config_global filter.a.process "rot13-filter.pl a.log clean smudge delay" && +test_expect_success 'delayed checkout in process filter' ' + test_config_global filter.a.process "test-tool rot13-filter --log=a.log clean smudge delay" && test_config_global filter.a.required true && - test_config_global filter.b.process "rot13-filter.pl b.log clean smudge delay" && + test_config_global filter.b.process "test-tool rot13-filter --log=b.log clean smudge delay" && test_config_global filter.b.required true && rm -rf repo && @@ -940,8 +937,8 @@ test_expect_success PERL 'delayed checkout in process filter' ' ) ' -test_expect_success PERL 'missing file in delayed checkout' ' - test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" && +test_expect_success 'missing file in delayed checkout' ' + test_config_global filter.bug.process "test-tool rot13-filter --log=bug.log clean smudge delay" && test_config_global filter.bug.required true && rm -rf repo && @@ -960,8 +957,8 @@ test_expect_success PERL 'missing file in delayed checkout' ' grep "error: .missing-delay\.a. was not filtered properly" git-stderr.log ' -test_expect_success PERL 'invalid file in delayed checkout' ' - test_config_global filter.bug.process "rot13-filter.pl bug.log clean smudge delay" && +test_expect_success 'invalid file in delayed checkout' ' + test_config_global filter.bug.process "test-tool rot13-filter --log=bug.log clean smudge delay" && test_config_global filter.bug.required true && rm -rf repo && @@ -990,10 +987,10 @@ do mode_prereq='UTF8_NFD_TO_NFC' ;; esac - test_expect_success PERL,SYMLINKS,$mode_prereq \ + test_expect_success SYMLINKS,$mode_prereq \ "delayed checkout with $mode-collision don't write to the wrong place" ' test_config_global filter.delay.process \ - "\"$TEST_ROOT/rot13-filter.pl\" --always-delay delayed.log clean smudge delay" && + "test-tool rot13-filter --always-delay --log=delayed.log clean smudge delay" && test_config_global filter.delay.required true && git init $mode-collision && @@ -1026,12 +1023,12 @@ do ' done -test_expect_success PERL,SYMLINKS,CASE_INSENSITIVE_FS \ +test_expect_success SYMLINKS,CASE_INSENSITIVE_FS \ "delayed checkout with submodule collision don't write to the wrong place" ' git init collision-with-submodule && ( cd collision-with-submodule && - git config filter.delay.process "\"$TEST_ROOT/rot13-filter.pl\" --always-delay delayed.log clean smudge delay" && + git config filter.delay.process "test-tool rot13-filter --always-delay --log=delayed.log clean smudge delay" && git config filter.delay.required true && # We need Git to treat the submodule "a" and the @@ -1062,11 +1059,11 @@ test_expect_success PERL,SYMLINKS,CASE_INSENSITIVE_FS \ ) ' -test_expect_success PERL 'setup for progress tests' ' +test_expect_success 'setup for progress tests' ' git init progress && ( cd progress && - git config filter.delay.process "rot13-filter.pl delay-progress.log clean smudge delay" && + git config filter.delay.process "test-tool rot13-filter --log=delay-progress.log clean smudge delay" && git config filter.delay.required true && echo "*.a filter=delay" >.gitattributes && @@ -1132,12 +1129,12 @@ do ' done -test_expect_success PERL 'delayed checkout correctly reports the number of updated entries' ' +test_expect_success 'delayed checkout correctly reports the number of updated entries' ' rm -rf repo && git init repo && ( cd repo && - git config filter.delay.process "../rot13-filter.pl delayed.log clean smudge delay" && + git config filter.delay.process "test-tool rot13-filter --log=delayed.log clean smudge delay" && git config filter.delay.required true && echo "*.a filter=delay" >.gitattributes && diff --git a/t/t0021/rot13-filter.pl b/t/t0021/rot13-filter.pl deleted file mode 100644 index 7bb93768f3..0000000000 --- a/t/t0021/rot13-filter.pl +++ /dev/null @@ -1,247 +0,0 @@ -# -# Example implementation for the Git filter protocol version 2 -# See Documentation/gitattributes.txt, section "Filter Protocol" -# -# Usage: rot13-filter.pl [--always-delay] <log path> <capabilities> -# -# Log path defines a debug log file that the script writes to. The -# subsequent arguments define a list of supported protocol capabilities -# ("clean", "smudge", etc). -# -# When --always-delay is given all pathnames with the "can-delay" flag -# that don't appear on the list bellow are delayed with a count of 1 -# (see more below). -# -# This implementation supports special test cases: -# (1) If data with the pathname "clean-write-fail.r" is processed with -# a "clean" operation then the write operation will die. -# (2) If data with the pathname "smudge-write-fail.r" is processed with -# a "smudge" operation then the write operation will die. -# (3) If data with the pathname "error.r" is processed with any -# operation then the filter signals that it cannot or does not want -# to process the file. -# (4) If data with the pathname "abort.r" is processed with any -# operation then the filter signals that it cannot or does not want -# to process the file and any file after that is processed with the -# same command. -# (5) If data with a pathname that is a key in the DELAY hash is -# requested (e.g. "test-delay10.a") then the filter responds with -# a "delay" status and sets the "requested" field in the DELAY hash. -# The filter will signal the availability of this object after -# "count" (field in DELAY hash) "list_available_blobs" commands. -# (6) If data with the pathname "missing-delay.a" is processed that the -# filter will drop the path from the "list_available_blobs" response. -# (7) If data with the pathname "invalid-delay.a" is processed that the -# filter will add the path "unfiltered" which was not delayed before -# to the "list_available_blobs" response. -# - -use 5.008; -sub gitperllib { - # Git assumes that all path lists are Unix-y colon-separated ones. But - # when the Git for Windows executes the test suite, its MSYS2 Bash - # calls git.exe, and colon-separated path lists are converted into - # Windows-y semicolon-separated lists of *Windows* paths (which - # naturally contain a colon after the drive letter, so splitting by - # colons simply does not cut it). - # - # Detect semicolon-separated path list and handle them appropriately. - - if ($ENV{GITPERLLIB} =~ /;/) { - return split(/;/, $ENV{GITPERLLIB}); - } - return split(/:/, $ENV{GITPERLLIB}); -} -use lib (gitperllib()); -use strict; -use warnings; -use IO::File; -use Git::Packet; - -my $MAX_PACKET_CONTENT_SIZE = 65516; - -my $always_delay = 0; -if ( $ARGV[0] eq '--always-delay' ) { - $always_delay = 1; - shift @ARGV; -} - -my $log_file = shift @ARGV; -my @capabilities = @ARGV; - -open my $debug, ">>", $log_file or die "cannot open log file: $!"; - -my %DELAY = ( - 'test-delay10.a' => { "requested" => 0, "count" => 1 }, - 'test-delay11.a' => { "requested" => 0, "count" => 1 }, - 'test-delay20.a' => { "requested" => 0, "count" => 2 }, - 'test-delay10.b' => { "requested" => 0, "count" => 1 }, - 'missing-delay.a' => { "requested" => 0, "count" => 1 }, - 'invalid-delay.a' => { "requested" => 0, "count" => 1 }, -); - -sub rot13 { - my $str = shift; - $str =~ y/A-Za-z/N-ZA-Mn-za-m/; - return $str; -} - -print $debug "START\n"; -$debug->flush(); - -packet_initialize("git-filter", 2); - -my %remote_caps = packet_read_and_check_capabilities("clean", "smudge", "delay"); -packet_check_and_write_capabilities(\%remote_caps, @capabilities); - -print $debug "init handshake complete\n"; -$debug->flush(); - -while (1) { - my ( $res, $command ) = packet_key_val_read("command"); - if ( $res == -1 ) { - print $debug "STOP\n"; - exit(); - } - print $debug "IN: $command"; - $debug->flush(); - - if ( $command eq "list_available_blobs" ) { - # Flush - packet_compare_lists([1, ""], packet_bin_read()) || - die "bad list_available_blobs end"; - - foreach my $pathname ( sort keys %DELAY ) { - if ( $DELAY{$pathname}{"requested"} >= 1 ) { - $DELAY{$pathname}{"count"} = $DELAY{$pathname}{"count"} - 1; - if ( $pathname eq "invalid-delay.a" ) { - # Send Git a pathname that was not delayed earlier - packet_txt_write("pathname=unfiltered"); - } - if ( $pathname eq "missing-delay.a" ) { - # Do not signal Git that this file is available - } elsif ( $DELAY{$pathname}{"count"} == 0 ) { - print $debug " $pathname"; - packet_txt_write("pathname=$pathname"); - } - } - } - - packet_flush(); - - print $debug " [OK]\n"; - $debug->flush(); - packet_txt_write("status=success"); - packet_flush(); - } else { - my ( $res, $pathname ) = packet_key_val_read("pathname"); - if ( $res == -1 ) { - die "unexpected EOF while expecting pathname"; - } - print $debug " $pathname"; - $debug->flush(); - - # Read until flush - my ( $done, $buffer ) = packet_txt_read(); - while ( $buffer ne '' ) { - if ( $buffer eq "can-delay=1" ) { - if ( exists $DELAY{$pathname} and $DELAY{$pathname}{"requested"} == 0 ) { - $DELAY{$pathname}{"requested"} = 1; - } elsif ( !exists $DELAY{$pathname} and $always_delay ) { - $DELAY{$pathname} = { "requested" => 1, "count" => 1 }; - } - } elsif ($buffer =~ /^(ref|treeish|blob)=/) { - print $debug " $buffer"; - } else { - # In general, filters need to be graceful about - # new metadata, since it's documented that we - # can pass any key-value pairs, but for tests, - # let's be a little stricter. - die "Unknown message '$buffer'"; - } - - ( $done, $buffer ) = packet_txt_read(); - } - if ( $done == -1 ) { - die "unexpected EOF after pathname '$pathname'"; - } - - my $input = ""; - { - binmode(STDIN); - my $buffer; - my $done = 0; - while ( !$done ) { - ( $done, $buffer ) = packet_bin_read(); - $input .= $buffer; - } - if ( $done == -1 ) { - die "unexpected EOF while reading input for '$pathname'"; - } - print $debug " " . length($input) . " [OK] -- "; - $debug->flush(); - } - - my $output; - if ( exists $DELAY{$pathname} and exists $DELAY{$pathname}{"output"} ) { - $output = $DELAY{$pathname}{"output"} - } elsif ( $pathname eq "error.r" or $pathname eq "abort.r" ) { - $output = ""; - } elsif ( $command eq "clean" and grep( /^clean$/, @capabilities ) ) { - $output = rot13($input); - } elsif ( $command eq "smudge" and grep( /^smudge$/, @capabilities ) ) { - $output = rot13($input); - } else { - die "bad command '$command'"; - } - - if ( $pathname eq "error.r" ) { - print $debug "[ERROR]\n"; - $debug->flush(); - packet_txt_write("status=error"); - packet_flush(); - } elsif ( $pathname eq "abort.r" ) { - print $debug "[ABORT]\n"; - $debug->flush(); - packet_txt_write("status=abort"); - packet_flush(); - } elsif ( $command eq "smudge" and - exists $DELAY{$pathname} and - $DELAY{$pathname}{"requested"} == 1 ) { - print $debug "[DELAYED]\n"; - $debug->flush(); - packet_txt_write("status=delayed"); - packet_flush(); - $DELAY{$pathname}{"requested"} = 2; - $DELAY{$pathname}{"output"} = $output; - } else { - packet_txt_write("status=success"); - packet_flush(); - - if ( $pathname eq "${command}-write-fail.r" ) { - print $debug "[WRITE FAIL]\n"; - $debug->flush(); - die "${command} write error"; - } - - print $debug "OUT: " . length($output) . " "; - $debug->flush(); - - while ( length($output) > 0 ) { - my $packet = substr( $output, 0, $MAX_PACKET_CONTENT_SIZE ); - packet_bin_write($packet); - # dots represent the number of packets - print $debug "."; - if ( length($output) > $MAX_PACKET_CONTENT_SIZE ) { - $output = substr( $output, $MAX_PACKET_CONTENT_SIZE ); - } else { - $output = ""; - } - } - packet_flush(); - print $debug " [OK]\n"; - $debug->flush(); - packet_flush(); - } - } -} diff --git a/t/t0027-auto-crlf.sh b/t/t0027-auto-crlf.sh index 7f80f46393..a94ac1eae3 100755 --- a/t/t0027-auto-crlf.sh +++ b/t/t0027-auto-crlf.sh @@ -2,6 +2,7 @@ test_description='CRLF conversion all combinations' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh compare_files () { @@ -386,9 +387,7 @@ test_expect_success 'setup main' ' test_tick ' -# Disable extra chain-linting for the next set of tests. There are many -# auto-generated ones that are not worth checking over and over. -GIT_TEST_CHAIN_LINT_HARDER_DEFAULT=0 + warn_LF_CRLF="LF will be replaced by CRLF" warn_CRLF_LF="CRLF will be replaced by LF" @@ -605,9 +604,6 @@ do checkout_files "" "$id" "crlf" true "" CRLF CRLF CRLF CRLF_mix_CR CRLF_nul done -# The rest of the tests are unique; do the usual linting. -unset GIT_TEST_CHAIN_LINT_HARDER_DEFAULT - # Should be the last test case: remove some files from the worktree test_expect_success 'ls-files --eol -d -z' ' rm crlf_false_attr__CRLF.txt crlf_false_attr__CRLF_mix_LF.txt crlf_false_attr__LF.txt .gitattributes && diff --git a/t/t0032-reftable-unittest.sh b/t/t0032-reftable-unittest.sh index 0ed14971a5..471cb37ac2 100755 --- a/t/t0032-reftable-unittest.sh +++ b/t/t0032-reftable-unittest.sh @@ -5,6 +5,7 @@ test_description='reftable unittests' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'unittests' ' diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh index f4d737dadd..dc3496897a 100755 --- a/t/t0033-safe-directory.sh +++ b/t/t0033-safe-directory.sh @@ -2,6 +2,7 @@ test_description='verify safe.directory checks' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh GIT_TEST_ASSUME_DIFFERENT_OWNER=1 @@ -70,4 +71,13 @@ test_expect_success 'safe.directory=*, but is reset' ' expect_rejected_dir ' +test_expect_success 'safe.directory in included file' ' + cat >gitconfig-include <<-EOF && + [safe] + directory = "$(pwd)" + EOF + git config --global --add include.path "$(pwd)/gitconfig-include" && + git status +' + test_done diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh index ecbdc8238d..11c15a48aa 100755 --- a/t/t0035-safe-bare-repository.sh +++ b/t/t0035-safe-bare-repository.sh @@ -51,4 +51,13 @@ test_expect_success 'safe.bareRepository on the command line' ' -c safe.bareRepository=all ' +test_expect_success 'safe.bareRepository in included file' ' + cat >gitconfig-include <<-\EOF && + [safe] + bareRepository = explicit + EOF + git config --global --add include.path "$(pwd)/gitconfig-include" && + expect_rejected -C outer-repo/bare-repo +' + test_done diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index ed2fb620a9..5cc62306e3 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -456,4 +456,257 @@ test_expect_success '--end-of-options treats remainder as args' ' --end-of-options --verbose ' +test_expect_success 'KEEP_DASHDASH works' ' + test-tool parse-options-flags --keep-dashdash cmd --opt=1 -- --opt=2 --unknown >actual && + cat >expect <<-\EOF && + opt: 1 + arg 00: -- + arg 01: --opt=2 + arg 02: --unknown + EOF + test_cmp expect actual +' + +test_expect_success 'KEEP_ARGV0 works' ' + test-tool parse-options-flags --keep-argv0 cmd arg0 --opt=3 >actual && + cat >expect <<-\EOF && + opt: 3 + arg 00: cmd + arg 01: arg0 + EOF + test_cmp expect actual +' + +test_expect_success 'STOP_AT_NON_OPTION works' ' + test-tool parse-options-flags --stop-at-non-option cmd --opt=4 arg0 --opt=5 --unknown >actual && + cat >expect <<-\EOF && + opt: 4 + arg 00: arg0 + arg 01: --opt=5 + arg 02: --unknown + EOF + test_cmp expect actual +' + +test_expect_success 'KEEP_UNKNOWN_OPT works' ' + test-tool parse-options-flags --keep-unknown-opt cmd --unknown=1 --opt=6 -u2 >actual && + cat >expect <<-\EOF && + opt: 6 + arg 00: --unknown=1 + arg 01: -u2 + EOF + test_cmp expect actual +' + +test_expect_success 'NO_INTERNAL_HELP works for -h' ' + test_expect_code 129 test-tool parse-options-flags --no-internal-help cmd -h 2>err && + grep "^error: unknown switch \`h$SQ" err && + grep "^usage: " err +' + +for help_opt in help help-all +do + test_expect_success "NO_INTERNAL_HELP works for --$help_opt" " + test_expect_code 129 test-tool parse-options-flags --no-internal-help cmd --$help_opt 2>err && + grep '^error: unknown option \`'$help_opt\' err && + grep '^usage: ' err + " +done + +test_expect_success 'KEEP_UNKNOWN_OPT | NO_INTERNAL_HELP works' ' + test-tool parse-options-flags --keep-unknown-opt --no-internal-help cmd -h --help --help-all >actual && + cat >expect <<-\EOF && + opt: 0 + arg 00: -h + arg 01: --help + arg 02: --help-all + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - no subcommand shows error and usage' ' + test_expect_code 129 test-tool parse-subcommand cmd 2>err && + grep "^error: need a subcommand" err && + grep ^usage: err +' + +test_expect_success 'subcommand - subcommand after -- shows error and usage' ' + test_expect_code 129 test-tool parse-subcommand cmd -- subcmd-one 2>err && + grep "^error: need a subcommand" err && + grep ^usage: err +' + +test_expect_success 'subcommand - subcommand after --end-of-options shows error and usage' ' + test_expect_code 129 test-tool parse-subcommand cmd --end-of-options subcmd-one 2>err && + grep "^error: need a subcommand" err && + grep ^usage: err +' + +test_expect_success 'subcommand - unknown subcommand shows error and usage' ' + test_expect_code 129 test-tool parse-subcommand cmd nope 2>err && + grep "^error: unknown subcommand: \`nope$SQ" err && + grep ^usage: err +' + +test_expect_success 'subcommand - subcommands cannot be abbreviated' ' + test_expect_code 129 test-tool parse-subcommand cmd subcmd-o 2>err && + grep "^error: unknown subcommand: \`subcmd-o$SQ$" err && + grep ^usage: err +' + +test_expect_success 'subcommand - no negated subcommands' ' + test_expect_code 129 test-tool parse-subcommand cmd no-subcmd-one 2>err && + grep "^error: unknown subcommand: \`no-subcmd-one$SQ" err && + grep ^usage: err +' + +test_expect_success 'subcommand - simple' ' + test-tool parse-subcommand cmd subcmd-two >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_two + arg 00: subcmd-two + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - stop parsing at the first subcommand' ' + test-tool parse-subcommand cmd --opt=1 subcmd-two subcmd-one --opt=2 >actual && + cat >expect <<-\EOF && + opt: 1 + fn: subcmd_two + arg 00: subcmd-two + arg 01: subcmd-one + arg 02: --opt=2 + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - KEEP_ARGV0' ' + test-tool parse-subcommand --keep-argv0 cmd subcmd-two >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_two + arg 00: cmd + arg 01: subcmd-two + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + subcommand not given' ' + test-tool parse-subcommand --subcommand-optional cmd >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_one + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + given subcommand' ' + test-tool parse-subcommand --subcommand-optional cmd subcmd-two branch file >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_two + arg 00: subcmd-two + arg 01: branch + arg 02: file + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + subcommand not given + unknown dashless args' ' + test-tool parse-subcommand --subcommand-optional cmd branch file >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_one + arg 00: branch + arg 01: file + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL + subcommand not given + unknown option' ' + test_expect_code 129 test-tool parse-subcommand --subcommand-optional cmd --subcommand-opt 2>err && + grep "^error: unknown option" err && + grep ^usage: err +' + +test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT + subcommand not given + unknown option' ' + test-tool parse-subcommand --subcommand-optional --keep-unknown-opt cmd --subcommand-opt >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_one + arg 00: --subcommand-opt + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT + subcommand ignored after unknown option' ' + test-tool parse-subcommand --subcommand-optional --keep-unknown-opt cmd --subcommand-opt subcmd-two >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_one + arg 00: --subcommand-opt + arg 01: subcmd-two + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT + command and subcommand options cannot be mixed' ' + test-tool parse-subcommand --subcommand-optional --keep-unknown-opt cmd --subcommand-opt branch --opt=1 >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_one + arg 00: --subcommand-opt + arg 01: branch + arg 02: --opt=1 + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT | KEEP_ARGV0' ' + test-tool parse-subcommand --subcommand-optional --keep-unknown-opt --keep-argv0 cmd --subcommand-opt branch >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_one + arg 00: cmd + arg 01: --subcommand-opt + arg 02: branch + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - SUBCOMMAND_OPTIONAL | KEEP_UNKNOWN_OPT | KEEP_DASHDASH' ' + test-tool parse-subcommand --subcommand-optional --keep-unknown-opt --keep-dashdash cmd -- --subcommand-opt file >actual && + cat >expect <<-\EOF && + opt: 0 + fn: subcmd_one + arg 00: -- + arg 01: --subcommand-opt + arg 02: file + EOF + test_cmp expect actual +' + +test_expect_success 'subcommand - completion helper' ' + test-tool parse-subcommand cmd --git-completion-helper >actual && + echo "subcmd-one subcmd-two --opt= --no-opt" >expect && + test_cmp expect actual +' + +test_expect_success 'subcommands are incompatible with STOP_AT_NON_OPTION' ' + test_must_fail test-tool parse-subcommand --stop-at-non-option cmd subcmd-one 2>err && + grep ^BUG err +' + +test_expect_success 'subcommands are incompatible with KEEP_UNKNOWN_OPT unless in combination with SUBCOMMAND_OPTIONAL' ' + test_must_fail test-tool parse-subcommand --keep-unknown-opt cmd subcmd-two 2>err && + grep ^BUG err +' + +test_expect_success 'subcommands are incompatible with KEEP_DASHDASH unless in combination with SUBCOMMAND_OPTIONAL' ' + test_must_fail test-tool parse-subcommand --keep-dashdash cmd subcmd-two 2>err && + grep ^BUG err +' + test_done diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh index 5c9dc90d0b..325eb1c3cd 100755 --- a/t/t0050-filesystem.sh +++ b/t/t0050-filesystem.sh @@ -5,6 +5,7 @@ test_description='Various filesystem issues' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh auml=$(printf '\303\244') diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 1f2007e62b..68e29c904a 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -22,7 +22,7 @@ relative_path() { test_submodule_relative_url() { test_expect_success "test_submodule_relative_url: $1 $2 $3 => $4" " - actual=\$(git submodule--helper resolve-relative-url-test '$1' '$2' '$3') && + actual=\$(test-tool submodule resolve-relative-url '$1' '$2' '$3') && test \"\$actual\" = '$4' " } diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh index 08f5fe9cae..b6d2f591ac 100755 --- a/t/t0091-bugreport.sh +++ b/t/t0091-bugreport.sh @@ -78,4 +78,52 @@ test_expect_success 'indicates populated hooks' ' test_cmp expect actual ' +test_expect_success UNZIP '--diagnose creates diagnostics zip archive' ' + test_when_finished rm -rf report && + + git bugreport --diagnose -o report -s test >out && + + zip_path=report/git-diagnostics-test.zip && + grep "Available space" out && + test_path_is_file "$zip_path" && + + # Check zipped archive content + "$GIT_UNZIP" -p "$zip_path" diagnostics.log >out && + test_file_not_empty out && + + "$GIT_UNZIP" -p "$zip_path" packs-local.txt >out && + grep ".git/objects" out && + + "$GIT_UNZIP" -p "$zip_path" objects-local.txt >out && + grep "^Total: [0-9][0-9]*" out && + + # Should not include .git directory contents by default + ! "$GIT_UNZIP" -l "$zip_path" | grep ".git/" +' + +test_expect_success UNZIP '--diagnose=stats excludes .git dir contents' ' + test_when_finished rm -rf report && + + git bugreport --diagnose=stats -o report -s test >out && + + # Includes pack quantity/size info + "$GIT_UNZIP" -p "$zip_path" packs-local.txt >out && + grep ".git/objects" out && + + # Does not include .git directory contents + ! "$GIT_UNZIP" -l "$zip_path" | grep ".git/" +' + +test_expect_success UNZIP '--diagnose=all includes .git dir contents' ' + test_when_finished rm -rf report && + + git bugreport --diagnose=all -o report -s test >out && + + # Includes .git directory contents + "$GIT_UNZIP" -l "$zip_path" | grep ".git/" && + + "$GIT_UNZIP" -p "$zip_path" .git/HEAD >out && + test_file_not_empty out +' + test_done diff --git a/t/t0092-diagnose.sh b/t/t0092-diagnose.sh new file mode 100755 index 0000000000..133e5747d6 --- /dev/null +++ b/t/t0092-diagnose.sh @@ -0,0 +1,72 @@ +#!/bin/sh + +test_description='git diagnose' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success UNZIP 'creates diagnostics zip archive' ' + test_when_finished rm -rf report && + + git diagnose -o report -s test >out && + grep "Available space" out && + + zip_path=report/git-diagnostics-test.zip && + test_path_is_file "$zip_path" && + + # Check zipped archive content + "$GIT_UNZIP" -p "$zip_path" diagnostics.log >out && + test_file_not_empty out && + + "$GIT_UNZIP" -p "$zip_path" packs-local.txt >out && + grep ".git/objects" out && + + "$GIT_UNZIP" -p "$zip_path" objects-local.txt >out && + grep "^Total: [0-9][0-9]*" out && + + # Should not include .git directory contents by default + ! "$GIT_UNZIP" -l "$zip_path" | grep ".git/" +' + +test_expect_success UNZIP 'counts loose objects' ' + test_commit A && + + # After committing, should have non-zero loose objects + git diagnose -o test-count -s 1 >out && + zip_path=test-count/git-diagnostics-1.zip && + "$GIT_UNZIP" -p "$zip_path" objects-local.txt >out && + grep "^Total: [1-9][0-9]* loose objects" out +' + +test_expect_success UNZIP '--mode=stats excludes .git dir contents' ' + test_when_finished rm -rf report && + + git diagnose -o report -s test --mode=stats >out && + + # Includes pack quantity/size info + zip_path=report/git-diagnostics-test.zip && + "$GIT_UNZIP" -p "$zip_path" packs-local.txt >out && + grep ".git/objects" out && + + # Does not include .git directory contents + ! "$GIT_UNZIP" -l "$zip_path" | grep ".git/" +' + +test_expect_success UNZIP '--mode=all includes .git dir contents' ' + test_when_finished rm -rf report && + + git diagnose -o report -s test --mode=all >out && + + # Includes pack quantity/size info + zip_path=report/git-diagnostics-test.zip && + "$GIT_UNZIP" -p "$zip_path" packs-local.txt >out && + grep ".git/objects" out && + + # Includes .git directory contents + "$GIT_UNZIP" -l "$zip_path" | grep ".git/" && + + "$GIT_UNZIP" -p "$zip_path" .git/HEAD >out && + test_file_not_empty out +' + +test_done diff --git a/t/t0095-bloom.sh b/t/t0095-bloom.sh index daeb4a5e3e..b567383eb8 100755 --- a/t/t0095-bloom.sh +++ b/t/t0095-bloom.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='Testing the various Bloom filter computations in bloom.c' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'compute unseeded murmur3 hash for empty string' ' diff --git a/t/t0202-gettext-perl.sh b/t/t0202-gettext-perl.sh index df2ea34932..5a6f28051b 100755 --- a/t/t0202-gettext-perl.sh +++ b/t/t0202-gettext-perl.sh @@ -7,22 +7,12 @@ test_description='Perl gettext interface (Git::I18N)' TEST_PASSES_SANITIZE_LEAK=true . ./lib-gettext.sh +. "$TEST_DIRECTORY"/lib-perl.sh +skip_all_if_no_Test_More -if ! test_have_prereq PERL; then - skip_all='skipping perl interface tests, perl not available' - test_done -fi - -perl -MTest::More -e 0 2>/dev/null || { - skip_all="Perl Test::More unavailable, skipping test" - test_done -} - -# The external test will outputs its own plan -test_external_has_tap=1 - -test_external_without_stderr \ - 'Perl Git::I18N API' \ - perl "$TEST_DIRECTORY"/t0202/test.pl +test_expect_success 'run t0202/test.pl to test Git::I18N.pm' ' + "$PERL_PATH" "$TEST_DIRECTORY"/t0202/test.pl 2>stderr && + test_must_be_empty stderr +' test_done diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh index 0ce1f22eff..86cff324ff 100755 --- a/t/t0203-gettext-setlocale-sanity.sh +++ b/t/t0203-gettext-setlocale-sanity.sh @@ -5,6 +5,7 @@ test_description="The Git C functions aren't broken by setlocale(3)" +TEST_PASSES_SANITIZE_LEAK=true . ./lib-gettext.sh test_expect_success 'git show a ISO-8859-1 commit under C locale' ' diff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh index 22d0845544..0b3436e8ca 100755 --- a/t/t0211-trace2-perf.sh +++ b/t/t0211-trace2-perf.sh @@ -173,4 +173,99 @@ test_expect_success 'using global config, perf stream, return code 0' ' test_cmp expect actual ' +# Exercise the stopwatch timers in a loop and confirm that we have +# as many start/stop intervals as expected. We cannot really test the +# actual (total, min, max) timer values, so we have to assume that they +# are good, but we can verify the interval count. +# +# The timer "test/test1" should only emit a global summary "timer" event. +# The timer "test/test2" should emit per-thread "th_timer" events and a +# global summary "timer" event. + +have_timer_event () { + thread=$1 event=$2 category=$3 name=$4 intervals=$5 file=$6 && + + pattern="d0|${thread}|${event}||||${category}|name:${name} intervals:${intervals}" && + + grep "${pattern}" ${file} +} + +test_expect_success 'stopwatch timer test/test1' ' + test_when_finished "rm trace.perf actual" && + test_config_global trace2.perfBrief 1 && + test_config_global trace2.perfTarget "$(pwd)/trace.perf" && + + # Use the timer "test1" 5 times from "main". + test-tool trace2 100timer 5 10 && + + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual && + + have_timer_event "main" "timer" "test" "test1" 5 actual +' + +test_expect_success 'stopwatch timer test/test2' ' + test_when_finished "rm trace.perf actual" && + test_config_global trace2.perfBrief 1 && + test_config_global trace2.perfTarget "$(pwd)/trace.perf" && + + # Use the timer "test2" 5 times each in 3 threads. + test-tool trace2 101timer 5 10 3 && + + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual && + + # So we should have 3 per-thread events of 5 each. + have_timer_event "th01:ut_101" "th_timer" "test" "test2" 5 actual && + have_timer_event "th02:ut_101" "th_timer" "test" "test2" 5 actual && + have_timer_event "th03:ut_101" "th_timer" "test" "test2" 5 actual && + + # And we should have 15 total uses. + have_timer_event "main" "timer" "test" "test2" 15 actual +' + +# Exercise the global counters and confirm that we get the expected values. +# +# The counter "test/test1" should only emit a global summary "counter" event. +# The counter "test/test2" could emit per-thread "th_counter" events and a +# global summary "counter" event. + +have_counter_event () { + thread=$1 event=$2 category=$3 name=$4 value=$5 file=$6 && + + pattern="d0|${thread}|${event}||||${category}|name:${name} value:${value}" && + + grep "${patern}" ${file} +} + +test_expect_success 'global counter test/test1' ' + test_when_finished "rm trace.perf actual" && + test_config_global trace2.perfBrief 1 && + test_config_global trace2.perfTarget "$(pwd)/trace.perf" && + + # Use the counter "test1" and add n integers. + test-tool trace2 200counter 1 2 3 4 5 && + + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual && + + have_counter_event "main" "counter" "test" "test1" 15 actual +' + +test_expect_success 'global counter test/test2' ' + test_when_finished "rm trace.perf actual" && + test_config_global trace2.perfBrief 1 && + test_config_global trace2.perfTarget "$(pwd)/trace.perf" && + + # Add 2 integers to the counter "test2" in each of 3 threads. + test-tool trace2 201counter 7 13 3 && + + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <trace.perf >actual && + + # So we should have 3 per-thread events of 5 each. + have_counter_event "th01:ut_201" "th_counter" "test" "test2" 20 actual && + have_counter_event "th02:ut_201" "th_counter" "test" "test2" 20 actual && + have_counter_event "th03:ut_201" "th_counter" "test" "test2" 20 actual && + + # And we should have a single event with the total across all threads. + have_counter_event "main" "counter" "test" "test2" 60 actual +' + test_done diff --git a/t/t0211/scrub_perf.perl b/t/t0211/scrub_perf.perl index 299999f0f8..7a50bae646 100644 --- a/t/t0211/scrub_perf.perl +++ b/t/t0211/scrub_perf.perl @@ -64,6 +64,12 @@ while (<>) { goto SKIP_LINE; } } + elsif ($tokens[$col_event] =~ m/timer/) { + # This also captures "th_timer" events + $tokens[$col_rest] =~ s/ total:\d+\.\d*/ total:_T_TOTAL_/; + $tokens[$col_rest] =~ s/ min:\d+\.\d*/ min:_T_MIN_/; + $tokens[$col_rest] =~ s/ max:\d+\.\d*/ max:_T_MAX_/; + } # t_abs and t_rel are either blank or a float. Replace the float # with a constant for matching the HEREDOC in the test script. diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index 1e864cf317..5b7bee888d 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -215,6 +215,20 @@ test_expect_success 'fetching of missing objects' ' grep "$HASH" out ' +test_expect_success 'fetching of a promised object that promisor remote no longer has' ' + rm -f err && + test_create_repo unreliable-server && + git -C unreliable-server config uploadpack.allowanysha1inwant 1 && + git -C unreliable-server config uploadpack.allowfilter 1 && + test_commit -C unreliable-server foo && + + git clone --filter=blob:none --no-checkout "file://$(pwd)/unreliable-server" unreliable-client && + + rm -rf unreliable-server/.git/objects/* && + test_must_fail git -C unreliable-client checkout HEAD 2>err && + grep "could not fetch.*from promisor remote" err +' + test_expect_success 'fetching of missing objects works with ref-in-want enabled' ' # ref-in-want requires protocol version 2 git -C server config protocol.version 2 && diff --git a/t/t0450-txt-doc-vs-help.sh b/t/t0450-txt-doc-vs-help.sh new file mode 100755 index 0000000000..cd3969e852 --- /dev/null +++ b/t/t0450-txt-doc-vs-help.sh @@ -0,0 +1,172 @@ +#!/bin/sh + +test_description='assert (unbuilt) Documentation/*.txt and -h output + +Run this with --debug to see a summary of where we still fail to make +the two versions consistent with one another.' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'setup: list of builtins' ' + git --list-cmds=builtins >builtins +' + +test_expect_success 'list of txt and help mismatches is sorted' ' + sort -u "$TEST_DIRECTORY"/t0450/txt-help-mismatches >expect && + if ! test_cmp expect "$TEST_DIRECTORY"/t0450/txt-help-mismatches + then + BUG "please keep the list of txt and help mismatches sorted" + fi +' + +help_to_synopsis () { + builtin="$1" && + out_dir="out/$builtin" && + out="$out_dir/help.synopsis" && + if test -f "$out" + then + echo "$out" && + return 0 + fi && + mkdir -p "$out_dir" && + test_expect_code 129 git $builtin -h >"$out.raw" 2>&1 && + sed -n \ + -e '1,/^$/ { + /^$/d; + s/^usage: //; + s/^ *or: //; + p; + }' <"$out.raw" >"$out" && + echo "$out" +} + +builtin_to_txt () { + echo "$GIT_BUILD_DIR/Documentation/git-$1.txt" +} + +txt_to_synopsis () { + builtin="$1" && + out_dir="out/$builtin" && + out="$out_dir/txt.synopsis" && + if test -f "$out" + then + echo "$out" && + return 0 + fi && + b2t="$(builtin_to_txt "$builtin")" && + sed -n \ + -e '/^\[verse\]$/,/^$/ { + /^$/d; + /^\[verse\]$/d; + + s/{litdd}/--/g; + s/'\''\(git[ a-z-]*\)'\''/\1/g; + + p; + }' \ + <"$b2t" >"$out" && + echo "$out" +} + +check_dashed_labels () { + ! grep -E "<[^>_-]+_" "$1" +} + +HT=" " + +align_after_nl () { + builtin="$1" && + len=$(printf "git %s " "$builtin" | wc -c) && + pad=$(printf "%${len}s" "") && + + sed "s/^[ $HT][ $HT]*/$pad/" +} + +test_debug '>failing' +while read builtin +do + # -h output assertions + test_expect_success "$builtin -h output has no \t" ' + h2s="$(help_to_synopsis "$builtin")" && + ! grep "$HT" "$h2s" + ' + + test_expect_success "$builtin -h output has dashed labels" ' + check_dashed_labels "$(help_to_synopsis "$builtin")" + ' + + test_expect_success "$builtin -h output has consistent spacing" ' + h2s="$(help_to_synopsis "$builtin")" && + sed -n \ + -e "/^ / { + s/[^ ].*//; + p; + }" \ + <"$h2s" >help && + sort -u help >help.ws && + if test -s help.ws + then + test_line_count = 1 help.ws + fi + ' + + txt="$(builtin_to_txt "$builtin")" && + preq="$(echo BUILTIN_TXT_$builtin | tr '[:lower:]-' '[:upper:]_')" && + + if test -f "$txt" + then + test_set_prereq "$preq" + fi && + + # *.txt output assertions + test_expect_success "$preq" "$builtin *.txt SYNOPSIS has dashed labels" ' + check_dashed_labels "$(txt_to_synopsis "$builtin")" + ' + + # *.txt output consistency assertions + result= + if grep -q "^$builtin$" "$TEST_DIRECTORY"/t0450/txt-help-mismatches + then + result=failure + else + result=success + fi && + test_expect_$result "$preq" "$builtin -h output and SYNOPSIS agree" ' + t2s="$(txt_to_synopsis "$builtin")" && + if test "$builtin" = "merge-tree" + then + test_when_finished "rm -f t2s.new" && + sed -e '\''s/ (deprecated)$//g'\'' <"$t2s" >t2s.new + t2s=t2s.new + fi && + h2s="$(help_to_synopsis "$builtin")" && + + # The *.txt and -h use different spacing for the + # alignment of continued usage output, normalize it. + align_after_nl "$builtin" <"$t2s" >txt && + align_after_nl "$builtin" <"$h2s" >help && + test_cmp txt help + ' + + if test_have_prereq "$preq" && test -e txt && test -e help + then + test_debug ' + if test_cmp txt help >cmp 2>/dev/null + then + echo "=== DONE: $builtin ===" + else + echo "=== TODO: $builtin ===" && + cat cmp + fi >>failing + ' + + # Not in test_expect_success in case --run is being + # used with --debug + rm -f txt help tmp 2>/dev/null + fi +done <builtins + +test_debug 'say "$(cat failing)"' + +test_done diff --git a/t/t0450/txt-help-mismatches b/t/t0450/txt-help-mismatches new file mode 100644 index 0000000000..a0777acd66 --- /dev/null +++ b/t/t0450/txt-help-mismatches @@ -0,0 +1,58 @@ +add +am +apply +archive +bisect +blame +branch +check-ref-format +checkout +checkout-index +clone +column +config +credential +credential-cache +credential-store +fast-export +fast-import +fetch-pack +fmt-merge-msg +for-each-ref +format-patch +fsck-objects +fsmonitor--daemon +gc +grep +index-pack +init-db +log +ls-files +ls-tree +mailinfo +mailsplit +maintenance +merge +merge-file +merge-index +merge-one-file +multi-pack-index +name-rev +notes +pack-objects +push +range-diff +rebase +remote +remote-ext +remote-fd +repack +reset +restore +rev-parse +show +stage +switch +update-index +update-ref +whatchanged diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index bd5313caec..cdc077ce12 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -154,7 +154,7 @@ test_expect_success \ read_tree_u_must_succeed --reset -u $treeH && echo frotz frotz >frotz && git update-index --add frotz && - if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' + ! read_tree_u_must_succeed -m -u $treeH $treeM' test_expect_success \ '9 - conflicting addition.' \ @@ -163,7 +163,7 @@ test_expect_success \ echo frotz frotz >frotz && git update-index --add frotz && echo frotz >frotz && - if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' + ! read_tree_u_must_succeed -m -u $treeH $treeM' test_expect_success \ '10 - path removed.' \ @@ -186,7 +186,7 @@ test_expect_success \ echo rezrov >rezrov && git update-index --add rezrov && echo rezrov rezrov >rezrov && - if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' + ! read_tree_u_must_succeed -m -u $treeH $treeM' test_expect_success \ '12 - unmatching local changes being removed.' \ @@ -194,7 +194,7 @@ test_expect_success \ read_tree_u_must_succeed --reset -u $treeH && echo rezrov rezrov >rezrov && git update-index --add rezrov && - if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' + ! read_tree_u_must_succeed -m -u $treeH $treeM' test_expect_success \ '13 - unmatching local changes being removed.' \ @@ -203,7 +203,7 @@ test_expect_success \ echo rezrov rezrov >rezrov && git update-index --add rezrov && echo rezrov >rezrov && - if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' + ! read_tree_u_must_succeed -m -u $treeH $treeM' cat >expected <<EOF -100644 X 0 nitfol @@ -251,7 +251,7 @@ test_expect_success \ read_tree_u_must_succeed --reset -u $treeH && echo bozbar bozbar >bozbar && git update-index --add bozbar && - if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' + ! read_tree_u_must_succeed -m -u $treeH $treeM' test_expect_success \ '17 - conflicting local change.' \ @@ -260,7 +260,7 @@ test_expect_success \ echo bozbar bozbar >bozbar && git update-index --add bozbar && echo bozbar bozbar bozbar >bozbar && - if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' + ! read_tree_u_must_succeed -m -u $treeH $treeM' test_expect_success \ '18 - local change already having a good result.' \ @@ -316,7 +316,7 @@ test_expect_success \ echo bozbar >bozbar && git update-index --add bozbar && echo gnusto gnusto >bozbar && - if read_tree_u_must_succeed -m -u $treeH $treeM; then false; else :; fi' + ! read_tree_u_must_succeed -m -u $treeH $treeM' # Also make sure we did not break DF vs DF/DF case. test_expect_success \ diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index dadf3b1458..23b8942edb 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -88,7 +88,8 @@ done for opt in --buffer \ --follow-symlinks \ - --batch-all-objects + --batch-all-objects \ + -z do test_expect_success "usage: bad option combination: $opt without batch mode" ' test_incompatible_usage git cat-file $opt && @@ -100,6 +101,10 @@ echo_without_newline () { printf '%s' "$*" } +echo_without_newline_nul () { + echo_without_newline "$@" | tr '\n' '\0' +} + strlen () { echo_without_newline "$1" | wc -c | sed -e 's/^ *//' } @@ -398,6 +403,12 @@ test_expect_success '--batch with multiple sha1s gives correct format' ' test "$(maybe_remove_timestamp "$batch_output" 1)" = "$(maybe_remove_timestamp "$(echo_without_newline "$batch_input" | git cat-file --batch)" 1)" ' +test_expect_success '--batch, -z with multiple sha1s gives correct format' ' + echo_without_newline_nul "$batch_input" >in && + test "$(maybe_remove_timestamp "$batch_output" 1)" = \ + "$(maybe_remove_timestamp "$(git cat-file --batch -z <in)" 1)" +' + batch_check_input="$hello_sha1 $tree_sha1 $commit_sha1 @@ -418,6 +429,30 @@ test_expect_success "--batch-check with multiple sha1s gives correct format" ' "$(echo_without_newline "$batch_check_input" | git cat-file --batch-check)" ' +test_expect_success "--batch-check, -z with multiple sha1s gives correct format" ' + echo_without_newline_nul "$batch_check_input" >in && + test "$batch_check_output" = "$(git cat-file --batch-check -z <in)" +' + +test_expect_success FUNNYNAMES '--batch-check, -z with newline in input' ' + touch -- "newline${LF}embedded" && + git add -- "newline${LF}embedded" && + git commit -m "file with newline embedded" && + test_tick && + + printf "HEAD:newline${LF}embedded" >in && + git cat-file --batch-check -z <in >actual && + + echo "$(git rev-parse "HEAD:newline${LF}embedded") blob 0" >expect && + test_cmp expect actual +' + +batch_command_multiple_info="info $hello_sha1 +info $tree_sha1 +info $commit_sha1 +info $tag_sha1 +info deadbeef" + test_expect_success '--batch-command with multiple info calls gives correct format' ' cat >expect <<-EOF && $hello_sha1 blob $hello_size @@ -427,17 +462,23 @@ test_expect_success '--batch-command with multiple info calls gives correct form deadbeef missing EOF - git cat-file --batch-command --buffer >actual <<-EOF && - info $hello_sha1 - info $tree_sha1 - info $commit_sha1 - info $tag_sha1 - info deadbeef - EOF + echo "$batch_command_multiple_info" >in && + git cat-file --batch-command --buffer <in >actual && + + test_cmp expect actual && + + echo "$batch_command_multiple_info" | tr "\n" "\0" >in && + git cat-file --batch-command --buffer -z <in >actual && test_cmp expect actual ' +batch_command_multiple_contents="contents $hello_sha1 +contents $commit_sha1 +contents $tag_sha1 +contents deadbeef +flush" + test_expect_success '--batch-command with multiple command calls gives correct format' ' remove_timestamp >expect <<-EOF && $hello_sha1 blob $hello_size @@ -449,13 +490,14 @@ test_expect_success '--batch-command with multiple command calls gives correct f deadbeef missing EOF - git cat-file --batch-command --buffer >actual_raw <<-EOF && - contents $hello_sha1 - contents $commit_sha1 - contents $tag_sha1 - contents deadbeef - flush - EOF + echo "$batch_command_multiple_contents" >in && + git cat-file --batch-command --buffer <in >actual_raw && + + remove_timestamp <actual_raw >actual && + test_cmp expect actual && + + echo "$batch_command_multiple_contents" | tr "\n" "\0" >in && + git cat-file --batch-command --buffer -z <in >actual_raw && remove_timestamp <actual_raw >actual && test_cmp expect actual diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index 9fdbb2af80..45eef9457f 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -6,6 +6,7 @@ test_description='Try various core-level commands in subdirectory. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-read-tree.sh diff --git a/t/t1060-object-corruption.sh b/t/t1060-object-corruption.sh index 5b8e47e346..35261afc9d 100755 --- a/t/t1060-object-corruption.sh +++ b/t/t1060-object-corruption.sh @@ -139,4 +139,11 @@ test_expect_success 'internal tree objects are not "missing"' ' ) ' +test_expect_success 'partial clone of corrupted repository' ' + test_config -C misnamed uploadpack.allowFilter true && + git clone --no-local --no-checkout --filter=blob:none \ + misnamed corrupt-partial && \ + test_must_fail git -C corrupt-partial checkout --force +' + test_done diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index de1ec89007..b563d6c263 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -560,7 +560,8 @@ test_expect_success 'interaction with submodules' ' ( cd super && mkdir modules && - git submodule add ../repo modules/child && + git -c protocol.file.allow=always \ + submodule add ../repo modules/child && git add . && git commit -m "add submodule" && git sparse-checkout init --cone && diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index 763c6cc684..801919009e 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -162,6 +162,19 @@ init_repos () { git -C sparse-index sparse-checkout set deep } +init_repos_as_submodules () { + git reset --hard && + init_repos && + git submodule add ./full-checkout && + git submodule add ./sparse-checkout && + git submodule add ./sparse-index && + + git submodule status >actual && + grep full-checkout actual && + grep sparse-checkout actual && + grep sparse-index actual +} + run_on_sparse () { ( cd sparse-checkout && @@ -372,6 +385,23 @@ test_expect_success 'deep changes during checkout' ' test_all_match git checkout base ' +test_expect_success 'checkout with modified sparse directory' ' + init_repos && + + test_all_match git checkout rename-in-to-out -- . && + test_sparse_match git sparse-checkout reapply && + test_all_match git checkout base +' + +test_expect_success 'checkout orphan then non-orphan' ' + init_repos && + + test_all_match git checkout --orphan test-orphan && + test_all_match git status --porcelain=v2 && + test_all_match git checkout base && + test_all_match git status --porcelain=v2 +' + test_expect_success 'add outside sparse cone' ' init_repos && @@ -548,7 +578,7 @@ test_expect_success 'blame with pathspec inside sparse definition' ' deep/deeper1/a \ deep/deeper1/deepest/a do - test_all_match git blame $file + test_all_match git blame $file || return 1 done ' @@ -559,7 +589,7 @@ test_expect_success 'blame with pathspec outside sparse definition' ' init_repos && test_sparse_match git sparse-checkout set && - for file in a \ + for file in \ deep/a \ deep/deeper1/a \ deep/deeper1/deepest/a @@ -571,7 +601,7 @@ test_expect_success 'blame with pathspec outside sparse definition' ' # We compare sparse-checkout-err and sparse-index-err in # `test_sparse_match`. Given we know they are the same, we # only check the content of sparse-index-err here. - test_cmp expect sparse-index-err + test_cmp expect sparse-index-err || return 1 done ' @@ -687,6 +717,23 @@ test_expect_success 'reset with wildcard pathspec' ' test_all_match git ls-files -s -- folder1 ' +test_expect_success 'reset hard with removed sparse dir' ' + init_repos && + + run_on_all git rm -r --sparse folder1 && + test_all_match git status --porcelain=v2 && + + test_all_match git reset --hard && + test_all_match git status --porcelain=v2 && + + cat >expect <<-\EOF && + folder1/ + EOF + + git -C sparse-index ls-files --sparse folder1 >out && + test_cmp expect out +' + test_expect_success 'update-index modify outside sparse definition' ' init_repos && @@ -912,7 +959,7 @@ test_expect_success 'read-tree --prefix' ' test_all_match git read-tree --prefix=deep/deeper1/deepest -u deepest && test_all_match git status --porcelain=v2 && - test_all_match git rm -rf --sparse folder1/ && + run_on_all git rm -rf --sparse folder1/ && test_all_match git read-tree --prefix=folder1/ -u update-folder1 && test_all_match git status --porcelain=v2 && @@ -1268,6 +1315,8 @@ test_expect_success 'submodule handling' ' test_all_match git add modules && test_all_match git commit -m "add modules directory" && + test_config_global protocol.file.allow always && + run_on_all git submodule add "$(pwd)/initial-repo" modules/sub && test_all_match git commit -m "add submodule" && @@ -1340,10 +1389,14 @@ ensure_not_expanded () { shift && test_must_fail env \ GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ - git -C sparse-index "$@" || return 1 + git -C sparse-index "$@" \ + >sparse-index-out \ + 2>sparse-index-error || return 1 else GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ - git -C sparse-index "$@" || return 1 + git -C sparse-index "$@" \ + >sparse-index-out \ + 2>sparse-index-error || return 1 fi && test_region ! index ensure_full_index trace2.txt } @@ -1542,7 +1595,7 @@ test_expect_success 'sparse index is not expanded: blame' ' deep/deeper1/a \ deep/deeper1/deepest/a do - ensure_not_expanded blame $file + ensure_not_expanded blame $file || return 1 done ' @@ -1853,4 +1906,153 @@ test_expect_success 'mv directory from out-of-cone to in-cone' ' grep -e "H deep/0/1" actual ' +test_expect_success 'rm pathspec inside sparse definition' ' + init_repos && + + test_all_match git rm deep/a && + test_all_match git status --porcelain=v2 && + + # test wildcard + run_on_all git reset --hard && + test_all_match git rm deep/* && + test_all_match git status --porcelain=v2 && + + # test recursive rm + run_on_all git reset --hard && + test_all_match git rm -r deep && + test_all_match git status --porcelain=v2 +' + +test_expect_success 'rm pathspec outside sparse definition' ' + init_repos && + + for file in folder1/a folder1/0/1 + do + test_sparse_match test_must_fail git rm $file && + test_sparse_match test_must_fail git rm --cached $file && + test_sparse_match git rm --sparse $file && + test_sparse_match git status --porcelain=v2 || return 1 + done && + + cat >folder1-full <<-EOF && + rm ${SQ}folder1/0/0/0${SQ} + rm ${SQ}folder1/0/1${SQ} + rm ${SQ}folder1/a${SQ} + EOF + + cat >folder1-sparse <<-EOF && + rm ${SQ}folder1/${SQ} + EOF + + # test wildcard + run_on_sparse git reset --hard && + run_on_sparse git sparse-checkout reapply && + test_sparse_match test_must_fail git rm folder1/* && + run_on_sparse git rm --sparse folder1/* && + test_cmp folder1-full sparse-checkout-out && + test_cmp folder1-sparse sparse-index-out && + test_sparse_match git status --porcelain=v2 && + + # test recursive rm + run_on_sparse git reset --hard && + run_on_sparse git sparse-checkout reapply && + test_sparse_match test_must_fail git rm --sparse folder1 && + run_on_sparse git rm --sparse -r folder1 && + test_cmp folder1-full sparse-checkout-out && + test_cmp folder1-sparse sparse-index-out && + test_sparse_match git status --porcelain=v2 +' + +test_expect_success 'rm pathspec expands index when necessary' ' + init_repos && + + # in-cone pathspec (do not expand) + ensure_not_expanded rm "deep/deep*" && + test_must_be_empty sparse-index-err && + + # out-of-cone pathspec (expand) + ! ensure_not_expanded rm --sparse "folder1/a*" && + test_must_be_empty sparse-index-err && + + # pathspec that should expand index + ! ensure_not_expanded rm "*/a" && + test_must_be_empty sparse-index-err && + + ! ensure_not_expanded rm "**a" && + test_must_be_empty sparse-index-err +' + +test_expect_success 'sparse index is not expanded: rm' ' + init_repos && + + ensure_not_expanded rm deep/a && + + # test in-cone wildcard + git -C sparse-index reset --hard && + ensure_not_expanded rm deep/* && + + # test recursive rm + git -C sparse-index reset --hard && + ensure_not_expanded rm -r deep +' + +test_expect_success 'grep with and --cached' ' + init_repos && + + test_all_match git grep --cached a && + test_all_match git grep --cached a -- "folder1/*" +' + +test_expect_success 'grep is not expanded' ' + init_repos && + + ensure_not_expanded grep a && + ensure_not_expanded grep a -- deep/* && + + # All files within the folder1/* pathspec are sparse, + # so this command does not find any matches + ensure_not_expanded ! grep a -- folder1/* && + + # test out-of-cone pathspec with or without wildcard + ensure_not_expanded grep --cached a -- "folder1/a" && + ensure_not_expanded grep --cached a -- "folder1/*" && + + # test in-cone pathspec with or without wildcard + ensure_not_expanded grep --cached a -- "deep/a" && + ensure_not_expanded grep --cached a -- "deep/*" +' + +# NEEDSWORK: when running `grep` in the superproject with --recurse-submodules, +# Git expands the index of the submodules unexpectedly. Even though `grep` +# builtin is marked as "command_requires_full_index = 0", this config is only +# useful for the superproject. Namely, the submodules have their own configs, +# which are _not_ populated by the one-time sparse-index feature switch. +test_expect_failure 'grep within submodules is not expanded' ' + init_repos_as_submodules && + + # do not use ensure_not_expanded() here, becasue `grep` should be + # run in the superproject, not in "./sparse-index" + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + git grep --cached --recurse-submodules a -- "*/folder1/*" && + test_region ! index ensure_full_index trace2.txt +' + +# NEEDSWORK: this test is not actually testing the code. The design purpose +# of this test is to verify the grep result when the submodules are using a +# sparse-index. Namely, we want "folder1/" as a tree (a sparse directory); but +# because of the index expansion, we are now grepping the "folder1/a" blob. +# Because of the problem stated above 'grep within submodules is not expanded', +# we don't have the ideal test environment yet. +test_expect_success 'grep sparse directory within submodules' ' + init_repos_as_submodules && + + cat >expect <<-\EOF && + full-checkout/folder1/a:a + sparse-checkout/folder1/a:a + sparse-index/folder1/a:a + EOF + git grep --cached --recurse-submodules a -- "*/folder1/*" >actual && + test_cmp actual expect +' + test_done diff --git a/t/t1304-default-acl.sh b/t/t1304-default-acl.sh index 335d3f3211..c69ae41306 100755 --- a/t/t1304-default-acl.sh +++ b/t/t1304-default-acl.sh @@ -18,7 +18,7 @@ test_expect_success 'checking for a working acl setup' ' if setfacl -m d:m:rwx -m u:root:rwx . && getfacl . | grep user:root:rwx && touch should-have-readable-acl && - getfacl should-have-readable-acl | egrep "mask::?rw-" + getfacl should-have-readable-acl | grep -E "mask::?rw-" then test_set_prereq SETFACL fi @@ -34,7 +34,7 @@ check_perms_and_acl () { getfacl "$1" > actual && grep -q "user:root:rwx" actual && grep -q "user:${LOGNAME}:rwx" actual && - egrep "mask::?r--" actual > /dev/null 2>&1 && + grep -E "mask::?r--" actual > /dev/null 2>&1 && grep -q "group::---" actual || false } diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index 9fb0b90f25..d708acdb81 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -165,4 +165,28 @@ test_expect_success 'symbolic-ref can resolve d/f name (ENOTDIR)' ' test_cmp expect actual ' +test_expect_success 'symbolic-ref refuses invalid target for non-HEAD' ' + test_must_fail git symbolic-ref refs/heads/invalid foo..bar +' + +test_expect_success 'symbolic-ref allows top-level target for non-HEAD' ' + git symbolic-ref refs/heads/top-level FETCH_HEAD && + git update-ref FETCH_HEAD HEAD && + test_cmp_rev top-level HEAD +' + +test_expect_success 'symbolic-ref pointing at another' ' + git update-ref refs/heads/maint-2.37 HEAD && + git symbolic-ref refs/heads/maint refs/heads/maint-2.37 && + git checkout maint && + + git symbolic-ref HEAD >actual && + echo refs/heads/maint-2.37 >expect && + test_cmp expect actual && + + git symbolic-ref --no-recurse HEAD >actual && + echo refs/heads/maint >expect && + test_cmp expect actual +' + test_done diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh index 51f8291628..e4627cf1b6 100755 --- a/t/t1405-main-ref-store.sh +++ b/t/t1405-main-ref-store.sh @@ -5,6 +5,7 @@ test_description='test main ref store api' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh RUN="test-tool ref-store main" diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh index ad8006c813..05b1881c59 100755 --- a/t/t1407-worktree-ref-store.sh +++ b/t/t1407-worktree-ref-store.sh @@ -5,6 +5,7 @@ test_description='test worktree ref store api' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh RWT="test-tool ref-store worktree:wt" diff --git a/t/t1418-reflog-exists.sh b/t/t1418-reflog-exists.sh index d51ecd5e92..2268bca3c1 100755 --- a/t/t1418-reflog-exists.sh +++ b/t/t1418-reflog-exists.sh @@ -4,6 +4,7 @@ test_description='Test reflog display routines' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index ab7f31f1dc..ace4556788 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -364,6 +364,20 @@ test_expect_success 'tree entry with type mismatch' ' test_i18ngrep ! "dangling blob" out ' +test_expect_success 'tree entry with bogus mode' ' + test_when_finished "remove_object \$blob" && + test_when_finished "remove_object \$tree" && + blob=$(echo blob | git hash-object -w --stdin) && + blob_oct=$(echo $blob | hex2oct) && + tree=$(printf "100000 foo\0${blob_oct}" | + git hash-object -t tree --stdin -w --literally) && + git fsck 2>err && + cat >expect <<-EOF && + warning in tree $tree: badFilemode: contains bad file modes + EOF + test_cmp expect err +' + test_expect_success 'tag pointing to nonexistent' ' badoid=$(test_oid deadbeef) && cat >invalid-tag <<-EOF && @@ -493,6 +507,54 @@ test_expect_success 'rev-list --verify-objects with bad sha1' ' test_i18ngrep -q "error: hash mismatch $(dirname $new)$(test_oid ff_2)" out ' +# An actual bit corruption is more likely than swapped commits, but +# this provides an easy way to have commits which don't match their purported +# hashes, but which aren't so broken we can't read them at all. +test_expect_success 'rev-list --verify-objects notices swapped commits' ' + git init swapped-commits && + ( + cd swapped-commits && + test_commit one && + test_commit two && + one_oid=$(git rev-parse HEAD) && + two_oid=$(git rev-parse HEAD^) && + one=.git/objects/$(test_oid_to_path $one_oid) && + two=.git/objects/$(test_oid_to_path $two_oid) && + mv $one tmp && + mv $two $one && + mv tmp $two && + test_must_fail git rev-list --verify-objects HEAD + ) +' + +test_expect_success 'set up repository with commit-graph' ' + git init corrupt-graph && + ( + cd corrupt-graph && + test_commit one && + test_commit two && + git commit-graph write --reachable + ) +' + +corrupt_graph_obj () { + oid=$(git -C corrupt-graph rev-parse "$1") && + obj=corrupt-graph/.git/objects/$(test_oid_to_path $oid) && + test_when_finished 'mv backup $obj' && + mv $obj backup && + echo garbage >$obj +} + +test_expect_success 'rev-list --verify-objects with commit graph (tip)' ' + corrupt_graph_obj HEAD && + test_must_fail git -C corrupt-graph rev-list --verify-objects HEAD +' + +test_expect_success 'rev-list --verify-objects with commit graph (parent)' ' + corrupt_graph_obj HEAD^ && + test_must_fail git -C corrupt-graph rev-list --verify-objects HEAD +' + test_expect_success 'force fsck to ignore double author' ' git cat-file commit HEAD >basis && sed "s/^author .*/&,&/" <basis | tr , \\n >multiple-authors && diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 1c2df08333..81de584ea2 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -4,6 +4,7 @@ test_description='test git rev-parse' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_one () { @@ -225,7 +226,8 @@ test_expect_success 'showing the superproject correctly' ' test_commit -C super test_commit && test_create_repo sub && test_commit -C sub test_commit && - git -C super submodule add ../sub dir/sub && + git -c protocol.file.allow=always \ + -C super submodule add ../sub dir/sub && echo $(pwd)/super >expect && git -C super/dir/sub rev-parse --show-superproject-working-tree >out && test_cmp expect out && diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index 284fe18e72..de1d48f3ba 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -306,6 +306,13 @@ test_expect_success 'test --parseopt help output: "wrapped" options normal "or:" test_cmp expect actual ' +test_expect_success 'test --parseopt invalid opt-spec' ' + test_write_lines x -- "=, x" >spec && + echo "fatal: missing opt-spec before option flags" >expect && + test_must_fail git rev-parse --parseopt -- >out <spec 2>err && + test_cmp expect err +' + test_expect_success 'test --parseopt help output: multi-line blurb after empty line' ' sed -e "s/^|//" >spec <<-\EOF && |cmd [--some-option] diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh index ba43168d12..bc136833c1 100755 --- a/t/t1503-rev-parse-verify.sh +++ b/t/t1503-rev-parse-verify.sh @@ -132,7 +132,7 @@ test_expect_success 'use --default' ' test_must_fail git rev-parse --verify --default bar ' -test_expect_success !SANITIZE_LEAK 'main@{n} for various n' ' +test_expect_success 'main@{n} for various n' ' git reflog >out && N=$(wc -l <out) && Nm1=$(($N-1)) && diff --git a/t/t1701-racy-split-index.sh b/t/t1701-racy-split-index.sh index 5dc221ef38..d8fa489998 100755 --- a/t/t1701-racy-split-index.sh +++ b/t/t1701-racy-split-index.sh @@ -5,6 +5,7 @@ test_description='racy split index' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh index 210f429887..43fcb7c0bf 100755 --- a/t/t1800-hook.sh +++ b/t/t1800-hook.sh @@ -151,4 +151,30 @@ test_expect_success TTY 'git commit: stdout and stderr are connected to a TTY' ' test_hook_tty commit -m"B.new" ' +test_expect_success 'git hook run a hook with a bad shebang' ' + test_when_finished "rm -rf bad-hooks" && + mkdir bad-hooks && + write_script bad-hooks/test-hook "/bad/path/no/spaces" </dev/null && + + # TODO: We should emit the same (or at least a more similar) + # error on MINGW (essentially Git for Windows) and all other + # platforms.. See the OS-specific code in start_command() + if test_have_prereq !MINGW + then + cat >expect <<-\EOF + fatal: cannot run bad-hooks/test-hook: ... + EOF + else + cat >expect <<-\EOF + error: cannot spawn bad-hooks/test-hook: ... + EOF + fi && + test_expect_code 1 git \ + -c core.hooksPath=bad-hooks \ + hook run test-hook >out 2>err && + test_must_be_empty out && + sed -e "s/test-hook: .*/test-hook: .../" <err >actual && + test_cmp expect actual +' + test_done diff --git a/t/t2006-checkout-index-basic.sh b/t/t2006-checkout-index-basic.sh index 7705e3a317..5d119871d4 100755 --- a/t/t2006-checkout-index-basic.sh +++ b/t/t2006-checkout-index-basic.sh @@ -3,6 +3,7 @@ test_description='basic checkout-index tests ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'checkout-index --gobbledegook' ' diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh index bc46713a43..2eab6474f8 100755 --- a/t/t2020-checkout-detach.sh +++ b/t/t2020-checkout-detach.sh @@ -4,6 +4,7 @@ test_description='checkout into detached HEAD state' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh check_detached () { diff --git a/t/t2023-checkout-m.sh b/t/t2023-checkout-m.sh index 7b327b7544..81e772fb4e 100755 --- a/t/t2023-checkout-m.sh +++ b/t/t2023-checkout-m.sh @@ -7,6 +7,7 @@ Ensures that checkout -m on a resolved file restores the conflicted file' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t2080-parallel-checkout-basics.sh b/t/t2080-parallel-checkout-basics.sh index c683e60007..5ffe1a41e2 100755 --- a/t/t2080-parallel-checkout-basics.sh +++ b/t/t2080-parallel-checkout-basics.sh @@ -41,6 +41,8 @@ TEST_NO_CREATE_REPO=1 # - m/m (file) # test_expect_success 'setup repo for checkout with various types of changes' ' + test_config_global protocol.file.allow always && + git init sub && ( cd sub && @@ -140,6 +142,7 @@ do esac test_expect_success "$mode checkout on clone" ' + test_config_global protocol.file.allow always && repo=various_${mode}_clone && set_checkout_config $workers $threshold && test_checkout_workers $expected_workers \ @@ -230,12 +233,9 @@ test_expect_success SYMLINKS 'parallel checkout checks for symlinks in leading d # check the final report including sequential, parallel, and delayed entries # all at the same time. So we must have finer control of the parallel checkout # variables. -test_expect_success PERL '"git checkout ." report should not include failed entries' ' - write_script rot13-filter.pl "$PERL_PATH" \ - <"$TEST_DIRECTORY"/t0021/rot13-filter.pl && - +test_expect_success '"git checkout ." report should not include failed entries' ' test_config_global filter.delay.process \ - "\"$(pwd)/rot13-filter.pl\" --always-delay delayed.log clean smudge delay" && + "test-tool rot13-filter --always-delay --log=delayed.log clean smudge delay" && test_config_global filter.delay.required true && test_config_global filter.cat.clean cat && test_config_global filter.cat.smudge cat && diff --git a/t/t2082-parallel-checkout-attributes.sh b/t/t2082-parallel-checkout-attributes.sh index 2525457961..f3511cd43a 100755 --- a/t/t2082-parallel-checkout-attributes.sh +++ b/t/t2082-parallel-checkout-attributes.sh @@ -138,12 +138,9 @@ test_expect_success 'parallel-checkout and external filter' ' # The delayed queue is independent from the parallel queue, and they should be # able to work together in the same checkout process. # -test_expect_success PERL 'parallel-checkout and delayed checkout' ' - write_script rot13-filter.pl "$PERL_PATH" \ - <"$TEST_DIRECTORY"/t0021/rot13-filter.pl && - +test_expect_success 'parallel-checkout and delayed checkout' ' test_config_global filter.delay.process \ - "\"$(pwd)/rot13-filter.pl\" --always-delay \"$(pwd)/delayed.log\" clean smudge delay" && + "test-tool rot13-filter --always-delay --log=\"$(pwd)/delayed.log\" clean smudge delay" && test_config_global filter.delay.required true && echo "abcd" >original && diff --git a/t/t2205-add-worktree-config.sh b/t/t2205-add-worktree-config.sh index 43d950de64..98265ba1b4 100755 --- a/t/t2205-add-worktree-config.sh +++ b/t/t2205-add-worktree-config.sh @@ -17,6 +17,7 @@ outside the repository. Two instances for which this can occur are tested: repository can be added to the index. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success '1a: setup--config worktree' ' diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index f3242fef6b..d587e0b20d 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -669,6 +669,7 @@ test_expect_success '"add" should not fail because of another bad worktree' ' ' test_expect_success '"add" with uninitialized submodule, with submodule.recurse unset' ' + test_config_global protocol.file.allow always && test_create_repo submodule && test_commit -C submodule first && test_create_repo project && @@ -684,6 +685,7 @@ test_expect_success '"add" with uninitialized submodule, with submodule.recurse ' test_expect_success '"add" with initialized submodule, with submodule.recurse unset' ' + test_config_global protocol.file.allow always && git -C project-clone submodule update --init && git -C project-clone worktree add ../project-4 ' diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh index a4e1a178e0..230a55e99a 100755 --- a/t/t2403-worktree-move.sh +++ b/t/t2403-worktree-move.sh @@ -2,6 +2,7 @@ test_description='test git worktree move, remove, lock and unlock' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' @@ -138,7 +139,8 @@ test_expect_success 'move a repo with uninitialized submodule' ' ( cd withsub && test_commit initial && - git submodule add "$PWD"/.git sub && + git -c protocol.file.allow=always \ + submodule add "$PWD"/.git sub && git commit -m withsub && git worktree add second HEAD && git worktree move second third @@ -148,7 +150,7 @@ test_expect_success 'move a repo with uninitialized submodule' ' test_expect_success 'not move a repo with initialized submodule' ' ( cd withsub && - git -C third submodule update && + git -c protocol.file.allow=always -C third submodule update && test_must_fail git worktree move third forth ) ' @@ -227,6 +229,7 @@ test_expect_success 'remove cleans up .git/worktrees when empty' ' ' test_expect_success 'remove a repo with uninitialized submodule' ' + test_config_global protocol.file.allow always && ( cd withsub && git worktree add to-remove HEAD && @@ -235,6 +238,7 @@ test_expect_success 'remove a repo with uninitialized submodule' ' ' test_expect_success 'not remove a repo with initialized submodule' ' + test_config_global protocol.file.allow always && ( cd withsub && git worktree add to-remove HEAD && diff --git a/t/t2405-worktree-submodule.sh b/t/t2405-worktree-submodule.sh index b172c26ca4..11018f37c7 100755 --- a/t/t2405-worktree-submodule.sh +++ b/t/t2405-worktree-submodule.sh @@ -10,6 +10,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME base_path=$(pwd -P) test_expect_success 'setup: create origin repos' ' + git config --global protocol.file.allow always && git init origin/sub && test_commit -C origin/sub file1 && git init origin/main && diff --git a/t/t2407-worktree-heads.sh b/t/t2407-worktree-heads.sh index 50815acd3e..019a40df2c 100755 --- a/t/t2407-worktree-heads.sh +++ b/t/t2407-worktree-heads.sh @@ -41,10 +41,10 @@ test_expect_success 'setup' ' test_expect_success 'refuse to overwrite: checked out in worktree' ' for i in 1 2 3 4 do - test_must_fail git branch -f wt-$i HEAD 2>err + test_must_fail git branch -f wt-$i HEAD 2>err && grep "cannot force update the branch" err && - test_must_fail git branch -D wt-$i 2>err + test_must_fail git branch -D wt-$i 2>err && grep "Cannot delete branch" err || return 1 done ' diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index e07ac6c6dc..1ed0aa967e 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -103,7 +103,7 @@ test_expect_success 'git ls-files --others with various exclude options.' ' test_cmp expect output ' -test_expect_success !SANITIZE_LEAK 'restore gitignore' ' +test_expect_success 'restore gitignore' ' git checkout --ignore-skip-worktree-bits $allignores && rm .git/index ' @@ -126,7 +126,7 @@ cat > expect << EOF # three/ EOF -test_expect_success !SANITIZE_LEAK 'git status honors core.excludesfile' \ +test_expect_success 'git status honors core.excludesfile' \ 'test_cmp expect output' test_expect_success 'trailing slash in exclude allows directory match(1)' ' diff --git a/t/t3012-ls-files-dedup.sh b/t/t3012-ls-files-dedup.sh index 2682b1f43a..190e2f6eed 100755 --- a/t/t3012-ls-files-dedup.sh +++ b/t/t3012-ls-files-dedup.sh @@ -2,6 +2,7 @@ test_description='git ls-files --deduplicate test' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh index f9539968e4..5d871fde96 100755 --- a/t/t3070-wildmatch.sh +++ b/t/t3070-wildmatch.sh @@ -5,11 +5,6 @@ test_description='wildmatch tests' TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh -# Disable expensive chain-lint tests; all of the tests in this script -# are variants of a few trivial test-tool invocations, and there are a lot of -# them. -GIT_TEST_CHAIN_LINT_HARDER_DEFAULT=0 - should_create_test_file() { file=$1 diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 9723c2827c..7f605f865b 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -201,8 +201,8 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou test_expect_success 'git branch -M baz bam should add entries to .git/logs/HEAD' ' msg="Branch: renamed refs/heads/baz to refs/heads/bam" && - grep " 0\{40\}.*$msg$" .git/logs/HEAD && - grep "^0\{40\}.*$msg$" .git/logs/HEAD + grep " $ZERO_OID.*$msg$" .git/logs/HEAD && + grep "^$ZERO_OID.*$msg$" .git/logs/HEAD ' test_expect_success 'git branch -M should leave orphaned HEAD alone' ' @@ -268,6 +268,17 @@ test_expect_success 'git branch -M topic topic should work when main is checked git branch -M topic topic ' +test_expect_success 'git branch -M and -C fail on detached HEAD' ' + git checkout HEAD^{} && + test_when_finished git checkout - && + echo "fatal: cannot rename the current branch while not on any." >expect && + test_must_fail git branch -M must-fail 2>err && + test_cmp expect err && + echo "fatal: cannot copy the current branch while not on any." >expect && + test_must_fail git branch -C must-fail 2>err && + test_cmp expect err +' + test_expect_success 'git branch -v -d t should work' ' git branch t && git rev-parse --verify refs/heads/t && @@ -306,6 +317,7 @@ test_expect_success 'deleting checked-out branch from repo that is a submodule' git init repo1 && git init repo1/sub && test_commit -C repo1/sub x && + test_config_global protocol.file.allow always && git -C repo1 submodule add ./sub && git -C repo1 commit -m "adding sub" && @@ -1381,6 +1393,9 @@ test_expect_success 'branch --delete --force removes dangling branch' ' ' test_expect_success 'use --edit-description' ' + EDITOR=: git branch --edit-description && + test_expect_code 1 git config branch.main.description && + write_script editor <<-\EOF && echo "New contents" >"$1" EOF diff --git a/t/t3202-show-branch.sh b/t/t3202-show-branch.sh index f2b9199007..ea7cfd1951 100755 --- a/t/t3202-show-branch.sh +++ b/t/t3202-show-branch.sh @@ -7,6 +7,28 @@ test_description='test show-branch' # arbitrary reference time: 2009-08-30 19:20:00 GIT_TEST_DATE_NOW=1251660000; export GIT_TEST_DATE_NOW +test_expect_success 'error descriptions on empty repository' ' + current=$(git branch --show-current) && + cat >expect <<-EOF && + error: No commit on branch '\''$current'\'' yet. + EOF + test_must_fail git branch --edit-description 2>actual && + test_cmp expect actual && + test_must_fail git branch --edit-description $current 2>actual && + test_cmp expect actual +' + +test_expect_success 'fatal descriptions on empty repository' ' + current=$(git branch --show-current) && + cat >expect <<-EOF && + fatal: No commit on branch '\''$current'\'' yet. + EOF + test_must_fail git branch --set-upstream-to=non-existent 2>actual && + test_cmp expect actual && + test_must_fail git branch -c new-branch 2>actual && + test_cmp expect actual +' + test_expect_success 'setup' ' test_commit initial && for i in $(test_seq 1 10) @@ -175,4 +197,28 @@ done <<\EOF --reflog --current EOF +test_expect_success 'error descriptions on non-existent branch' ' + cat >expect <<-EOF && + error: No branch named '\''non-existent'\'.' + EOF + test_must_fail git branch --edit-description non-existent 2>actual && + test_cmp expect actual +' + +test_expect_success 'fatal descriptions on non-existent branch' ' + cat >expect <<-EOF && + fatal: branch '\''non-existent'\'' does not exist + EOF + test_must_fail git branch --set-upstream-to=non-existent non-existent 2>actual && + test_cmp expect actual && + + cat >expect <<-EOF && + fatal: No branch named '\''non-existent'\''. + EOF + test_must_fail git branch -c non-existent new-branch 2>actual && + test_cmp expect actual && + test_must_fail git branch -m non-existent new-branch 2>actual && + test_cmp expect actual +' + test_done diff --git a/t/t3204-branch-name-interpretation.sh b/t/t3204-branch-name-interpretation.sh index 993a6b5eff..793bf4d269 100755 --- a/t/t3204-branch-name-interpretation.sh +++ b/t/t3204-branch-name-interpretation.sh @@ -133,4 +133,28 @@ test_expect_success 'checkout does not treat remote @{upstream} as a branch' ' expect_branch HEAD one ' +test_expect_success 'edit-description via @{-1}' ' + git checkout -b desc-branch && + git checkout -b non-desc-branch && + write_script editor <<-\EOF && + echo "Branch description" >"$1" + EOF + EDITOR=./editor git branch --edit-description @{-1} && + test_must_fail git config branch.non-desc-branch.description && + git config branch.desc-branch.description >actual && + printf "Branch description\n\n" >expect && + test_cmp expect actual +' + +test_expect_success 'modify branch upstream via "@{-1}" and "@{-1}@{upstream}"' ' + git checkout -b upstream-branch && + git checkout -b upstream-other -t upstream-branch && + git branch --set-upstream-to upstream-other @{-1} && + git config branch.upstream-branch.merge >actual && + echo "refs/heads/upstream-other" >expect && + test_cmp expect actual && + git branch --unset-upstream @{-1}@{upstream} && + test_must_fail git config branch.upstream-other.merge +' + test_done diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh index d12e4e4cc6..84dd0cd26d 100755 --- a/t/t3206-range-diff.sh +++ b/t/t3206-range-diff.sh @@ -162,7 +162,7 @@ test_expect_success 'A^! and A^-<n> (unmodified)' ' ' test_expect_success 'A^{/..} is not mistaken for a range' ' - test_must_fail git range-diff topic^.. topic^{/..} 2>error && + test_must_fail git range-diff topic^.. topic^{/..} -- 2>error && test_i18ngrep "not a commit range" error ' @@ -772,6 +772,17 @@ test_expect_success '--left-only/--right-only' ' test_cmp expect actual ' +test_expect_success 'ranges with pathspecs' ' + git range-diff topic...mode-only-change -- other-file >actual && + test_line_count = 2 actual && + topic_oid=$(git rev-parse --short topic) && + mode_change_oid=$(git rev-parse --short mode-only-change^) && + file_change_oid=$(git rev-parse --short mode-only-change) && + grep "$mode_change_oid" actual && + ! grep "$file_change_oid" actual && + ! grep "$topic_oid" actual +' + test_expect_success 'submodule changes are shown irrespective of diff.submodule' ' git init sub-repo && test_commit -C sub-repo sub-first && @@ -782,7 +793,7 @@ test_expect_success 'submodule changes are shown irrespective of diff.submodule' sub_oid3=$(git -C sub-repo rev-parse HEAD) && git checkout -b main-sub topic && - git submodule add ./sub-repo sub && + git -c protocol.file.allow=always submodule add ./sub-repo sub && git -C sub checkout --detach sub-first && git commit -m "add sub" sub && sup_oid1=$(git rev-parse --short HEAD) && diff --git a/t/t3207-branch-submodule.sh b/t/t3207-branch-submodule.sh index cfde6b237f..fe72b24716 100755 --- a/t/t3207-branch-submodule.sh +++ b/t/t3207-branch-submodule.sh @@ -28,6 +28,7 @@ test_no_branch () { } test_expect_success 'setup superproject and submodule' ' + git config --global protocol.file.allow always && mkdir test_dirs && ( cd test_dirs && diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index d742be8840..3288aaec7d 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -505,6 +505,11 @@ test_expect_success 'list notes with "git notes"' ' test_cmp expect actual ' +test_expect_success '"git notes" without subcommand does not take arguments' ' + test_expect_code 129 git notes HEAD^^ 2>err && + grep "^error: unknown subcommand" err +' + test_expect_success 'list specific note with "git notes list <object>"' ' git rev-parse refs/notes/commits:$commit_3 >expect && git notes list HEAD^^ >actual && diff --git a/t/t3305-notes-fanout.sh b/t/t3305-notes-fanout.sh index 64a9915761..1ec1fb6715 100755 --- a/t/t3305-notes-fanout.sh +++ b/t/t3305-notes-fanout.sh @@ -9,7 +9,7 @@ path_has_fanout() { path=$1 && fanout=$2 && after_last_slash=$(($(test_oid hexsz) - $fanout * 2)) && - echo $path | grep -q "^\([0-9a-f]\{2\}/\)\{$fanout\}[0-9a-f]\{$after_last_slash\}$" + echo $path | grep -q -E "^([0-9a-f]{2}/){$fanout}[0-9a-f]{$after_last_slash}$" } touched_one_note_with_fanout() { @@ -51,7 +51,7 @@ test_expect_success 'creating many notes with git-notes' ' done ' -test_expect_success !SANITIZE_LEAK 'many notes created correctly with git-notes' ' +test_expect_success 'many notes created correctly with git-notes' ' git log >output.raw && grep "^ " output.raw >output && i=$num_notes && diff --git a/t/t3307-notes-man.sh b/t/t3307-notes-man.sh index 1aa366a410..ae316502c4 100755 --- a/t/t3307-notes-man.sh +++ b/t/t3307-notes-man.sh @@ -4,6 +4,7 @@ test_description='Examples from the git-notes man page Make sure the manual is not full of lies.' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 688b01e3eb..4f5abb5ad2 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1244,9 +1244,9 @@ test_expect_success 'short commit ID collide' ' test $colliding_id = "$(git rev-parse HEAD | cut -c 1-4)" && grep "^pick $colliding_id " \ .git/rebase-merge/git-rebase-todo.tmp && - grep "^pick [0-9a-f]\{$hexsz\}" \ + grep -E "^pick [0-9a-f]{$hexsz}" \ .git/rebase-merge/git-rebase-todo && - grep "^pick [0-9a-f]\{$hexsz\}" \ + grep -E "^pick [0-9a-f]{$hexsz}" \ .git/rebase-merge/git-rebase-todo.backup && git rebase --continue ) && @@ -1261,7 +1261,7 @@ test_expect_success 'respect core.abbrev' ' set_cat_todo_editor && test_must_fail git rebase -i HEAD~4 >todo-list ) && - test 4 = $(grep -c "pick [0-9a-f]\{12,\}" todo-list) + test 4 = $(grep -c -E "pick [0-9a-f]{12,}" todo-list) ' test_expect_success 'todo count' ' diff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh index d17b450e81..ceca160005 100755 --- a/t/t3406-rebase-message.sh +++ b/t/t3406-rebase-message.sh @@ -10,10 +10,16 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME test_expect_success 'setup' ' test_commit O fileO && test_commit X fileX && + git branch fast-forward && test_commit A fileA && test_commit B fileB && test_commit Y fileY && + git checkout -b conflicts O && + test_commit P && + test_commit conflict-X fileX && + test_commit Q && + git checkout -b topic O && git cherry-pick A B && test_commit Z fileZ && @@ -79,54 +85,165 @@ test_expect_success 'error out early upon -C<n> or --whitespace=<bad>' ' test_i18ngrep "Invalid whitespace option" err ' -test_expect_success 'GIT_REFLOG_ACTION' ' - git checkout start && - test_commit reflog-onto && - git checkout -b reflog-topic start && - test_commit reflog-to-rebase && +write_reflog_expect () { + if test $mode = --apply + then + sed 's/(continue)/(pick)/' + else + cat + fi >expect +} - git rebase reflog-onto && - git log -g --format=%gs -3 >actual && - cat >expect <<-\EOF && - rebase (finish): returning to refs/heads/reflog-topic - rebase (pick): reflog-to-rebase - rebase (start): checkout reflog-onto +test_reflog () { + mode=$1 + reflog_action="$2" + + test_expect_success "rebase $mode reflog${reflog_action:+ GIT_REFLOG_ACTION=$reflog_action}" ' + git checkout conflicts && + test_when_finished "git reset --hard Q" && + + ( + if test -n "$reflog_action" + then + GIT_REFLOG_ACTION="$reflog_action" && + export GIT_REFLOG_ACTION + fi && + test_must_fail git rebase $mode main && + echo resolved >fileX && + git add fileX && + git rebase --continue + ) && + + git log -g --format=%gs -5 >actual && + write_reflog_expect <<-EOF && + ${reflog_action:-rebase} (finish): returning to refs/heads/conflicts + ${reflog_action:-rebase} (pick): Q + ${reflog_action:-rebase} (continue): conflict-X + ${reflog_action:-rebase} (pick): P + ${reflog_action:-rebase} (start): checkout main EOF test_cmp expect actual && - git checkout -b reflog-prefix reflog-to-rebase && - GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto && - git log -g --format=%gs -3 >actual && - cat >expect <<-\EOF && - change-the-reflog (finish): returning to refs/heads/reflog-prefix - change-the-reflog (pick): reflog-to-rebase - change-the-reflog (start): checkout reflog-onto + git log -g --format=%gs -1 conflicts >actual && + write_reflog_expect <<-EOF && + ${reflog_action:-rebase} (finish): refs/heads/conflicts onto $(git rev-parse main) + EOF + test_cmp expect actual && + + # check there is only one new entry in the branch reflog + test_cmp_rev conflicts@{1} Q + ' + + test_expect_success "rebase $mode fast-forward reflog${reflog_action:+ GIT_REFLOG_ACTION=$reflog_action}" ' + git checkout fast-forward && + test_when_finished "git reset --hard X" && + + ( + if test -n "$reflog_action" + then + GIT_REFLOG_ACTION="$reflog_action" && + export GIT_REFLOG_ACTION + fi && + git rebase $mode main + ) && + + git log -g --format=%gs -2 >actual && + write_reflog_expect <<-EOF && + ${reflog_action:-rebase} (finish): returning to refs/heads/fast-forward + ${reflog_action:-rebase} (start): checkout main + EOF + test_cmp expect actual && + + git log -g --format=%gs -1 fast-forward >actual && + write_reflog_expect <<-EOF && + ${reflog_action:-rebase} (finish): refs/heads/fast-forward onto $(git rev-parse main) + EOF + test_cmp expect actual && + + # check there is only one new entry in the branch reflog + test_cmp_rev fast-forward@{1} X + ' + + test_expect_success "rebase $mode --skip reflog${reflog_action:+ GIT_REFLOG_ACTION=$reflog_action}" ' + git checkout conflicts && + test_when_finished "git reset --hard Q" && + + ( + if test -n "$reflog_action" + then + GIT_REFLOG_ACTION="$reflog_action" && + export GIT_REFLOG_ACTION + fi && + test_must_fail git rebase $mode main && + git rebase --skip + ) && + + git log -g --format=%gs -4 >actual && + write_reflog_expect <<-EOF && + ${reflog_action:-rebase} (finish): returning to refs/heads/conflicts + ${reflog_action:-rebase} (pick): Q + ${reflog_action:-rebase} (pick): P + ${reflog_action:-rebase} (start): checkout main EOF test_cmp expect actual -' + ' -test_expect_success 'rebase --apply reflog' ' - git checkout -b reflog-apply start && - old_head_reflog="$(git log -g --format=%gs -1 HEAD)" && + test_expect_success "rebase $mode --abort reflog${reflog_action:+ GIT_REFLOG_ACTION=$reflog_action}" ' + git checkout conflicts && + test_when_finished "git reset --hard Q" && - git rebase --apply Y && + git log -g -1 conflicts >branch-expect && + ( + if test -n "$reflog_action" + then + GIT_REFLOG_ACTION="$reflog_action" && + export GIT_REFLOG_ACTION + fi && + test_must_fail git rebase $mode main && + git rebase --abort + ) && - git log -g --format=%gs -4 HEAD >actual && - cat >expect <<-EOF && - rebase finished: returning to refs/heads/reflog-apply - rebase: Z - rebase: checkout Y - $old_head_reflog + git log -g --format=%gs -3 >actual && + write_reflog_expect <<-EOF && + ${reflog_action:-rebase} (abort): returning to refs/heads/conflicts + ${reflog_action:-rebase} (pick): P + ${reflog_action:-rebase} (start): checkout main EOF test_cmp expect actual && - git log -g --format=%gs -2 reflog-apply >actual && - cat >expect <<-EOF && - rebase finished: refs/heads/reflog-apply onto $(git rev-parse Y) - branch: Created from start + # check branch reflog is unchanged + git log -g -1 conflicts >branch-actual && + test_cmp branch-expect branch-actual + ' + + test_expect_success "rebase $mode --abort detached HEAD reflog${reflog_action:+ GIT_REFLOG_ACTION=$reflog_action}" ' + git checkout Q && + test_when_finished "git reset --hard Q" && + + ( + if test -n "$reflog_action" + then + GIT_REFLOG_ACTION="$reflog_action" && + export GIT_REFLOG_ACTION + fi && + test_must_fail git rebase $mode main && + git rebase --abort + ) && + + git log -g --format=%gs -3 >actual && + write_reflog_expect <<-EOF && + ${reflog_action:-rebase} (abort): returning to $(git rev-parse Q) + ${reflog_action:-rebase} (pick): P + ${reflog_action:-rebase} (start): checkout main EOF test_cmp expect actual -' + ' +} + +test_reflog --merge +test_reflog --merge my-reflog-action +test_reflog --apply +test_reflog --apply my-reflog-action test_expect_success 'rebase -i onto unrelated history' ' git init unrelated && diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 78c27496d6..a364530d76 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -232,6 +232,19 @@ test_expect_success 'auto squash that matches longer sha1' ' test_line_count = 1 actual ' +test_expect_success 'auto squash of fixup commit that matches branch name which points back to fixup commit' ' + git reset --hard base && + git commit --allow-empty -m "fixup! self-cycle" && + git branch self-cycle && + GIT_SEQUENCE_EDITOR="cat >tmp" git rebase --autosquash -i HEAD^^ && + sed -ne "/^[^#]/{s/[0-9a-f]\{7,\}/HASH/g;p;}" tmp >actual && + cat <<-EOF >expect && + pick HASH second commit + pick HASH fixup! self-cycle # empty + EOF + test_cmp expect actual +' + test_auto_commit_flags () { git reset --hard base && echo 1 >file1 && diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index 3e04802cb0..ea501f2b42 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -79,8 +79,10 @@ test_expect_success 'rebase -i --onto main...topic' ' git reset --hard && git checkout topic && git reset --hard G && - set_fake_editor && - EXPECT_COUNT=1 git rebase -i --onto main...topic F && + ( + set_fake_editor && + EXPECT_COUNT=1 git rebase -i --onto main...topic F + ) && git rev-parse HEAD^1 >actual && git rev-parse C^0 >expect && test_cmp expect actual @@ -90,20 +92,22 @@ test_expect_success 'rebase -i --onto main...' ' git reset --hard && git checkout topic && git reset --hard G && - set_fake_editor && - EXPECT_COUNT=1 git rebase -i --onto main... F && + ( + set_fake_editor && + EXPECT_COUNT=1 git rebase -i --onto main... F + ) && git rev-parse HEAD^1 >actual && git rev-parse C^0 >expect && test_cmp expect actual ' -test_expect_success 'rebase -i --onto main...side' ' +test_expect_success 'rebase --onto main...side requires a single merge-base' ' git reset --hard && git checkout side && git reset --hard K && - set_fake_editor && - test_must_fail git rebase -i --onto main...side J + test_must_fail git rebase -i --onto main...side J 2>err && + grep "need exactly one merge base" err ' test_expect_success 'rebase --keep-base --onto incompatible' ' @@ -156,8 +160,10 @@ test_expect_success 'rebase -i --keep-base main from topic' ' git checkout topic && git reset --hard G && - set_fake_editor && - EXPECT_COUNT=2 git rebase -i --keep-base main && + ( + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base main + ) && git rev-parse C >base.expect && git merge-base main HEAD >base.actual && test_cmp base.expect base.actual && @@ -171,8 +177,10 @@ test_expect_success 'rebase -i --keep-base main topic from main' ' git checkout main && git branch -f topic G && - set_fake_editor && - EXPECT_COUNT=2 git rebase -i --keep-base main topic && + ( + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base main topic + ) && git rev-parse C >base.expect && git merge-base main HEAD >base.actual && test_cmp base.expect base.actual && @@ -182,13 +190,39 @@ test_expect_success 'rebase -i --keep-base main topic from main' ' test_cmp expect actual ' -test_expect_success 'rebase -i --keep-base main from side' ' +test_expect_success 'rebase --keep-base requires a single merge base' ' git reset --hard && git checkout side && git reset --hard K && - set_fake_editor && - test_must_fail git rebase -i --keep-base main + test_must_fail git rebase -i --keep-base main 2>err && + grep "need exactly one merge base with branch" err +' + +test_expect_success 'rebase --keep-base keeps cherry picks' ' + git checkout -f -B main E && + git cherry-pick F && + ( + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base HEAD G + ) && + test_cmp_rev HEAD G +' + +test_expect_success 'rebase --keep-base --no-reapply-cherry-picks' ' + git checkout -f -B main E && + git cherry-pick F && + ( + set_fake_editor && + EXPECT_COUNT=1 git rebase -i --keep-base \ + --no-reapply-cherry-picks HEAD G + ) && + test_cmp_rev HEAD^ C +' + +# This must be the last test in this file +test_expect_success '$EDITOR and friends are unchanged' ' + test_editor_unchanged ' test_done diff --git a/t/t3419-rebase-patch-id.sh b/t/t3419-rebase-patch-id.sh index 295040f2fe..7181f176b8 100755 --- a/t/t3419-rebase-patch-id.sh +++ b/t/t3419-rebase-patch-id.sh @@ -43,15 +43,26 @@ test_expect_success 'setup: 500 lines' ' git add newfile && git commit -q -m "add small file" && - git cherry-pick main >/dev/null 2>&1 -' + git cherry-pick main >/dev/null 2>&1 && + + git branch -f squashed main && + git checkout -q -f squashed && + git reset -q --soft HEAD~2 && + git commit -q -m squashed && + + git branch -f mode main && + git checkout -q -f mode && + test_chmod +x file && + git commit -q -a --amend && -test_expect_success 'setup attributes' ' - echo "file binary" >.gitattributes + git branch -f modeother other && + git checkout -q -f modeother && + test_chmod +x file && + git commit -q -a --amend ' test_expect_success 'detect upstream patch' ' - git checkout -q main && + git checkout -q main^{} && scramble file && git add file && git commit -q -m "change big file again" && @@ -61,14 +72,46 @@ test_expect_success 'detect upstream patch' ' test_must_be_empty revs ' +test_expect_success 'detect upstream patch binary' ' + echo "file binary" >.gitattributes && + git checkout -q other^{} && + git rebase main && + git rev-list main...HEAD~ >revs && + test_must_be_empty revs && + test_when_finished "rm .gitattributes" +' + +test_expect_success 'detect upstream patch modechange' ' + git checkout -q modeother^{} && + git rebase mode && + git rev-list mode...HEAD~ >revs && + test_must_be_empty revs +' + test_expect_success 'do not drop patch' ' - git branch -f squashed main && - git checkout -q -f squashed && - git reset -q --soft HEAD~2 && - git commit -q -m squashed && git checkout -q other^{} && test_must_fail git rebase squashed && - git rebase --quit + test_when_finished "git rebase --abort" +' + +test_expect_success 'do not drop patch binary' ' + echo "file binary" >.gitattributes && + git checkout -q other^{} && + test_must_fail git rebase squashed && + test_when_finished "git rebase --abort" && + test_when_finished "rm .gitattributes" +' + +test_expect_success 'do not drop patch modechange' ' + git checkout -q modeother^{} && + git rebase other && + cat >expected <<-\EOF && + diff --git a/file b/file + old mode 100644 + new mode 100755 + EOF + git diff HEAD~ >modediff && + test_cmp expected modediff ' test_done diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index 43fcb68f27..693934ee8b 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -310,7 +310,7 @@ test_expect_success 'autostash is saved on editor failure with conflict' ' test_expect_success 'autostash with dirty submodules' ' test_when_finished "git reset --hard && git checkout main" && git checkout -b with-submodule && - git submodule add ./ sub && + git -c protocol.file.allow=always submodule add ./ sub && test_tick && git commit -m add-submodule && echo changed >sub/file0 && diff --git a/t/t3426-rebase-submodule.sh b/t/t3426-rebase-submodule.sh index 7a9f1127a4..ba069dccbd 100755 --- a/t/t3426-rebase-submodule.sh +++ b/t/t3426-rebase-submodule.sh @@ -48,7 +48,8 @@ test_expect_success 'rebase interactive ignores modified submodules' ' git init sub && git -C sub commit --allow-empty -m "Initial commit" && git init super && - git -C super submodule add ../sub && + git -c protocol.file.allow=always \ + -C super submodule add ../sub && git -C super config submodule.sub.ignore dirty && >super/foo && git -C super add foo && diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 1d0b15380e..70e8136356 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -50,7 +50,7 @@ test_rebase () { test_rebase 'G F E D B A' test_rebase 'G F D B A' --onto D -test_rebase 'G F B A' --keep-base +test_rebase 'G F C B A' --keep-base test_rebase 'G F C E D B A' --no-fork-point test_rebase 'G F C D B A' --no-fork-point --onto D test_rebase 'G F C B A' --no-fork-point --keep-base diff --git a/t/t3435-rebase-gpg-sign.sh b/t/t3435-rebase-gpg-sign.sh index 5f8ba2c739..6aa2aeb628 100755 --- a/t/t3435-rebase-gpg-sign.sh +++ b/t/t3435-rebase-gpg-sign.sh @@ -64,14 +64,6 @@ test_rebase_gpg_sign ! true -i --no-gpg-sign test_rebase_gpg_sign ! true -i --gpg-sign --no-gpg-sign test_rebase_gpg_sign false -i --no-gpg-sign --gpg-sign -test_expect_failure 'rebase -p --no-gpg-sign override commit.gpgsign' ' - test_when_finished "git clean -f" && - git reset --hard merged && - git config commit.gpgsign true && - git rebase -p --no-gpg-sign --onto=one fork-point main && - test_must_fail git verify-commit HEAD -' - test_expect_success 'rebase -r, merge strategy, --gpg-sign will sign commit' ' git reset --hard merged && test_unconfig commit.gpgsign && diff --git a/t/t3438-rebase-broken-files.sh b/t/t3438-rebase-broken-files.sh new file mode 100755 index 0000000000..b92a3ce46b --- /dev/null +++ b/t/t3438-rebase-broken-files.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +test_description='rebase behavior when on-disk files are broken' +. ./test-lib.sh + +test_expect_success 'set up conflicting branches' ' + test_commit base file && + git checkout -b branch1 && + test_commit one file && + git checkout -b branch2 HEAD^ && + test_commit two file +' + +create_conflict () { + test_when_finished "git rebase --abort" && + git checkout -B tmp branch2 && + test_must_fail git rebase branch1 +} + +check_resolve_fails () { + echo resolved >file && + git add file && + test_must_fail git rebase --continue +} + +for item in NAME EMAIL DATE +do + test_expect_success "detect missing GIT_AUTHOR_$item" ' + create_conflict && + + grep -v $item .git/rebase-merge/author-script >tmp && + mv tmp .git/rebase-merge/author-script && + + check_resolve_fails + ' +done + +for item in NAME EMAIL DATE +do + test_expect_success "detect duplicate GIT_AUTHOR_$item" ' + create_conflict && + + grep -i $item .git/rebase-merge/author-script >tmp && + cat tmp >>.git/rebase-merge/author-script && + + check_resolve_fails + ' +done + +test_expect_success 'unknown key in author-script' ' + create_conflict && + + echo "GIT_AUTHOR_BOGUS=${SQ}whatever${SQ}" \ + >>.git/rebase-merge/author-script && + + check_resolve_fails +' + +test_done diff --git a/t/t3512-cherry-pick-submodule.sh b/t/t3512-cherry-pick-submodule.sh index c657840db3..f22d1ddead 100755 --- a/t/t3512-cherry-pick-submodule.sh +++ b/t/t3512-cherry-pick-submodule.sh @@ -16,6 +16,8 @@ fi test_submodule_switch "cherry-pick" test_expect_success 'unrelated submodule/file conflict is ignored' ' + test_config_global protocol.file.allow always && + test_create_repo sub && touch sub/file && diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index e74a318ac3..0e8afe49ed 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -333,7 +333,7 @@ test_expect_success 'rm removes empty submodules from work tree' ' test_expect_success 'rm removes removed submodule from index and .gitmodules' ' git reset --hard && - git submodule update && + git -c protocol.file.allow=always submodule update && rm -rf submod && git rm submod && git status -s -uno --ignore-submodules=none >actual && @@ -639,6 +639,7 @@ cat >expect.deepmodified <<EOF EOF test_expect_success 'setup subsubmodule' ' + test_config_global protocol.file.allow always && git reset --hard && git submodule update && ( diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 8689b48589..51afbd7b24 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -291,7 +291,7 @@ test_expect_success BSLASHPSPEC "git add 'fo\\[ou\\]bar' ignores foobar" ' git reset --hard && touch fo\[ou\]bar foobar && git add '\''fo\[ou\]bar'\'' && - git ls-files fo\[ou\]bar | fgrep fo\[ou\]bar && + git ls-files fo\[ou\]bar | grep -F fo\[ou\]bar && ! ( git ls-files foobar | grep foobar ) ' diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index b354fb39de..5841f280fb 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -7,9 +7,9 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh -if ! test_have_prereq PERL +if test_have_prereq !ADD_I_USE_BUILTIN,!PERL then - skip_all='skipping add -i tests, perl not available' + skip_all='skipping add -i (scripted) tests, perl not available' test_done fi @@ -761,9 +761,33 @@ test_expect_success 'detect bogus diffFilter output' ' git reset --hard && echo content >test && - test_config interactive.diffFilter "sed 1d" && + test_config interactive.diffFilter "sed 6d" && printf y >y && - force_color test_must_fail git add -p <y + force_color test_must_fail git add -p <y >output 2>&1 && + grep "mismatched output" output +' + +test_expect_success 'handle iffy colored hunk headers' ' + git reset --hard && + + echo content >test && + printf n >n && + force_color git -c interactive.diffFilter="sed s/.*@@.*/XX/" \ + add -p >output 2>&1 <n && + grep "^XX$" output +' + +test_expect_success 'handle very large filtered diff' ' + git reset --hard && + # The specific number here is not important, but it must + # be large enough that the output of "git diff --color" + # fills up the pipe buffer. 10,000 results in ~200k of + # colored output. + test_seq 10000 >test && + test_config interactive.diffFilter cat && + printf y >y && + force_color git add -p >output 2>&1 <y && + git diff-files --exit-code -- test ' test_expect_success 'diff.algorithm is passed to `git diff-files`' ' @@ -931,6 +955,18 @@ test_expect_success 'status ignores dirty submodules (except HEAD)' ' ! grep dirty-otherwise output ' +test_expect_success 'handle submodules' ' + echo 123 >>for-submodules/dirty-otherwise/initial.t && + + force_color git -C for-submodules add -p dirty-otherwise >output 2>&1 && + grep "No changes" output && + + force_color git -C for-submodules add -p dirty-head >output 2>&1 <y && + git -C for-submodules ls-files --stage dirty-head >actual && + rev="$(git -C for-submodules/dirty-head rev-parse HEAD)" && + grep "$rev" actual +' + test_expect_success 'set up pathological context' ' git reset --hard && test_write_lines a a a a a a a a a a a >a && diff --git a/t/t3702-add-edit.sh b/t/t3702-add-edit.sh index a1801a8cbd..82bfb2fd2a 100755 --- a/t/t3702-add-edit.sh +++ b/t/t3702-add-edit.sh @@ -100,7 +100,7 @@ EOF echo "#!$SHELL_PATH" >fake-editor.sh cat >> fake-editor.sh <<\EOF -egrep -v '^index' "$1" >orig-patch && +grep -E -v '^index' "$1" >orig-patch && mv -f patch "$1" EOF diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 2a4c3fd61c..376cc8f4ab 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -25,7 +25,7 @@ test_expect_success 'usage on main command -h emits a summary of subcommands' ' grep -F "or: git stash show" usage ' -test_expect_failure 'usage for subcommands should emit subcommand usage' ' +test_expect_success 'usage for subcommands should emit subcommand usage' ' test_expect_code 129 git stash push -h >usage && grep -F "usage: git stash [push" usage ' diff --git a/t/t3906-stash-submodule.sh b/t/t3906-stash-submodule.sh index a52e53dd2d..0f7348ec21 100755 --- a/t/t3906-stash-submodule.sh +++ b/t/t3906-stash-submodule.sh @@ -36,7 +36,7 @@ setup_basic () { git init main && ( cd main && - git submodule add ../sub && + git -c protocol.file.allow=always submodule add ../sub && test_commit main_file ) } diff --git a/t/t3920-crlf-messages.sh b/t/t3920-crlf-messages.sh index 0276edbe3d..4c661d4d54 100755 --- a/t/t3920-crlf-messages.sh +++ b/t/t3920-crlf-messages.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='Test ref-filter and pretty APIs for commit and tag messages using CRLF' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh LIB_CRLF_BRANCHES="" diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index c509143c81..c64d9d2f40 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -113,20 +113,20 @@ test_expect_success 'diff --no-index with binary creation' ' ' cat >expect <<EOF - binfile | Bin 0 -> 1026 bytes - textfile | 10000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + binfilë | Bin 0 -> 1026 bytes + tëxtfilë | 10000 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ EOF test_expect_success 'diff --stat with binary files and big change count' ' - printf "\01\00%1024d" 1 >binfile && - git add binfile && + printf "\01\00%1024d" 1 >binfilë && + git add binfilë && i=0 && while test $i -lt 10000; do echo $i && i=$(($i + 1)) || return 1 - done >textfile && - git add textfile && - git diff --cached --stat binfile textfile >output && + done >tëxtfilë && + git add tëxtfilë && + git -c core.quotepath=false diff --cached --stat binfilë tëxtfilë >output && grep " | " output >actual && test_cmp expect actual ' diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 056e922164..dfcf3a0aaa 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -352,6 +352,8 @@ log -GF -p --pickaxe-all master log -IA -IB -I1 -I2 -p master log --decorate --all log --decorate=full --all +log --decorate --clear-decorations --all +log --decorate=full --clear-decorations --all rev-list --parents HEAD rev-list --children HEAD diff --git a/t/t4013/diff.log_--decorate=full_--all b/t/t4013/diff.log_--decorate=full_--all index 3f9b872ece..6b0b334a5d 100644 --- a/t/t4013/diff.log_--decorate=full_--all +++ b/t/t4013/diff.log_--decorate=full_--all @@ -20,7 +20,7 @@ Date: Mon Jun 26 00:06:00 2006 +0000 Rearranged lines in dir/sub -commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits) +commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:06:00 2006 +0000 diff --git a/t/t4013/diff.log_--decorate=full_--clear-decorations_--all b/t/t4013/diff.log_--decorate=full_--clear-decorations_--all new file mode 100644 index 0000000000..1c030a6554 --- /dev/null +++ b/t/t4013/diff.log_--decorate=full_--clear-decorations_--all @@ -0,0 +1,61 @@ +$ git log --decorate=full --clear-decorations --all +commit b7e0bc69303b488b47deca799a7d723971dfa6cd (refs/heads/mode) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode + +commit a6f364368ca320bc5a92e18912e16fa6b3dff598 (refs/heads/note) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode (file2) + +Notes: + note + +commit cd4e72fd96faed3f0ba949dc42967430374e2290 (refs/heads/rearrange) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Rearranged lines in dir/sub + +commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Notes added by 'git notes add' + +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/master) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (refs/heads/side) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +commit 444ac553ac7612cc88969031b02b3767fb8a353a (refs/heads/initial) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.log_--decorate=full_--decorate-all_--all b/t/t4013/diff.log_--decorate=full_--decorate-all_--all new file mode 100644 index 0000000000..d6e7928784 --- /dev/null +++ b/t/t4013/diff.log_--decorate=full_--decorate-all_--all @@ -0,0 +1,61 @@ +$ git log --decorate=full --decorate-all --all +commit b7e0bc69303b488b47deca799a7d723971dfa6cd (refs/heads/mode) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode + +commit a6f364368ca320bc5a92e18912e16fa6b3dff598 (refs/heads/note) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode (file2) + +Notes: + note + +commit cd4e72fd96faed3f0ba949dc42967430374e2290 (refs/heads/rearrange) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Rearranged lines in dir/sub + +commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Notes added by 'git notes add' + +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> refs/heads/master) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (refs/heads/side) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +commit 444ac553ac7612cc88969031b02b3767fb8a353a (refs/heads/initial) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.log_--decorate_--all b/t/t4013/diff.log_--decorate_--all index f5e20e1e14..c7df1f5814 100644 --- a/t/t4013/diff.log_--decorate_--all +++ b/t/t4013/diff.log_--decorate_--all @@ -20,7 +20,7 @@ Date: Mon Jun 26 00:06:00 2006 +0000 Rearranged lines in dir/sub -commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits) +commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 Author: A U Thor <author@example.com> Date: Mon Jun 26 00:06:00 2006 +0000 diff --git a/t/t4013/diff.log_--decorate_--clear-decorations_--all b/t/t4013/diff.log_--decorate_--clear-decorations_--all new file mode 100644 index 0000000000..88be82cce3 --- /dev/null +++ b/t/t4013/diff.log_--decorate_--clear-decorations_--all @@ -0,0 +1,61 @@ +$ git log --decorate --clear-decorations --all +commit b7e0bc69303b488b47deca799a7d723971dfa6cd (mode) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode + +commit a6f364368ca320bc5a92e18912e16fa6b3dff598 (note) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode (file2) + +Notes: + note + +commit cd4e72fd96faed3f0ba949dc42967430374e2290 (rearrange) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Rearranged lines in dir/sub + +commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Notes added by 'git notes add' + +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> master) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (side) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +commit 444ac553ac7612cc88969031b02b3767fb8a353a (initial) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4013/diff.log_--decorate_--decorate-all_--all b/t/t4013/diff.log_--decorate_--decorate-all_--all new file mode 100644 index 0000000000..5d22618bb6 --- /dev/null +++ b/t/t4013/diff.log_--decorate_--decorate-all_--all @@ -0,0 +1,61 @@ +$ git log --decorate --decorate-all --all +commit b7e0bc69303b488b47deca799a7d723971dfa6cd (mode) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode + +commit a6f364368ca320bc5a92e18912e16fa6b3dff598 (note) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + update mode (file2) + +Notes: + note + +commit cd4e72fd96faed3f0ba949dc42967430374e2290 (rearrange) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Rearranged lines in dir/sub + +commit cbacedd14cb8b89255a2c02b59e77a2e9a8021a0 (refs/notes/commits) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:06:00 2006 +0000 + + Notes added by 'git notes add' + +commit 59d314ad6f356dd08601a4cd5e530381da3e3c64 (HEAD -> master) +Merge: 9a6d494 c7a2ab9 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:04:00 2006 +0000 + + Merge branch 'side' + +commit c7a2ab9e8eac7b117442a607d5a9b3950ae34d5a (side) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:03:00 2006 +0000 + + Side + +commit 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:02:00 2006 +0000 + + Third + +commit 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:01:00 2006 +0000 + + Second + + This is the second commit. + +commit 444ac553ac7612cc88969031b02b3767fb8a353a (initial) +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial +$ diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index fbec8ad2ef..de1da4673d 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1400,6 +1400,43 @@ test_expect_success '--from omits redundant in-body header' ' test_cmp expect patch.head ' +test_expect_success 'with --force-in-body-from, redundant in-body from is kept' ' + git format-patch --force-in-body-from \ + -1 --stdout --from="A U Thor <author@example.com>" >patch && + cat >expect <<-\EOF && + From: A U Thor <author@example.com> + + From: A U Thor <author@example.com> + + EOF + sed -ne "/^From:/p; /^$/p; /^---$/q" patch >patch.head && + test_cmp expect patch.head +' + +test_expect_success 'format.forceInBodyFrom, equivalent to --force-in-body-from' ' + git -c format.forceInBodyFrom=yes format-patch \ + -1 --stdout --from="A U Thor <author@example.com>" >patch && + cat >expect <<-\EOF && + From: A U Thor <author@example.com> + + From: A U Thor <author@example.com> + + EOF + sed -ne "/^From:/p; /^$/p; /^---$/q" patch >patch.head && + test_cmp expect patch.head +' + +test_expect_success 'format.forceInBodyFrom, equivalent to --force-in-body-from' ' + git -c format.forceInBodyFrom=yes format-patch --no-force-in-body-from \ + -1 --stdout --from="A U Thor <author@example.com>" >patch && + cat >expect <<-\EOF && + From: A U Thor <author@example.com> + + EOF + sed -ne "/^From:/p; /^$/p; /^---$/q" patch >patch.head && + test_cmp expect patch.head +' + test_expect_success 'in-body headers trigger content encoding' ' test_env GIT_AUTHOR_NAME="éxötìc" test_commit exotic && test_when_finished "git reset --hard HEAD^" && @@ -1420,7 +1457,7 @@ append_signoff() C=$(git commit-tree HEAD^^{tree} -p HEAD) && git format-patch --stdout --signoff $C^..$C >append_signoff.patch && sed -n -e "1,/^---$/p" append_signoff.patch | - egrep -n "^Subject|Sign|^$" + grep -E -n "^Subject|Sign|^$" } test_expect_success 'signoff: commit with no body' ' @@ -2237,10 +2274,10 @@ test_expect_success 'format-patch --base with --attach' ' test_expect_success 'format-patch --attach cover-letter only is non-multipart' ' test_when_finished "rm -fr patches" && git format-patch -o patches --cover-letter --attach=mimemime --base=HEAD~ -1 && - ! egrep "^--+mimemime" patches/0000*.patch && - egrep "^--+mimemime$" patches/0001*.patch >output && + ! grep -E "^--+mimemime" patches/0000*.patch && + grep -E "^--+mimemime$" patches/0001*.patch >output && test_line_count = 2 output && - egrep "^--+mimemime--$" patches/0001*.patch >output && + grep -E "^--+mimemime--$" patches/0001*.patch >output && test_line_count = 1 output ' diff --git a/t/t4017-diff-retval.sh b/t/t4017-diff-retval.sh index ed461f481e..5bc28ad9f0 100755 --- a/t/t4017-diff-retval.sh +++ b/t/t4017-diff-retval.sh @@ -5,6 +5,7 @@ test_description='Return value of diffs' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index 858a5522f9..c1ac09ecc7 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -33,7 +33,7 @@ test_expect_success 'GIT_EXTERNAL_DIFF environment' ' ' -test_expect_success !SANITIZE_LEAK 'GIT_EXTERNAL_DIFF environment should apply only to diff' ' +test_expect_success 'GIT_EXTERNAL_DIFF environment should apply only to diff' ' GIT_EXTERNAL_DIFF=echo git log -p -1 HEAD >out && grep "^diff --git a/file b/file" out @@ -74,7 +74,7 @@ test_expect_success 'diff.external' ' test_cmp expect actual ' -test_expect_success !SANITIZE_LEAK 'diff.external should apply only to diff' ' +test_expect_success 'diff.external should apply only to diff' ' test_config diff.external echo && git log -p -1 HEAD >out && grep "^diff --git a/file b/file" out diff --git a/t/t4038-diff-combined.sh b/t/t4038-diff-combined.sh index 9a292bac70..2ce26e585c 100755 --- a/t/t4038-diff-combined.sh +++ b/t/t4038-diff-combined.sh @@ -80,11 +80,21 @@ test_expect_success 'check combined output (1)' ' verify_helper sidewithone ' +test_expect_success 'check combined output (1) with git diff <rev>^!' ' + git diff sidewithone^! -- >sidewithone && + verify_helper sidewithone +' + test_expect_success 'check combined output (2)' ' git show sidesansone -- >sidesansone && verify_helper sidesansone ' +test_expect_success 'check combined output (2) with git diff <rev>^!' ' + git diff sidesansone^! -- >sidesansone && + verify_helper sidesansone +' + test_expect_success 'diagnose truncated file' ' >file && git add file && diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh index 4838a1df8b..725278ad19 100755 --- a/t/t4051-diff-function-context.sh +++ b/t/t4051-diff-function-context.sh @@ -2,6 +2,7 @@ test_description='diff function context' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh dir="$TEST_DIRECTORY/t4051" diff --git a/t/t4057-diff-combined-paths.sh b/t/t4057-diff-combined-paths.sh index 04b8a1542a..9a7505cbb8 100755 --- a/t/t4057-diff-combined-paths.sh +++ b/t/t4057-diff-combined-paths.sh @@ -5,6 +5,7 @@ test_description='combined diff show only paths that are different to all parent GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # verify that diffc.expect matches output of diff --git a/t/t4059-diff-submodule-not-initialized.sh b/t/t4059-diff-submodule-not-initialized.sh index 49bca7b48d..d489230df8 100755 --- a/t/t4059-diff-submodule-not-initialized.sh +++ b/t/t4059-diff-submodule-not-initialized.sh @@ -49,7 +49,7 @@ test_expect_success 'setup - submodules' ' ' test_expect_success 'setup - git submodule add' ' - git submodule add ./sm2 sm1 && + git -c protocol.file.allow=always submodule add ./sm2 sm1 && commit_file sm1 .gitmodules && git diff-tree -p --no-commit-id --submodule=log HEAD -- sm1 >actual && cat >expected <<-EOF && diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh index d86e38abd8..97c6424cd5 100755 --- a/t/t4060-diff-submodule-option-diff-format.sh +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -840,7 +840,7 @@ rm sm2 mv sm2-bak sm2 test_expect_success 'setup nested submodule' ' - git -C sm2 submodule add ../sm2 nested && + git -c protocol.file.allow=always -C sm2 submodule add ../sm2 nested && git -C sm2 commit -a -m "nested sub" && head10=$(git -C sm2 rev-parse --short --verify HEAD) ' diff --git a/t/t4067-diff-partial-clone.sh b/t/t4067-diff-partial-clone.sh index 804f2a82e8..28f42a4046 100755 --- a/t/t4067-diff-partial-clone.sh +++ b/t/t4067-diff-partial-clone.sh @@ -77,6 +77,7 @@ test_expect_success 'diff skips same-OID blobs' ' test_expect_success 'when fetching missing objects, diff skips GITLINKs' ' test_when_finished "rm -rf sub server client trace" && + test_config_global protocol.file.allow always && test_create_repo sub && test_commit -C sub first && diff --git a/t/t4069-remerge-diff.sh b/t/t4069-remerge-diff.sh index 35f94957fc..07323ebafe 100755 --- a/t/t4069-remerge-diff.sh +++ b/t/t4069-remerge-diff.sh @@ -56,6 +56,11 @@ test_expect_success 'remerge-diff on a clean merge' ' test_cmp expect actual ' +test_expect_success 'remerge-diff on a clean merge with a filter' ' + git show --oneline --remerge-diff --diff-filter=U bc_resolution >actual && + test_must_be_empty actual +' + test_expect_success 'remerge-diff with both a resolved conflict and an unrelated change' ' git log -1 --oneline ab_resolution >tmp && cat <<-EOF >>tmp && @@ -89,6 +94,22 @@ test_expect_success 'remerge-diff with both a resolved conflict and an unrelated test_cmp expect actual ' +test_expect_success 'pickaxe still includes additional headers for relevant changes' ' + # reuses "expect" from the previous testcase + + git log --oneline --remerge-diff -Sacht ab_resolution >tmp && + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual && + test_cmp expect actual +' + +test_expect_success 'can filter out additional headers with pickaxe' ' + git show --remerge-diff --submodule=log --find-object=HEAD ab_resolution >actual && + test_must_be_empty actual && + + git show --remerge-diff -S"not present" --all >actual && + test_must_be_empty actual +' + test_expect_success 'setup non-content conflicts' ' git switch --orphan base && @@ -184,6 +205,14 @@ test_expect_success 'remerge-diff w/ diff-filter=U: all conflict headers, no dif test_cmp expect actual ' +test_expect_success 'submodule formatting ignores additional headers' ' + # Reuses "expect" from last testcase + + git show --oneline --remerge-diff --diff-filter=U --submodule=log >tmp && + sed -e "s/[0-9a-f]\{7,\}/HASH/g" tmp >actual && + test_cmp expect actual +' + test_expect_success 'remerge-diff w/ diff-filter=R: relevant file + conflict header' ' git log -1 --oneline resolution >tmp && cat <<-EOF >>tmp && diff --git a/t/t4114-apply-typechange.sh b/t/t4114-apply-typechange.sh index da3e64f811..8ff3640766 100755 --- a/t/t4114-apply-typechange.sh +++ b/t/t4114-apply-typechange.sh @@ -7,6 +7,7 @@ test_description='git apply should not get confused with type changes. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup repository and commits' ' diff --git a/t/t4141-apply-too-large.sh b/t/t4141-apply-too-large.sh new file mode 100755 index 0000000000..58742d4fc5 --- /dev/null +++ b/t/t4141-apply-too-large.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test_description='git apply with too-large patch' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success EXPENSIVE 'git apply rejects patches that are too large' ' + sz=$((1024 * 1024 * 1023)) && + { + cat <<-\EOF && + diff --git a/file b/file + new file mode 100644 + --- /dev/null + +++ b/file + @@ -0,0 +1 @@ + EOF + test-tool genzeros + } | test_copy_bytes $sz | test_must_fail git apply 2>err && + grep "git apply: failed to read" err +' + +test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index 3095b1b2ff..8e4effebdb 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -83,6 +83,13 @@ test_expect_success 'pretty format' ' test_cmp expect log.predictable ' +test_expect_success 'pretty format (with --date)' ' + sed "s/SUBJECT/2005-04-07 OBJECT_NAME/" expect.template >expect && + git shortlog --format="%ad %H" --date=short HEAD >log && + fuzz log >log.predictable && + test_cmp expect log.predictable +' + test_expect_success '--abbrev' ' sed s/SUBJECT/OBJID/ expect.template >expect && git shortlog --format="%h" --abbrev=35 HEAD >log && @@ -237,6 +244,26 @@ test_expect_success 'shortlog --group=trailer:signed-off-by' ' test_cmp expect actual ' +test_expect_success 'shortlog --group=format' ' + git shortlog -s --date="format:%Y" --group="format:%cN (%cd)" \ + HEAD >actual && + cat >expect <<-\EOF && + 4 C O Mitter (2005) + 1 Sin Nombre (2005) + EOF + test_cmp expect actual +' + +test_expect_success 'shortlog --group=<format> DWIM' ' + git shortlog -s --date="format:%Y" --group="%cN (%cd)" HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'shortlog bogus --group' ' + test_must_fail git shortlog --group=bogus HEAD 2>err && + grep "unknown group type" err +' + test_expect_success 'trailer idents are split' ' cat >expect <<-\EOF && 2 C O Mitter @@ -319,6 +346,18 @@ test_expect_success 'shortlog can match multiple groups' ' test_cmp expect actual ' +test_expect_success 'shortlog can match multiple format groups' ' + GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" \ + git commit --allow-empty -m "identical names" && + test_tick && + cat >expect <<-\EOF && + 2 A U Thor + 1 C O Mitter + EOF + git shortlog -ns --group="%cn" --group="%an" -2 HEAD >actual && + test_cmp expect actual +' + test_expect_success 'set up option selection tests' ' git commit --allow-empty -F - <<-\EOF subject diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 6e66352558..2ce2b41174 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -249,6 +249,15 @@ test_expect_success 'log --grep' ' test_cmp expect actual ' +for noop_opt in --invert-grep --all-match +do + test_expect_success "log $noop_opt without --grep is a NOOP" ' + git log >expect && + git log $noop_opt >actual && + test_cmp expect actual + ' +done + cat > expect << EOF second initial @@ -704,9 +713,12 @@ test_expect_success 'set up more tangled history' ' git checkout -b tangle HEAD~6 && test_commit tangle-a tangle-a a && git merge main~3 && + git update-ref refs/prefetch/merge HEAD && git merge side~1 && + git update-ref refs/rewritten/merge HEAD && git checkout main && git merge tangle && + git update-ref refs/hidden/tangle HEAD && git checkout -b reach && test_commit reach && git checkout main && @@ -974,9 +986,9 @@ test_expect_success 'decorate-refs-exclude and simplify-by-decoration' ' Merge-tag-reach (HEAD -> main) reach (tag: reach, reach) seventh (tag: seventh) - Merge-branch-tangle - Merge-branch-side-early-part-into-tangle (tangle) - tangle-a (tag: tangle-a) + Merge-branch-tangle (refs/hidden/tangle) + Merge-branch-side-early-part-into-tangle (refs/rewritten/merge, tangle) + Merge-branch-main-early-part-into-tangle (refs/prefetch/merge) EOF git log -n6 --decorate=short --pretty="tformat:%f%d" \ --decorate-refs-exclude="*octopus*" \ @@ -1025,6 +1037,115 @@ test_expect_success 'decorate-refs and simplify-by-decoration without output' ' test_cmp expect actual ' +test_expect_success 'decorate-refs-exclude HEAD' ' + git log --decorate=full --oneline \ + --decorate-refs-exclude="HEAD" >actual && + ! grep HEAD actual +' + +test_expect_success 'decorate-refs focus from default' ' + git log --decorate=full --oneline \ + --decorate-refs="refs/heads" >actual && + ! grep HEAD actual +' + +test_expect_success '--clear-decorations overrides defaults' ' + cat >expect.default <<-\EOF && + Merge-tag-reach (HEAD -> refs/heads/main) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: refs/tags/seventh) + octopus-b (tag: refs/tags/octopus-b, refs/heads/octopus-b) + octopus-a (tag: refs/tags/octopus-a, refs/heads/octopus-a) + reach (tag: refs/tags/reach, refs/heads/reach) + Merge-branch-tangle + Merge-branch-side-early-part-into-tangle (refs/heads/tangle) + Merge-branch-main-early-part-into-tangle + tangle-a (tag: refs/tags/tangle-a) + Merge-branch-side + side-2 (tag: refs/tags/side-2, refs/heads/side) + side-1 (tag: refs/tags/side-1) + Second + sixth + fifth + fourth + third + second + initial + EOF + git log --decorate=full --pretty="tformat:%f%d" >actual && + test_cmp expect.default actual && + + cat >expect.all <<-\EOF && + Merge-tag-reach (HEAD -> refs/heads/main) + Merge-tags-octopus-a-and-octopus-b + seventh (tag: refs/tags/seventh) + octopus-b (tag: refs/tags/octopus-b, refs/heads/octopus-b) + octopus-a (tag: refs/tags/octopus-a, refs/heads/octopus-a) + reach (tag: refs/tags/reach, refs/heads/reach) + Merge-branch-tangle (refs/hidden/tangle) + Merge-branch-side-early-part-into-tangle (refs/rewritten/merge, refs/heads/tangle) + Merge-branch-main-early-part-into-tangle (refs/prefetch/merge) + tangle-a (tag: refs/tags/tangle-a) + Merge-branch-side + side-2 (tag: refs/tags/side-2, refs/heads/side) + side-1 (tag: refs/tags/side-1) + Second + sixth + fifth + fourth + third + second + initial + EOF + git log --decorate=full --pretty="tformat:%f%d" \ + --clear-decorations >actual && + test_cmp expect.all actual && + git -c log.initialDecorationSet=all log \ + --decorate=full --pretty="tformat:%f%d" >actual && + test_cmp expect.all actual +' + +test_expect_success '--clear-decorations clears previous exclusions' ' + cat >expect.all <<-\EOF && + Merge-tag-reach (HEAD -> refs/heads/main) + reach (tag: refs/tags/reach, refs/heads/reach) + Merge-tags-octopus-a-and-octopus-b + octopus-b (tag: refs/tags/octopus-b, refs/heads/octopus-b) + octopus-a (tag: refs/tags/octopus-a, refs/heads/octopus-a) + seventh (tag: refs/tags/seventh) + Merge-branch-tangle (refs/hidden/tangle) + Merge-branch-side-early-part-into-tangle (refs/rewritten/merge, refs/heads/tangle) + Merge-branch-main-early-part-into-tangle (refs/prefetch/merge) + tangle-a (tag: refs/tags/tangle-a) + side-2 (tag: refs/tags/side-2, refs/heads/side) + side-1 (tag: refs/tags/side-1) + initial + EOF + + git log --decorate=full --pretty="tformat:%f%d" \ + --simplify-by-decoration \ + --decorate-refs-exclude="heads/octopus*" \ + --decorate-refs="heads" \ + --clear-decorations >actual && + test_cmp expect.all actual && + + cat >expect.filtered <<-\EOF && + Merge-tags-octopus-a-and-octopus-b + octopus-b (refs/heads/octopus-b) + octopus-a (refs/heads/octopus-a) + initial + EOF + + git log --decorate=full --pretty="tformat:%f%d" \ + --simplify-by-decoration \ + --decorate-refs-exclude="heads/octopus" \ + --decorate-refs="heads" \ + --clear-decorations \ + --decorate-refs-exclude="tags/" \ + --decorate-refs="heads/octopus*" >actual && + test_cmp expect.filtered actual +' + test_expect_success 'log.decorate config parsing' ' git log --oneline --decorate=full >expect.full && git log --oneline --decorate=short >expect.short && @@ -2112,9 +2233,9 @@ test_expect_success REFFILES 'log diagnoses bogus HEAD hash' ' test_i18ngrep broken stderr ' -test_expect_success 'log diagnoses bogus HEAD symref' ' +test_expect_success REFFILES 'log diagnoses bogus HEAD symref' ' git init empty && - git --git-dir empty/.git symbolic-ref HEAD refs/heads/invalid.lock && + echo "ref: refs/heads/invalid.lock" > empty/.git/HEAD && test_must_fail git -C empty log 2>stderr && test_i18ngrep broken stderr && test_must_fail git -C empty log --default totally-bogus 2>stderr && @@ -2192,6 +2313,20 @@ test_expect_success 'log --decorate includes all levels of tag annotated tags' ' test_cmp expect actual ' +test_expect_success 'log --decorate does not include things outside filter' ' + reflist="refs/prefetch refs/rebase-merge refs/bundle" && + + for ref in $reflist + do + git update-ref $ref/fake HEAD || return 1 + done && + + git log --decorate=full --oneline >actual && + + # None of the refs are visible: + ! grep /fake actual +' + test_expect_success 'log --end-of-options' ' git update-ref refs/heads/--source HEAD && git log --end-of-options --source >actual && diff --git a/t/t4204-patch-id.sh b/t/t4204-patch-id.sh index a730c0db98..a7fa94ce0a 100755 --- a/t/t4204-patch-id.sh +++ b/t/t4204-patch-id.sh @@ -8,13 +8,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh test_expect_success 'setup' ' - as="a a a a a a a a" && # eight a - test_write_lines $as >foo && - test_write_lines $as >bar && + str="ab cd ef gh ij kl mn op" && + test_write_lines $str >foo && + test_write_lines $str >bar && git add foo bar && git commit -a -m initial && - test_write_lines $as b >foo && - test_write_lines $as b >bar && + test_write_lines $str b >foo && + test_write_lines $str b >bar && git commit -a -m first && git checkout -b same main && git commit --amend -m same-msg && @@ -22,8 +22,23 @@ test_expect_success 'setup' ' echo c >foo && echo c >bar && git commit --amend -a -m notsame-msg && + git checkout -b with_space main~ && + cat >foo <<-\EOF && + a b + c d + e f + g h + i j + k l + m n + op + EOF + cp foo bar && + git add foo bar && + git commit --amend -m "with spaces" && test_write_lines bar foo >bar-then-foo && test_write_lines foo bar >foo-then-bar + ' test_expect_success 'patch-id output is well-formed' ' @@ -42,7 +57,7 @@ calc_patch_id () { } get_top_diff () { - git log -p -1 "$@" -O bar-then-foo -- + git log -p -1 "$@" -O bar-then-foo --full-index -- } get_patch_id () { @@ -61,6 +76,33 @@ test_expect_success 'patch-id detects inequality' ' get_patch_id notsame && ! test_cmp patch-id_main patch-id_notsame ' +test_expect_success 'patch-id detects equality binary' ' + cat >.gitattributes <<-\EOF && + foo binary + bar binary + EOF + get_patch_id main && + get_patch_id same && + git log -p -1 --binary main >top-diff.output && + calc_patch_id <top-diff.output main_binpatch && + git log -p -1 --binary same >top-diff.output && + calc_patch_id <top-diff.output same_binpatch && + test_cmp patch-id_main patch-id_main_binpatch && + test_cmp patch-id_same patch-id_same_binpatch && + test_cmp patch-id_main patch-id_same && + test_when_finished "rm .gitattributes" +' + +test_expect_success 'patch-id detects inequality binary' ' + cat >.gitattributes <<-\EOF && + foo binary + bar binary + EOF + get_patch_id main && + get_patch_id notsame && + ! test_cmp patch-id_main patch-id_notsame && + test_when_finished "rm .gitattributes" +' test_expect_success 'patch-id supports git-format-patch output' ' get_patch_id main && @@ -101,9 +143,21 @@ test_patch_id_file_order () { git format-patch -1 --stdout -O foo-then-bar >format-patch.output && calc_patch_id <format-patch.output "ordered-$name" "$@" && cmp_patch_id $relevant "$name" "ordered-$name" +} +test_patch_id_whitespace () { + relevant="$1" + shift + name="ws-${1}-$relevant" + shift + get_top_diff "main~" >top-diff.output && + calc_patch_id <top-diff.output "$name" "$@" && + get_top_diff "with_space" >top-diff.output && + calc_patch_id <top-diff.output "ws-$name" "$@" && + cmp_patch_id $relevant "$name" "ws-$name" } + # combined test for options: add more tests here to make them # run with all options test_patch_id () { @@ -119,6 +173,14 @@ test_expect_success 'file order is relevant with --unstable' ' test_patch_id_file_order relevant --unstable --unstable ' +test_expect_success 'whitespace is relevant with --verbatim' ' + test_patch_id_whitespace relevant --verbatim --verbatim +' + +test_expect_success 'whitespace is irrelevant without --verbatim' ' + test_patch_id_whitespace irrelevant --stable --stable +' + #Now test various option combinations. test_expect_success 'default is unstable' ' test_patch_id relevant default @@ -134,6 +196,17 @@ test_expect_success 'patchid.stable = false is unstable' ' test_patch_id relevant patchid.stable=false ' +test_expect_success 'patchid.verbatim = true is correct and stable' ' + test_config patchid.verbatim true && + test_patch_id_whitespace relevant patchid.verbatim=true && + test_patch_id irrelevant patchid.verbatim=true +' + +test_expect_success 'patchid.verbatim = false is unstable' ' + test_config patchid.verbatim false && + test_patch_id relevant patchid.verbatim=false +' + test_expect_success '--unstable overrides patchid.stable = true' ' test_config patchid.stable true && test_patch_id relevant patchid.stable=true--unstable --unstable @@ -144,6 +217,11 @@ test_expect_success '--stable overrides patchid.stable = false' ' test_patch_id irrelevant patchid.stable=false--stable --stable ' +test_expect_success '--verbatim overrides patchid.stable = false' ' + test_config patchid.stable false && + test_patch_id_whitespace relevant stable=false--verbatim --verbatim +' + test_expect_success 'patch-id supports git-format-patch MIME output' ' get_patch_id main && git checkout same && @@ -198,7 +276,10 @@ test_expect_success 'patch-id handles no-nl-at-eof markers' ' EOF calc_patch_id nonl <nonl && calc_patch_id withnl <withnl && - test_cmp patch-id_nonl patch-id_withnl + test_cmp patch-id_nonl patch-id_withnl && + calc_patch_id nonl-inc-ws --verbatim <nonl && + calc_patch_id withnl-inc-ws --verbatim <withnl && + ! test_cmp patch-id_nonl-inc-ws patch-id_withnl-inc-ws ' test_expect_success 'patch-id handles diffs with one line of before/after' ' diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh index 36ac6aff1e..ded33a82e2 100755 --- a/t/t4207-log-decoration-colors.sh +++ b/t/t4207-log-decoration-colors.sh @@ -3,7 +3,7 @@ # Copyright (c) 2010 Nazri Ramliy # -test_description='Test for "git log --decorate" colors' +test_description='test "git log --decorate" colors' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME @@ -17,6 +17,7 @@ test_expect_success setup ' git config color.decorate.remoteBranch red && git config color.decorate.tag "reverse bold yellow" && git config color.decorate.stash magenta && + git config color.decorate.grafted black && git config color.decorate.HEAD cyan && c_reset="<RESET>" && @@ -27,6 +28,7 @@ test_expect_success setup ' c_tag="<BOLD;REVERSE;YELLOW>" && c_stash="<MAGENTA>" && c_HEAD="<CYAN>" && + c_grafted="<BLACK>" && test_commit A && git clone . other && @@ -42,25 +44,79 @@ test_expect_success setup ' git stash save Changes to A.t ' -cat >expected <<EOF -${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD ->\ - ${c_reset}${c_branch}main${c_reset}${c_commit},\ - ${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit},\ - ${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B -${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A1${c_reset}${c_commit},\ - ${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1 -${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_stash}refs/stash${c_reset}${c_commit})${c_reset}\ - On main: Changes to A.t -${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A -EOF +cmp_filtered_decorations () { + sed "s/$OID_REGEX/COMMIT_ID/" actual | test_decode_color >filtered && + test_cmp expect filtered +} # We want log to show all, but the second parent to refs/stash is irrelevant # to this test since it does not contain any decoration, hence --first-parent -test_expect_success 'Commit Decorations Colored Correctly' ' - git log --first-parent --abbrev=10 --all --decorate --oneline --color=always | - sed "s/[0-9a-f]\{10,10\}/COMMIT_ID/" | - test_decode_color >out && - test_cmp expected out +test_expect_success 'commit decorations colored correctly' ' + cat >expect <<-EOF && + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \ +${c_reset}${c_branch}main${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B +${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A1${c_reset}${c_commit}, \ +${c_reset}${c_remoteBranch}other/main${c_reset}${c_commit})${c_reset} A1 + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_stash}refs/stash${c_reset}${c_commit})${c_reset} \ +On main: Changes to A.t + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A + EOF + + git log --first-parent --no-abbrev --decorate --oneline --color=always --all >actual && + cmp_filtered_decorations +' + +test_expect_success 'test coloring with replace-objects' ' + test_when_finished rm -rf .git/refs/replace* && + test_commit C && + test_commit D && + + git replace HEAD~1 HEAD~2 && + + cat >expect <<-EOF && + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \ +${c_reset}${c_branch}main${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: D${c_reset}${c_commit})${c_reset} D + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: C${c_reset}${c_commit}, \ +${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} B + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A +EOF + + git log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual && + cmp_filtered_decorations && + git replace -d HEAD~1 && + + GIT_REPLACE_REF_BASE=refs/replace2/ git replace HEAD~1 HEAD~2 && + GIT_REPLACE_REF_BASE=refs/replace2/ git log --first-parent \ + --no-abbrev --decorate --oneline --color=always HEAD >actual && + cmp_filtered_decorations +' + +test_expect_success 'test coloring with grafted commit' ' + test_when_finished rm -rf .git/refs/replace* && + + git replace --graft HEAD HEAD~2 && + + cat >expect <<-EOF && + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_HEAD}HEAD -> \ +${c_reset}${c_branch}main${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: D${c_reset}${c_commit}, \ +${c_reset}${c_grafted}replaced${c_reset}${c_commit})${c_reset} D + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: v1.0${c_reset}${c_commit}, \ +${c_reset}${c_tag}tag: B${c_reset}${c_commit})${c_reset} B + ${c_commit}COMMIT_ID${c_reset}${c_commit} (${c_reset}${c_tag}tag: A${c_reset}${c_commit})${c_reset} A + EOF + + git log --first-parent --no-abbrev --decorate --oneline --color=always HEAD >actual && + cmp_filtered_decorations && + git replace -d HEAD && + + GIT_REPLACE_REF_BASE=refs/replace2/ git replace --graft HEAD HEAD~2 && + GIT_REPLACE_REF_BASE=refs/replace2/ git log --first-parent \ + --no-abbrev --decorate --oneline --color=always HEAD >actual && + cmp_filtered_decorations ' test_done diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh index 7f0c1dcc0f..2e8f5ad7b8 100755 --- a/t/t4208-log-magic-pathspec.sh +++ b/t/t4208-log-magic-pathspec.sh @@ -124,6 +124,7 @@ test_expect_success 'command line pathspec parsing for "git log"' ' test_expect_success 'tree_entry_interesting does not match past submodule boundaries' ' test_when_finished "rm -rf repo submodule" && + test_config_global protocol.file.allow always && git init submodule && test_commit -C submodule initial && git init repo && diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh index f091259a55..cac85591b5 100755 --- a/t/t4301-merge-tree-write-tree.sh +++ b/t/t4301-merge-tree-write-tree.sh @@ -137,6 +137,579 @@ test_expect_success 'test conflict notices and such' ' test_cmp expect actual ' +# directory rename + content conflict +# Commit O: foo, olddir/{a,b,c} +# Commit A: modify foo, newdir/{a,b,c} +# Commit B: modify foo differently & rename foo -> olddir/bar +# Expected: CONFLICT(content) for for newdir/bar (not olddir/bar or foo) + +test_expect_success 'directory rename + content conflict' ' + # Setup + git init dir-rename-and-content && + ( + cd dir-rename-and-content && + test_write_lines 1 2 3 4 5 >foo && + mkdir olddir && + for i in a b c; do echo $i >olddir/$i || exit 1; done && + git add foo olddir && + git commit -m "original" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 6 >foo && + git add foo && + git mv olddir newdir && + git commit -m "Modify foo, rename olddir to newdir" && + + git checkout B && + test_write_lines 1 2 3 4 5 six >foo && + git add foo && + git mv foo olddir/bar && + git commit -m "Modify foo & rename foo -> olddir/bar" + ) && + # Testing + ( + cd dir-rename-and-content && + + test_expect_code 1 \ + git merge-tree -z A^0 B^0 >out && + echo >>out && + anonymize_hash out >actual && + q_to_tab <<-\EOF | lf_to_nul >expect && + HASH + 100644 HASH 1Qnewdir/bar + 100644 HASH 2Qnewdir/bar + 100644 HASH 3Qnewdir/bar + EOF + + q_to_nul <<-EOF >>expect && + Q2Qnewdir/barQolddir/barQCONFLICT (directory rename suggested)QCONFLICT (file location): foo renamed to olddir/bar in B^0, inside a directory that was renamed in A^0, suggesting it should perhaps be moved to newdir/bar. + Q1Qnewdir/barQAuto-mergingQAuto-merging newdir/bar + Q1Qnewdir/barQCONFLICT (contents)QCONFLICT (content): Merge conflict in newdir/bar + Q + EOF + test_cmp expect actual + ) +' + +# rename/delete + modify/delete handling +# Commit O: foo +# Commit A: modify foo + rename to bar +# Commit B: delete foo +# Expected: CONFLICT(rename/delete) + CONFLICT(modify/delete) + +test_expect_success 'rename/delete handling' ' + # Setup + git init rename-delete && + ( + cd rename-delete && + test_write_lines 1 2 3 4 5 >foo && + git add foo && + git commit -m "original" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 6 >foo && + git add foo && + git mv foo bar && + git commit -m "Modify foo, rename to bar" && + + git checkout B && + git rm foo && + git commit -m "remove foo" + ) && + # Testing + ( + cd rename-delete && + + test_expect_code 1 \ + git merge-tree -z A^0 B^0 >out && + echo >>out && + anonymize_hash out >actual && + q_to_tab <<-\EOF | lf_to_nul >expect && + HASH + 100644 HASH 1Qbar + 100644 HASH 2Qbar + EOF + + q_to_nul <<-EOF >>expect && + Q2QbarQfooQCONFLICT (rename/delete)QCONFLICT (rename/delete): foo renamed to bar in A^0, but deleted in B^0. + Q1QbarQCONFLICT (modify/delete)QCONFLICT (modify/delete): bar deleted in B^0 and modified in A^0. Version A^0 of bar left in tree. + Q + EOF + test_cmp expect actual + ) +' + +# rename/add handling +# Commit O: foo +# Commit A: modify foo, add different bar +# Commit B: modify & rename foo->bar +# Expected: CONFLICT(add/add) [via rename collide] for bar + +test_expect_success 'rename/add handling' ' + # Setup + git init rename-add && + ( + cd rename-add && + test_write_lines original 1 2 3 4 5 >foo && + git add foo && + git commit -m "original" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 >foo && + echo "different file" >bar && + git add foo bar && + git commit -m "Modify foo, add bar" && + + git checkout B && + test_write_lines original 1 2 3 4 5 6 >foo && + git add foo && + git mv foo bar && + git commit -m "rename foo to bar" + ) && + # Testing + ( + cd rename-add && + + test_expect_code 1 \ + git merge-tree -z A^0 B^0 >out && + echo >>out && + + # + # First, check that the bar that appears at stage 3 does not + # correspond to an individual blob anywhere in history + # + hash=$(cat out | tr "\0" "\n" | head -n 3 | grep 3.bar | cut -f 2 -d " ") && + git rev-list --objects --all >all_blobs && + ! grep $hash all_blobs && + + # + # Second, check anonymized hash output against expectation + # + anonymize_hash out >actual && + q_to_tab <<-\EOF | lf_to_nul >expect && + HASH + 100644 HASH 2Qbar + 100644 HASH 3Qbar + EOF + + q_to_nul <<-EOF >>expect && + Q1QbarQAuto-mergingQAuto-merging bar + Q1QbarQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in bar + Q1QfooQAuto-mergingQAuto-merging foo + Q + EOF + test_cmp expect actual + ) +' + +# rename/add, where add is a mode conflict +# Commit O: foo +# Commit A: modify foo, add symlink bar +# Commit B: modify & rename foo->bar +# Expected: CONFLICT(distinct modes) for bar + +test_expect_success SYMLINKS 'rename/add, where add is a mode conflict' ' + # Setup + git init rename-add-symlink && + ( + cd rename-add-symlink && + test_write_lines original 1 2 3 4 5 >foo && + git add foo && + git commit -m "original" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 >foo && + ln -s foo bar && + git add foo bar && + git commit -m "Modify foo, add symlink bar" && + + git checkout B && + test_write_lines original 1 2 3 4 5 6 >foo && + git add foo && + git mv foo bar && + git commit -m "rename foo to bar" + ) && + # Testing + ( + cd rename-add-symlink && + + test_expect_code 1 \ + git merge-tree -z A^0 B^0 >out && + echo >>out && + + # + # First, check that the bar that appears at stage 3 does not + # correspond to an individual blob anywhere in history + # + hash=$(cat out | tr "\0" "\n" | head -n 3 | grep 3.bar | cut -f 2 -d " ") && + git rev-list --objects --all >all_blobs && + ! grep $hash all_blobs && + + # + # Second, check anonymized hash output against expectation + # + anonymize_hash out >actual && + q_to_tab <<-\EOF | lf_to_nul >expect && + HASH + 120000 HASH 2Qbar + 100644 HASH 3Qbar~B^0 + EOF + + q_to_nul <<-EOF >>expect && + Q2QbarQbar~B^0QCONFLICT (distinct modes)QCONFLICT (distinct types): bar had different types on each side; renamed one of them so each can be recorded somewhere. + Q1QfooQAuto-mergingQAuto-merging foo + Q + EOF + test_cmp expect actual + ) +' + +# rename/rename(1to2) + content conflict handling +# Commit O: foo +# Commit A: modify foo & rename to bar +# Commit B: modify foo & rename to baz +# Expected: CONFLICT(rename/rename) + +test_expect_success 'rename/rename + content conflict' ' + # Setup + git init rr-plus-content && + ( + cd rr-plus-content && + test_write_lines 1 2 3 4 5 >foo && + git add foo && + git commit -m "original" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 six >foo && + git add foo && + git mv foo bar && + git commit -m "Modify foo + rename to bar" && + + git checkout B && + test_write_lines 1 2 3 4 5 6 >foo && + git add foo && + git mv foo baz && + git commit -m "Modify foo + rename to baz" + ) && + # Testing + ( + cd rr-plus-content && + + test_expect_code 1 \ + git merge-tree -z A^0 B^0 >out && + echo >>out && + anonymize_hash out >actual && + q_to_tab <<-\EOF | lf_to_nul >expect && + HASH + 100644 HASH 2Qbar + 100644 HASH 3Qbaz + 100644 HASH 1Qfoo + EOF + + q_to_nul <<-EOF >>expect && + Q1QfooQAuto-mergingQAuto-merging foo + Q3QfooQbarQbazQCONFLICT (rename/rename)QCONFLICT (rename/rename): foo renamed to bar in A^0 and to baz in B^0. + Q + EOF + test_cmp expect actual + ) +' + +# rename/add/delete +# Commit O: foo +# Commit A: rm foo, add different bar +# Commit B: rename foo->bar +# Expected: CONFLICT (rename/delete), CONFLICT(add/add) [via rename collide] +# for bar + +test_expect_success 'rename/add/delete conflict' ' + # Setup + git init rad && + ( + cd rad && + echo "original file" >foo && + git add foo && + git commit -m "original" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm foo && + echo "different file" >bar && + git add bar && + git commit -m "Remove foo, add bar" && + + git checkout B && + git mv foo bar && + git commit -m "rename foo to bar" + ) && + # Testing + ( + cd rad && + + test_expect_code 1 \ + git merge-tree -z B^0 A^0 >out && + echo >>out && + anonymize_hash out >actual && + + q_to_tab <<-\EOF | lf_to_nul >expect && + HASH + 100644 HASH 2Qbar + 100644 HASH 3Qbar + + EOF + + q_to_nul <<-EOF >>expect && + 2QbarQfooQCONFLICT (rename/delete)QCONFLICT (rename/delete): foo renamed to bar in B^0, but deleted in A^0. + Q1QbarQAuto-mergingQAuto-merging bar + Q1QbarQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in bar + Q + EOF + test_cmp expect actual + ) +' + +# rename/rename(2to1)/delete/delete +# Commit O: foo, bar +# Commit A: rename foo->baz, rm bar +# Commit B: rename bar->baz, rm foo +# Expected: 2x CONFLICT (rename/delete), CONFLICT (add/add) via colliding +# renames for baz + +test_expect_success 'rename/rename(2to1)/delete/delete conflict' ' + # Setup + git init rrdd && + ( + cd rrdd && + echo foo >foo && + echo bar >bar && + git add foo bar && + git commit -m O && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv foo baz && + git rm bar && + git commit -m "Rename foo, remove bar" && + + git checkout B && + git mv bar baz && + git rm foo && + git commit -m "Rename bar, remove foo" + ) && + # Testing + ( + cd rrdd && + + test_expect_code 1 \ + git merge-tree -z A^0 B^0 >out && + echo >>out && + anonymize_hash out >actual && + + q_to_tab <<-\EOF | lf_to_nul >expect && + HASH + 100644 HASH 2Qbaz + 100644 HASH 3Qbaz + + EOF + + q_to_nul <<-EOF >>expect && + 2QbazQbarQCONFLICT (rename/delete)QCONFLICT (rename/delete): bar renamed to baz in B^0, but deleted in A^0. + Q2QbazQfooQCONFLICT (rename/delete)QCONFLICT (rename/delete): foo renamed to baz in A^0, but deleted in B^0. + Q1QbazQAuto-mergingQAuto-merging baz + Q1QbazQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in baz + Q + EOF + test_cmp expect actual + ) +' + +# mod6: chains of rename/rename(1to2) + add/add via colliding renames +# Commit O: one, three, five +# Commit A: one->two, three->four, five->six +# Commit B: one->six, three->two, five->four +# Expected: three CONFLICT(rename/rename) messages + three CONFLICT(add/add) +# messages; each path in two of the multi-way merged contents +# found in two, four, six + +test_expect_success 'mod6: chains of rename/rename(1to2) and add/add via colliding renames' ' + # Setup + git init mod6 && + ( + cd mod6 && + test_seq 11 19 >one && + test_seq 31 39 >three && + test_seq 51 59 >five && + git add . && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 10 19 >one && + echo 40 >>three && + git add one three && + git mv one two && + git mv three four && + git mv five six && + test_tick && + git commit -m "A" && + + git checkout B && + echo 20 >>one && + echo forty >>three && + echo 60 >>five && + git add one three five && + git mv one six && + git mv three two && + git mv five four && + test_tick && + git commit -m "B" + ) && + # Testing + ( + cd mod6 && + + test_expect_code 1 \ + git merge-tree -z A^0 B^0 >out && + echo >>out && + + # + # First, check that some of the hashes that appear as stage + # conflict entries do not appear as individual blobs anywhere + # in history. + # + hash1=$(cat out | tr "\0" "\n" | head | grep 2.four | cut -f 2 -d " ") && + hash2=$(cat out | tr "\0" "\n" | head | grep 3.two | cut -f 2 -d " ") && + git rev-list --objects --all >all_blobs && + ! grep $hash1 all_blobs && + ! grep $hash2 all_blobs && + + # + # Now compare anonymized hash output with expectation + # + anonymize_hash out >actual && + q_to_tab <<-\EOF | lf_to_nul >expect && + HASH + 100644 HASH 1Qfive + 100644 HASH 2Qfour + 100644 HASH 3Qfour + 100644 HASH 1Qone + 100644 HASH 2Qsix + 100644 HASH 3Qsix + 100644 HASH 1Qthree + 100644 HASH 2Qtwo + 100644 HASH 3Qtwo + + EOF + + q_to_nul <<-EOF >>expect && + 3QfiveQsixQfourQCONFLICT (rename/rename)QCONFLICT (rename/rename): five renamed to six in A^0 and to four in B^0. + Q1QfourQAuto-mergingQAuto-merging four + Q1QfourQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in four + Q1QoneQAuto-mergingQAuto-merging one + Q3QoneQtwoQsixQCONFLICT (rename/rename)QCONFLICT (rename/rename): one renamed to two in A^0 and to six in B^0. + Q1QsixQAuto-mergingQAuto-merging six + Q1QsixQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in six + Q1QthreeQAuto-mergingQAuto-merging three + Q3QthreeQfourQtwoQCONFLICT (rename/rename)QCONFLICT (rename/rename): three renamed to four in A^0 and to two in B^0. + Q1QtwoQAuto-mergingQAuto-merging two + Q1QtwoQCONFLICT (contents)QCONFLICT (add/add): Merge conflict in two + Q + EOF + test_cmp expect actual + ) +' + +# directory rename + rename/delete + modify/delete + directory/file conflict +# Commit O: foo, olddir/{a,b,c} +# Commit A: delete foo, rename olddir/ -> newdir/, add newdir/bar/file +# Commit B: modify foo & rename foo -> olddir/bar +# Expected: CONFLICT(content) for for newdir/bar (not olddir/bar or foo) + +test_expect_success 'directory rename + rename/delete + modify/delete + directory/file conflict' ' + # Setup + git init 4-stacked-conflict && + ( + cd 4-stacked-conflict && + test_write_lines 1 2 3 4 5 >foo && + mkdir olddir && + for i in a b c; do echo $i >olddir/$i || exit 1; done && + git add foo olddir && + git commit -m "original" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm foo && + git mv olddir newdir && + mkdir newdir/bar && + >newdir/bar/file && + git add newdir/bar/file && + git commit -m "rm foo, olddir/ -> newdir/, + newdir/bar/file" && + + git checkout B && + test_write_lines 1 2 3 4 5 6 >foo && + git add foo && + git mv foo olddir/bar && + git commit -m "Modify foo & rename foo -> olddir/bar" + ) && + # Testing + ( + cd 4-stacked-conflict && + + test_expect_code 1 \ + git merge-tree -z A^0 B^0 >out && + echo >>out && + anonymize_hash out >actual && + + q_to_tab <<-\EOF | lf_to_nul >expect && + HASH + 100644 HASH 1Qnewdir/bar~B^0 + 100644 HASH 3Qnewdir/bar~B^0 + EOF + + q_to_nul <<-EOF >>expect && + Q2Qnewdir/barQolddir/barQCONFLICT (directory rename suggested)QCONFLICT (file location): foo renamed to olddir/bar in B^0, inside a directory that was renamed in A^0, suggesting it should perhaps be moved to newdir/bar. + Q2Qnewdir/barQfooQCONFLICT (rename/delete)QCONFLICT (rename/delete): foo renamed to newdir/bar in B^0, but deleted in A^0. + Q2Qnewdir/bar~B^0Qnewdir/barQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of newdir/bar from B^0; moving it to newdir/bar~B^0 instead. + Q1Qnewdir/bar~B^0QCONFLICT (modify/delete)QCONFLICT (modify/delete): newdir/bar~B^0 deleted in A^0 and modified in B^0. Version B^0 of newdir/bar~B^0 left in tree. + Q + EOF + test_cmp expect actual + ) +' + for opt in $(git merge-tree --git-completion-helper-all) do if test $opt = "--trivial-merge" || test $opt = "--write-tree" @@ -187,8 +760,8 @@ test_expect_success 'NUL terminated conflicted file "lines"' ' git commit -m "Renamed numbers" && test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out && + echo >>out && anonymize_hash out >actual && - printf "\\n" >>actual && # Expected results: # "greeting" should merge with conflicts @@ -237,4 +810,54 @@ test_expect_success 'can override merge of unrelated histories' ' test_cmp expect actual ' +test_expect_success SANITY 'merge-ort fails gracefully in a read-only repository' ' + git init --bare read-only && + git push read-only side1 side2 side3 && + test_when_finished "chmod -R u+w read-only" && + chmod -R a-w read-only && + test_must_fail git -C read-only merge-tree side1 side3 && + test_must_fail git -C read-only merge-tree side1 side2 +' + +test_expect_success '--stdin with both a successful and a conflicted merge' ' + printf "side1 side3\nside1 side2" | git merge-tree --stdin >actual && + + git checkout side1^0 && + git merge side3 && + + printf "1\0" >expect && + git rev-parse HEAD^{tree} | lf_to_nul >>expect && + printf "\0" >>expect && + + git checkout side1^0 && + test_must_fail git merge side2 && + sed s/HEAD/side1/ greeting >tmp && + mv tmp greeting && + git add -u && + git mv whatever~HEAD whatever~side1 && + + printf "0\0" >>expect && + git write-tree | lf_to_nul >>expect && + + cat <<-EOF | q_to_tab | lf_to_nul >>expect && + 100644 $(git rev-parse side1~1:greeting) 1Qgreeting + 100644 $(git rev-parse side1:greeting) 2Qgreeting + 100644 $(git rev-parse side2:greeting) 3Qgreeting + 100644 $(git rev-parse side1~1:whatever) 1Qwhatever~side1 + 100644 $(git rev-parse side1:whatever) 2Qwhatever~side1 + EOF + + q_to_nul <<-EOF >>expect && + Q1QgreetingQAuto-mergingQAuto-merging greeting + Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting + Q1QnumbersQAuto-mergingQAuto-merging numbers + Q2Qwhatever~side1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead. + Q1Qwhatever~side1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree. + EOF + + printf "\0\0" >>expect && + + test_cmp expect actual +' + test_done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index cebad1048c..db11cababd 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -201,13 +201,13 @@ test_expect_success 'mailinfo -b double [PATCH]' ' test z"$subj" = z"Subject: message" ' -test_expect_failure 'mailinfo -b trailing [PATCH]' ' +test_expect_success 'mailinfo -b trailing [PATCH]' ' subj="$(echo "Subject: [other] [PATCH] message" | git mailinfo -b /dev/null /dev/null)" && test z"$subj" = z"Subject: [other] message" ' -test_expect_failure 'mailinfo -b separated double [PATCH]' ' +test_expect_success 'mailinfo -b separated double [PATCH]' ' subj="$(echo "Subject: [PATCH] [other] [PATCH] message" | git mailinfo -b /dev/null /dev/null)" && test z"$subj" = z"Subject: [other] message" diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index f775fc1ce6..6d693eef82 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -26,22 +26,415 @@ has_any () { grep -Ff "$1" "$2" } -setup_bitmap_history - -test_expect_success 'setup writing bitmaps during repack' ' - git config repack.writeBitmaps true -' - -test_expect_success 'full repack creates bitmaps' ' - GIT_TRACE2_EVENT="$(pwd)/trace" \ +test_bitmap_cases () { + writeLookupTable=false + for i in "$@" + do + case "$i" in + "pack.writeBitmapLookupTable") writeLookupTable=true;; + esac + done + + test_expect_success 'setup test repository' ' + rm -fr * .git && + git init && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' + ' + setup_bitmap_history + + test_expect_success 'setup writing bitmaps during repack' ' + git config repack.writeBitmaps true + ' + + test_expect_success 'full repack creates bitmaps' ' + GIT_TRACE2_EVENT="$(pwd)/trace" \ + git repack -ad && + ls .git/objects/pack/ | grep bitmap >output && + test_line_count = 1 output && + grep "\"key\":\"num_selected_commits\",\"value\":\"106\"" trace && + grep "\"key\":\"num_maximal_commits\",\"value\":\"107\"" trace + ' + + basic_bitmap_tests + + test_expect_success 'pack-objects respects --local (non-local loose)' ' + git init --bare alt.git && + echo $(pwd)/alt.git/objects >.git/objects/info/alternates && + echo content1 >file1 && + # non-local loose object which is not present in bitmapped pack + altblob=$(GIT_DIR=alt.git git hash-object -w file1) && + # non-local loose object which is also present in bitmapped pack + git cat-file blob $blob | GIT_DIR=alt.git git hash-object -w --stdin && + git add file1 && + test_tick && + git commit -m commit_file1 && + echo HEAD | git pack-objects --local --stdout --revs >1.pack && + git index-pack 1.pack && + list_packed_objects 1.idx >1.objects && + printf "%s\n" "$altblob" "$blob" >nonlocal-loose && + ! has_any nonlocal-loose 1.objects + ' + + test_expect_success 'pack-objects respects --honor-pack-keep (local non-bitmapped pack)' ' + echo content2 >file2 && + blob2=$(git hash-object -w file2) && + git add file2 && + test_tick && + git commit -m commit_file2 && + printf "%s\n" "$blob2" "$bitmaptip" >keepobjects && + pack2=$(git pack-objects pack2 <keepobjects) && + mv pack2-$pack2.* .git/objects/pack/ && + >.git/objects/pack/pack2-$pack2.keep && + rm $(objpath $blob2) && + echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >2a.pack && + git index-pack 2a.pack && + list_packed_objects 2a.idx >2a.objects && + ! has_any keepobjects 2a.objects + ' + + test_expect_success 'pack-objects respects --local (non-local pack)' ' + mv .git/objects/pack/pack2-$pack2.* alt.git/objects/pack/ && + echo HEAD | git pack-objects --local --stdout --revs >2b.pack && + git index-pack 2b.pack && + list_packed_objects 2b.idx >2b.objects && + ! has_any keepobjects 2b.objects + ' + + test_expect_success 'pack-objects respects --honor-pack-keep (local bitmapped pack)' ' + ls .git/objects/pack/ | grep bitmap >output && + test_line_count = 1 output && + packbitmap=$(basename $(cat output) .bitmap) && + list_packed_objects .git/objects/pack/$packbitmap.idx >packbitmap.objects && + test_when_finished "rm -f .git/objects/pack/$packbitmap.keep" && + >.git/objects/pack/$packbitmap.keep && + echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >3a.pack && + git index-pack 3a.pack && + list_packed_objects 3a.idx >3a.objects && + ! has_any packbitmap.objects 3a.objects + ' + + test_expect_success 'pack-objects respects --local (non-local bitmapped pack)' ' + mv .git/objects/pack/$packbitmap.* alt.git/objects/pack/ && + rm -f .git/objects/pack/multi-pack-index && + test_when_finished "mv alt.git/objects/pack/$packbitmap.* .git/objects/pack/" && + echo HEAD | git pack-objects --local --stdout --revs >3b.pack && + git index-pack 3b.pack && + list_packed_objects 3b.idx >3b.objects && + ! has_any packbitmap.objects 3b.objects + ' + + test_expect_success 'pack-objects to file can use bitmap' ' + # make sure we still have 1 bitmap index from previous tests + ls .git/objects/pack/ | grep bitmap >output && + test_line_count = 1 output && + # verify equivalent packs are generated with/without using bitmap index + packasha1=$(git pack-objects --no-use-bitmap-index --all packa </dev/null) && + packbsha1=$(git pack-objects --use-bitmap-index --all packb </dev/null) && + list_packed_objects packa-$packasha1.idx >packa.objects && + list_packed_objects packb-$packbsha1.idx >packb.objects && + test_cmp packa.objects packb.objects + ' + + test_expect_success 'full repack, reusing previous bitmaps' ' git repack -ad && - ls .git/objects/pack/ | grep bitmap >output && - test_line_count = 1 output && - grep "\"key\":\"num_selected_commits\",\"value\":\"106\"" trace && - grep "\"key\":\"num_maximal_commits\",\"value\":\"107\"" trace -' + ls .git/objects/pack/ | grep bitmap >output && + test_line_count = 1 output + ' + + test_expect_success 'fetch (full bitmap)' ' + git --git-dir=clone.git fetch origin second:second && + git rev-parse HEAD >expect && + git --git-dir=clone.git rev-parse HEAD >actual && + test_cmp expect actual + ' + + test_expect_success 'create objects for missing-HAVE tests' ' + blob=$(echo "missing have" | git hash-object -w --stdin) && + tree=$(printf "100644 blob $blob\tfile\n" | git mktree) && + parent=$(echo parent | git commit-tree $tree) && + commit=$(echo commit | git commit-tree $tree -p $parent) && + cat >revs <<-EOF + HEAD + ^HEAD^ + ^$commit + EOF + ' + + test_expect_success 'pack-objects respects --incremental' ' + cat >revs2 <<-EOF && + HEAD + $commit + EOF + git pack-objects --incremental --stdout --revs <revs2 >4.pack && + git index-pack 4.pack && + list_packed_objects 4.idx >4.objects && + test_line_count = 4 4.objects && + git rev-list --objects $commit >revlist && + cut -d" " -f1 revlist |sort >objects && + test_cmp 4.objects objects + ' + + test_expect_success 'pack with missing blob' ' + rm $(objpath $blob) && + git pack-objects --stdout --revs <revs >/dev/null + ' + + test_expect_success 'pack with missing tree' ' + rm $(objpath $tree) && + git pack-objects --stdout --revs <revs >/dev/null + ' + + test_expect_success 'pack with missing parent' ' + rm $(objpath $parent) && + git pack-objects --stdout --revs <revs >/dev/null + ' + + test_expect_success JGIT,SHA1 'we can read jgit bitmaps' ' + git clone --bare . compat-jgit.git && + ( + cd compat-jgit.git && + rm -f objects/pack/*.bitmap && + jgit gc && + git rev-list --test-bitmap HEAD + ) + ' + + test_expect_success JGIT,SHA1 'jgit can read our bitmaps' ' + git clone --bare . compat-us.git && + ( + cd compat-us.git && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && + git repack -adb && + # jgit gc will barf if it does not like our bitmaps + jgit gc + ) + ' + + test_expect_success 'splitting packs does not generate bogus bitmaps' ' + test-tool genrandom foo $((1024 * 1024)) >rand && + git add rand && + git commit -m "commit with big file" && + git -c pack.packSizeLimit=500k repack -adb && + git init --bare no-bitmaps.git && + git -C no-bitmaps.git fetch .. HEAD + ' + + test_expect_success 'set up reusable pack' ' + rm -f .git/objects/pack/*.keep && + git repack -adb && + reusable_pack () { + git for-each-ref --format="%(objectname)" | + git pack-objects --delta-base-offset --revs --stdout "$@" + } + ' + + test_expect_success 'pack reuse respects --honor-pack-keep' ' + test_when_finished "rm -f .git/objects/pack/*.keep" && + for i in .git/objects/pack/*.pack + do + >${i%.pack}.keep || return 1 + done && + reusable_pack --honor-pack-keep >empty.pack && + git index-pack empty.pack && + git show-index <empty.idx >actual && + test_must_be_empty actual + ' + + test_expect_success 'pack reuse respects --local' ' + mv .git/objects/pack/* alt.git/objects/pack/ && + test_when_finished "mv alt.git/objects/pack/* .git/objects/pack/" && + reusable_pack --local >empty.pack && + git index-pack empty.pack && + git show-index <empty.idx >actual && + test_must_be_empty actual + ' + + test_expect_success 'pack reuse respects --incremental' ' + reusable_pack --incremental >empty.pack && + git index-pack empty.pack && + git show-index <empty.idx >actual && + test_must_be_empty actual + ' + + test_expect_success 'truncated bitmap fails gracefully (ewah)' ' + test_config pack.writebitmaphashcache false && + test_config pack.writebitmaplookuptable false && + git repack -ad && + git rev-list --use-bitmap-index --count --all >expect && + bitmap=$(ls .git/objects/pack/*.bitmap) && + test_when_finished "rm -f $bitmap" && + test_copy_bytes 256 <$bitmap >$bitmap.tmp && + mv -f $bitmap.tmp $bitmap && + git rev-list --use-bitmap-index --count --all >actual 2>stderr && + test_cmp expect actual && + test_i18ngrep corrupt.ewah.bitmap stderr + ' + + test_expect_success 'truncated bitmap fails gracefully (cache)' ' + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && + git repack -ad && + git rev-list --use-bitmap-index --count --all >expect && + bitmap=$(ls .git/objects/pack/*.bitmap) && + test_when_finished "rm -f $bitmap" && + test_copy_bytes 512 <$bitmap >$bitmap.tmp && + mv -f $bitmap.tmp $bitmap && + git rev-list --use-bitmap-index --count --all >actual 2>stderr && + test_cmp expect actual && + test_i18ngrep corrupted.bitmap.index stderr + ' + + # Create a state of history with these properties: + # + # - refs that allow a client to fetch some new history, while sharing some old + # history with the server; we use branches delta-reuse-old and + # delta-reuse-new here + # + # - the new history contains an object that is stored on the server as a delta + # against a base that is in the old history + # + # - the base object is not immediately reachable from the tip of the old + # history; finding it would involve digging down through history we know the + # other side has + # + # This should result in a state where fetching from old->new would not + # traditionally reuse the on-disk delta (because we'd have to dig to realize + # that the client has it), but we will do so if bitmaps can tell us cheaply + # that the other side has it. + test_expect_success 'set up thin delta-reuse parent' ' + # This first commit contains the buried base object. + test-tool genrandom delta 16384 >file && + git add file && + git commit -m "delta base" && + base=$(git rev-parse --verify HEAD:file) && + + # These intermediate commits bury the base back in history. + # This becomes the "old" state. + for i in 1 2 3 4 5 + do + echo $i >file && + git commit -am "intermediate $i" || return 1 + done && + git branch delta-reuse-old && + + # And now our new history has a delta against the buried base. Note + # that this must be smaller than the original file, since pack-objects + # prefers to create deltas from smaller objects to larger. + test-tool genrandom delta 16300 >file && + git commit -am "delta result" && + delta=$(git rev-parse --verify HEAD:file) && + git branch delta-reuse-new && + + # Repack with bitmaps and double check that we have the expected delta + # relationship. + git repack -adb && + have_delta $delta $base + ' + + # Now we can sanity-check the non-bitmap behavior (that the server is not able + # to reuse the delta). This isn't strictly something we care about, so this + # test could be scrapped in the future. But it makes sure that the next test is + # actually triggering the feature we want. + # + # Note that our tools for working with on-the-wire "thin" packs are limited. So + # we actually perform the fetch, retain the resulting pack, and inspect the + # result. + test_expect_success 'fetch without bitmaps ignores delta against old base' ' + test_config pack.usebitmaps false && + test_when_finished "rm -rf client.git" && + git init --bare client.git && + ( + cd client.git && + git config transfer.unpackLimit 1 && + git fetch .. delta-reuse-old:delta-reuse-old && + git fetch .. delta-reuse-new:delta-reuse-new && + have_delta $delta $ZERO_OID + ) + ' + + # And do the same for the bitmap case, where we do expect to find the delta. + test_expect_success 'fetch with bitmaps can reuse old base' ' + test_config pack.usebitmaps true && + test_when_finished "rm -rf client.git" && + git init --bare client.git && + ( + cd client.git && + git config transfer.unpackLimit 1 && + git fetch .. delta-reuse-old:delta-reuse-old && + git fetch .. delta-reuse-new:delta-reuse-new && + have_delta $delta $base + ) + ' + + test_expect_success 'pack.preferBitmapTips' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && + + # create enough commits that not all are receive bitmap + # coverage even if they are all at the tip of some reference. + test_commit_bulk --message="%s" 103 && + + git rev-list HEAD >commits.raw && + sort <commits.raw >commits && + + git log --format="create refs/tags/%s %H" HEAD >refs && + git update-ref --stdin <refs && + + git repack -adb && + test-tool bitmap list-commits | sort >bitmaps && + + # remember which commits did not receive bitmaps + comm -13 bitmaps commits >before && + test_file_not_empty before && + + # mark the commits which did not receive bitmaps as preferred, + # and generate the bitmap again + perl -pe "s{^}{create refs/tags/include/$. }" <before | + git update-ref --stdin && + git -c pack.preferBitmapTips=refs/tags/include repack -adb && + + # finally, check that the commit(s) without bitmap coverage + # are not the same ones as before + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >after && + + ! test_cmp before after + ) + ' + + test_expect_success 'complains about multiple pack bitmaps' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && + + test_commit base && + + git repack -adb && + bitmap="$(ls .git/objects/pack/pack-*.bitmap)" && + mv "$bitmap" "$bitmap.bak" && + + test_commit other && + git repack -ab && + + mv "$bitmap.bak" "$bitmap" && + + find .git/objects/pack -type f -name "*.pack" >packs && + find .git/objects/pack -type f -name "*.bitmap" >bitmaps && + test_line_count = 2 packs && + test_line_count = 2 bitmaps && + + git rev-list --use-bitmap-index HEAD 2>err && + grep "ignoring extra bitmap file" err + ) + ' +} -basic_bitmap_tests +test_bitmap_cases test_expect_success 'incremental repack fails when bitmaps are requested' ' test_commit more-1 && @@ -54,219 +447,17 @@ test_expect_success 'incremental repack can disable bitmaps' ' git repack -d --no-write-bitmap-index ' -test_expect_success 'pack-objects respects --local (non-local loose)' ' - git init --bare alt.git && - echo $(pwd)/alt.git/objects >.git/objects/info/alternates && - echo content1 >file1 && - # non-local loose object which is not present in bitmapped pack - altblob=$(GIT_DIR=alt.git git hash-object -w file1) && - # non-local loose object which is also present in bitmapped pack - git cat-file blob $blob | GIT_DIR=alt.git git hash-object -w --stdin && - git add file1 && - test_tick && - git commit -m commit_file1 && - echo HEAD | git pack-objects --local --stdout --revs >1.pack && - git index-pack 1.pack && - list_packed_objects 1.idx >1.objects && - printf "%s\n" "$altblob" "$blob" >nonlocal-loose && - ! has_any nonlocal-loose 1.objects -' - -test_expect_success 'pack-objects respects --honor-pack-keep (local non-bitmapped pack)' ' - echo content2 >file2 && - blob2=$(git hash-object -w file2) && - git add file2 && - test_tick && - git commit -m commit_file2 && - printf "%s\n" "$blob2" "$bitmaptip" >keepobjects && - pack2=$(git pack-objects pack2 <keepobjects) && - mv pack2-$pack2.* .git/objects/pack/ && - >.git/objects/pack/pack2-$pack2.keep && - rm $(objpath $blob2) && - echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >2a.pack && - git index-pack 2a.pack && - list_packed_objects 2a.idx >2a.objects && - ! has_any keepobjects 2a.objects -' - -test_expect_success 'pack-objects respects --local (non-local pack)' ' - mv .git/objects/pack/pack2-$pack2.* alt.git/objects/pack/ && - echo HEAD | git pack-objects --local --stdout --revs >2b.pack && - git index-pack 2b.pack && - list_packed_objects 2b.idx >2b.objects && - ! has_any keepobjects 2b.objects -' - -test_expect_success 'pack-objects respects --honor-pack-keep (local bitmapped pack)' ' - ls .git/objects/pack/ | grep bitmap >output && - test_line_count = 1 output && - packbitmap=$(basename $(cat output) .bitmap) && - list_packed_objects .git/objects/pack/$packbitmap.idx >packbitmap.objects && - test_when_finished "rm -f .git/objects/pack/$packbitmap.keep" && - >.git/objects/pack/$packbitmap.keep && - echo HEAD | git pack-objects --honor-pack-keep --stdout --revs >3a.pack && - git index-pack 3a.pack && - list_packed_objects 3a.idx >3a.objects && - ! has_any packbitmap.objects 3a.objects -' - -test_expect_success 'pack-objects respects --local (non-local bitmapped pack)' ' - mv .git/objects/pack/$packbitmap.* alt.git/objects/pack/ && - rm -f .git/objects/pack/multi-pack-index && - test_when_finished "mv alt.git/objects/pack/$packbitmap.* .git/objects/pack/" && - echo HEAD | git pack-objects --local --stdout --revs >3b.pack && - git index-pack 3b.pack && - list_packed_objects 3b.idx >3b.objects && - ! has_any packbitmap.objects 3b.objects -' - -test_expect_success 'pack-objects to file can use bitmap' ' - # make sure we still have 1 bitmap index from previous tests - ls .git/objects/pack/ | grep bitmap >output && - test_line_count = 1 output && - # verify equivalent packs are generated with/without using bitmap index - packasha1=$(git pack-objects --no-use-bitmap-index --all packa </dev/null) && - packbsha1=$(git pack-objects --use-bitmap-index --all packb </dev/null) && - list_packed_objects packa-$packasha1.idx >packa.objects && - list_packed_objects packb-$packbsha1.idx >packb.objects && - test_cmp packa.objects packb.objects -' - -test_expect_success 'full repack, reusing previous bitmaps' ' - git repack -ad && - ls .git/objects/pack/ | grep bitmap >output && - test_line_count = 1 output -' - -test_expect_success 'fetch (full bitmap)' ' - git --git-dir=clone.git fetch origin second:second && - git rev-parse HEAD >expect && - git --git-dir=clone.git rev-parse HEAD >actual && - test_cmp expect actual -' - -test_expect_success 'create objects for missing-HAVE tests' ' - blob=$(echo "missing have" | git hash-object -w --stdin) && - tree=$(printf "100644 blob $blob\tfile\n" | git mktree) && - parent=$(echo parent | git commit-tree $tree) && - commit=$(echo commit | git commit-tree $tree -p $parent) && - cat >revs <<-EOF - HEAD - ^HEAD^ - ^$commit - EOF -' - -test_expect_success 'pack-objects respects --incremental' ' - cat >revs2 <<-EOF && - HEAD - $commit - EOF - git pack-objects --incremental --stdout --revs <revs2 >4.pack && - git index-pack 4.pack && - list_packed_objects 4.idx >4.objects && - test_line_count = 4 4.objects && - git rev-list --objects $commit >revlist && - cut -d" " -f1 revlist |sort >objects && - test_cmp 4.objects objects -' - -test_expect_success 'pack with missing blob' ' - rm $(objpath $blob) && - git pack-objects --stdout --revs <revs >/dev/null -' - -test_expect_success 'pack with missing tree' ' - rm $(objpath $tree) && - git pack-objects --stdout --revs <revs >/dev/null -' - -test_expect_success 'pack with missing parent' ' - rm $(objpath $parent) && - git pack-objects --stdout --revs <revs >/dev/null -' - -test_expect_success JGIT,SHA1 'we can read jgit bitmaps' ' - git clone --bare . compat-jgit.git && - ( - cd compat-jgit.git && - rm -f objects/pack/*.bitmap && - jgit gc && - git rev-list --test-bitmap HEAD - ) -' - -test_expect_success JGIT,SHA1 'jgit can read our bitmaps' ' - git clone --bare . compat-us.git && - ( - cd compat-us.git && - git repack -adb && - # jgit gc will barf if it does not like our bitmaps - jgit gc - ) -' - -test_expect_success 'splitting packs does not generate bogus bitmaps' ' - test-tool genrandom foo $((1024 * 1024)) >rand && - git add rand && - git commit -m "commit with big file" && - git -c pack.packSizeLimit=500k repack -adb && - git init --bare no-bitmaps.git && - git -C no-bitmaps.git fetch .. HEAD -' - -test_expect_success 'set up reusable pack' ' - rm -f .git/objects/pack/*.keep && - git repack -adb && - reusable_pack () { - git for-each-ref --format="%(objectname)" | - git pack-objects --delta-base-offset --revs --stdout "$@" - } -' - -test_expect_success 'pack reuse respects --honor-pack-keep' ' - test_when_finished "rm -f .git/objects/pack/*.keep" && - for i in .git/objects/pack/*.pack - do - >${i%.pack}.keep || return 1 - done && - reusable_pack --honor-pack-keep >empty.pack && - git index-pack empty.pack && - git show-index <empty.idx >actual && - test_must_be_empty actual -' - -test_expect_success 'pack reuse respects --local' ' - mv .git/objects/pack/* alt.git/objects/pack/ && - test_when_finished "mv alt.git/objects/pack/* .git/objects/pack/" && - reusable_pack --local >empty.pack && - git index-pack empty.pack && - git show-index <empty.idx >actual && - test_must_be_empty actual -' +test_bitmap_cases "pack.writeBitmapLookupTable" -test_expect_success 'pack reuse respects --incremental' ' - reusable_pack --incremental >empty.pack && - git index-pack empty.pack && - git show-index <empty.idx >actual && - test_must_be_empty actual +test_expect_success 'verify writing bitmap lookup table when enabled' ' + GIT_TRACE2_EVENT="$(pwd)/trace2" \ + git repack -ad && + grep "\"label\":\"writing_lookup_table\"" trace2 ' -test_expect_success 'truncated bitmap fails gracefully (ewah)' ' +test_expect_success 'truncated bitmap fails gracefully (lookup table)' ' test_config pack.writebitmaphashcache false && - git repack -ad && - git rev-list --use-bitmap-index --count --all >expect && - bitmap=$(ls .git/objects/pack/*.bitmap) && - test_when_finished "rm -f $bitmap" && - test_copy_bytes 256 <$bitmap >$bitmap.tmp && - mv -f $bitmap.tmp $bitmap && - git rev-list --use-bitmap-index --count --all >actual 2>stderr && - test_cmp expect actual && - test_i18ngrep corrupt.ewah.bitmap stderr -' - -test_expect_success 'truncated bitmap fails gracefully (cache)' ' - git repack -ad && + git repack -adb && git rev-list --use-bitmap-index --count --all >expect && bitmap=$(ls .git/objects/pack/*.bitmap) && test_when_finished "rm -f $bitmap" && @@ -277,152 +468,4 @@ test_expect_success 'truncated bitmap fails gracefully (cache)' ' test_i18ngrep corrupted.bitmap.index stderr ' -# Create a state of history with these properties: -# -# - refs that allow a client to fetch some new history, while sharing some old -# history with the server; we use branches delta-reuse-old and -# delta-reuse-new here -# -# - the new history contains an object that is stored on the server as a delta -# against a base that is in the old history -# -# - the base object is not immediately reachable from the tip of the old -# history; finding it would involve digging down through history we know the -# other side has -# -# This should result in a state where fetching from old->new would not -# traditionally reuse the on-disk delta (because we'd have to dig to realize -# that the client has it), but we will do so if bitmaps can tell us cheaply -# that the other side has it. -test_expect_success 'set up thin delta-reuse parent' ' - # This first commit contains the buried base object. - test-tool genrandom delta 16384 >file && - git add file && - git commit -m "delta base" && - base=$(git rev-parse --verify HEAD:file) && - - # These intermediate commits bury the base back in history. - # This becomes the "old" state. - for i in 1 2 3 4 5 - do - echo $i >file && - git commit -am "intermediate $i" || return 1 - done && - git branch delta-reuse-old && - - # And now our new history has a delta against the buried base. Note - # that this must be smaller than the original file, since pack-objects - # prefers to create deltas from smaller objects to larger. - test-tool genrandom delta 16300 >file && - git commit -am "delta result" && - delta=$(git rev-parse --verify HEAD:file) && - git branch delta-reuse-new && - - # Repack with bitmaps and double check that we have the expected delta - # relationship. - git repack -adb && - have_delta $delta $base -' - -# Now we can sanity-check the non-bitmap behavior (that the server is not able -# to reuse the delta). This isn't strictly something we care about, so this -# test could be scrapped in the future. But it makes sure that the next test is -# actually triggering the feature we want. -# -# Note that our tools for working with on-the-wire "thin" packs are limited. So -# we actually perform the fetch, retain the resulting pack, and inspect the -# result. -test_expect_success 'fetch without bitmaps ignores delta against old base' ' - test_config pack.usebitmaps false && - test_when_finished "rm -rf client.git" && - git init --bare client.git && - ( - cd client.git && - git config transfer.unpackLimit 1 && - git fetch .. delta-reuse-old:delta-reuse-old && - git fetch .. delta-reuse-new:delta-reuse-new && - have_delta $delta $ZERO_OID - ) -' - -# And do the same for the bitmap case, where we do expect to find the delta. -test_expect_success 'fetch with bitmaps can reuse old base' ' - test_config pack.usebitmaps true && - test_when_finished "rm -rf client.git" && - git init --bare client.git && - ( - cd client.git && - git config transfer.unpackLimit 1 && - git fetch .. delta-reuse-old:delta-reuse-old && - git fetch .. delta-reuse-new:delta-reuse-new && - have_delta $delta $base - ) -' - -test_expect_success 'pack.preferBitmapTips' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - - # create enough commits that not all are receive bitmap - # coverage even if they are all at the tip of some reference. - test_commit_bulk --message="%s" 103 && - - git rev-list HEAD >commits.raw && - sort <commits.raw >commits && - - git log --format="create refs/tags/%s %H" HEAD >refs && - git update-ref --stdin <refs && - - git repack -adb && - test-tool bitmap list-commits | sort >bitmaps && - - # remember which commits did not receive bitmaps - comm -13 bitmaps commits >before && - test_file_not_empty before && - - # mark the commits which did not receive bitmaps as preferred, - # and generate the bitmap again - perl -pe "s{^}{create refs/tags/include/$. }" <before | - git update-ref --stdin && - git -c pack.preferBitmapTips=refs/tags/include repack -adb && - - # finally, check that the commit(s) without bitmap coverage - # are not the same ones as before - test-tool bitmap list-commits | sort >bitmaps && - comm -13 bitmaps commits >after && - - ! test_cmp before after - ) -' - -test_expect_success 'complains about multiple pack bitmaps' ' - rm -fr repo && - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - - test_commit base && - - git repack -adb && - bitmap="$(ls .git/objects/pack/pack-*.bitmap)" && - mv "$bitmap" "$bitmap.bak" && - - test_commit other && - git repack -ab && - - mv "$bitmap.bak" "$bitmap" && - - find .git/objects/pack -type f -name "*.pack" >packs && - find .git/objects/pack -type f -name "*.bitmap" >bitmaps && - test_line_count = 2 packs && - test_line_count = 2 bitmaps && - - git rev-list --use-bitmap-index HEAD 2>err && - grep "ignoring extra bitmap file" err - ) -' - test_done diff --git a/t/t5311-pack-bitmaps-shallow.sh b/t/t5311-pack-bitmaps-shallow.sh index 872a95df33..9dae60f73e 100755 --- a/t/t5311-pack-bitmaps-shallow.sh +++ b/t/t5311-pack-bitmaps-shallow.sh @@ -17,23 +17,40 @@ test_description='check bitmap operation with shallow repositories' # the tree for A. But in a shallow one, we've grafted away # A, and fetching A to B requires that the other side send # us the tree for file=1. -test_expect_success 'setup shallow repo' ' - echo 1 >file && - git add file && - git commit -m orig && - echo 2 >file && - git commit -a -m update && - git clone --no-local --bare --depth=1 . shallow.git && - echo 1 >file && - git commit -a -m repeat -' - -test_expect_success 'turn on bitmaps in the parent' ' - git repack -adb -' - -test_expect_success 'shallow fetch from bitmapped repo' ' - (cd shallow.git && git fetch) -' +test_shallow_bitmaps () { + writeLookupTable=false + + for i in "$@" + do + case $i in + "pack.writeBitmapLookupTable") writeLookupTable=true;; + esac + done + + test_expect_success 'setup shallow repo' ' + rm -rf * .git && + git init && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && + echo 1 >file && + git add file && + git commit -m orig && + echo 2 >file && + git commit -a -m update && + git clone --no-local --bare --depth=1 . shallow.git && + echo 1 >file && + git commit -a -m repeat + ' + + test_expect_success 'turn on bitmaps in the parent' ' + git repack -adb + ' + + test_expect_success 'shallow fetch from bitmapped repo' ' + (cd shallow.git && git fetch) + ' +} + +test_shallow_bitmaps +test_shallow_bitmaps "pack.writeBitmapLookupTable" test_done diff --git a/t/t5315-pack-objects-compression.sh b/t/t5315-pack-objects-compression.sh index 8bacd96275..c80ea9e8b7 100755 --- a/t/t5315-pack-objects-compression.sh +++ b/t/t5315-pack-objects-compression.sh @@ -2,6 +2,7 @@ test_description='pack-object compression configuration' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index 1b0cd82359..049c5fc8ea 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -12,12 +12,12 @@ test_expect_success 'usage' ' test_expect_success 'usage shown without sub-command' ' test_expect_code 129 git commit-graph 2>err && - ! grep error: err + grep usage: err ' test_expect_success 'usage shown with an error on unknown sub-command' ' cat >expect <<-\EOF && - error: unrecognized subcommand: unknown + error: unknown subcommand: `unknown'\'' EOF test_expect_code 129 git commit-graph unknown 2>stderr && grep error stderr >actual && diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index afbe93f162..b5f9b10922 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -784,6 +784,70 @@ test_expect_success 'repack creates a new pack' ' ) ' +test_expect_success 'repack (all) ignores cruft pack' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit base && + test_commit --no-tag unreachable && + + git reset --hard base && + git reflog expire --all --expire=all && + git repack --cruft -d && + + git multi-pack-index write && + + find $objdir/pack | sort >before && + git multi-pack-index repack --batch-size=0 && + find $objdir/pack | sort >after && + + test_cmp before after + ) +' + +test_expect_success 'repack (--batch-size) ignores cruft pack' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit_bulk 5 && + test_commit --no-tag unreachable && + + git reset --hard HEAD^ && + git reflog expire --all --expire=all && + git repack --cruft -d && + + test_commit four && + + find $objdir/pack -type f -name "*.pack" | sort >before && + git repack -d && + find $objdir/pack -type f -name "*.pack" | sort >after && + + pack="$(comm -13 before after)" && + test_file_size "$pack" >sz && + # Set --batch-size to twice the size of the pack created + # in the previous step, since this is enough to + # accommodate it and the cruft pack. + # + # This means that the MIDX machinery *could* combine the + # new and cruft packs together. + # + # We ensure that it does not below. + batch="$((($(cat sz) * 2)))" && + + git multi-pack-index write && + + find $objdir/pack | sort >before && + git multi-pack-index repack --batch-size=$batch && + find $objdir/pack | sort >after && + + test_cmp before after + ) +' + test_expect_success 'expire removes repacked packs' ' ( cd dup && @@ -847,6 +911,36 @@ test_expect_success 'expire respects .keep files' ' ) ' +test_expect_success 'expiring unreferenced cruft pack retains pack' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit base && + test_commit --no-tag unreachable && + unreachable=$(git rev-parse HEAD) && + + git reset --hard base && + git reflog expire --all --expire=all && + git repack --cruft -d && + mtimes="$(ls $objdir/pack/pack-*.mtimes)" && + + echo "base..$unreachable" >in && + pack="$(git pack-objects --revs --delta-base-offset \ + $objdir/pack/pack <in)" && + + # Preferring the contents of "$pack" will leave the + # cruft pack unreferenced (ie., none of the objects + # contained in the cruft pack will have their MIDX copy + # selected from the cruft pack). + git multi-pack-index write --preferred-pack="pack-$pack.pack" && + git multi-pack-index expire && + + test_path_is_file "$mtimes" + ) +' + test_expect_success 'repack --batch-size=0 repacks everything' ' cp -r dup dup2 && ( diff --git a/t/t5320-delta-islands.sh b/t/t5320-delta-islands.sh index 124d47603d..406363381f 100755 --- a/t/t5320-delta-islands.sh +++ b/t/t5320-delta-islands.sh @@ -134,7 +134,7 @@ test_expect_success 'island core places core objects first' ' repack -adfi && git verify-pack -v .git/objects/pack/*.pack | cut -d" " -f1 | - egrep "$root|$two" >actual && + grep -E "$root|$two" >actual && test_cmp expect actual ' diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh index 4fe57414c1..0882cbb6e4 100755 --- a/t/t5326-multi-pack-bitmaps.sh +++ b/t/t5326-multi-pack-bitmaps.sh @@ -15,17 +15,24 @@ GIT_TEST_MULTI_PACK_INDEX_WRITE_BITMAP=0 sane_unset GIT_TEST_MIDX_WRITE_REV sane_unset GIT_TEST_MIDX_READ_RIDX -midx_bitmap_core - bitmap_reuse_tests() { from=$1 to=$2 + writeLookupTable=false + + for i in $3-${$#} + do + case $i in + "pack.writeBitmapLookupTable") writeLookupTable=true;; + esac + done test_expect_success "setup pack reuse tests ($from -> $to)" ' rm -fr repo && git init repo && ( cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && test_commit_bulk 16 && git tag old-tip && @@ -43,6 +50,7 @@ bitmap_reuse_tests() { test_expect_success "build bitmap from existing ($from -> $to)" ' ( cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && test_commit_bulk --id=further 16 && git tag new-tip && @@ -59,6 +67,7 @@ bitmap_reuse_tests() { test_expect_success "verify resulting bitmaps ($from -> $to)" ' ( cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && git for-each-ref && git rev-list --test-bitmap refs/tags/old-tip && git rev-list --test-bitmap refs/tags/new-tip @@ -66,244 +75,362 @@ bitmap_reuse_tests() { ' } -bitmap_reuse_tests 'pack' 'MIDX' -bitmap_reuse_tests 'MIDX' 'pack' -bitmap_reuse_tests 'MIDX' 'MIDX' +test_midx_bitmap_cases () { + writeLookupTable=false + writeBitmapLookupTable= + + for i in "$@" + do + case $i in + "pack.writeBitmapLookupTable") + writeLookupTable=true + writeBitmapLookupTable="$i" + ;; + esac + done + + test_expect_success 'setup test_repository' ' + rm -rf * .git && + git init && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' + ' -test_expect_success 'missing object closure fails gracefully' ' - rm -fr repo && - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && + midx_bitmap_core - test_commit loose && - test_commit packed && + bitmap_reuse_tests 'pack' 'MIDX' "$writeBitmapLookupTable" + bitmap_reuse_tests 'MIDX' 'pack' "$writeBitmapLookupTable" + bitmap_reuse_tests 'MIDX' 'MIDX' "$writeBitmapLookupTable" - # Do not pass "--revs"; we want a pack without the "loose" - # commit. - git pack-objects $objdir/pack/pack <<-EOF && - $(git rev-parse packed) - EOF + test_expect_success 'missing object closure fails gracefully' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && - test_must_fail git multi-pack-index write --bitmap 2>err && - grep "doesn.t have full closure" err && - test_path_is_missing $midx - ) -' + test_commit loose && + test_commit packed && -midx_bitmap_partial_tests + # Do not pass "--revs"; we want a pack without the "loose" + # commit. + git pack-objects $objdir/pack/pack <<-EOF && + $(git rev-parse packed) + EOF -test_expect_success 'removing a MIDX clears stale bitmaps' ' - rm -fr repo && - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && - test_commit base && - git repack && - git multi-pack-index write --bitmap && + test_must_fail git multi-pack-index write --bitmap 2>err && + grep "doesn.t have full closure" err && + test_path_is_missing $midx + ) + ' - # Write a MIDX and bitmap; remove the MIDX but leave the bitmap. - stale_bitmap=$midx-$(midx_checksum $objdir).bitmap && - rm $midx && + midx_bitmap_partial_tests - # Then write a new MIDX. - test_commit new && - git repack && - git multi-pack-index write --bitmap && + test_expect_success 'removing a MIDX clears stale bitmaps' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && + test_commit base && + git repack && + git multi-pack-index write --bitmap && + + # Write a MIDX and bitmap; remove the MIDX but leave the bitmap. + stale_bitmap=$midx-$(midx_checksum $objdir).bitmap && + rm $midx && + + # Then write a new MIDX. + test_commit new && + git repack && + git multi-pack-index write --bitmap && + + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + test_path_is_missing $stale_bitmap + ) + ' - test_path_is_file $midx && - test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_missing $stale_bitmap - ) -' + test_expect_success 'pack.preferBitmapTips' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && -test_expect_success 'pack.preferBitmapTips' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && + test_commit_bulk --message="%s" 103 && - test_commit_bulk --message="%s" 103 && + git log --format="%H" >commits.raw && + sort <commits.raw >commits && - git log --format="%H" >commits.raw && - sort <commits.raw >commits && + git log --format="create refs/tags/%s %H" HEAD >refs && + git update-ref --stdin <refs && - git log --format="create refs/tags/%s %H" HEAD >refs && - git update-ref --stdin <refs && + git multi-pack-index write --bitmap && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - git multi-pack-index write --bitmap && - test_path_is_file $midx && - test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >before && + test_line_count = 1 before && - test-tool bitmap list-commits | sort >bitmaps && - comm -13 bitmaps commits >before && - test_line_count = 1 before && + perl -ne "printf(\"create refs/tags/include/%d \", $.); print" \ + <before | git update-ref --stdin && - perl -ne "printf(\"create refs/tags/include/%d \", $.); print" \ - <before | git update-ref --stdin && + rm -fr $midx-$(midx_checksum $objdir).bitmap && + rm -fr $midx && - rm -fr $midx-$(midx_checksum $objdir).bitmap && - rm -fr $midx && + git -c pack.preferBitmapTips=refs/tags/include \ + multi-pack-index write --bitmap && + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >after && - git -c pack.preferBitmapTips=refs/tags/include \ - multi-pack-index write --bitmap && - test-tool bitmap list-commits | sort >bitmaps && - comm -13 bitmaps commits >after && + ! test_cmp before after + ) + ' - ! test_cmp before after - ) -' + test_expect_success 'writing a bitmap with --refs-snapshot' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && -test_expect_success 'writing a bitmap with --refs-snapshot' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && + test_commit one && + test_commit two && - test_commit one && - test_commit two && + git rev-parse one >snapshot && - git rev-parse one >snapshot && + git repack -ad && - git repack -ad && + # First, write a MIDX which see both refs/tags/one and + # refs/tags/two (causing both of those commits to receive + # bitmaps). + git multi-pack-index write --bitmap && - # First, write a MIDX which see both refs/tags/one and - # refs/tags/two (causing both of those commits to receive - # bitmaps). - git multi-pack-index write --bitmap && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_file $midx && - test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + test-tool bitmap list-commits | sort >bitmaps && + grep "$(git rev-parse one)" bitmaps && + grep "$(git rev-parse two)" bitmaps && - test-tool bitmap list-commits | sort >bitmaps && - grep "$(git rev-parse one)" bitmaps && - grep "$(git rev-parse two)" bitmaps && + rm -fr $midx-$(midx_checksum $objdir).bitmap && + rm -fr $midx && - rm -fr $midx-$(midx_checksum $objdir).bitmap && - rm -fr $midx && + # Then again, but with a refs snapshot which only sees + # refs/tags/one. + git multi-pack-index write --bitmap --refs-snapshot=snapshot && - # Then again, but with a refs snapshot which only sees - # refs/tags/one. - git multi-pack-index write --bitmap --refs-snapshot=snapshot && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - test_path_is_file $midx && - test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + test-tool bitmap list-commits | sort >bitmaps && + grep "$(git rev-parse one)" bitmaps && + ! grep "$(git rev-parse two)" bitmaps + ) + ' - test-tool bitmap list-commits | sort >bitmaps && - grep "$(git rev-parse one)" bitmaps && - ! grep "$(git rev-parse two)" bitmaps - ) -' + test_expect_success 'write a bitmap with --refs-snapshot (preferred tips)' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && -test_expect_success 'write a bitmap with --refs-snapshot (preferred tips)' ' - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && + test_commit_bulk --message="%s" 103 && - test_commit_bulk --message="%s" 103 && + git log --format="%H" >commits.raw && + sort <commits.raw >commits && - git log --format="%H" >commits.raw && - sort <commits.raw >commits && + git log --format="create refs/tags/%s %H" HEAD >refs && + git update-ref --stdin <refs && - git log --format="create refs/tags/%s %H" HEAD >refs && - git update-ref --stdin <refs && + git multi-pack-index write --bitmap && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && - git multi-pack-index write --bitmap && - test_path_is_file $midx && - test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >before && + test_line_count = 1 before && - test-tool bitmap list-commits | sort >bitmaps && - comm -13 bitmaps commits >before && - test_line_count = 1 before && + ( + grep -vf before commits.raw && + # mark missing commits as preferred + sed "s/^/+/" before + ) >snapshot && + rm -fr $midx-$(midx_checksum $objdir).bitmap && + rm -fr $midx && + + git multi-pack-index write --bitmap --refs-snapshot=snapshot && + test-tool bitmap list-commits | sort >bitmaps && + comm -13 bitmaps commits >after && + + ! test_cmp before after + ) + ' + + test_expect_success 'hash-cache values are propagated from pack bitmaps' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && ( - grep -vf before commits.raw && - # mark missing commits as preferred - sed "s/^/+/" before - ) >snapshot && + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && - rm -fr $midx-$(midx_checksum $objdir).bitmap && - rm -fr $midx && + test_commit base && + test_commit base2 && + git repack -adb && - git multi-pack-index write --bitmap --refs-snapshot=snapshot && - test-tool bitmap list-commits | sort >bitmaps && - comm -13 bitmaps commits >after && + test-tool bitmap dump-hashes >pack.raw && + test_file_not_empty pack.raw && + sort pack.raw >pack.hashes && - ! test_cmp before after - ) -' + test_commit new && + git repack && + git multi-pack-index write --bitmap && -test_expect_success 'hash-cache values are propagated from pack bitmaps' ' - rm -fr repo && - git init repo && - test_when_finished "rm -fr repo" && - ( - cd repo && + test-tool bitmap dump-hashes >midx.raw && + sort midx.raw >midx.hashes && - test_commit base && - test_commit base2 && - git repack -adb && + # ensure that every namehash in the pack bitmap can be found in + # the midx bitmap (i.e., that there are no oid-namehash pairs + # unique to the pack bitmap). + comm -23 pack.hashes midx.hashes >dropped.hashes && + test_must_be_empty dropped.hashes + ) + ' - test-tool bitmap dump-hashes >pack.raw && - test_file_not_empty pack.raw && - sort pack.raw >pack.hashes && + test_expect_success 'no .bitmap is written without any objects' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && - test_commit new && - git repack && - git multi-pack-index write --bitmap && + empty="$(git pack-objects $objdir/pack/pack </dev/null)" && + cat >packs <<-EOF && + pack-$empty.idx + EOF - test-tool bitmap dump-hashes >midx.raw && - sort midx.raw >midx.hashes && + git multi-pack-index write --bitmap --stdin-packs \ + <packs 2>err && - # ensure that every namehash in the pack bitmap can be found in - # the midx bitmap (i.e., that there are no oid-namehash pairs - # unique to the pack bitmap). - comm -23 pack.hashes midx.hashes >dropped.hashes && - test_must_be_empty dropped.hashes - ) -' + grep "bitmap without any objects" err && + + test_path_is_file $midx && + test_path_is_missing $midx-$(midx_checksum $objdir).bitmap + ) + ' + + test_expect_success 'graceful fallback when missing reverse index' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' && + + test_commit base && + + # write a pack and MIDX bitmap containing base + git repack -adb && + git multi-pack-index write --bitmap && + + GIT_TEST_MIDX_READ_RIDX=0 \ + git rev-list --use-bitmap-index HEAD 2>err && + ! grep "ignoring extra bitmap file" err + ) + ' +} + +test_midx_bitmap_cases -test_expect_success 'no .bitmap is written without any objects' ' +test_midx_bitmap_cases "pack.writeBitmapLookupTable" + +test_expect_success 'multi-pack-index write writes lookup table if enabled' ' rm -fr repo && git init repo && test_when_finished "rm -fr repo" && ( cd repo && + test_commit base && + git config pack.writeBitmapLookupTable true && + git repack -ad && + GIT_TRACE2_EVENT="$(pwd)/trace" \ + git multi-pack-index write --bitmap && + grep "\"label\":\"writing_lookup_table\"" trace + ) +' + +test_expect_success 'preferred pack change with existing MIDX bitmap' ' + git init preferred-pack-with-existing && + ( + cd preferred-pack-with-existing && + + test_commit base && + test_commit other && + + git rev-list --objects --no-object-names base >p1.objects && + git rev-list --objects --no-object-names other >p2.objects && - empty="$(git pack-objects $objdir/pack/pack </dev/null)" && - cat >packs <<-EOF && - pack-$empty.idx - EOF + p1="$(git pack-objects "$objdir/pack/pack" \ + --delta-base-offset <p1.objects)" && + p2="$(git pack-objects "$objdir/pack/pack" \ + --delta-base-offset <p2.objects)" && - git multi-pack-index write --bitmap --stdin-packs \ - <packs 2>err && + # Generate a MIDX containing the first two packs, + # marking p1 as preferred, and ensure that it can be + # successfully cloned. + git multi-pack-index write --bitmap \ + --preferred-pack="pack-$p1.pack" && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + git clone --no-local . clone1 && - grep "bitmap without any objects" err && + # Then generate a new pack which sorts ahead of any + # existing pack (by tweaking the pack prefix). + test_commit foo && + git pack-objects --all --unpacked $objdir/pack/pack0 && + # Generate a new MIDX which changes the preferred pack + # to a pack contained in the existing MIDX. + git multi-pack-index write --bitmap \ + --preferred-pack="pack-$p2.pack" && test_path_is_file $midx && - test_path_is_missing $midx-$(midx_checksum $objdir).bitmap + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + + # When the above circumstances are met, the preferred + # pack should change appropriately and clones should + # (still) succeed. + git clone --no-local . clone2 ) ' -test_expect_success 'graceful fallback when missing reverse index' ' +test_expect_success 'tagged commits are selected for bitmapping' ' rm -fr repo && git init repo && test_when_finished "rm -fr repo" && ( cd repo && - test_commit base && + test_commit --annotate base && + git repack -d && + + # Remove refs/heads/main which points at the commit directly, + # leaving only a reference to the annotated tag. + git branch -M main && + git checkout base && + git branch -d main && - # write a pack and MIDX bitmap containing base - git repack -adb && git multi-pack-index write --bitmap && - GIT_TEST_MIDX_READ_RIDX=0 \ - git rev-list --use-bitmap-index HEAD 2>err && - ! grep "ignoring extra bitmap file" err + git rev-parse HEAD >want && + test-tool bitmap list-commits >actual && + grep $(cat want) actual ) ' diff --git a/t/t5327-multi-pack-bitmaps-rev.sh b/t/t5327-multi-pack-bitmaps-rev.sh index d30ba632c8..e65e311cd7 100755 --- a/t/t5327-multi-pack-bitmaps-rev.sh +++ b/t/t5327-multi-pack-bitmaps-rev.sh @@ -17,7 +17,27 @@ GIT_TEST_MIDX_READ_RIDX=0 export GIT_TEST_MIDX_WRITE_REV export GIT_TEST_MIDX_READ_RIDX -midx_bitmap_core rev -midx_bitmap_partial_tests rev +test_midx_bitmap_rev () { + writeLookupTable=false + + for i in "$@" + do + case $i in + "pack.writeBitmapLookupTable") writeLookupTable=true;; + esac + done + + test_expect_success 'setup bitmap config' ' + rm -rf * .git && + git init && + git config pack.writeBitmapLookupTable '"$writeLookupTable"' + ' + + midx_bitmap_core rev + midx_bitmap_partial_tests rev +} + +test_midx_bitmap_rev +test_midx_bitmap_rev "pack.writeBitmapLookupTable" test_done diff --git a/t/t5329-pack-objects-cruft.sh b/t/t5329-pack-objects-cruft.sh index 8968f7a08d..303f7a5d84 100755 --- a/t/t5329-pack-objects-cruft.sh +++ b/t/t5329-pack-objects-cruft.sh @@ -29,7 +29,8 @@ basic_cruft_pack_tests () { while read oid do path="$objdir/$(test_oid_to_path "$oid")" && - printf "%s %d\n" "$oid" "$(test-tool chmtime --get "$path")" + printf "%s %d\n" "$oid" "$(test-tool chmtime --get "$path")" || + echo "object list generation failed for $oid" done | sort -k1 ) >expect && @@ -232,7 +233,7 @@ test_expect_success 'cruft tags rescue tagged objects' ' while read oid do test-tool chmtime -1000 \ - "$objdir/$(test_oid_to_path $oid)" + "$objdir/$(test_oid_to_path $oid)" || exit 1 done <objects && test-tool chmtime -500 \ @@ -272,7 +273,7 @@ test_expect_success 'cruft commits rescue parents, trees' ' while read object do test-tool chmtime -1000 \ - "$objdir/$(test_oid_to_path $object)" + "$objdir/$(test_oid_to_path $object)" || exit 1 done <objects && test-tool chmtime +500 "$objdir/$(test_oid_to_path \ $(git rev-parse HEAD))" && @@ -345,7 +346,7 @@ test_expect_success 'expired objects are pruned' ' while read object do test-tool chmtime -1000 \ - "$objdir/$(test_oid_to_path $object)" + "$objdir/$(test_oid_to_path $object)" || exit 1 done <objects && keep="$(basename "$(ls $packdir/pack-*.pack)")" && diff --git a/t/t5351-unpack-large-objects.sh b/t/t5351-unpack-large-objects.sh index f785cb0617..8c8af99b84 100755 --- a/t/t5351-unpack-large-objects.sh +++ b/t/t5351-unpack-large-objects.sh @@ -5,6 +5,7 @@ test_description='git unpack-objects with large objects' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh prepare_dest () { @@ -70,9 +71,15 @@ test_expect_success 'unpack big object in stream (core.fsyncmethod=batch)' ' GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ GIT_TEST_FSYNC=true \ git -C dest.git $BATCH_CONFIGURATION unpack-objects <pack-$PACK.pack && - check_fsync_events trace2.txt <<-\EOF && + if grep "core.fsyncMethod = batch is unsupported" trace2.txt + then + flush_count=7 + else + flush_count=1 + fi && + check_fsync_events trace2.txt <<-EOF && "key":"fsync/writeout-only","value":"6" - "key":"fsync/hardware-flush","value":"1" + "key":"fsync/hardware-flush","value":"$flush_count" EOF test_dir_is_empty dest.git/objects/pack && @@ -87,7 +94,7 @@ test_expect_success 'do not unpack existing large objects' ' # The destination came up with the exact same pack... DEST_PACK=$(echo dest.git/objects/pack/pack-*.pack) && - test_cmp pack-$PACK.pack $DEST_PACK && + cmp pack-$PACK.pack $DEST_PACK && # ...and wrote no loose objects test_stdout_line_count = 0 find dest.git/objects -type f ! -name "pack-*" diff --git a/t/t5402-post-merge-hook.sh b/t/t5402-post-merge-hook.sh index 915af2de95..46ebdfbeeb 100755 --- a/t/t5402-post-merge-hook.sh +++ b/t/t5402-post-merge-hook.sh @@ -7,6 +7,7 @@ test_description='Test the post-merge hook.' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index ee6d2dde9f..d18f2823d8 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -407,6 +407,7 @@ test_expect_success 'in_vain not triggered before first ACK' ' ' test_expect_success 'in_vain resetted upon ACK' ' + test_when_finished rm -f log trace2 && rm -rf myserver myclient && git init myserver && @@ -432,7 +433,8 @@ test_expect_success 'in_vain resetted upon ACK' ' # first. The 256th commit is common between the client and the server, # and should reset in_vain. This allows negotiation to continue until # the client reports that first_anotherbranch_commit is common. - git -C myclient fetch --progress origin main 2>log && + GIT_TRACE2_EVENT="$(pwd)/trace2" git -C myclient fetch --progress origin main 2>log && + grep \"key\":\"total_rounds\",\"value\":\"6\" trace2 && test_i18ngrep "Total 3 " log ' diff --git a/t/t5503-tagfollow.sh b/t/t5503-tagfollow.sh index 195fc64dd4..5ebbaa4896 100755 --- a/t/t5503-tagfollow.sh +++ b/t/t5503-tagfollow.sh @@ -5,6 +5,7 @@ test_description='test automatic tag following' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # End state of the repository: diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index b0b795aca9..ac4099ca89 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -352,4 +352,21 @@ test_expect_success \ grep "Cannot demote unterminatedheader" act ' +test_expect_success 'badFilemode is not a strict error' ' + git init --bare badmode.git && + tree=$( + cd badmode.git && + blob=$(echo blob | git hash-object -w --stdin | hex2oct) && + printf "123456 foo\0${blob}" | + git hash-object -t tree --stdin -w --literally + ) && + + rm -rf dst.git && + git init --bare dst.git && + git -C dst.git config transfer.fsckObjects true && + + git -C badmode.git push ../dst.git $tree:refs/tags/tree 2>err && + grep "$tree: badFilemode" err +' + test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index 6c7370f87f..43b7bcd715 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -241,6 +241,26 @@ test_expect_success 'add invalid foreign_vcs remote' ' test_cmp expect actual ' +test_expect_success 'without subcommand' ' + echo origin >expect && + git -C test remote >actual && + test_cmp expect actual +' + +test_expect_success 'without subcommand accepts -v' ' + cat >expect <<-EOF && + origin $(pwd)/one (fetch) + origin $(pwd)/one (push) + EOF + git -C test remote -v >actual && + test_cmp expect actual +' + +test_expect_success 'without subcommand does not take arguments' ' + test_expect_code 129 git -C test remote origin 2>err && + grep "^error: unknown subcommand:" err +' + cat >test/expect <<EOF * remote origin Fetch URL: $(pwd)/one @@ -882,6 +902,17 @@ test_expect_success 'rename a remote renames repo remote.pushDefault but keeps g ) ' +test_expect_success 'rename handles remote without fetch refspec' ' + git clone --bare one no-refspec.git && + # confirm assumption that bare clone does not create refspec + test_expect_code 5 \ + git -C no-refspec.git config --unset-all remote.origin.fetch && + git -C no-refspec.git config remote.origin.url >expect && + git -C no-refspec.git remote rename origin foo && + git -C no-refspec.git config remote.foo.url >actual && + test_cmp expect actual +' + test_expect_success 'rename does not update a non-default fetch refspec' ' git clone one four.one && ( diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index b45879a760..c0b745e33b 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -790,6 +790,7 @@ test_expect_success 'fetch.writeCommitGraph' ' ' test_expect_success 'fetch.writeCommitGraph with submodules' ' + test_config_global protocol.file.allow always && git clone dups super && ( cd super && diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index f3356f9ea8..4f2bfaf005 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -200,7 +200,10 @@ test_expect_success 'push with negotiation' ' test_commit -C testrepo unrelated_commit && git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit && test_when_finished "rm event" && - GIT_TRACE2_EVENT="$(pwd)/event" git -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main && + GIT_TRACE2_EVENT="$(pwd)/event" \ + git -c protocol.version=2 -c push.negotiate=1 \ + push testrepo refs/heads/main:refs/remotes/origin/main && + grep \"key\":\"total_rounds\",\"value\":\"1\" event && grep_wrote 2 event # 1 commit, 1 tree ' @@ -219,12 +222,16 @@ test_expect_success 'push with negotiation proceeds anyway even if negotiation f test_expect_success 'push with negotiation does not attempt to fetch submodules' ' mk_empty submodule_upstream && test_commit -C submodule_upstream submodule_commit && + test_config_global protocol.file.allow always && git submodule add ./submodule_upstream submodule && mk_empty testrepo && git push testrepo $the_first_commit:refs/remotes/origin/first_commit && test_commit -C testrepo unrelated_commit && git -C testrepo config receive.hideRefs refs/remotes/origin/first_commit && - git -c submodule.recurse=true -c protocol.version=2 -c push.negotiate=1 push testrepo refs/heads/main:refs/remotes/origin/main 2>err && + GIT_TRACE2_EVENT="$(pwd)/event" git -c submodule.recurse=true \ + -c protocol.version=2 -c push.negotiate=1 \ + push testrepo refs/heads/main:refs/remotes/origin/main 2>err && + grep \"key\":\"total_rounds\",\"value\":\"1\" event && ! grep "Fetching submodule" err ' @@ -1846,37 +1853,6 @@ test_expect_success 'refuse to push a hidden ref, and make sure do not pollute t test_dir_is_empty testrepo/.git/objects/pack ' -test_expect_success LIBCURL 'fetch warns or fails when using username:password' ' - message="URL '\''https://username:<redacted>@localhost/'\'' uses plaintext credentials" && - test_must_fail git -c transfer.credentialsInUrl=allow fetch https://username:password@localhost 2>err && - ! grep "$message" err && - - test_must_fail git -c transfer.credentialsInUrl=warn fetch https://username:password@localhost 2>err && - grep "warning: $message" err >warnings && - test_line_count = 3 warnings && - - test_must_fail git -c transfer.credentialsInUrl=die fetch https://username:password@localhost 2>err && - grep "fatal: $message" err >warnings && - test_line_count = 1 warnings && - - test_must_fail git -c transfer.credentialsInUrl=die fetch https://username:@localhost 2>err && - grep "fatal: $message" err >warnings && - test_line_count = 1 warnings -' - - -test_expect_success LIBCURL 'push warns or fails when using username:password' ' - message="URL '\''https://username:<redacted>@localhost/'\'' uses plaintext credentials" && - test_must_fail git -c transfer.credentialsInUrl=allow push https://username:password@localhost 2>err && - ! grep "$message" err && - - test_must_fail git -c transfer.credentialsInUrl=warn push https://username:password@localhost 2>err && - grep "warning: $message" err >warnings && - test_must_fail git -c transfer.credentialsInUrl=die push https://username:password@localhost 2>err && - grep "fatal: $message" err >warnings && - test_line_count = 1 warnings -' - test_expect_success 'push with config push.useBitmaps' ' mk_test testrepo heads/main && git checkout main && diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 081808009b..0b72112fb1 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -218,6 +218,23 @@ test_expect_success 'fail if upstream branch does not exist' ' test_cmp expect file ' +test_expect_success 'fetch upstream branch even if refspec excludes it' ' + # the branch names are not important here except that + # the first one must not be a prefix of the second, + # since otherwise the ref-prefix protocol extension + # would match both + git branch in-refspec HEAD^ && + git branch not-in-refspec HEAD && + git init -b in-refspec downstream && + git -C downstream remote add -t in-refspec origin "file://$(pwd)/.git" && + git -C downstream config branch.in-refspec.remote origin && + git -C downstream config branch.in-refspec.merge refs/heads/not-in-refspec && + git -C downstream pull && + git rev-parse --verify not-in-refspec >expect && + git -C downstream rev-parse --verify HEAD >actual && + test_cmp expect actual +' + test_expect_success 'fail if the index has unresolved entries' ' git checkout -b third second^ && test_when_finished "git checkout -f copy && git branch -D third" && diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index a301b56db8..75da8acf8f 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -127,6 +127,7 @@ verify_fetch_result () { } test_expect_success setup ' + git config --global protocol.file.allow always && mkdir deepsubmodule && ( cd deepsubmodule && @@ -714,7 +715,13 @@ test_expect_success 'fetching submodules respects parallel settings' ' GIT_TRACE=$(pwd)/trace.out git fetch && grep "8 tasks" trace.out && GIT_TRACE=$(pwd)/trace.out git fetch --jobs 9 && - grep "9 tasks" trace.out + grep "9 tasks" trace.out && + >trace.out && + + GIT_TRACE=$(pwd)/trace.out git -c submodule.fetchJobs=0 fetch && + grep "preparing to run up to [0-9]* tasks" trace.out && + ! grep "up to 0 tasks" trace.out && + >trace.out ) ' diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh index 10e9a7ff26..37f7547a4c 100755 --- a/t/t5537-fetch-shallow.sh +++ b/t/t5537-fetch-shallow.sh @@ -162,6 +162,8 @@ test_expect_success 'fetch --update-shallow' ' ' test_expect_success 'fetch --update-shallow into a repo with submodules' ' + test_config_global protocol.file.allow always && + git init a-submodule && test_commit -C a-submodule foo && @@ -175,7 +177,8 @@ test_expect_success 'fetch --update-shallow into a repo with submodules' ' test_expect_success 'fetch --update-shallow a commit that is also a shallow point into a repo with submodules' ' test_when_finished "rm -rf repo-with-sub" && git init repo-with-sub && - git -C repo-with-sub submodule add ../a-submodule a-submodule && + git -c protocol.file.allow=always -C repo-with-sub \ + submodule add ../a-submodule a-submodule && git -C repo-with-sub commit -m "added submodule" && SHALLOW=$(cat shallow/.git/shallow) && diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh index 214228349a..a158e7d2c0 100755 --- a/t/t5545-push-options.sh +++ b/t/t5545-push-options.sh @@ -119,6 +119,7 @@ test_expect_success 'push options and submodules' ' test_commit -C parent one && git -C parent push --mirror up && + test_config_global protocol.file.allow always && git -C parent submodule add ../upstream workbench && git -C parent/workbench remote add up ../../upstream && git -C parent commit -m "add submodule" && diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index d7cf85ffea..8f182a3cbf 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -234,7 +234,7 @@ test_expect_success 'http-fetch --packfile' ' --index-pack-arg=--keep \ "$HTTPD_URL"/dumb/repo_pack.git/$p >out && - grep "^keep.[0-9a-f]\{16,\}$" out && + grep -E "^keep.[0-9a-f]{16,}$" out && cut -c6- out >packhash && # Ensure that the expected files are generated diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 245532df88..64c6c9f59e 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -181,8 +181,8 @@ test_expect_success 'no-op half-auth fetch does not require a password' ' # This is not possible with protocol v2, since both objects and refs # are obtained from the "git-upload-pack" path. A solution to this is # to teach the server and client to be able to inline ls-refs requests - # as an Extra Parameter (see pack-protocol.txt), so that "info/refs" - # can serve refs, just like it does in protocol v0. + # as an Extra Parameter (see "git help gitformat-pack-protocol"), so that + # "info/refs" can serve refs, just like it does in protocol v0. GIT_TEST_PROTOCOL_VERSION=0 git --git-dir=half-auth fetch && expect_askpass none ' @@ -580,4 +580,81 @@ test_expect_success 'passing hostname resolution information works' ' git -c "http.curloptResolve=$BOGUS_HOST:$LIB_HTTPD_PORT:127.0.0.1" ls-remote "$BOGUS_HTTPD_URL/smart/repo.git" >/dev/null ' +# here user%40host is the URL-encoded version of user@host, +# which is our intentionally-odd username to catch parsing errors +url_user=$HTTPD_URL_USER/auth/smart/repo.git +url_userpass=$HTTPD_URL_USER_PASS/auth/smart/repo.git +url_userblank=$HTTPD_PROTO://user%40host:@$HTTPD_DEST/auth/smart/repo.git +message="URL .*:<redacted>@.* uses plaintext credentials" + +test_expect_success 'clone warns or fails when using username:password' ' + test_when_finished "rm -rf attempt*" && + + git -c transfer.credentialsInUrl=allow \ + clone $url_userpass attempt1 2>err && + ! grep "$message" err && + + git -c transfer.credentialsInUrl=warn \ + clone $url_userpass attempt2 2>err && + grep "warning: $message" err >warnings && + test_line_count -ge 1 warnings && + + test_must_fail git -c transfer.credentialsInUrl=die \ + clone $url_userpass attempt3 2>err && + grep "fatal: $message" err >warnings && + test_line_count -ge 1 warnings && + + test_must_fail git -c transfer.credentialsInUrl=die \ + clone $url_userblank attempt4 2>err && + grep "fatal: $message" err >warnings && + test_line_count -ge 1 warnings +' + +test_expect_success 'clone does not detect username:password when it is https://username@domain:port/' ' + test_when_finished "rm -rf attempt1" && + + # we are relying on lib-httpd for url construction, so document our + # assumptions + case "$HTTPD_URL_USER" in + *:[0-9]*) : ok ;; + *) BUG "httpd url does not have port: $HTTPD_URL_USER" + esac && + + git -c transfer.credentialsInUrl=warn clone $url_user attempt1 2>err && + ! grep "uses plaintext credentials" err +' + +test_expect_success 'fetch warns or fails when using username:password' ' + git -c transfer.credentialsInUrl=allow fetch $url_userpass 2>err && + ! grep "$message" err && + + git -c transfer.credentialsInUrl=warn fetch $url_userpass 2>err && + grep "warning: $message" err >warnings && + test_line_count -ge 1 warnings && + + test_must_fail git -c transfer.credentialsInUrl=die \ + fetch $url_userpass 2>err && + grep "fatal: $message" err >warnings && + test_line_count -ge 1 warnings && + + test_must_fail git -c transfer.credentialsInUrl=die \ + fetch $url_userblank 2>err && + grep "fatal: $message" err >warnings && + test_line_count -ge 1 warnings +' + + +test_expect_success 'push warns or fails when using username:password' ' + git -c transfer.credentialsInUrl=allow push $url_userpass 2>err && + ! grep "$message" err && + + git -c transfer.credentialsInUrl=warn push $url_userpass 2>err && + grep "warning: $message" err >warnings && + + test_must_fail git -c transfer.credentialsInUrl=die \ + push $url_userpass 2>err && + grep "fatal: $message" err >warnings && + test_line_count -ge 1 warnings +' + test_done diff --git a/t/t5557-http-get.sh b/t/t5557-http-get.sh new file mode 100755 index 0000000000..76a4bbd16a --- /dev/null +++ b/t/t5557-http-get.sh @@ -0,0 +1,39 @@ +#!/bin/sh + +test_description='test downloading a file by URL' + +TEST_PASSES_SANITIZE_LEAK=true + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'get by URL: 404' ' + test_when_finished "rm -f file.temp" && + url="$HTTPD_URL/none.txt" && + cat >input <<-EOF && + capabilities + get $url file1 + EOF + + test_must_fail git remote-http $url <input 2>err && + test_path_is_missing file1 && + grep "failed to download file at URL" err +' + +test_expect_success 'get by URL: 200' ' + echo data >"$HTTPD_DOCUMENT_ROOT_PATH/exists.txt" && + + url="$HTTPD_URL/exists.txt" && + cat >input <<-EOF && + capabilities + get $url file2 + + EOF + + git remote-http $url <input && + test_cmp "$HTTPD_DOCUMENT_ROOT_PATH/exists.txt" file2 +' + +test_done diff --git a/t/t5558-clone-bundle-uri.sh b/t/t5558-clone-bundle-uri.sh new file mode 100755 index 0000000000..9155f31fa2 --- /dev/null +++ b/t/t5558-clone-bundle-uri.sh @@ -0,0 +1,356 @@ +#!/bin/sh + +test_description='test fetching bundles with --bundle-uri' + +. ./test-lib.sh + +test_expect_success 'fail to clone from non-existent file' ' + test_when_finished rm -rf test && + git clone --bundle-uri="$(pwd)/does-not-exist" . test 2>err && + grep "failed to download bundle from URI" err +' + +test_expect_success 'fail to clone from non-bundle file' ' + test_when_finished rm -rf test && + echo bogus >bogus && + git clone --bundle-uri="$(pwd)/bogus" . test 2>err && + grep "is not a bundle" err +' + +test_expect_success 'create bundle' ' + git init clone-from && + git -C clone-from checkout -b topic && + test_commit -C clone-from A && + test_commit -C clone-from B && + git -C clone-from bundle create B.bundle topic +' + +test_expect_success 'clone with path bundle' ' + git clone --bundle-uri="clone-from/B.bundle" \ + clone-from clone-path && + git -C clone-path rev-parse refs/bundles/topic >actual && + git -C clone-from rev-parse topic >expect && + test_cmp expect actual +' + +test_expect_success 'clone with file:// bundle' ' + git clone --bundle-uri="file://$(pwd)/clone-from/B.bundle" \ + clone-from clone-file && + git -C clone-file rev-parse refs/bundles/topic >actual && + git -C clone-from rev-parse topic >expect && + test_cmp expect actual +' + +# To get interesting tests for bundle lists, we need to construct a +# somewhat-interesting commit history. +# +# ---------------- bundle-4 +# +# 4 +# / \ +# ----|---|------- bundle-3 +# | | +# | 3 +# | | +# ----|---|------- bundle-2 +# | | +# 2 | +# | | +# ----|---|------- bundle-1 +# \ / +# 1 +# | +# (previous commits) +test_expect_success 'construct incremental bundle list' ' + ( + cd clone-from && + git checkout -b base && + test_commit 1 && + git checkout -b left && + test_commit 2 && + git checkout -b right base && + test_commit 3 && + git checkout -b merge left && + git merge right -m "4" && + + git bundle create bundle-1.bundle base && + git bundle create bundle-2.bundle base..left && + git bundle create bundle-3.bundle base..right && + git bundle create bundle-4.bundle merge --not left right + ) +' + +test_expect_success 'clone bundle list (file, no heuristic)' ' + cat >bundle-list <<-EOF && + [bundle] + version = 1 + mode = all + + [bundle "bundle-1"] + uri = file://$(pwd)/clone-from/bundle-1.bundle + + [bundle "bundle-2"] + uri = file://$(pwd)/clone-from/bundle-2.bundle + + [bundle "bundle-3"] + uri = file://$(pwd)/clone-from/bundle-3.bundle + + [bundle "bundle-4"] + uri = file://$(pwd)/clone-from/bundle-4.bundle + EOF + + git clone --bundle-uri="file://$(pwd)/bundle-list" \ + clone-from clone-list-file 2>err && + ! grep "Repository lacks these prerequisite commits" err && + + git -C clone-from for-each-ref --format="%(objectname)" >oids && + git -C clone-list-file cat-file --batch-check <oids && + + git -C clone-list-file for-each-ref --format="%(refname)" >refs && + grep "refs/bundles/" refs >actual && + cat >expect <<-\EOF && + refs/bundles/base + refs/bundles/left + refs/bundles/merge + refs/bundles/right + EOF + test_cmp expect actual +' + +test_expect_success 'clone bundle list (file, all mode, some failures)' ' + cat >bundle-list <<-EOF && + [bundle] + version = 1 + mode = all + + # Does not exist. Should be skipped. + [bundle "bundle-0"] + uri = file://$(pwd)/clone-from/bundle-0.bundle + + [bundle "bundle-1"] + uri = file://$(pwd)/clone-from/bundle-1.bundle + + [bundle "bundle-2"] + uri = file://$(pwd)/clone-from/bundle-2.bundle + + # No bundle-3 means bundle-4 will not apply. + + [bundle "bundle-4"] + uri = file://$(pwd)/clone-from/bundle-4.bundle + + # Does not exist. Should be skipped. + [bundle "bundle-5"] + uri = file://$(pwd)/clone-from/bundle-5.bundle + EOF + + GIT_TRACE2_PERF=1 \ + git clone --bundle-uri="file://$(pwd)/bundle-list" \ + clone-from clone-all-some 2>err && + ! grep "Repository lacks these prerequisite commits" err && + ! grep "fatal" err && + grep "warning: failed to download bundle from URI" err && + + git -C clone-from for-each-ref --format="%(objectname)" >oids && + git -C clone-all-some cat-file --batch-check <oids && + + git -C clone-all-some for-each-ref --format="%(refname)" >refs && + grep "refs/bundles/" refs >actual && + cat >expect <<-\EOF && + refs/bundles/base + refs/bundles/left + EOF + test_cmp expect actual +' + +test_expect_success 'clone bundle list (file, all mode, all failures)' ' + cat >bundle-list <<-EOF && + [bundle] + version = 1 + mode = all + + # Does not exist. Should be skipped. + [bundle "bundle-0"] + uri = file://$(pwd)/clone-from/bundle-0.bundle + + # Does not exist. Should be skipped. + [bundle "bundle-5"] + uri = file://$(pwd)/clone-from/bundle-5.bundle + EOF + + git clone --bundle-uri="file://$(pwd)/bundle-list" \ + clone-from clone-all-fail 2>err && + ! grep "Repository lacks these prerequisite commits" err && + ! grep "fatal" err && + grep "warning: failed to download bundle from URI" err && + + git -C clone-from for-each-ref --format="%(objectname)" >oids && + git -C clone-all-fail cat-file --batch-check <oids && + + git -C clone-all-fail for-each-ref --format="%(refname)" >refs && + ! grep "refs/bundles/" refs +' + +test_expect_success 'clone bundle list (file, any mode)' ' + cat >bundle-list <<-EOF && + [bundle] + version = 1 + mode = any + + # Does not exist. Should be skipped. + [bundle "bundle-0"] + uri = file://$(pwd)/clone-from/bundle-0.bundle + + [bundle "bundle-1"] + uri = file://$(pwd)/clone-from/bundle-1.bundle + + # Does not exist. Should be skipped. + [bundle "bundle-5"] + uri = file://$(pwd)/clone-from/bundle-5.bundle + EOF + + git clone --bundle-uri="file://$(pwd)/bundle-list" \ + clone-from clone-any-file 2>err && + ! grep "Repository lacks these prerequisite commits" err && + + git -C clone-from for-each-ref --format="%(objectname)" >oids && + git -C clone-any-file cat-file --batch-check <oids && + + git -C clone-any-file for-each-ref --format="%(refname)" >refs && + grep "refs/bundles/" refs >actual && + cat >expect <<-\EOF && + refs/bundles/base + EOF + test_cmp expect actual +' + +test_expect_success 'clone bundle list (file, any mode, all failures)' ' + cat >bundle-list <<-EOF && + [bundle] + version = 1 + mode = any + + # Does not exist. Should be skipped. + [bundle "bundle-0"] + uri = $HTTPD_URL/bundle-0.bundle + + # Does not exist. Should be skipped. + [bundle "bundle-5"] + uri = $HTTPD_URL/bundle-5.bundle + EOF + + git clone --bundle-uri="file://$(pwd)/bundle-list" \ + clone-from clone-any-fail 2>err && + ! grep "fatal" err && + grep "warning: failed to download bundle from URI" err && + + git -C clone-from for-each-ref --format="%(objectname)" >oids && + git -C clone-any-fail cat-file --batch-check <oids && + + git -C clone-any-fail for-each-ref --format="%(refname)" >refs && + ! grep "refs/bundles/" refs +' + +######################################################################### +# HTTP tests begin here + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'fail to fetch from non-existent HTTP URL' ' + test_when_finished rm -rf test && + git clone --bundle-uri="$HTTPD_URL/does-not-exist" . test 2>err && + grep "failed to download bundle from URI" err +' + +test_expect_success 'fail to fetch from non-bundle HTTP URL' ' + test_when_finished rm -rf test && + echo bogus >"$HTTPD_DOCUMENT_ROOT_PATH/bogus" && + git clone --bundle-uri="$HTTPD_URL/bogus" . test 2>err && + grep "is not a bundle" err +' + +test_expect_success 'clone HTTP bundle' ' + cp clone-from/B.bundle "$HTTPD_DOCUMENT_ROOT_PATH/B.bundle" && + + git clone --no-local --mirror clone-from \ + "$HTTPD_DOCUMENT_ROOT_PATH/fetch.git" && + + git clone --bundle-uri="$HTTPD_URL/B.bundle" \ + "$HTTPD_URL/smart/fetch.git" clone-http && + git -C clone-http rev-parse refs/bundles/topic >actual && + git -C clone-from rev-parse topic >expect && + test_cmp expect actual && + + test_config -C clone-http log.excludedecoration refs/bundle/ +' + +test_expect_success 'clone bundle list (HTTP, no heuristic)' ' + cp clone-from/bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" && + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = all + + [bundle "bundle-1"] + uri = $HTTPD_URL/bundle-1.bundle + + [bundle "bundle-2"] + uri = $HTTPD_URL/bundle-2.bundle + + [bundle "bundle-3"] + uri = $HTTPD_URL/bundle-3.bundle + + [bundle "bundle-4"] + uri = $HTTPD_URL/bundle-4.bundle + EOF + + git clone --bundle-uri="$HTTPD_URL/bundle-list" \ + clone-from clone-list-http 2>err && + ! grep "Repository lacks these prerequisite commits" err && + + git -C clone-from for-each-ref --format="%(objectname)" >oids && + git -C clone-list-http cat-file --batch-check <oids +' + +test_expect_success 'clone bundle list (HTTP, any mode)' ' + cp clone-from/bundle-*.bundle "$HTTPD_DOCUMENT_ROOT_PATH/" && + cat >"$HTTPD_DOCUMENT_ROOT_PATH/bundle-list" <<-EOF && + [bundle] + version = 1 + mode = any + + # Does not exist. Should be skipped. + [bundle "bundle-0"] + uri = $HTTPD_URL/bundle-0.bundle + + [bundle "bundle-1"] + uri = $HTTPD_URL/bundle-1.bundle + + # Does not exist. Should be skipped. + [bundle "bundle-5"] + uri = $HTTPD_URL/bundle-5.bundle + EOF + + git clone --bundle-uri="$HTTPD_URL/bundle-list" \ + clone-from clone-any-http 2>err && + ! grep "fatal" err && + grep "warning: failed to download bundle from URI" err && + + git -C clone-from for-each-ref --format="%(objectname)" >oids && + git -C clone-any-http cat-file --batch-check <oids && + + git -C clone-list-file for-each-ref --format="%(refname)" >refs && + grep "refs/bundles/" refs >actual && + cat >expect <<-\EOF && + refs/bundles/base + refs/bundles/left + refs/bundles/merge + refs/bundles/right + EOF + test_cmp expect actual +' + +# Do not add tests here unless they use the HTTP server, as they will +# not run unless the HTTP dependencies exist. + +test_done diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh index a35396fadf..09097eff3f 100755 --- a/t/t5572-pull-submodule.sh +++ b/t/t5572-pull-submodule.sh @@ -52,6 +52,10 @@ then fi test_submodule_switch_func "git_pull_noff" +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'pull --recurse-submodule setup' ' test_create_repo child && test_commit -C child bar && diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index cf3be0584f..b2524a24c2 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -71,29 +71,6 @@ test_expect_success 'clone respects GIT_WORK_TREE' ' ' -test_expect_success LIBCURL 'clone warns or fails when using username:password' ' - message="URL '\''https://username:<redacted>@localhost/'\'' uses plaintext credentials" && - test_must_fail git -c transfer.credentialsInUrl=allow clone https://username:password@localhost attempt1 2>err && - ! grep "$message" err && - - test_must_fail git -c transfer.credentialsInUrl=warn clone https://username:password@localhost attempt2 2>err && - grep "warning: $message" err >warnings && - test_line_count = 2 warnings && - - test_must_fail git -c transfer.credentialsInUrl=die clone https://username:password@localhost attempt3 2>err && - grep "fatal: $message" err >warnings && - test_line_count = 1 warnings && - - test_must_fail git -c transfer.credentialsInUrl=die clone https://username:@localhost attempt3 2>err && - grep "fatal: $message" err >warnings && - test_line_count = 1 warnings -' - -test_expect_success LIBCURL 'clone does not detect username:password when it is https://username@domain:port/' ' - test_must_fail git -c transfer.credentialsInUrl=warn clone https://username@localhost:8080 attempt3 2>err && - ! grep "uses plaintext credentials" err -' - test_expect_success 'clone from hooks' ' test_create_repo r0 && @@ -743,7 +720,11 @@ test_expect_success 'batch missing blob request during checkout' ' # Ensure that there is only one negotiation by checking that there is # only "done" line sent. ("done" marks the end of negotiation.) - GIT_TRACE_PACKET="$(pwd)/trace" git -C client checkout HEAD^ && + GIT_TRACE_PACKET="$(pwd)/trace" \ + GIT_TRACE2_EVENT="$(pwd)/trace2_event" \ + git -C client -c trace2.eventNesting=5 checkout HEAD^ && + grep \"key\":\"total_rounds\",\"value\":\"1\" trace2_event >trace_lines && + test_line_count = 1 trace_lines && grep "fetch> done" trace >done_lines && test_line_count = 1 done_lines ' @@ -763,6 +744,7 @@ test_expect_success 'batch missing blob request does not inadvertently try to fe echo aa >server/a && echo bb >server/b && # Also add a gitlink pointing to an arbitrary repository + test_config_global protocol.file.allow always && git -C server submodule add "$(pwd)/repo_for_submodule" c && git -C server add a b c && git -C server commit -m x && diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh index 24340e6d56..2734e37e88 100755 --- a/t/t5604-clone-reference.sh +++ b/t/t5604-clone-reference.sh @@ -303,8 +303,6 @@ test_expect_success SYMLINKS 'setup repo with manually symlinked or unknown file ln -s ../an-object $obj && cd ../ && - find . -type f | sort >../../../T.objects-files.raw && - find . -type l | sort >../../../T.objects-symlinks.raw && echo unknown_content >unknown_file ) && git -C T fsck && @@ -313,19 +311,27 @@ test_expect_success SYMLINKS 'setup repo with manually symlinked or unknown file test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at objects/' ' - for option in --local --no-hardlinks --shared --dissociate + # None of these options work when cloning locally, since T has + # symlinks in its `$GIT_DIR/objects` directory + for option in --local --no-hardlinks --dissociate do - git clone $option T T$option || return 1 && - git -C T$option fsck || return 1 && - git -C T$option rev-list --all --objects >T$option.objects && - test_cmp T.objects T$option.objects && - ( - cd T$option/.git/objects && - find . -type f | sort >../../../T$option.objects-files.raw && - find . -type l | sort >../../../T$option.objects-symlinks.raw - ) + test_must_fail git clone $option T T$option 2>err || return 1 && + test_i18ngrep "symlink.*exists" err || return 1 done && + # But `--shared` clones should still work, even when specifying + # a local path *and* that repository has symlinks present in its + # `$GIT_DIR/objects` directory. + git clone --shared T T--shared && + git -C T--shared fsck && + git -C T--shared rev-list --all --objects >T--shared.objects && + test_cmp T.objects T--shared.objects && + ( + cd T--shared/.git/objects && + find . -type f | sort >../../../T--shared.objects-files.raw && + find . -type l | sort >../../../T--shared.objects-symlinks.raw + ) && + for raw in $(ls T*.raw) do sed -e "s!/../!/Y/!; s![0-9a-f]\{38,\}!Z!" -e "/commit-graph/d" \ @@ -333,26 +339,6 @@ test_expect_success SYMLINKS 'clone repo with symlinked or unknown files at obje sort $raw.de-sha-1 >$raw.de-sha || return 1 done && - cat >expected-files <<-EOF && - ./Y/Z - ./Y/Z - ./Y/Z - ./a-loose-dir/Z - ./an-object - ./info/packs - ./pack/pack-Z.idx - ./pack/pack-Z.pack - ./packs/pack-Z.idx - ./packs/pack-Z.pack - ./unknown_file - EOF - - for option in --local --no-hardlinks --dissociate - do - test_cmp expected-files T$option.objects-files.raw.de-sha || return 1 && - test_must_be_empty T$option.objects-symlinks.raw.de-sha || return 1 - done && - echo ./info/alternates >expected-files && test_cmp expected-files T--shared.objects-files.raw && test_must_be_empty T--shared.objects-symlinks.raw diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh index 8f676d6b0c..cf221e92c4 100755 --- a/t/t5606-clone-options.sh +++ b/t/t5606-clone-options.sh @@ -42,11 +42,12 @@ test_expect_success 'rejects invalid -o/--origin' ' ' -test_expect_success 'disallows --bare with --origin' ' +test_expect_success 'clone --bare -o' ' - test_must_fail git clone -o foo --bare parent clone-bare-o 2>err && - test_debug "cat err" && - test_i18ngrep -e "options .--bare. and .--origin foo. cannot be used together" err + git clone -o foo --bare parent clone-bare-o && + (cd parent && pwd) >expect && + git -C clone-bare-o config remote.foo.url >actual && + test_cmp expect actual ' @@ -58,6 +59,14 @@ test_expect_success 'disallows --bare with --separate-git-dir' ' ' +test_expect_success 'disallows --bundle-uri with shallow options' ' + for option in --depth=1 --shallow-since=01-01-2000 --shallow-exclude=HEAD + do + test_must_fail git clone --bundle-uri=bundle $option from to 2>err && + grep "bundle-uri is incompatible" err || return 1 + done +' + test_expect_success 'reject cloning shallow repository' ' test_when_finished "rm -rf repo" && test_must_fail git clone --reject-shallow shallow-repo out 2>err && diff --git a/t/t5614-clone-submodules-shallow.sh b/t/t5614-clone-submodules-shallow.sh index 5504b519c7..0c85ef834a 100755 --- a/t/t5614-clone-submodules-shallow.sh +++ b/t/t5614-clone-submodules-shallow.sh @@ -24,6 +24,7 @@ test_expect_success 'setup' ' test_expect_success 'nonshallow clone implies nonshallow submodule' ' test_when_finished "rm -rf super_clone" && + test_config_global protocol.file.allow always && git clone --recurse-submodules "file://$pwd/." super_clone && git -C super_clone log --oneline >lines && test_line_count = 3 lines && @@ -33,6 +34,7 @@ test_expect_success 'nonshallow clone implies nonshallow submodule' ' test_expect_success 'shallow clone with shallow submodule' ' test_when_finished "rm -rf super_clone" && + test_config_global protocol.file.allow always && git clone --recurse-submodules --depth 2 --shallow-submodules "file://$pwd/." super_clone && git -C super_clone log --oneline >lines && test_line_count = 2 lines && @@ -42,6 +44,7 @@ test_expect_success 'shallow clone with shallow submodule' ' test_expect_success 'shallow clone does not imply shallow submodule' ' test_when_finished "rm -rf super_clone" && + test_config_global protocol.file.allow always && git clone --recurse-submodules --depth 2 "file://$pwd/." super_clone && git -C super_clone log --oneline >lines && test_line_count = 2 lines && @@ -51,6 +54,7 @@ test_expect_success 'shallow clone does not imply shallow submodule' ' test_expect_success 'shallow clone with non shallow submodule' ' test_when_finished "rm -rf super_clone" && + test_config_global protocol.file.allow always && git clone --recurse-submodules --depth 2 --no-shallow-submodules "file://$pwd/." super_clone && git -C super_clone log --oneline >lines && test_line_count = 2 lines && @@ -60,6 +64,7 @@ test_expect_success 'shallow clone with non shallow submodule' ' test_expect_success 'non shallow clone with shallow submodule' ' test_when_finished "rm -rf super_clone" && + test_config_global protocol.file.allow always && git clone --recurse-submodules --no-local --shallow-submodules "file://$pwd/." super_clone && git -C super_clone log --oneline >lines && test_line_count = 3 lines && @@ -69,6 +74,7 @@ test_expect_success 'non shallow clone with shallow submodule' ' test_expect_success 'clone follows shallow recommendation' ' test_when_finished "rm -rf super_clone" && + test_config_global protocol.file.allow always && git config -f .gitmodules submodule.sub.shallow true && git add .gitmodules && git commit -m "recommend shallow for sub" && @@ -87,6 +93,7 @@ test_expect_success 'clone follows shallow recommendation' ' test_expect_success 'get unshallow recommended shallow submodule' ' test_when_finished "rm -rf super_clone" && + test_config_global protocol.file.allow always && git clone --no-local "file://$pwd/." super_clone && ( cd super_clone && @@ -103,6 +110,7 @@ test_expect_success 'get unshallow recommended shallow submodule' ' test_expect_success 'clone follows non shallow recommendation' ' test_when_finished "rm -rf super_clone" && + test_config_global protocol.file.allow always && git config -f .gitmodules submodule.sub.shallow false && git add .gitmodules && git commit -m "recommend non shallow for sub" && diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 4a3778d04a..037941b95d 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -49,6 +49,13 @@ test_expect_success 'do partial clone 1' ' test "$(git -C pc1 config --local remote.origin.partialclonefilter)" = "blob:none" ' +test_expect_success 'rev-list --missing=allow-promisor on partial clone' ' + git -C pc1 rev-list --objects --missing=allow-promisor HEAD >actual && + git -C pc1 rev-list --objects --missing=print HEAD >expect.raw && + grep -v "^?" expect.raw >expect && + test_cmp expect actual +' + test_expect_success 'verify that .promisor file contains refs fetched' ' ls pc1/.git/objects/pack/pack-*.promisor >promisorlist && test_line_count = 1 promisorlist && @@ -253,6 +260,8 @@ test_expect_success 'partial clone with transfer.fsckobjects=1 works with submod test_config -C src_with_sub uploadpack.allowfilter 1 && test_config -C src_with_sub uploadpack.allowanysha1inwant 1 && + test_config_global protocol.file.allow always && + git -C src_with_sub submodule add "file://$(pwd)/submodule" mysub && git -C src_with_sub commit -m "commit with submodule" && diff --git a/t/t5617-clone-submodules-remote.sh b/t/t5617-clone-submodules-remote.sh index ca8f80083a..6884338249 100755 --- a/t/t5617-clone-submodules-remote.sh +++ b/t/t5617-clone-submodules-remote.sh @@ -10,6 +10,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME pwd=$(pwd) test_expect_success 'setup' ' + git config --global protocol.file.allow always && git checkout -b main && test_commit commit1 && mkdir sub && diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 5d42a355a8..b33cd4afca 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -1001,7 +1001,7 @@ test_expect_success 'part of packfile response provided as URI' ' do git verify-pack --object-format=$(test_oid algo) --verbose $idx >out && { - grep "^[0-9a-f]\{16,\} " out || : + grep -E "^[0-9a-f]{16,} " out || : } >out.objectlist && if test_line_count = 1 out.objectlist then diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh index 9d6cd7d986..df74f80061 100755 --- a/t/t5703-upload-pack-ref-in-want.sh +++ b/t/t5703-upload-pack-ref-in-want.sh @@ -229,14 +229,16 @@ test_expect_success 'setup repos for fetching with ref-in-want tests' ' ' test_expect_success 'fetching with exact OID' ' - test_when_finished "rm -f log" && + test_when_finished "rm -f log trace2" && rm -rf local && cp -r "$LOCAL_PRISTINE" local && oid=$(git -C "$REPO" rev-parse d) && - GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \ + GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE2_EVENT="$(pwd)/trace2" \ + git -C local fetch origin \ "$oid":refs/heads/actual && + grep \"key\":\"total_rounds\",\"value\":\"2\" trace2 && git -C "$REPO" rev-parse "d" >expected && git -C local rev-parse refs/heads/actual >actual && test_cmp expected actual && diff --git a/t/t5750-bundle-uri-parse.sh b/t/t5750-bundle-uri-parse.sh new file mode 100755 index 0000000000..c2fe3f9c5a --- /dev/null +++ b/t/t5750-bundle-uri-parse.sh @@ -0,0 +1,171 @@ +#!/bin/sh + +test_description="Test bundle-uri bundle_uri_parse_line()" + +TEST_NO_CREATE_REPO=1 +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'bundle_uri_parse_line() just URIs' ' + cat >in <<-\EOF && + bundle.one.uri=http://example.com/bundle.bdl + bundle.two.uri=https://example.com/bundle.bdl + bundle.three.uri=file:///usr/share/git/bundle.bdl + EOF + + cat >expect <<-\EOF && + [bundle] + version = 1 + mode = all + [bundle "one"] + uri = http://example.com/bundle.bdl + [bundle "two"] + uri = https://example.com/bundle.bdl + [bundle "three"] + uri = file:///usr/share/git/bundle.bdl + EOF + + test-tool bundle-uri parse-key-values in >actual 2>err && + test_must_be_empty err && + test_cmp_config_output expect actual +' + +test_expect_success 'bundle_uri_parse_line() parsing edge cases: empty key or value' ' + cat >in <<-\EOF && + =bogus-value + bogus-key= + EOF + + cat >err.expect <<-EOF && + error: bundle-uri: line has empty key or value + error: bad line: '\''=bogus-value'\'' + error: bundle-uri: line has empty key or value + error: bad line: '\''bogus-key='\'' + EOF + + cat >expect <<-\EOF && + [bundle] + version = 1 + mode = all + EOF + + test_must_fail test-tool bundle-uri parse-key-values in >actual 2>err && + test_cmp err.expect err && + test_cmp_config_output expect actual +' + +test_expect_success 'bundle_uri_parse_line() parsing edge cases: empty lines' ' + cat >in <<-\EOF && + bundle.one.uri=http://example.com/bundle.bdl + + bundle.two.uri=https://example.com/bundle.bdl + + bundle.three.uri=file:///usr/share/git/bundle.bdl + EOF + + cat >err.expect <<-\EOF && + error: bundle-uri: got an empty line + error: bad line: '\'''\'' + error: bundle-uri: got an empty line + error: bad line: '\'''\'' + EOF + + # We fail, but try to continue parsing regardless + cat >expect <<-\EOF && + [bundle] + version = 1 + mode = all + [bundle "one"] + uri = http://example.com/bundle.bdl + [bundle "two"] + uri = https://example.com/bundle.bdl + [bundle "three"] + uri = file:///usr/share/git/bundle.bdl + EOF + + test_must_fail test-tool bundle-uri parse-key-values in >actual 2>err && + test_cmp err.expect err && + test_cmp_config_output expect actual +' + +test_expect_success 'bundle_uri_parse_line() parsing edge cases: duplicate lines' ' + cat >in <<-\EOF && + bundle.one.uri=http://example.com/bundle.bdl + bundle.two.uri=https://example.com/bundle.bdl + bundle.one.uri=https://example.com/bundle-2.bdl + bundle.three.uri=file:///usr/share/git/bundle.bdl + EOF + + cat >err.expect <<-\EOF && + error: bad line: '\''bundle.one.uri=https://example.com/bundle-2.bdl'\'' + EOF + + # We fail, but try to continue parsing regardless + cat >expect <<-\EOF && + [bundle] + version = 1 + mode = all + [bundle "one"] + uri = http://example.com/bundle.bdl + [bundle "two"] + uri = https://example.com/bundle.bdl + [bundle "three"] + uri = file:///usr/share/git/bundle.bdl + EOF + + test_must_fail test-tool bundle-uri parse-key-values in >actual 2>err && + test_cmp err.expect err && + test_cmp_config_output expect actual +' + +test_expect_success 'parse config format: just URIs' ' + cat >expect <<-\EOF && + [bundle] + version = 1 + mode = all + [bundle "one"] + uri = http://example.com/bundle.bdl + [bundle "two"] + uri = https://example.com/bundle.bdl + [bundle "three"] + uri = file:///usr/share/git/bundle.bdl + EOF + + test-tool bundle-uri parse-config expect >actual 2>err && + test_must_be_empty err && + test_cmp_config_output expect actual +' + +test_expect_success 'parse config format edge cases: empty key or value' ' + cat >in1 <<-\EOF && + = bogus-value + EOF + + cat >err1 <<-EOF && + error: bad config line 1 in file in1 + EOF + + cat >expect <<-\EOF && + [bundle] + version = 1 + mode = all + EOF + + test_must_fail test-tool bundle-uri parse-config in1 >actual 2>err && + test_cmp err1 err && + test_cmp_config_output expect actual && + + cat >in2 <<-\EOF && + bogus-key = + EOF + + cat >err2 <<-EOF && + error: bad config line 1 in file in2 + EOF + + test_must_fail test-tool bundle-uri parse-config in2 >actual 2>err && + test_cmp err2 err && + test_cmp_config_output expect actual +' + +test_done diff --git a/t/t6008-rev-list-submodule.sh b/t/t6008-rev-list-submodule.sh index 3153a0d891..2cdef6fdf9 100755 --- a/t/t6008-rev-list-submodule.sh +++ b/t/t6008-rev-list-submodule.sh @@ -8,6 +8,7 @@ test_description='git rev-list involving submodules that this repo has' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' @@ -26,7 +27,7 @@ test_expect_success 'setup' ' : > super-file && git add super-file && - git submodule add "$(pwd)" sub && + git -c protocol.file.allow=always submodule add "$(pwd)" sub && git symbolic-ref HEAD refs/heads/super && test_tick && git commit -m super-initial && diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh index af57a04b7f..738da23628 100755 --- a/t/t6019-rev-list-ancestry-path.sh +++ b/t/t6019-rev-list-ancestry-path.sh @@ -8,8 +8,13 @@ test_description='--ancestry-path' # / \ # A-------K---------------L--M # -# D..M == E F G H I J K L M -# --ancestry-path D..M == E F H I J L M +# D..M == E F G H I J K L M +# --ancestry-path D..M == E F H I J L M +# --ancestry-path=F D..M == E F J L M +# --ancestry-path=G D..M == G H I J L M +# --ancestry-path=H D..M == E G H I J L M +# --ancestry-path=K D..M == K L M +# --ancestry-path=K --ancestry-path=F D..M == E F J K L M # # D..M -- M.t == M # --ancestry-path D..M -- M.t == M @@ -50,73 +55,41 @@ test_expect_success setup ' test_commit M ' -test_expect_success 'rev-list D..M' ' - test_write_lines E F G H I J K L M >expect && - git rev-list --format=%s D..M | - sed -e "/^commit /d" | - sort >actual && - test_cmp expect actual -' - -test_expect_success 'rev-list --ancestry-path D..M' ' - test_write_lines E F H I J L M >expect && - git rev-list --ancestry-path --format=%s D..M | - sed -e "/^commit /d" | - sort >actual && - test_cmp expect actual -' - -test_expect_success 'rev-list D..M -- M.t' ' - echo M >expect && - git rev-list --format=%s D..M -- M.t | - sed -e "/^commit /d" >actual && - test_cmp expect actual -' - -test_expect_success 'rev-list --ancestry-path D..M -- M.t' ' - echo M >expect && - git rev-list --ancestry-path --format=%s D..M -- M.t | - sed -e "/^commit /d" >actual && - test_cmp expect actual -' +test_ancestry () { + args=$1 + expected=$2 + test_expect_success "log $args" " + test_write_lines $expected >expect && + git log --format=%s $args >raw && + + if test -n \"$expected\" + then + sort raw >actual && + test_cmp expect actual + else + test_must_be_empty raw + fi + " +} -test_expect_success 'rev-list F...I' ' - test_write_lines F G H I >expect && - git rev-list --format=%s F...I | - sed -e "/^commit /d" | - sort >actual && - test_cmp expect actual -' +test_ancestry "D..M" "E F G H I J K L M" -test_expect_success 'rev-list --ancestry-path F...I' ' - test_write_lines F H I >expect && - git rev-list --ancestry-path --format=%s F...I | - sed -e "/^commit /d" | - sort >actual && - test_cmp expect actual -' +test_ancestry "--ancestry-path D..M" "E F H I J L M" +test_ancestry "--ancestry-path=F D..M" "E F J L M" +test_ancestry "--ancestry-path=G D..M" "G H I J L M" +test_ancestry "--ancestry-path=H D..M" "E G H I J L M" +test_ancestry "--ancestry-path=K D..M" "K L M" +test_ancestry "--ancestry-path=F --ancestry-path=K D..M" "E F J K L M" -# G.t is dropped in an "-s ours" merge -test_expect_success 'rev-list G..M -- G.t' ' - git rev-list --format=%s G..M -- G.t | - sed -e "/^commit /d" >actual && - test_must_be_empty actual -' +test_ancestry "D..M -- M.t" "M" +test_ancestry "--ancestry-path D..M -- M.t" "M" -test_expect_success 'rev-list --ancestry-path G..M -- G.t' ' - echo L >expect && - git rev-list --ancestry-path --format=%s G..M -- G.t | - sed -e "/^commit /d" >actual && - test_cmp expect actual -' +test_ancestry "F...I" "F G H I" +test_ancestry "--ancestry-path F...I" "F H I" -test_expect_success 'rev-list --ancestry-path --simplify-merges G^..M -- G.t' ' - test_write_lines G L >expect && - git rev-list --ancestry-path --simplify-merges --format=%s G^..M -- G.t | - sed -e "/^commit /d" | - sort >actual && - test_cmp expect actual -' +test_ancestry "G..M -- G.t" "" +test_ancestry "--ancestry-path G..M -- G.t" "L" +test_ancestry "--ancestry-path --simplify-merges G^..M -- G.t" "G L" # b---bc # / \ / diff --git a/t/t6102-rev-list-unexpected-objects.sh b/t/t6102-rev-list-unexpected-objects.sh index cf0195e826..4a9a4436e2 100755 --- a/t/t6102-rev-list-unexpected-objects.sh +++ b/t/t6102-rev-list-unexpected-objects.sh @@ -17,7 +17,7 @@ test_expect_success 'setup unexpected non-blob entry' ' broken_tree="$(git hash-object -w --literally -t tree broken-tree)" ' -test_expect_success !SANITIZE_LEAK 'TODO (should fail!): traverse unexpected non-blob entry (lone)' ' +test_expect_success 'TODO (should fail!): traverse unexpected non-blob entry (lone)' ' sed "s/Z$//" >expect <<-EOF && $broken_tree Z $tree foo @@ -121,7 +121,7 @@ test_expect_success 'setup unexpected non-blob tag' ' tag=$(git hash-object -w --literally -t tag broken-tag) ' -test_expect_success !SANITIZE_LEAK 'TODO (should fail!): traverse unexpected non-blob tag (lone)' ' +test_expect_success 'TODO (should fail!): traverse unexpected non-blob tag (lone)' ' git rev-list --objects $tag ' diff --git a/t/t6115-rev-list-du.sh b/t/t6115-rev-list-du.sh index b4aef32b71..d59111dede 100755 --- a/t/t6115-rev-list-du.sh +++ b/t/t6115-rev-list-du.sh @@ -48,4 +48,26 @@ check_du HEAD check_du --objects HEAD check_du --objects HEAD^..HEAD +# As mentioned above, don't use hardcode sizes as actual size, but use the +# output from git cat-file. +test_expect_success 'rev-list --disk-usage=human' ' + git rev-list --objects HEAD --disk-usage=human >actual && + disk_usage_slow --objects HEAD >actual_size && + grep "$(cat actual_size) bytes" actual +' + +test_expect_success 'rev-list --disk-usage=human with bitmaps' ' + git rev-list --objects HEAD --use-bitmap-index --disk-usage=human >actual && + disk_usage_slow --objects HEAD >actual_size && + grep "$(cat actual_size) bytes" actual +' + +test_expect_success 'rev-list use --disk-usage unproperly' ' + test_must_fail git rev-list --objects HEAD --disk-usage=typo 2>err && + cat >expect <<-\EOF && + fatal: invalid value for '\''--disk-usage=<format>'\'': '\''typo'\'', the only allowed format is '\''human'\'' + EOF + test_cmp err expect +' + test_done diff --git a/t/t6132-pathspec-exclude.sh b/t/t6132-pathspec-exclude.sh index 9fdafeb1e9..cada952f9a 100755 --- a/t/t6132-pathspec-exclude.sh +++ b/t/t6132-pathspec-exclude.sh @@ -293,7 +293,11 @@ test_expect_success 'add with all negative' ' test_cmp expect actual ' -test_expect_success 'add -p with all negative' ' +test_lazy_prereq ADD_I_USE_BUILTIN_OR_PERL ' + test_have_prereq ADD_I_USE_BUILTIN || test_have_prereq PERL +' + +test_expect_success ADD_I_USE_BUILTIN_OR_PERL 'add -p with all negative' ' H=$(git rev-parse HEAD) && git reset --hard $H && git clean -f && diff --git a/t/t6134-pathspec-in-submodule.sh b/t/t6134-pathspec-in-submodule.sh index 0f1cb49ced..3214d9db97 100755 --- a/t/t6134-pathspec-in-submodule.sh +++ b/t/t6134-pathspec-in-submodule.sh @@ -2,6 +2,7 @@ test_description='test case exclude pathspec' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup a submodule' ' @@ -9,7 +10,7 @@ test_expect_success 'setup a submodule' ' : >pretzel/a && git -C pretzel add a && git -C pretzel commit -m "add a file" -- a && - git submodule add ./pretzel sub && + git -c protocol.file.allow=always submodule add ./pretzel sub && git commit -a -m "add submodule" && git submodule deinit --all ' diff --git a/t/t6400-merge-df.sh b/t/t6400-merge-df.sh index 57a67cf362..3de4ef6bd9 100755 --- a/t/t6400-merge-df.sh +++ b/t/t6400-merge-df.sh @@ -126,7 +126,7 @@ test_expect_success 'Simple merge in repo with interesting pathnames' ' # foo/bar-2/baz # The fact that foo/bar-2 appears between foo/bar and foo/bar/baz # can trip up some codepaths, and is the point of this test. - test_create_repo name-ordering && + git init name-ordering && ( cd name-ordering && diff --git a/t/t6404-recursive-merge.sh b/t/t6404-recursive-merge.sh index b8735c6db4..36215518b6 100755 --- a/t/t6404-recursive-merge.sh +++ b/t/t6404-recursive-merge.sh @@ -4,6 +4,7 @@ test_description='Test merge without common ancestors' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # This scenario is based on a real-world repository of Shawn Pearce. diff --git a/t/t6405-merge-symlinks.sh b/t/t6405-merge-symlinks.sh index 7435fce71e..29e2b25ce5 100755 --- a/t/t6405-merge-symlinks.sh +++ b/t/t6405-merge-symlinks.sh @@ -11,6 +11,7 @@ if core.symlinks is false.' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh index 99abefd44b..8650a88c40 100755 --- a/t/t6406-merge-attr.sh +++ b/t/t6406-merge-attr.sh @@ -162,8 +162,8 @@ test_expect_success 'custom merge backend' ' ' test_expect_success 'up-to-date merge without common ancestor' ' - test_create_repo repo1 && - test_create_repo repo2 && + git init repo1 && + git init repo2 && test_tick && ( cd repo1 && diff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh index 0753fc95f4..e8a28717ce 100755 --- a/t/t6407-merge-binary.sh +++ b/t/t6407-merge-binary.sh @@ -5,7 +5,6 @@ test_description='ask merge-recursive to merge binary files' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME -TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t6408-merge-up-to-date.sh b/t/t6408-merge-up-to-date.sh index 7763c1ba98..8a1ba6d23a 100755 --- a/t/t6408-merge-up-to-date.sh +++ b/t/t6408-merge-up-to-date.sh @@ -2,6 +2,7 @@ test_description='merge fast-forward and up to date' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t6411-merge-filemode.sh b/t/t6411-merge-filemode.sh index 6ae2489286..b6182723aa 100755 --- a/t/t6411-merge-filemode.sh +++ b/t/t6411-merge-filemode.sh @@ -4,6 +4,7 @@ test_description='merge: handle file mode' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'set up mode change in one branch' ' diff --git a/t/t6413-merge-crlf.sh b/t/t6413-merge-crlf.sh index affea255fe..b4f4a313f4 100755 --- a/t/t6413-merge-crlf.sh +++ b/t/t6413-merge-crlf.sh @@ -11,6 +11,7 @@ test_description='merge conflict in crlf repo GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t6416-recursive-corner-cases.sh b/t/t6416-recursive-corner-cases.sh index 690c8482b1..17b54d625d 100755 --- a/t/t6416-recursive-corner-cases.sh +++ b/t/t6416-recursive-corner-cases.sh @@ -19,7 +19,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME # test_expect_success 'setup basic criss-cross + rename with no modifications' ' - test_create_repo basic-rename && + git init basic-rename && ( cd basic-rename && @@ -85,7 +85,7 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' ' # test_expect_success 'setup criss-cross + rename merges with basic modification' ' - test_create_repo rename-modify && + git init rename-modify && ( cd rename-modify && @@ -160,7 +160,7 @@ test_expect_success 'merge criss-cross + rename merges with basic modification' # test_expect_success 'setup differently handled merges of rename/add conflict' ' - test_create_repo rename-add && + git init rename-add && ( cd rename-add && @@ -324,7 +324,7 @@ test_expect_success 'git detects differently handled merges conflict, swapped' ' # Merging commits D & E should result in modify/delete conflict. test_expect_success 'setup criss-cross + modify/delete resolved differently' ' - test_create_repo modify-delete && + git init modify-delete && ( cd modify-delete && @@ -499,7 +499,7 @@ test_expect_success 'git detects conflict merging criss-cross+modify/delete, rev # test_expect_success 'setup differently handled merges of directory/file conflict' ' - test_create_repo directory-file && + git init directory-file && ( cd directory-file && @@ -867,7 +867,7 @@ test_expect_failure 'merge of D2 & E4 merges a2s & reports conflict for a/file' # but that may cancel out at the final merge stage". test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' - test_create_repo rename-squared-squared && + git init rename-squared-squared && ( cd rename-squared-squared && @@ -944,7 +944,7 @@ test_expect_success 'handle rename/rename(1to2)/modify followed by what looks li # content merge handled. test_expect_success 'setup criss-cross + rename/rename/add-source + modify/modify' ' - test_create_repo rename-rename-add-source && + git init rename-rename-add-source && ( cd rename-rename-add-source && @@ -1032,7 +1032,7 @@ test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' # base of B & C needs to not delete B:c for that to work, though... test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' ' - test_create_repo rename-rename-add-dest && + git init rename-rename-add-dest && ( cd rename-rename-add-dest && @@ -1111,7 +1111,7 @@ test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' ' # git detect it? test_expect_success 'setup symlink modify/modify' ' - test_create_repo symlink-modify-modify && + git init symlink-modify-modify && ( cd symlink-modify-modify && @@ -1178,7 +1178,7 @@ test_expect_merge_algorithm failure success 'check symlink modify/modify' ' # git detect it? test_expect_success 'setup symlink add/add' ' - test_create_repo symlink-add-add && + git init symlink-add-add && ( cd symlink-add-add && @@ -1244,11 +1244,11 @@ test_expect_merge_algorithm failure success 'check symlink add/add' ' # git detect it? test_expect_success 'setup submodule modify/modify' ' - test_create_repo submodule-modify-modify && + git init submodule-modify-modify && ( cd submodule-modify-modify && - test_create_repo submod && + git init submod && ( cd submod && touch file-A && @@ -1332,11 +1332,11 @@ test_expect_merge_algorithm failure success 'check submodule modify/modify' ' # git detect it? test_expect_success 'setup submodule add/add' ' - test_create_repo submodule-add-add && + git init submodule-add-add && ( cd submodule-add-add && - test_create_repo submod && + git init submod && ( cd submod && touch file-A && @@ -1419,11 +1419,11 @@ test_expect_merge_algorithm failure success 'check submodule add/add' ' # This is an obvious add/add conflict for 'path'. Can git detect it? test_expect_success 'setup conflicting entry types (submodule vs symlink)' ' - test_create_repo submodule-symlink-add-add && + git init submodule-symlink-add-add && ( cd submodule-symlink-add-add && - test_create_repo path && + git init path && ( cd path && touch file-B && @@ -1494,7 +1494,7 @@ test_expect_merge_algorithm failure success 'check conflicting entry types (subm # This is an obvious add/add mode conflict. Can git detect it? test_expect_success 'setup conflicting modes for regular file' ' - test_create_repo regular-file-mode-conflict && + git init regular-file-mode-conflict && ( cd regular-file-mode-conflict && @@ -1571,7 +1571,7 @@ test_expect_failure 'check conflicting modes for regular file' ' # to ensure that we handle it as well as practical. test_expect_success 'setup nested conflicts' ' - test_create_repo nested_conflicts && + git init nested_conflicts && ( cd nested_conflicts && @@ -1757,7 +1757,7 @@ test_expect_success 'check nested conflicts' ' # have three levels of conflict markers. Can we distinguish all three? test_expect_success 'setup virtual merge base with nested conflicts' ' - test_create_repo virtual_merge_base_has_nested_conflicts && + git init virtual_merge_base_has_nested_conflicts && ( cd virtual_merge_base_has_nested_conflicts && diff --git a/t/t6421-merge-partial-clone.sh b/t/t6421-merge-partial-clone.sh index 36bcd7c328..5413e5dd9d 100755 --- a/t/t6421-merge-partial-clone.sh +++ b/t/t6421-merge-partial-clone.sh @@ -31,7 +31,7 @@ test_description="limiting blob downloads when merging with partial clones" test_setup_repo () { test -d server && return - test_create_repo server && + git init server && ( cd server && diff --git a/t/t6422-merge-rename-corner-cases.sh b/t/t6422-merge-rename-corner-cases.sh index 9b65768aed..346253c7c8 100755 --- a/t/t6422-merge-rename-corner-cases.sh +++ b/t/t6422-merge-rename-corner-cases.sh @@ -11,7 +11,7 @@ TEST_PASSES_SANITIZE_LEAK=true . "$TEST_DIRECTORY"/lib-merge.sh test_setup_rename_delete_untracked () { - test_create_repo rename-delete-untracked && + git init rename-delete-untracked && ( cd rename-delete-untracked && @@ -56,7 +56,7 @@ test_expect_success "Does git preserve Gollum's precious artifact?" ' # We should be able to merge B & C cleanly test_setup_rename_modify_add_source () { - test_create_repo rename-modify-add-source && + git init rename-modify-add-source && ( cd rename-modify-add-source && @@ -96,7 +96,7 @@ test_expect_failure 'rename/modify/add-source conflict resolvable' ' ' test_setup_break_detection_1 () { - test_create_repo break-detection-1 && + git init break-detection-1 && ( cd break-detection-1 && @@ -144,7 +144,7 @@ test_expect_failure 'conflict caused if rename not detected' ' ' test_setup_break_detection_2 () { - test_create_repo break-detection-2 && + git init break-detection-2 && ( cd break-detection-2 && @@ -192,7 +192,7 @@ test_expect_failure 'missed conflict if rename not detected' ' # Commit C: rename a->b, add unrelated a test_setup_break_detection_3 () { - test_create_repo break-detection-3 && + git init break-detection-3 && ( cd break-detection-3 && @@ -268,7 +268,7 @@ test_expect_failure 'detect rename/add-source and preserve all data, merge other ' test_setup_rename_directory () { - test_create_repo rename-directory-$1 && + git init rename-directory-$1 && ( cd rename-directory-$1 && @@ -386,7 +386,7 @@ test_expect_success 'rename/directory conflict + content merge conflict' ' ' test_setup_rename_directory_2 () { - test_create_repo rename-directory-2 && + git init rename-directory-2 && ( cd rename-directory-2 && @@ -445,7 +445,7 @@ test_expect_success 'disappearing dir in rename/directory conflict handled' ' # Commit B: modify a, add different b test_setup_rename_with_content_merge_and_add () { - test_create_repo rename-with-content-merge-and-add-$1 && + git init rename-with-content-merge-and-add-$1 && ( cd rename-with-content-merge-and-add-$1 && @@ -570,7 +570,7 @@ test_expect_success 'handle rename-with-content-merge vs. add, merge other way' # * Nothing else should be present. Is anything? test_setup_rename_rename_2to1 () { - test_create_repo rename-rename-2to1 && + git init rename-rename-2to1 && ( cd rename-rename-2to1 && @@ -642,7 +642,7 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' ' # Commit B: rename a->b # Commit C: rename a->c test_setup_rename_rename_1to2 () { - test_create_repo rename-rename-1to2 && + git init rename-rename-1to2 && ( cd rename-rename-1to2 && @@ -700,7 +700,7 @@ test_expect_success 'merge has correct working tree contents' ' # Merging of B & C should NOT be clean; there's a rename/rename conflict test_setup_rename_rename_1to2_add_source_1 () { - test_create_repo rename-rename-1to2-add-source-1 && + git init rename-rename-1to2-add-source-1 && ( cd rename-rename-1to2-add-source-1 && @@ -748,7 +748,7 @@ test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' ' test_setup_rename_rename_1to2_add_source_2 () { - test_create_repo rename-rename-1to2-add-source-2 && + git init rename-rename-1to2-add-source-2 && ( cd rename-rename-1to2-add-source-2 && @@ -794,7 +794,7 @@ test_expect_failure 'rename/rename/add-source still tracks new a file' ' ' test_setup_rename_rename_1to2_add_dest () { - test_create_repo rename-rename-1to2-add-dest && + git init rename-rename-1to2-add-dest && ( cd rename-rename-1to2-add-dest && @@ -874,7 +874,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting # Expected: CONFLICT (rename/add/delete), two-way merged bar test_setup_rad () { - test_create_repo rad && + git init rad && ( cd rad && echo "original file" >foo && @@ -946,7 +946,7 @@ test_expect_merge_algorithm failure success 'rad-check: rename/add/delete confli # Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz test_setup_rrdd () { - test_create_repo rrdd && + git init rrdd && ( cd rrdd && echo foo >foo && @@ -1022,7 +1022,7 @@ test_expect_merge_algorithm failure success 'rrdd-check: rename/rename(2to1)/del # multi-way merged contents found in two, four, six test_setup_mod6 () { - test_create_repo mod6 && + git init mod6 && ( cd mod6 && test_seq 11 19 >one && @@ -1160,7 +1160,7 @@ test_conflicts_with_adds_and_renames() { # tree test_setup_collision_conflict () { #test_expect_success "setup simple $sideL/$sideR conflict" ' - test_create_repo simple_${sideL}_${sideR} && + git init simple_${sideL}_${sideR} && ( cd simple_${sideL}_${sideR} && @@ -1308,7 +1308,7 @@ test_conflicts_with_adds_and_renames add add # So, we have four different conflicting files that all end up at path # 'three'. test_setup_nested_conflicts_from_rename_rename () { - test_create_repo nested_conflicts_from_rename_rename && + git init nested_conflicts_from_rename_rename && ( cd nested_conflicts_from_rename_rename && @@ -1417,7 +1417,7 @@ test_expect_success 'check nested conflicts from rename/rename(2to1)' ' # Expected: CONFLICT(rename/rename) message, three unstaged entries in the # index, and contents of orig-[AB] at path orig-[AB] test_setup_rename_rename_1_to_2_binary () { - test_create_repo rename_rename_1_to_2_binary && + git init rename_rename_1_to_2_binary && ( cd rename_rename_1_to_2_binary && diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index 99baf77cbf..944de75b80 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh @@ -40,7 +40,7 @@ test_description="recursive merge with directory renames" # Expected: y/{b,c,d,e/f} test_setup_1a () { - test_create_repo 1a && + git init 1a && ( cd 1a && @@ -106,7 +106,7 @@ test_expect_success '1a: Simple directory rename detection' ' # Expected: y/{b,c,d,e} test_setup_1b () { - test_create_repo 1b && + git init 1b && ( cd 1b && @@ -169,7 +169,7 @@ test_expect_success '1b: Merge a directory with another' ' # Expected: y/{b,c,d} (because x/d -> z/d -> y/d) test_setup_1c () { - test_create_repo 1c && + git init 1c && ( cd 1c && @@ -232,7 +232,7 @@ test_expect_success '1c: Transitive renaming' ' # y/wham_1 & z/wham_2 should too...giving us a conflict. test_setup_1d () { - test_create_repo 1d && + git init 1d && ( cd 1d && @@ -328,7 +328,7 @@ test_expect_success '1d: Directory renames cause a rename/rename(2to1) conflict' # Expected: y/{newb,newc,d} test_setup_1e () { - test_create_repo 1e && + git init 1e && ( cd 1e && @@ -387,7 +387,7 @@ test_expect_success '1e: Renamed directory, with all files being renamed too' ' # Expected: y/{b,c}, x/{d,e,f,g} test_setup_1f () { - test_create_repo 1f && + git init 1f && ( cd 1f && @@ -476,7 +476,7 @@ test_expect_success '1f: Split a directory into two other directories' ' # Commit B: z/{b,c,d} # Expected: y/b, w/c, z/d, with warning about z/ -> (y/ vs. w/) conflict test_setup_2a () { - test_create_repo 2a && + git init 2a && ( cd 2a && @@ -538,7 +538,7 @@ test_expect_success '2a: Directory split into two on one side, with equal number # Commit B: z/{b,c}, x/d # Expected: y/b, w/c, x/d; No warning about z/ -> (y/ vs. w/) conflict test_setup_2b () { - test_create_repo 2b && + git init 2b && ( cd 2b && @@ -620,7 +620,7 @@ test_expect_success '2b: Directory split into two on one side, with equal number # Commit B: y/{b,c}, x/d # Expected: y/{b,c}, x/d test_setup_3a () { - test_create_repo 3a && + git init 3a && ( cd 3a && @@ -684,7 +684,7 @@ test_expect_success '3a: Avoid implicit rename if involved as source on other si # end up with CONFLICT:(z/d -> y/d vs. x/d vs. w/d), i.e. a # rename/rename/rename(1to3) conflict, which is just weird. test_setup_3b () { - test_create_repo 3b && + git init 3b && ( cd 3b && @@ -807,7 +807,7 @@ test_expect_success '3b: Avoid implicit rename if involved as source on current # NOTE: Even though most files from z moved to y, we don't want f to follow. test_setup_4a () { - test_create_repo 4a && + git init 4a && ( cd 4a && @@ -896,7 +896,7 @@ test_expect_success '4a: Directory split, with original directory still present' # index. test_setup_5a () { - test_create_repo 5a && + git init 5a && ( cd 5a && @@ -971,7 +971,7 @@ test_expect_success '5a: Merge directories, other side adds files to original an # back to git behavior without the directory rename detection. test_setup_5b () { - test_create_repo 5b && + git init 5b && ( cd 5b && @@ -1048,7 +1048,7 @@ test_expect_success '5b: Rename/delete in order to get add/add/add conflict' ' # though, because it doesn't have anything in the way. test_setup_5c () { - test_create_repo 5c && + git init 5c && ( cd 5c && @@ -1138,7 +1138,7 @@ test_expect_success '5c: Transitive rename would cause rename/rename/rename/add/ # directory rename detection for z/f -> y/f. test_setup_5d () { - test_create_repo 5d && + git init 5d && ( cd 5d && @@ -1239,7 +1239,7 @@ test_expect_success '5d: Directory/file/file conflict due to directory rename' ' # it is also involved in a rename/delete conflict. test_setup_6a () { - test_create_repo 6a && + git init 6a && ( cd 6a && @@ -1337,7 +1337,7 @@ test_expect_success '6a: Tricky rename/delete' ' # the behavior on testcases 6b2 and 8e, and introduced this 6b1 testcase. test_setup_6b1 () { - test_create_repo 6b1 && + git init 6b1 && ( cd 6b1 && @@ -1415,7 +1415,7 @@ test_expect_merge_algorithm failure success '6b1: Same renames done on both side # the z/ -> y/ rename. test_setup_6b2 () { - test_create_repo 6b2 && + git init 6b2 && ( cd 6b2 && @@ -1479,7 +1479,7 @@ test_expect_merge_algorithm failure success '6b2: Same rename done on both sides # "accidentally detect a rename" and give us y/{b,c,d}. test_setup_6c () { - test_create_repo 6c && + git init 6c && ( cd 6c && @@ -1542,7 +1542,7 @@ test_expect_success '6c: Rename only done on same side' ' # doesn't "accidentally detect a rename" and give us y/{b,c,d}. test_setup_6d () { - test_create_repo 6d && + git init 6d && ( cd 6d && @@ -1605,7 +1605,7 @@ test_expect_success '6d: We do not always want transitive renaming' ' # add/add conflict on y/d_1 vs y/d_2. test_setup_6e () { - test_create_repo 6e && + git init 6e && ( cd 6e && @@ -1700,7 +1700,7 @@ test_expect_success '6e: Add/add from one side' ' # NOTE: There's a rename of z/ here, y/ has more renames, so z/d -> y/d. test_setup_7a () { - test_create_repo 7a && + git init 7a && ( cd 7a && @@ -1772,7 +1772,7 @@ test_expect_success '7a: rename-dir vs. rename-dir (NOT split evenly) PLUS add-o # Expected: y/{b,c}, CONFLICT(rename/rename(2to1): x/d_1, w/d_2 -> y_d) test_setup_7b () { - test_create_repo 7b && + git init 7b && ( cd 7b && @@ -1861,7 +1861,7 @@ test_expect_success '7b: rename/rename(2to1), but only due to transitive rename' # nor CONFLiCT x/d -> w/d vs. y/d vs. z/d) test_setup_7c () { - test_create_repo 7c && + git init 7c && ( cd 7c && @@ -1926,7 +1926,7 @@ test_expect_success '7c: rename/rename(1to...2or3); transitive rename may add co # NOTE: z->y so NOT CONFLICT(delete x/d vs rename to z/d) test_setup_7d () { - test_create_repo 7d && + git init 7d && ( cd 7d && @@ -2027,7 +2027,7 @@ test_expect_success '7d: transitive rename involved in rename/delete; how is it # how it's resolved. test_setup_7e () { - test_create_repo 7e && + git init 7e && ( cd 7e && @@ -2137,7 +2137,7 @@ test_expect_success '7e: transitive rename in rename/delete AND dirs in the way' # we potentially could. test_setup_8a () { - test_create_repo 8a && + git init 8a && ( cd 8a && @@ -2216,7 +2216,7 @@ test_expect_success '8a: Dual-directory rename, one into the others way' ' # e_1 and e_2. test_setup_8b () { - test_create_repo 8b && + git init 8b && ( cd 8b && @@ -2290,7 +2290,7 @@ test_expect_success '8b: Dual-directory rename, one into the others way, with co # notes in 8d. test_setup_8c () { - test_create_repo 8c && + git init 8c && ( cd 8c && @@ -2370,7 +2370,7 @@ test_expect_success '8c: modify/delete or rename+modify/delete' ' # differently. test_setup_8d () { - test_create_repo 8d && + git init 8d && ( cd 8d && @@ -2453,7 +2453,7 @@ test_expect_success '8d: rename/delete...or not?' ' # the behavior, and predict it without computing as many details. test_setup_8e () { - test_create_repo 8e && + git init 8e && ( cd 8e && @@ -2537,7 +2537,7 @@ test_expect_success '8e: Both sides rename, one side adds to original directory' # of that could take the new file in commit B at z/i to x/w/i or x/i. test_setup_9a () { - test_create_repo 9a && + git init 9a && ( cd 9a && @@ -2609,7 +2609,7 @@ test_expect_success '9a: Inner renamed directory within outer renamed directory' # Expected: y/{b,c,d_merged} test_setup_9b () { - test_create_repo 9b && + git init 9b && ( cd 9b && @@ -2697,7 +2697,7 @@ test_expect_success '9b: Transitive rename with content merge' ' # history for any implicit directory renames. test_setup_9c () { - test_create_repo 9c && + git init 9c && ( cd 9c && @@ -2786,7 +2786,7 @@ test_expect_success '9c: Doubly transitive rename?' ' # testcases and simplifies things for the user. test_setup_9d () { - test_create_repo 9d && + git init 9d && ( cd 9d && @@ -2861,7 +2861,7 @@ test_expect_success '9d: N-way transitive rename?' ' # dir1/yo, dir2/yo, dir3/yo, dirN/yo test_setup_9e () { - test_create_repo 9e && + git init 9e && ( cd 9e && @@ -2954,7 +2954,7 @@ test_expect_success '9e: N-to-1 whammo' ' # Expected: priority/{a,b}/$more_files, priority/c test_setup_9f () { - test_create_repo 9f && + git init 9f && ( cd 9f && @@ -3027,7 +3027,7 @@ test_expect_success '9f: Renamed directory that only contained immediate subdirs # viewpoint... test_setup_9g () { - test_create_repo 9g && + git init 9g && ( cd 9g && @@ -3096,7 +3096,7 @@ test_expect_failure '9g: Renamed directory that only contained immediate subdirs # NOTE: If we applied the z/ -> y/ rename to z/d, then we'd end up with # a rename/rename(1to2) conflict (z/d -> y/d vs. x/d) test_setup_9h () { - test_create_repo 9h && + git init 9h && ( cd 9h && @@ -3177,7 +3177,7 @@ test_expect_success '9h: Avoid dir rename on merely modified path' ' # ERROR_MSG(untracked working tree files would be overwritten by merge) test_setup_10a () { - test_create_repo 10a && + git init 10a && ( cd 10a && @@ -3243,7 +3243,7 @@ test_expect_success '10a: Overwrite untracked with normal rename/delete' ' # ERROR_MSG(refusing to lose untracked file at 'y/d') test_setup_10b () { - test_create_repo 10b && + git init 10b && ( cd 10b && @@ -3334,7 +3334,7 @@ test_expect_success '10b: Overwrite untracked with dir rename + delete' ' # ERROR_MSG(Refusing to lose untracked file at y/c) test_setup_10c () { - test_create_repo 10c_$1 && + git init 10c_$1 && ( cd 10c_$1 && @@ -3472,7 +3472,7 @@ test_expect_success '10c2: Overwrite untracked with dir rename/rename(1to2), oth # ERROR_MSG(Refusing to lose untracked file at y/wham) test_setup_10d () { - test_create_repo 10d && + git init 10d && ( cd 10d && @@ -3568,7 +3568,7 @@ test_expect_success '10d: Delete untracked with dir rename/rename(2to1)' ' # Expected: y/{a,b,c} + untracked z/c test_setup_10e () { - test_create_repo 10e && + git init 10e && ( cd 10e && @@ -3650,7 +3650,7 @@ test_expect_merge_algorithm failure success '10e: Does git complain about untrac # z/c with uncommitted mods on top of A:z/c_v1 test_setup_11a () { - test_create_repo 11a && + git init 11a && ( cd 11a && @@ -3728,7 +3728,7 @@ test_expect_success '11a: Avoid losing dirty contents with simple rename' ' test_setup_11b () { - test_create_repo 11b && + git init 11b && ( cd 11b && @@ -3810,7 +3810,7 @@ test_expect_success '11b: Avoid losing dirty file involved in directory rename' # y/c left untouched (still has uncommitted mods) test_setup_11c () { - test_create_repo 11c && + git init 11c && ( cd 11c && @@ -3883,7 +3883,7 @@ test_expect_success '11c: Avoid losing not-uptodate with rename + D/F conflict' # y/{a,c~HEAD,c/d}, x/b, now-untracked z/c_v1 with uncommitted mods test_setup_11d () { - test_create_repo 11d && + git init 11d && ( cd 11d && @@ -3968,7 +3968,7 @@ test_expect_success '11d: Avoid losing not-uptodate with rename + D/F conflict' # y/c has dirty file from before merge test_setup_11e () { - test_create_repo 11e && + git init 11e && ( cd 11e && @@ -4060,7 +4060,7 @@ test_expect_success '11e: Avoid deleting not-uptodate with dir rename/rename(1to # ERROR_MSG(Refusing to lose dirty file at y/wham) test_setup_11f () { - test_create_repo 11f && + git init 11f && ( cd 11f && @@ -4155,7 +4155,7 @@ test_expect_success '11f: Avoid deleting not-uptodate with dir rename/rename(2to # Expected: node1/{leaf1,leaf2,leaf5,node2/{leaf3,leaf4,leaf6}} test_setup_12a () { - test_create_repo 12a && + git init 12a && ( cd 12a && @@ -4238,7 +4238,7 @@ test_expect_success '12a: Moving one directory hierarchy into another' ' # node2/node1/{leaf1, leaf2} test_setup_12b1 () { - test_create_repo 12b1 && + git init 12b1 && ( cd 12b1 && @@ -4327,7 +4327,7 @@ test_expect_merge_algorithm failure success '12b1: Moving two directory hierarch # even simple rules give weird results when given weird inputs. test_setup_12b2 () { - test_create_repo 12b2 && + git init 12b2 && ( cd 12b2 && @@ -4402,7 +4402,7 @@ test_expect_success '12b2: Moving two directory hierarchies into each other' ' # each side of the merge. test_setup_12c1 () { - test_create_repo 12c1 && + git init 12c1 && ( cd 12c1 && @@ -4492,7 +4492,7 @@ test_expect_merge_algorithm failure success '12c1: Moving one directory hierarch # on each side of the merge. test_setup_12c2 () { - test_create_repo 12c2 && + git init 12c2 && ( cd 12c2 && @@ -4584,7 +4584,7 @@ test_expect_success '12c2: Moving one directory hierarchy into another w/ conten # Expected: subdir/foo, bar test_setup_12d () { - test_create_repo 12d && + git init 12d && ( cd 12d && @@ -4642,7 +4642,7 @@ test_expect_success '12d: Rename/merge subdir into the root, variant 1' ' # Expected: foo, bar test_setup_12e () { - test_create_repo 12e && + git init 12e && ( cd 12e && @@ -4743,7 +4743,7 @@ test_expect_success '12e: Rename/merge subdir into the root, variant 2' ' # pick and re-applying them in the subsequent one. test_setup_12f () { - test_create_repo 12f && + git init 12f && ( cd 12f && @@ -4902,7 +4902,7 @@ test_expect_merge_algorithm failure success '12f: Trivial directory resolve, cac # Expected: newfile_{merged}, newdir/{a_B,b_B,c_A} test_setup_12g () { - test_create_repo 12g && + git init 12g && ( cd 12g && @@ -4973,7 +4973,7 @@ test_expect_success '12g: Testcase with two kinds of "relevant" renames' ' # Expected: newdir/{alpha_2, b} test_setup_12h () { - test_create_repo 12h && + git init 12h && ( cd 12h && @@ -5032,7 +5032,7 @@ test_expect_failure '12h: renaming a file within a renamed directory' ' # source/bar vs. source/subdir/bar test_setup_12i () { - test_create_repo 12i && + git init 12i && ( cd 12i && @@ -5090,7 +5090,7 @@ test_expect_success '12i: Directory rename causes rename-to-self' ' # Expected: {foo, bar, baz_2}, with conflicts on bar vs. subdir/bar test_setup_12j () { - test_create_repo 12j && + git init 12j && ( cd 12j && @@ -5148,7 +5148,7 @@ test_expect_success '12j: Directory rename to root causes rename-to-self' ' # Expected: dirA/{foo, bar, baz_2}, with conflicts on dirA/bar vs. dirB/bar test_setup_12k () { - test_create_repo 12k && + git init 12k && ( cd 12k && @@ -5218,7 +5218,7 @@ test_expect_success '12k: Directory rename with sibling causes rename-to-self' ' # is needed for there to be a sub1/ -> sub3/ rename. test_setup_12l () { - test_create_repo 12l_$1 && + git init 12l_$1 && ( cd 12l_$1 && @@ -5304,6 +5304,62 @@ test_expect_merge_algorithm failure success '12l (A into B): Rename into each ot ) ' +# Testcase 12m, Directory rename, plus change of parent dir to symlink +# Commit O: dir/subdir/file +# Commit A: renamed-dir/subdir/file +# Commit B: dir/subdir +# In words: +# A: dir/subdir/ -> renamed-dir/subdir +# B: delete dir/subdir/file, add dir/subdir as symlink +# +# Expected: CONFLICT (rename/delete): renamed-dir/subdir/file, +# CONFLICT (file location): renamed-dir/subdir vs. dir/subdir +# CONFLICT (directory/file): renamed-dir/subdir symlink has +# renamed-dir/subdir in the way + +test_setup_12m () { + git init 12m && + ( + cd 12m && + + mkdir -p dir/subdir && + echo 1 >dir/subdir/file && + git add . && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv dir/ renamed-dir/ && + git add . && + git commit -m "A" && + + git switch B && + git rm dir/subdir/file && + mkdir dir && + ln -s /dev/null dir/subdir && + git add . && + git commit -m "B" + ) +} + +test_expect_merge_algorithm failure success '12m: Change parent of renamed-dir to symlink on other side' ' + test_setup_12m && + ( + cd 12m && + + git checkout -q A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && + + test_stdout_line_count = 3 git ls-files -s && + test_stdout_line_count = 2 ls -1 renamed-dir && + test_path_is_missing dir + ) +' + ########################################################################### # SECTION 13: Checking informational and conflict messages # @@ -5322,7 +5378,7 @@ test_expect_merge_algorithm failure success '12l (A into B): Rename into each ot # Expected: y/{b,c,d,e/f}, with notices/conflicts for both y/d and y/e/f test_setup_13a () { - test_create_repo 13a_$1 && + git init 13a_$1 && ( cd 13a_$1 && @@ -5409,7 +5465,7 @@ test_expect_success '13a(info): messages for newly added files' ' # one about content, and one about file location test_setup_13b () { - test_create_repo 13b_$1 && + git init 13b_$1 && ( cd 13b_$1 && @@ -5496,7 +5552,7 @@ test_expect_success '13b(info): messages for transitive rename with conflicted c # shown in testcase 13d. test_setup_13c () { - test_create_repo 13c_$1 && + git init 13c_$1 && ( cd 13c_$1 && @@ -5584,7 +5640,7 @@ test_expect_success '13c(info): messages for rename/rename(1to1) via transitive # No conflict in where a/y ends up, so put it in d/y. test_setup_13d () { - test_create_repo 13d_$1 && + git init 13d_$1 && ( cd 13d_$1 && @@ -5710,7 +5766,7 @@ test_expect_success '13d(info): messages for rename/rename(1to1) via dual transi # least avoids hitting a BUG(). # test_setup_13e () { - test_create_repo 13e && + git init 13e && ( cd 13e && diff --git a/t/t6425-merge-rename-delete.sh b/t/t6425-merge-rename-delete.sh index 459b431a60..93cd2869b1 100755 --- a/t/t6425-merge-rename-delete.sh +++ b/t/t6425-merge-rename-delete.sh @@ -4,6 +4,7 @@ test_description='Merge-recursive rename/delete conflict message' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'rename/delete' ' diff --git a/t/t6426-merge-skip-unneeded-updates.sh b/t/t6426-merge-skip-unneeded-updates.sh index 7b5f1c1dcd..2bb8e7f09b 100755 --- a/t/t6426-merge-skip-unneeded-updates.sh +++ b/t/t6426-merge-skip-unneeded-updates.sh @@ -38,7 +38,7 @@ test_description="merge cases" # Expected: b_2 test_setup_1a () { - test_create_repo 1a_$1 && + git init 1a_$1 && ( cd 1a_$1 && @@ -136,7 +136,7 @@ test_expect_success '1a-R: Modify(A)/Modify(B), change on B subset of A' ' # Expected: c_2 test_setup_2a () { - test_create_repo 2a_$1 && + git init 2a_$1 && ( cd 2a_$1 && @@ -229,7 +229,7 @@ test_expect_success '2a-R: Modify/rename, merge into rename side' ' # Expected: c_2 test_setup_2b () { - test_create_repo 2b_$1 && + git init 2b_$1 && ( cd 2b_$1 && @@ -336,7 +336,7 @@ test_expect_success '2b-R: Rename+Mod(A)/Mod(B), B mods subset of A' ' # not make that particular mistake. test_setup_2c () { - test_create_repo 2c && + git init 2c && ( cd 2c && @@ -437,7 +437,7 @@ test_expect_success '2c: Modify b & add c VS rename b->c' ' # Expected: bar/{bq_2, whatever} test_setup_3a () { - test_create_repo 3a_$1 && + git init 3a_$1 && ( cd 3a_$1 && @@ -537,7 +537,7 @@ test_expect_success '3a-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' # Expected: bar/{bq_2, whatever} test_setup_3b () { - test_create_repo 3b_$1 && + git init 3b_$1 && ( cd 3b_$1 && @@ -642,7 +642,7 @@ test_expect_success '3b-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' # Expected: b_2 for merge, b_4 in working copy test_setup_4a () { - test_create_repo 4a && + git init 4a && ( cd 4a && @@ -714,7 +714,7 @@ test_expect_merge_algorithm failure success '4a: Change on A, change on B subset # Expected: c_2 test_setup_4b () { - test_create_repo 4b && + git init 4b && ( cd 4b && diff --git a/t/t6427-diff3-conflict-markers.sh b/t/t6427-diff3-conflict-markers.sh index a9ee4cb207..dd5fe6a402 100755 --- a/t/t6427-diff3-conflict-markers.sh +++ b/t/t6427-diff3-conflict-markers.sh @@ -19,7 +19,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME # test_expect_success 'setup no merge base' ' - test_create_repo no_merge_base && + git init no_merge_base && ( cd no_merge_base && @@ -55,7 +55,7 @@ test_expect_success 'check no merge base' ' # test_expect_success 'setup unique merge base' ' - test_create_repo unique_merge_base && + git init unique_merge_base && ( cd unique_merge_base && @@ -116,7 +116,7 @@ test_expect_success 'check unique merge base' ' # test_expect_success 'setup multiple merge bases' ' - test_create_repo multiple_merge_bases && + git init multiple_merge_bases && ( cd multiple_merge_bases && @@ -190,7 +190,7 @@ test_expect_success 'check multiple merge bases' ' ' test_expect_success 'rebase --merge describes parent of commit being picked' ' - test_create_repo rebase && + git init rebase && ( cd rebase && test_commit base file && @@ -212,7 +212,7 @@ test_expect_success 'rebase --apply describes fake ancestor base' ' ' test_setup_zdiff3 () { - test_create_repo zdiff3 && + git init zdiff3 && ( cd zdiff3 && diff --git a/t/t6428-merge-conflicts-sparse.sh b/t/t6428-merge-conflicts-sparse.sh index 064be1b629..9919c3fa7c 100755 --- a/t/t6428-merge-conflicts-sparse.sh +++ b/t/t6428-merge-conflicts-sparse.sh @@ -29,7 +29,7 @@ test_description="merge cases" # Testcase basic, conflicting changes in 'numerals' test_setup_numerals () { - test_create_repo numerals_$1 && + git init numerals_$1 && ( cd numerals_$1 && diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh index e1ce919916..d02fa16614 100755 --- a/t/t6429-merge-sequence-rename-caching.sh +++ b/t/t6429-merge-sequence-rename-caching.sh @@ -35,7 +35,7 @@ test_description="remember regular & dir renames in sequence of merges" # preventing us from finding new renames. # test_expect_success 'caching renames does not preclude finding new ones' ' - test_create_repo caching-renames-and-new-renames && + git init caching-renames-and-new-renames && ( cd caching-renames-and-new-renames && @@ -106,7 +106,7 @@ test_expect_success 'caching renames does not preclude finding new ones' ' # should be able to only run rename detection on the upstream side one # time.) test_expect_success 'cherry-pick both a commit and its immediate revert' ' - test_create_repo pick-commit-and-its-immediate-revert && + git init pick-commit-and-its-immediate-revert && ( cd pick-commit-and-its-immediate-revert && @@ -162,7 +162,7 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' ' # could cause a spurious rename/add conflict. # test_expect_success 'rename same file identically, then reintroduce it' ' - test_create_repo rename-rename-1to1-then-add-old-filename && + git init rename-rename-1to1-then-add-old-filename && ( cd rename-rename-1to1-then-add-old-filename && @@ -229,7 +229,7 @@ test_expect_success 'rename same file identically, then reintroduce it' ' # cached, the directory rename could put newfile in the wrong directory. # test_expect_success 'rename same file identically, then add file to old dir' ' - test_create_repo rename-rename-1to1-then-add-file-to-old-dir && + git init rename-rename-1to1-then-add-file-to-old-dir && ( cd rename-rename-1to1-then-add-file-to-old-dir && @@ -311,7 +311,7 @@ test_expect_success 'rename same file identically, then add file to old dir' ' # should avoid the need to re-detect upstream renames.) # test_expect_success 'cached dir rename does not prevent noticing later conflict' ' - test_create_repo dir-rename-cache-not-occluding-later-conflict && + git init dir-rename-cache-not-occluding-later-conflict && ( cd dir-rename-cache-not-occluding-later-conflict && @@ -365,7 +365,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict' # Helper for the next two tests test_setup_upstream_rename () { - test_create_repo $1 && + git init $1 && ( cd $1 && @@ -537,7 +537,7 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir # Helper for the next two tests test_setup_topic_rename () { - test_create_repo $1 && + git init $1 && ( cd $1 && @@ -725,7 +725,7 @@ test_expect_success 'avoid assuming we detected renames' ' mkdir unrelated && for i in $(test_seq 1 10) do - >unrelated/$i + >unrelated/$i || exit 1 done && test_seq 2 10 >numbers && test_seq 12 20 >values && diff --git a/t/t6431-merge-criscross.sh b/t/t6431-merge-criscross.sh index 3824756a02..3fe14cd73e 100755 --- a/t/t6431-merge-criscross.sh +++ b/t/t6431-merge-criscross.sh @@ -2,6 +2,7 @@ test_description='merge-recursive backend test' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # A <- create some files diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index c253bf759a..c9a86f2e94 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -103,8 +103,25 @@ test_expect_success 'setup for merge search' ' echo "file-c" > file-c && git add file-c && git commit -m "sub-c") && - git commit -a -m "c" && + git commit -a -m "c") +' +test_expect_success 'merging should conflict for non fast-forward' ' + test_when_finished "git -C merge-search reset --hard" && + (cd merge-search && + git checkout -b test-nonforward-a b && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual + else + test_must_fail git merge c 2> actual + fi) +' + +test_expect_success 'finish setup for merge-search' ' + (cd merge-search && git checkout -b d a && (cd sub && git checkout -b sub-d sub-b && @@ -129,14 +146,16 @@ test_expect_success 'merge with one side as a fast-forward of the other' ' test_cmp expect actual) ' -test_expect_success 'merging should conflict for non fast-forward' ' +test_expect_success 'merging should conflict for non fast-forward (resolution exists)' ' (cd merge-search && - git checkout -b test-nonforward b && + git checkout -b test-nonforward-b b && (cd sub && git rev-parse --short sub-d > ../expect) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -161,7 +180,9 @@ test_expect_success 'merging should fail for ambiguous common parent' ' ) && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual + test_must_fail git merge c >actual && + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && + grep "$sub_expect" actual else test_must_fail git merge c 2> actual fi && @@ -205,7 +226,12 @@ test_expect_success 'merging should fail for changes that are backwards' ' git commit -a -m "f" && git checkout -b test-backward e && - test_must_fail git merge f) + test_must_fail git merge f >actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-d)" && + grep "$sub_expect" actual + fi) ' @@ -310,7 +336,7 @@ test_expect_success 'recursive merge with submodule' ' # Expected: path/ is submodule and file contents for B's path are somewhere test_expect_success 'setup file/submodule conflict' ' - test_create_repo file-submodule && + git init file-submodule && ( cd file-submodule && @@ -325,7 +351,7 @@ test_expect_success 'setup file/submodule conflict' ' git commit -m B && git checkout A && - test_create_repo path && + git init path && test_commit -C path world && git submodule add ./path && git commit -m A @@ -385,7 +411,7 @@ test_expect_success 'file/submodule conflict; merge --abort works afterward' ' # under the submodule to be treated as untracked or in the way. test_expect_success 'setup directory/submodule conflict' ' - test_create_repo directory-submodule && + git init directory-submodule && ( cd directory-submodule && @@ -408,7 +434,7 @@ test_expect_success 'setup directory/submodule conflict' ' git commit -m B2 && git checkout A && - test_create_repo path && + git init path && test_commit -C path hello world && git submodule add ./path && git commit -m A @@ -476,4 +502,44 @@ test_expect_failure 'directory/submodule conflict; merge --abort works afterward ) ' +# Setup: +# - Submodule has 2 commits: a and b +# - Superproject branch 'a' adds and commits submodule pointing to 'commit a' +# - Superproject branch 'b' adds and commits submodule pointing to 'commit b' +# If these two branches are now merged, there is no merge base +test_expect_success 'setup for null merge base' ' + mkdir no-merge-base && + (cd no-merge-base && + git init && + mkdir sub && + (cd sub && + git init && + echo "file-a" > file-a && + git add file-a && + git commit -m "commit a") && + git commit --allow-empty -m init && + git branch init && + git checkout -b a init && + git add sub && + git commit -m "a" && + git switch main && + (cd sub && + echo "file-b" > file-b && + git add file-b && + git commit -m "commit b")) +' + +test_expect_success 'merging should fail with no merge base' ' + (cd no-merge-base && + git checkout -b b init && + git add sub && + git commit -m "b" && + test_must_fail git merge a >actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short HEAD^1)" && + grep "$sub_expect" actual + fi) +' + test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index a402908142..8c37bceb33 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -324,6 +324,7 @@ test_expect_success SYMLINKS 'check moved symlink' ' rm -f moved symlink test_expect_success 'setup submodule' ' + test_config_global protocol.file.allow always && git commit -m initial && git reset --hard && git submodule add ./. sub && @@ -509,6 +510,7 @@ test_expect_success 'moving a submodule in nested directories' ' ' test_expect_success 'moving nested submodules' ' + test_config_global protocol.file.allow always && git commit -am "cleanup commit" && mkdir sub_nested_nested && ( diff --git a/t/t7002-mv-sparse-checkout.sh b/t/t7002-mv-sparse-checkout.sh index 71fe29690f..26582ae4e5 100755 --- a/t/t7002-mv-sparse-checkout.sh +++ b/t/t7002-mv-sparse-checkout.sh @@ -28,12 +28,25 @@ test_expect_success 'setup' " updated in the index: EOF - cat >sparse_hint <<-EOF + cat >sparse_hint <<-EOF && hint: If you intend to update such entries, try one of the following: hint: * Use the --sparse option. hint: * Disable or modify the sparsity rules. hint: Disable this message with \"git config advice.updateSparsePath false\" EOF + + cat >dirty_error_header <<-EOF && + The following paths have been moved outside the + sparse-checkout definition but are not sparse due to local + modifications. + EOF + + cat >dirty_hint <<-EOF + hint: To correct the sparsity of these paths, do the following: + hint: * Use \"git add --sparse <paths>\" to update the index + hint: * Use \"git sparse-checkout reapply\" to apply the sparsity rules + hint: Disable this message with \"git config advice.updateSparsePath false\" + EOF " test_expect_success 'mv refuses to move sparse-to-sparse' ' @@ -290,4 +303,215 @@ test_expect_success 'move sparse file to existing destination with --force and - test_cmp expect sub/file1 ' +test_expect_success 'move clean path from in-cone to out-of-cone' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + + test_must_fail git mv sub/d folder1 2>stderr && + cat sparse_error_header >expect && + echo "folder1/d" >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + + git mv --sparse sub/d folder1 2>stderr && + test_must_be_empty stderr && + + test_path_is_missing sub/d && + test_path_is_missing folder1/d && + git ls-files -t >actual && + ! grep "^H sub/d\$" actual && + grep "S folder1/d" actual +' + +test_expect_success 'move clean path from in-cone to out-of-cone overwrite' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + echo "sub/file1 overwrite" >sub/file1 && + git add sub/file1 && + + test_must_fail git mv sub/file1 folder1 2>stderr && + cat sparse_error_header >expect && + echo "folder1/file1" >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + + test_must_fail git mv --sparse sub/file1 folder1 2>stderr && + echo "fatal: destination exists in the index, source=sub/file1, destination=folder1/file1" \ + >expect && + test_cmp expect stderr && + + git mv --sparse -f sub/file1 folder1 2>stderr && + test_must_be_empty stderr && + + test_path_is_missing sub/file1 && + test_path_is_missing folder1/file1 && + git ls-files -t >actual && + ! grep "H sub/file1" actual && + grep "S folder1/file1" actual && + + # compare file content before move and after move + echo "sub/file1 overwrite" >expect && + git ls-files -s -- folder1/file1 | awk "{print \$2}" >oid && + git cat-file blob $(cat oid) >actual && + test_cmp expect actual +' + +# This test is testing the same behavior as the +# "move clean path from in-cone to out-of-cone overwrite" above. +# The only difference is the <destination> changes from "folder1" to "folder1/file1" +test_expect_success 'move clean path from in-cone to out-of-cone file overwrite' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + echo "sub/file1 overwrite" >sub/file1 && + git add sub/file1 && + + test_must_fail git mv sub/file1 folder1/file1 2>stderr && + cat sparse_error_header >expect && + echo "folder1/file1" >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + + test_must_fail git mv --sparse sub/file1 folder1/file1 2>stderr && + echo "fatal: destination exists in the index, source=sub/file1, destination=folder1/file1" \ + >expect && + test_cmp expect stderr && + + git mv --sparse -f sub/file1 folder1/file1 2>stderr && + test_must_be_empty stderr && + + test_path_is_missing sub/file1 && + test_path_is_missing folder1/file1 && + git ls-files -t >actual && + ! grep "H sub/file1" actual && + grep "S folder1/file1" actual && + + # compare file content before move and after move + echo "sub/file1 overwrite" >expect && + git ls-files -s -- folder1/file1 | awk "{print \$2}" >oid && + git cat-file blob $(cat oid) >actual && + test_cmp expect actual +' + +test_expect_success 'move directory with one of the files overwrite' ' + test_when_finished "cleanup_sparse_checkout" && + mkdir -p folder1/dir && + touch folder1/dir/file1 && + git add folder1 && + git sparse-checkout set --cone sub && + + echo test >sub/dir/file1 && + git add sub/dir/file1 && + + test_must_fail git mv sub/dir folder1 2>stderr && + cat sparse_error_header >expect && + echo "folder1/dir/e" >>expect && + echo "folder1/dir/file1" >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + + test_must_fail git mv --sparse sub/dir folder1 2>stderr && + echo "fatal: destination exists in the index, source=sub/dir/file1, destination=folder1/dir/file1" \ + >expect && + test_cmp expect stderr && + + git mv --sparse -f sub/dir folder1 2>stderr && + test_must_be_empty stderr && + + test_path_is_missing sub/dir/file1 && + test_path_is_missing sub/dir/e && + test_path_is_missing folder1/file1 && + git ls-files -t >actual && + ! grep "H sub/dir/file1" actual && + ! grep "H sub/dir/e" actual && + grep "S folder1/dir/file1" actual && + + # compare file content before move and after move + echo test >expect && + git ls-files -s -- folder1/dir/file1 | awk "{print \$2}" >oid && + git cat-file blob $(cat oid) >actual && + test_cmp expect actual +' + +test_expect_success 'move dirty path from in-cone to out-of-cone' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + echo "modified" >>sub/d && + + test_must_fail git mv sub/d folder1 2>stderr && + cat sparse_error_header >expect && + echo "folder1/d" >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + + git mv --sparse sub/d folder1 2>stderr && + cat dirty_error_header >expect && + echo "folder1/d" >>expect && + cat dirty_hint >>expect && + test_cmp expect stderr && + + test_path_is_missing sub/d && + test_path_is_file folder1/d && + git ls-files -t >actual && + ! grep "^H sub/d\$" actual && + grep "H folder1/d" actual +' + +test_expect_success 'move dir from in-cone to out-of-cone' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + mkdir sub/dir/deep && + + test_must_fail git mv sub/dir folder1 2>stderr && + cat sparse_error_header >expect && + echo "folder1/dir/e" >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + + git mv --sparse sub/dir folder1 2>stderr && + test_must_be_empty stderr && + + test_path_is_missing sub/dir && + test_path_is_missing folder1 && + git ls-files -t >actual && + ! grep "H sub/dir/e" actual && + grep "S folder1/dir/e" actual +' + +test_expect_success 'move partially-dirty dir from in-cone to out-of-cone' ' + test_when_finished "cleanup_sparse_checkout" && + setup_sparse_checkout && + mkdir sub/dir/deep && + touch sub/dir/e2 sub/dir/e3 && + git add sub/dir/e2 sub/dir/e3 && + echo "modified" >>sub/dir/e2 && + echo "modified" >>sub/dir/e3 && + + test_must_fail git mv sub/dir folder1 2>stderr && + cat sparse_error_header >expect && + echo "folder1/dir/e" >>expect && + echo "folder1/dir/e2" >>expect && + echo "folder1/dir/e3" >>expect && + cat sparse_hint >>expect && + test_cmp expect stderr && + + git mv --sparse sub/dir folder1 2>stderr && + cat dirty_error_header >expect && + echo "folder1/dir/e2" >>expect && + echo "folder1/dir/e3" >>expect && + cat dirty_hint >>expect && + test_cmp expect stderr && + + test_path_is_missing sub/dir && + test_path_is_missing folder1/dir/e && + test_path_is_file folder1/dir/e2 && + test_path_is_file folder1/dir/e3 && + git ls-files -t >actual && + ! grep "H sub/dir/e" actual && + ! grep "H sub/dir/e2" actual && + ! grep "H sub/dir/e3" actual && + grep "S folder1/dir/e" actual && + grep "H folder1/dir/e2" actual && + grep "H folder1/dir/e3" actual +' + test_done diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index e18a218952..f6aebe92ff 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -49,7 +49,7 @@ test_expect_success 'result is really identical' ' test_expect_success 'rewrite bare repository identically' ' (git config core.bare true && cd .git && git filter-branch branch > filter-output 2>&1 && - ! fgrep fatal filter-output) + ! grep fatal filter-output) ' git config core.bare false test_expect_success 'result is really identical' ' @@ -506,7 +506,7 @@ test_expect_success 'rewrite repository including refs that point at non-commit git tag -a -m "tag to a tree" treetag $new_tree && git reset --hard HEAD && git filter-branch -f -- --all >filter-output 2>&1 && - ! fgrep fatal filter-output + ! grep fatal filter-output ' test_expect_success 'filter-branch handles ref deletion' ' diff --git a/t/t7007-show.sh b/t/t7007-show.sh index d6cc69e0f2..f908a4d1ab 100755 --- a/t/t7007-show.sh +++ b/t/t7007-show.sh @@ -2,6 +2,7 @@ test_description='git show' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t7060-wtstatus.sh b/t/t7060-wtstatus.sh index 0f4344c55e..aaeb4a5334 100755 --- a/t/t7060-wtstatus.sh +++ b/t/t7060-wtstatus.sh @@ -5,6 +5,7 @@ test_description='basic work tree status reporting' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t7062-wtstatus-ignorecase.sh b/t/t7062-wtstatus-ignorecase.sh index 73709dbeee..caf372a3d4 100755 --- a/t/t7062-wtstatus-ignorecase.sh +++ b/t/t7062-wtstatus-ignorecase.sh @@ -2,6 +2,7 @@ test_description='git-status with core.ignorecase=true' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'status with hash collisions' ' diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh index 20a0d2afc2..11884d2fc3 100755 --- a/t/t7064-wtstatus-pv2.sh +++ b/t/t7064-wtstatus-pv2.sh @@ -473,6 +473,7 @@ test_expect_success 'create and add submodule, submodule appears clean (A. S...) git checkout initial-branch && git clone . sub_repo && git clone . super_repo && + test_config_global protocol.file.allow always && ( cd super_repo && git submodule add ../sub_repo sub1 && diff --git a/t/t7110-reset-merge.sh b/t/t7110-reset-merge.sh index 3d62e10b53..eb881be95b 100755 --- a/t/t7110-reset-merge.sh +++ b/t/t7110-reset-merge.sh @@ -5,6 +5,7 @@ test_description='Tests for "git reset" with "--merge" and "--keep" options' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t7111-reset-table.sh b/t/t7111-reset-table.sh index ce421ad5ac..78f25c1c7e 100755 --- a/t/t7111-reset-table.sh +++ b/t/t7111-reset-table.sh @@ -5,6 +5,7 @@ test_description='Tests to check that "reset" options follow a known table' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 0399701e62..c975eb54d2 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -480,6 +480,7 @@ test_expect_success 'should not clean submodules' ' git init && test_commit msg hello.world ) && + test_config_global protocol.file.allow always && git submodule add ./repo/.git sub1 && git commit -m "sub1" && git branch before_sub2 && diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index e7cec2e457..a989aafaf5 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -14,6 +14,36 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +test_expect_success 'setup - enable local submodules' ' + git config --global protocol.file.allow always +' + +test_expect_success 'submodule usage: -h' ' + git submodule -h >out 2>err && + grep "^usage: git submodule" out && + test_must_be_empty err +' + +test_expect_success 'submodule usage: --recursive' ' + test_expect_code 1 git submodule --recursive >out 2>err && + grep "^usage: git submodule" err && + test_must_be_empty out +' + +test_expect_success 'submodule usage: status --' ' + test_expect_code 1 git submodule -- && + test_expect_code 1 git submodule --end-of-options +' + +for opt in '--quiet' '--cached' +do + test_expect_success "submodule usage: status $opt" ' + git submodule $opt && + git submodule status $opt && + git submodule $opt status + ' +done + test_expect_success 'submodule deinit works on empty repository' ' git submodule deinit --all ' @@ -152,6 +182,11 @@ test_expect_success 'submodule add' ' test_must_be_empty untracked ' +test_expect_success !WINDOWS 'submodule add (absolute path)' ' + test_when_finished "git reset --hard" && + git submodule add "$submodurl" "$submodurl/add-abs" +' + test_expect_success 'setup parent and one repository' ' test_create_repo parent && test_commit -C parent one @@ -1224,31 +1259,6 @@ test_expect_success 'submodule add clone shallow submodule' ' ) ' -test_expect_success 'submodule helper list is not confused by common prefixes' ' - mkdir -p dir1/b && - ( - cd dir1/b && - git init && - echo hi >testfile2 && - git add . && - git commit -m "test1" - ) && - mkdir -p dir2/b && - ( - cd dir2/b && - git init && - echo hello >testfile1 && - git add . && - git commit -m "test2" - ) && - git submodule add /dir1/b dir1/b && - git submodule add /dir2/b dir2/b && - git commit -m "first submodule commit" && - git submodule--helper list dir1/b | cut -f 2 >actual && - echo "dir1/b" >expect && - test_cmp expect actual -' - test_expect_success 'setup superproject with submodules' ' git init sub1 && test_commit -C sub1 test && diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 9c3cc4cf40..542b3331a7 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -17,6 +17,7 @@ This test script tries to verify the sanity of summary subcommand of git submodu # various reasons, one of them being that there are lots of commands taking place # outside of 'test_expect_success' block, which is no longer in good-style. +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh add_file () { diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index 8e32f19007..ebeca12a71 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -104,7 +104,7 @@ test_expect_success 'rebasing submodule that should conflict' ' test_tick && git commit -m fourth && - test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 && + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 >actual_output && git ls-files -s submodule >actual && ( cd submodule && @@ -112,7 +112,12 @@ test_expect_success 'rebasing submodule that should conflict' ' echo "160000 $(git rev-parse HEAD^^) 2 submodule" && echo "160000 $(git rev-parse HEAD) 3 submodule" ) >expect && - test_cmp expect actual + test_cmp expect actual && + if test "$GIT_TEST_MERGE_ALGORITHM" = ort + then + sub_expect="go to submodule (submodule), and either merge commit $(git -C submodule rev-parse --short HEAD^0)" && + grep "$sub_expect" actual_output + fi ' test_done diff --git a/t/t7403-submodule-sync.sh b/t/t7403-submodule-sync.sh index 7d2ac3322b..ea92ef52a5 100755 --- a/t/t7403-submodule-sync.sh +++ b/t/t7403-submodule-sync.sh @@ -14,6 +14,8 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh test_expect_success setup ' + git config --global protocol.file.allow always && + echo file >file && git add file && test_tick && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 6cc07460dd..f094e3d7f3 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -25,6 +25,7 @@ compare_head() test_expect_success 'setup a submodule tree' ' + git config --global protocol.file.allow always && echo file > file && git add file && test_tick && @@ -769,7 +770,7 @@ test_expect_success 'submodule update continues after recursive checkout error' echo "" > file ) ) && - test_must_fail git submodule update --recursive && + test_expect_code 1 git submodule update --recursive && (cd submodule2 && git rev-parse --verify HEAD >../actual ) && diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index e2f110b786..59bd150166 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -16,6 +16,7 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME test_expect_success 'setup a submodule tree' ' + git config --global protocol.file.allow always && echo file > file && git add file && test_tick && diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh index c3a4545510..d6040e0a33 100755 --- a/t/t7408-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh @@ -17,6 +17,10 @@ test_alternate_is_used () { test_cmp expect actual } +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'preparing first repository' ' test_create_repo A && ( diff --git a/t/t7409-submodule-detached-work-tree.sh b/t/t7409-submodule-detached-work-tree.sh index e17ac81a89..374ed481e9 100755 --- a/t/t7409-submodule-detached-work-tree.sh +++ b/t/t7409-submodule-detached-work-tree.sh @@ -15,6 +15,10 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'submodule on detached working tree' ' git init --bare remote && test_create_repo bundle1 && diff --git a/t/t7411-submodule-config.sh b/t/t7411-submodule-config.sh index ad28e93880..c583c4e373 100755 --- a/t/t7411-submodule-config.sh +++ b/t/t7411-submodule-config.sh @@ -12,6 +12,9 @@ from the database and from the worktree works. TEST_NO_CREATE_REPO=1 . ./test-lib.sh +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' test_expect_success 'submodule config cache setup' ' mkdir submodule && (cd submodule && diff --git a/t/t7412-submodule-absorbgitdirs.sh b/t/t7412-submodule-absorbgitdirs.sh index 1cfa150768..2859695c6d 100755 --- a/t/t7412-submodule-absorbgitdirs.sh +++ b/t/t7412-submodule-absorbgitdirs.sh @@ -6,6 +6,7 @@ This test verifies that `git submodue absorbgitdirs` moves a submodules git directory into the superproject. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup a real submodule' ' diff --git a/t/t7413-submodule-is-active.sh b/t/t7413-submodule-is-active.sh index c8e7e98331..7cdc263764 100755 --- a/t/t7413-submodule-is-active.sh +++ b/t/t7413-submodule-is-active.sh @@ -1,14 +1,19 @@ #!/bin/sh -test_description='Test submodule--helper is-active +test_description='Test with test-tool submodule is-active -This test verifies that `git submodue--helper is-active` correctly identifies +This test verifies that `test-tool submodule is-active` correctly identifies submodules which are "active" and interesting to the user. + +This is a unit test of the submodule.c is_submodule_active() function, +which is also indirectly tested elsewhere. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' + git config --global protocol.file.allow always && git init sub && test_commit -C sub initial && git init super && @@ -25,13 +30,13 @@ test_expect_success 'setup' ' ' test_expect_success 'is-active works with urls' ' - git -C super submodule--helper is-active sub1 && - git -C super submodule--helper is-active sub2 && + test-tool -C super submodule is-active sub1 && + test-tool -C super submodule is-active sub2 && git -C super config --unset submodule.sub1.URL && - test_must_fail git -C super submodule--helper is-active sub1 && + test_must_fail test-tool -C super submodule is-active sub1 && git -C super config submodule.sub1.URL ../sub && - git -C super submodule--helper is-active sub1 + test-tool -C super submodule is-active sub1 ' test_expect_success 'is-active works with submodule.<name>.active config' ' @@ -39,11 +44,11 @@ test_expect_success 'is-active works with submodule.<name>.active config' ' test_when_finished "git -C super config submodule.sub1.URL ../sub" && git -C super config --bool submodule.sub1.active "false" && - test_must_fail git -C super submodule--helper is-active sub1 && + test_must_fail test-tool -C super submodule is-active sub1 && git -C super config --bool submodule.sub1.active "true" && git -C super config --unset submodule.sub1.URL && - git -C super submodule--helper is-active sub1 + test-tool -C super submodule is-active sub1 ' test_expect_success 'is-active works with basic submodule.active config' ' @@ -53,17 +58,17 @@ test_expect_success 'is-active works with basic submodule.active config' ' git -C super config --add submodule.active "." && git -C super config --unset submodule.sub1.URL && - git -C super submodule--helper is-active sub1 && - git -C super submodule--helper is-active sub2 + test-tool -C super submodule is-active sub1 && + test-tool -C super submodule is-active sub2 ' test_expect_success 'is-active correctly works with paths that are not submodules' ' test_when_finished "git -C super config --unset-all submodule.active" && - test_must_fail git -C super submodule--helper is-active not-a-submodule && + test_must_fail test-tool -C super submodule is-active not-a-submodule && git -C super config --add submodule.active "." && - test_must_fail git -C super submodule--helper is-active not-a-submodule + test_must_fail test-tool -C super submodule is-active not-a-submodule ' test_expect_success 'is-active works with exclusions in submodule.active config' ' @@ -72,8 +77,8 @@ test_expect_success 'is-active works with exclusions in submodule.active config' git -C super config --add submodule.active "." && git -C super config --add submodule.active ":(exclude)sub1" && - test_must_fail git -C super submodule--helper is-active sub1 && - git -C super submodule--helper is-active sub2 + test_must_fail test-tool -C super submodule is-active sub1 && + test-tool -C super submodule is-active sub2 ' test_expect_success 'is-active with submodule.active and submodule.<name>.active' ' @@ -85,8 +90,8 @@ test_expect_success 'is-active with submodule.active and submodule.<name>.active git -C super config --bool submodule.sub1.active "false" && git -C super config --bool submodule.sub2.active "true" && - test_must_fail git -C super submodule--helper is-active sub1 && - git -C super submodule--helper is-active sub2 + test_must_fail test-tool -C super submodule is-active sub1 && + test-tool -C super submodule is-active sub2 ' test_expect_success 'is-active, submodule.active and submodule add' ' diff --git a/t/t7414-submodule-mistakes.sh b/t/t7414-submodule-mistakes.sh index f2e7df59cf..101afff30f 100755 --- a/t/t7414-submodule-mistakes.sh +++ b/t/t7414-submodule-mistakes.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='handling of common mistakes people may make with submodules' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'create embedded repository' ' @@ -30,7 +32,8 @@ test_expect_success 'no warning when updating entry' ' test_expect_success 'submodule add does not warn' ' test_when_finished "git rm -rf submodule .gitmodules" && - git submodule add ./embed submodule 2>stderr && + git -c protocol.file.allow=always \ + submodule add ./embed submodule 2>stderr && test_i18ngrep ! warning stderr ' diff --git a/t/t7416-submodule-dash-url.sh b/t/t7416-submodule-dash-url.sh index d21dc8b009..3ebd985981 100755 --- a/t/t7416-submodule-dash-url.sh +++ b/t/t7416-submodule-dash-url.sh @@ -3,6 +3,10 @@ test_description='check handling of disallowed .gitmodule urls' . ./test-lib.sh +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'create submodule with protected dash in url' ' git init upstream && git -C upstream commit --allow-empty -m base && diff --git a/t/t7417-submodule-path-url.sh b/t/t7417-submodule-path-url.sh index f0f6b9fa9e..2f4b25dfd7 100755 --- a/t/t7417-submodule-path-url.sh +++ b/t/t7417-submodule-path-url.sh @@ -6,6 +6,10 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'create submodule with dash in path' ' git init upstream && git -C upstream commit --allow-empty -m base && diff --git a/t/t7418-submodule-sparse-gitmodules.sh b/t/t7418-submodule-sparse-gitmodules.sh index 57d7ab3ece..d5874200fd 100755 --- a/t/t7418-submodule-sparse-gitmodules.sh +++ b/t/t7418-submodule-sparse-gitmodules.sh @@ -17,6 +17,10 @@ export GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB . ./test-lib.sh +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'sparse checkout setup which hides .gitmodules' ' git init upstream && git init submodule && diff --git a/t/t7419-submodule-set-branch.sh b/t/t7419-submodule-set-branch.sh index 3b925c302f..232065504c 100755 --- a/t/t7419-submodule-set-branch.sh +++ b/t/t7419-submodule-set-branch.sh @@ -9,9 +9,14 @@ This test verifies that the set-branch subcommand of git-submodule is working as expected. ' +TEST_PASSES_SANITIZE_LEAK=true TEST_NO_CREATE_REPO=1 . ./test-lib.sh +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'submodule config cache setup' ' mkdir submodule && (cd submodule && diff --git a/t/t7420-submodule-set-url.sh b/t/t7420-submodule-set-url.sh index ef0cb6e8e1..d6bf62b3ac 100755 --- a/t/t7420-submodule-set-url.sh +++ b/t/t7420-submodule-set-url.sh @@ -12,6 +12,10 @@ as expected. TEST_NO_CREATE_REPO=1 . ./test-lib.sh +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'submodule config cache setup' ' mkdir submodule && ( diff --git a/t/t7421-submodule-summary-add.sh b/t/t7421-submodule-summary-add.sh index b070f13714..ce64d8b137 100755 --- a/t/t7421-submodule-summary-add.sh +++ b/t/t7421-submodule-summary-add.sh @@ -12,6 +12,10 @@ while making sure to add submodules using `git submodule add` instead of . ./test-lib.sh +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'summary test environment setup' ' git init sm && test_commit -C sm "add file" file file-content file-tag && diff --git a/t/t7450-bad-git-dotfiles.sh b/t/t7450-bad-git-dotfiles.sh index 41706c1c9f..ba1f569bcb 100755 --- a/t/t7450-bad-git-dotfiles.sh +++ b/t/t7450-bad-git-dotfiles.sh @@ -15,13 +15,17 @@ Such as: . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pack.sh +test_expect_success 'setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'check names' ' cat >expect <<-\EOF && valid valid/with/paths EOF - git submodule--helper check-name >actual <<-\EOF && + test-tool submodule check-name >actual <<-\EOF && valid valid/with/paths diff --git a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh index ad1eb64ba0..aa004b70a8 100755 --- a/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh +++ b/t/t7503-pre-commit-and-pre-merge-commit-hooks.sh @@ -5,6 +5,7 @@ test_description='pre-commit and pre-merge-commit hooks' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'root commit' ' diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index 3fcb44767f..d050091345 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -2,6 +2,7 @@ test_description='git status for submodule' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_create_repo_with_commit () { @@ -251,6 +252,7 @@ test_expect_success 'status with merge conflict in .gitmodules' ' test_create_repo_with_commit sub1 && test_tick && test_create_repo_with_commit sub2 && + test_config_global protocol.file.allow always && ( cd super && prev=$(git rev-parse HEAD) && @@ -326,6 +328,7 @@ test_expect_success 'diff --submodule with merge conflict in .gitmodules' ' # sub2 will have an untracked file # sub3 will have an untracked repository test_expect_success 'setup superproject with untracked file in nested submodule' ' + test_config_global protocol.file.allow always && ( cd super && git clean -dfx && diff --git a/t/t7507-commit-verbose.sh b/t/t7507-commit-verbose.sh index ed2653d46f..916470c48b 100755 --- a/t/t7507-commit-verbose.sh +++ b/t/t7507-commit-verbose.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='verbose commit template' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh write_script "check-for-diff" <<\EOF && @@ -74,6 +76,7 @@ test_expect_success 'diff in message is retained with -v' ' test_expect_success 'submodule log is stripped out too with -v' ' git config diff.submodule log && + test_config_global protocol.file.allow always && git submodule add ./. sub && git commit -m "sub added" && ( diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh index 56c0dfffea..4abc74db2b 100755 --- a/t/t7527-builtin-fsmonitor.sh +++ b/t/t7527-builtin-fsmonitor.sh @@ -813,6 +813,10 @@ my_match_and_clean () { git -C super/dir_1/dir_2/sub clean -d -f } +test_expect_success 'submodule setup' ' + git config --global protocol.file.allow always +' + test_expect_success 'submodule always visited' ' test_when_finished "git -C super fsmonitor--daemon stop; \ rm -rf super; \ @@ -939,9 +943,9 @@ test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' ' # directories and files that we touched. We may or may not get a # trailing slash on modified directories. # - egrep "^event: abc/?$" ./insensitive.trace && - egrep "^event: abc/def/?$" ./insensitive.trace && - egrep "^event: abc/def/xyz$" ./insensitive.trace + grep -E "^event: abc/?$" ./insensitive.trace && + grep -E "^event: abc/def/?$" ./insensitive.trace && + grep -E "^event: abc/def/xyz$" ./insensitive.trace ' # The variable "unicode_debug" is defined in the following library @@ -983,20 +987,20 @@ test_expect_success !UNICODE_COMPOSITION_SENSITIVE 'Unicode nfc/nfd' ' then # We should have seen NFC event from OS. # We should not have synthesized an NFD event. - egrep "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace && - egrep -v "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace + grep -E "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace && + grep -E -v "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace else # We should have seen NFD event from OS. # We should have synthesized an NFC event. - egrep "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace && - egrep "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace + grep -E "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace && + grep -E "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace fi && # We assume UNICODE_NFD_PRESERVED. # We should have seen explicit NFD from OS. # We should have synthesized an NFC event. - egrep "^event: nfd/d_${utf8_nfd}/?$" ./unicode.trace && - egrep "^event: nfd/d_${utf8_nfc}/?$" ./unicode.trace + grep -E "^event: nfd/d_${utf8_nfd}/?$" ./unicode.trace && + grep -E "^event: nfd/d_${utf8_nfc}/?$" ./unicode.trace ' test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index f0f6fda150..7c3f6ed994 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -255,6 +255,15 @@ test_expect_success 'merge --squash c3 with c7' ' test_cmp expect actual ' +test_expect_success 'merge --squash --autostash conflict does not attempt to apply autostash' ' + git reset --hard c3 && + >unrelated && + git add unrelated && + test_must_fail git merge --squash c7 --autostash >out 2>err && + ! grep "Applying autostash resulted in conflicts." err && + grep "When finished, apply stashed changes with \`git stash pop\`" out +' + test_expect_success 'merge c3 with c7 with commit.cleanup = scissors' ' git config commit.cleanup scissors && git reset --hard c3 && diff --git a/t/t7609-mergetool--lib.sh b/t/t7609-mergetool--lib.sh index 330d6d603d..8b1c3bd39f 100755 --- a/t/t7609-mergetool--lib.sh +++ b/t/t7609-mergetool--lib.sh @@ -4,6 +4,7 @@ test_description='git mergetool Testing basic merge tools options' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'mergetool --tool=vimdiff creates the expected layout' ' diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index ca45c4cd2c..5be483bf88 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -426,12 +426,73 @@ test_expect_success '--write-midx -b packs non-kept objects' ' ) ' +test_expect_success '--write-midx removes stale pack-based bitmaps' ' + rm -fr repo && + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + test_commit base && + GIT_TEST_MULTI_PACK_INDEX=0 git repack -Ab && + + pack_bitmap=$(ls $objdir/pack/pack-*.bitmap) && + test_path_is_file "$pack_bitmap" && + + test_commit tip && + GIT_TEST_MULTI_PACK_INDEX=0 git repack -bm && + + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap && + test_path_is_missing $pack_bitmap + ) +' + +test_expect_success '--write-midx with --pack-kept-objects' ' + git init repo && + test_when_finished "rm -fr repo" && + ( + cd repo && + + test_commit one && + test_commit two && + + one="$(echo "one" | git pack-objects --revs $objdir/pack/pack)" && + two="$(echo "one..two" | git pack-objects --revs $objdir/pack/pack)" && + + keep="$objdir/pack/pack-$one.keep" && + touch "$keep" && + + git repack --write-midx --write-bitmap-index --geometric=2 -d \ + --pack-kept-objects && + + test_path_is_file $keep && + test_path_is_file $midx && + test_path_is_file $midx-$(midx_checksum $objdir).bitmap + ) +' + test_expect_success TTY '--quiet disables progress' ' test_terminal env GIT_PROGRESS_DELAY=0 \ git -C midx repack -ad --quiet --write-midx 2>stderr && test_must_be_empty stderr ' +test_expect_success 'clean up .tmp-* packs on error' ' + test_must_fail ok=sigpipe git \ + -c repack.cruftwindow=bogus \ + repack -ad --cruft && + find $objdir/pack -name '.tmp-*' >tmpfiles && + test_must_be_empty tmpfiles +' + +test_expect_success 'repack -ad cleans up old .tmp-* packs' ' + git rev-parse HEAD >input && + git pack-objects $objdir/pack/.tmp-1234 <input && + git repack -ad && + find $objdir/pack -name '.tmp-*' >tmpfiles && + test_must_be_empty tmpfiles +' + test_expect_success 'setup for update-server-info' ' git init update-server-info && test_commit -C update-server-info message diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh index 937f89ee8c..b7ac4f598a 100755 --- a/t/t7701-repack-unpack-unreachable.sh +++ b/t/t7701-repack-unpack-unreachable.sh @@ -35,7 +35,7 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' ' git repack -A -d -l && # verify objects are packed in repository test 3 = $(git verify-pack -v -- .git/objects/pack/*.idx | - egrep "^($fsha1|$csha1|$tsha1) " | + grep -E "^($fsha1|$csha1|$tsha1) " | sort | uniq | wc -l) && git show $fsha1 && git show $csha1 && @@ -49,7 +49,7 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' ' git repack -A -d -l && # verify objects are retained unpacked test 0 = $(git verify-pack -v -- .git/objects/pack/*.idx | - egrep "^($fsha1|$csha1|$tsha1) " | + grep -E "^($fsha1|$csha1|$tsha1) " | sort | uniq | wc -l) && git show $fsha1 && git show $csha1 && diff --git a/t/t7703-repack-geometric.sh b/t/t7703-repack-geometric.sh index da87f8b2d8..8821fbd2dd 100755 --- a/t/t7703-repack-geometric.sh +++ b/t/t7703-repack-geometric.sh @@ -176,8 +176,12 @@ test_expect_success '--geometric ignores kept packs' ' # be repacked, too. git repack --geometric 2 -d --pack-kept-objects && + # After repacking, two packs remain: one new one (containing the + # objects in both the .keep and non-kept pack), and the .keep + # pack (since `--pack-kept-objects -d` does not actually delete + # the kept pack). find $objdir/pack -name "*.pack" >after && - test_line_count = 1 after + test_line_count = 2 after ) ' diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 096456292c..24297e26ca 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -636,6 +636,7 @@ test_expect_success 'difftool --no-symlinks detects conflict ' ' test_expect_success 'difftool properly honors gitlink and core.worktree' ' test_when_finished rm -rf submod/ule && + test_config_global protocol.file.allow always && git submodule add ./. submod/ule && test_config -C submod/ule diff.tool checktrees && test_config -C submod/ule difftool.checktrees.cmd '\'' diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 0f937990a0..8eded6ab27 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -18,6 +18,9 @@ test_invalid_grep_expression() { ' } +LC_ALL=en_US.UTF-8 test-tool regex '^.$' '¿' && + test_set_prereq MB_REGEX + cat >hello.c <<EOF #include <assert.h> #include <stdio.h> @@ -88,6 +91,10 @@ test_expect_success setup ' echo unusual >"\"unusual\" pathname" && echo unusual >"t/nested \"unusual\" pathname" fi && + if test_have_prereq MB_REGEX + then + echo "¿" >reverse-question-mark + fi && git add . && test_tick && git commit -m initial @@ -569,6 +576,14 @@ do ' done +test_expect_success MB_REGEX 'grep exactly one char in single-char multibyte file' ' + LC_ALL=en_US.UTF-8 git grep "^.$" reverse-question-mark +' + +test_expect_success MB_REGEX 'grep two chars in single-char multibyte file' ' + LC_ALL=en_US.UTF-8 test_expect_code 1 git grep ".." reverse-question-mark +' + cat >expected <<EOF file EOF diff --git a/t/t7814-grep-recurse-submodules.sh b/t/t7814-grep-recurse-submodules.sh index 3ad80526c4..8143817b19 100755 --- a/t/t7814-grep-recurse-submodules.sh +++ b/t/t7814-grep-recurse-submodules.sh @@ -197,6 +197,7 @@ test_expect_success !MINGW 'grep recurse submodule colon in name' ' git -C "su:b" commit -m "add fi:le" && test_tick && + test_config_global protocol.file.allow always && git -C parent submodule add "../su:b" "su:b" && git -C parent commit -m "add submodule" && test_tick && @@ -231,6 +232,7 @@ test_expect_success 'grep history with moved submoules' ' git -C sub commit -m "add file" && test_tick && + test_config_global protocol.file.allow always && git -C parent submodule add ../sub dir/sub && git -C parent commit -m "add submodule" && test_tick && @@ -275,6 +277,7 @@ test_expect_success 'grep using relative path' ' mkdir parent/src && echo "(1|2)d(3|4)" >parent/src/file2 && git -C parent add src/file2 && + test_config_global protocol.file.allow always && git -C parent submodule add ../sub && git -C parent commit -m "add files and submodule" && test_tick && @@ -317,6 +320,7 @@ test_expect_success 'grep from a subdir' ' mkdir parent/src && echo "(1|2)d(3|4)" >parent/src/file && git -C parent add src/file && + test_config_global protocol.file.allow always && git -C parent submodule add ../sub src/sub && git -C parent submodule add ../sub sub && git -C parent commit -m "add files and submodules" && @@ -550,6 +554,7 @@ test_expect_failure 'grep saves textconv cache in the appropriate repository' ' test_expect_success 'grep partially-cloned submodule' ' # Set up clean superproject and submodule for partial cloning. + test_config_global protocol.file.allow always && git init super && git init super/sub && ( diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 74aa638475..96bdd42045 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -32,11 +32,13 @@ test_systemd_analyze_verify () { } test_expect_success 'help text' ' - test_expect_code 129 git maintenance -h 2>err && - test_i18ngrep "usage: git maintenance <subcommand>" err && - test_expect_code 128 git maintenance barf 2>err && - test_i18ngrep "invalid subcommand: barf" err && + test_expect_code 129 git maintenance -h >actual && + test_i18ngrep "usage: git maintenance <subcommand>" actual && + test_expect_code 129 git maintenance barf 2>err && + test_i18ngrep "unknown subcommand: \`barf'\''" err && + test_i18ngrep "usage: git maintenance" err && test_expect_code 129 git maintenance 2>err && + test_i18ngrep "error: need a subcommand" err && test_i18ngrep "usage: git maintenance" err ' @@ -162,7 +164,6 @@ test_expect_success 'prefetch multiple remotes' ' test_cmp_rev refs/remotes/remote1/one refs/prefetch/remotes/remote1/one && test_cmp_rev refs/remotes/remote2/two refs/prefetch/remotes/remote2/two && - test_cmp_config refs/prefetch/ log.excludedecoration && git log --oneline --decorate --all >log && ! grep "prefetch" log && @@ -173,26 +174,6 @@ test_expect_success 'prefetch multiple remotes' ' test_subcommand git fetch remote2 $fetchargs <skip-remote1.txt ' -test_expect_success 'prefetch and existing log.excludeDecoration values' ' - git config --unset-all log.excludeDecoration && - git config log.excludeDecoration refs/remotes/remote1/ && - git maintenance run --task=prefetch && - - git config --get-all log.excludeDecoration >out && - grep refs/remotes/remote1/ out && - grep refs/prefetch/ out && - - git log --oneline --decorate --all >log && - ! grep "prefetch" log && - ! grep "remote1" log && - grep "remote2" log && - - # a second run does not change the config - git maintenance run --task=prefetch && - git log --oneline --decorate --all >log2 && - test_cmp log log2 -' - test_expect_success 'loose-objects task' ' # Repack everything so we know the state of the object dir git repack -adk && @@ -499,6 +480,11 @@ test_expect_success 'maintenance.strategy inheritance' ' test_expect_success 'register and unregister' ' test_when_finished git config --global --unset-all maintenance.repo && + + test_must_fail git maintenance unregister 2>err && + grep "is not registered" err && + git maintenance unregister --force && + git config --global --add maintenance.repo /existing1 && git config --global --add maintenance.repo /existing2 && git config --global --get-all maintenance.repo >before && @@ -512,7 +498,11 @@ test_expect_success 'register and unregister' ' git maintenance unregister && git config --global --get-all maintenance.repo >actual && - test_cmp before actual + test_cmp before actual && + + test_must_fail git maintenance unregister 2>err && + grep "is not registered" err && + git maintenance unregister --force ' test_expect_success !MINGW 'register and unregister with regex metacharacters' ' diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index 01c74b8b07..1130ef21b3 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -1519,7 +1519,7 @@ test_expect_success $PREREQ 'asks about and fixes 8bit encodings' ' grep "do not declare a Content-Transfer-Encoding" stdout && grep email-using-8bit stdout && grep "Which 8bit encoding" stdout && - egrep "Content|MIME" msgtxt1 >actual && + grep -E "Content|MIME" msgtxt1 >actual && test_cmp content-type-decl actual ' @@ -1530,7 +1530,7 @@ test_expect_success $PREREQ 'sendemail.8bitEncoding works' ' git send-email --from=author@example.com --to=nobody@example.com \ --smtp-server="$(pwd)/fake.sendmail" \ email-using-8bit >stdout && - egrep "Content|MIME" msgtxt1 >actual && + grep -E "Content|MIME" msgtxt1 >actual && test_cmp content-type-decl actual ' @@ -1545,7 +1545,7 @@ test_expect_success $PREREQ 'sendemail.8bitEncoding in .git/config overrides --g git send-email --from=author@example.com --to=nobody@example.com \ --smtp-server="$(pwd)/fake.sendmail" \ email-using-8bit >stdout && - egrep "Content|MIME" msgtxt1 >actual && + grep -E "Content|MIME" msgtxt1 >actual && test_cmp content-type-decl actual ' @@ -1557,7 +1557,7 @@ test_expect_success $PREREQ '--8bit-encoding overrides sendemail.8bitEncoding' ' --smtp-server="$(pwd)/fake.sendmail" \ --8bit-encoding=UTF-8 \ email-using-8bit >stdout && - egrep "Content|MIME" msgtxt1 >actual && + grep -E "Content|MIME" msgtxt1 >actual && test_cmp content-type-decl actual ' diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index 7c5b847f58..fea41b3c36 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -8,7 +8,6 @@ test_description='git svn basic tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh prepare_utf8_locale diff --git a/t/t9122-git-svn-author.sh b/t/t9122-git-svn-author.sh index 527ba3d293..0fc289ae0f 100755 --- a/t/t9122-git-svn-author.sh +++ b/t/t9122-git-svn-author.sh @@ -2,7 +2,6 @@ test_description='git svn authorship' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh test_expect_success 'setup svn repository' ' diff --git a/t/t9133-git-svn-nested-git-repo.sh b/t/t9133-git-svn-nested-git-repo.sh index f894860867..d8d536269c 100755 --- a/t/t9133-git-svn-nested-git-repo.sh +++ b/t/t9133-git-svn-nested-git-repo.sh @@ -35,7 +35,7 @@ test_expect_success 'SVN-side change outside of .git' ' echo b >> a && svn_cmd commit -m "SVN-side change outside of .git" && svn_cmd up && - svn_cmd log -v | fgrep "SVN-side change outside of .git" + svn_cmd log -v | grep -F "SVN-side change outside of .git" ) ' @@ -59,7 +59,7 @@ test_expect_success 'SVN-side change inside of .git' ' svn_cmd add --force .git && svn_cmd commit -m "SVN-side change inside of .git" && svn_cmd up && - svn_cmd log -v | fgrep "SVN-side change inside of .git" + svn_cmd log -v | grep -F "SVN-side change inside of .git" ) ' @@ -82,7 +82,7 @@ test_expect_success 'SVN-side change in and out of .git' ' git commit -m "add a inside an SVN repo" && svn_cmd commit -m "SVN-side change in and out of .git" && svn_cmd up && - svn_cmd log -v | fgrep "SVN-side change in and out of .git" + svn_cmd log -v | grep -F "SVN-side change in and out of .git" ) ' diff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh index 4a77eb9f60..3188400226 100755 --- a/t/t9134-git-svn-ignore-paths.sh +++ b/t/t9134-git-svn-ignore-paths.sh @@ -43,7 +43,7 @@ test_expect_success 'init+fetch an SVN repository with ignored www directory' ' test_expect_success 'verify ignore-paths config saved by clone' ' ( cd g && - git config --get svn-remote.svn.ignore-paths | fgrep "www" + git config --get svn-remote.svn.ignore-paths | grep www ) ' @@ -53,7 +53,7 @@ test_expect_success 'SVN-side change outside of www' ' echo b >> qqq/test_qqq.txt && svn_cmd commit -m "SVN-side change outside of www" && svn_cmd up && - svn_cmd log -v | fgrep "SVN-side change outside of www" + svn_cmd log -v | grep "SVN-side change outside of www" ) ' @@ -85,7 +85,7 @@ test_expect_success 'SVN-side change inside of ignored www' ' echo zaq >> www/test_www.txt && svn_cmd commit -m "SVN-side change inside of www/test_www.txt" && svn_cmd up && - svn_cmd log -v | fgrep "SVN-side change inside of www/test_www.txt" + svn_cmd log -v | grep -F "SVN-side change inside of www/test_www.txt" ) ' @@ -118,7 +118,7 @@ test_expect_success 'SVN-side change in and out of ignored www' ' echo ygg >> qqq/test_qqq.txt && svn_cmd commit -m "SVN-side change in and out of ignored www" && svn_cmd up && - svn_cmd log -v | fgrep "SVN-side change in and out of ignored www" + svn_cmd log -v | grep "SVN-side change in and out of ignored www" ) ' diff --git a/t/t9140-git-svn-reset.sh b/t/t9140-git-svn-reset.sh index e855904629..a420b2a87a 100755 --- a/t/t9140-git-svn-reset.sh +++ b/t/t9140-git-svn-reset.sh @@ -43,7 +43,7 @@ test_expect_success 'fetch fails on modified hidden file' ' git svn find-rev refs/remotes/git-svn > ../expect && test_must_fail git svn fetch 2> ../errors && git svn find-rev refs/remotes/git-svn > ../expect2 ) && - fgrep "not found in commit" errors && + grep "not found in commit" errors && test_cmp expect expect2 ' @@ -59,7 +59,7 @@ test_expect_success 'refetch succeeds not ignoring any files' ' ( cd g && git svn fetch && git svn rebase && - fgrep "mod hidden" hid/hid.txt + grep "mod hidden" hid/hid.txt ) ' diff --git a/t/t9147-git-svn-include-paths.sh b/t/t9147-git-svn-include-paths.sh index 257fc8f2f8..63fa0b6732 100755 --- a/t/t9147-git-svn-include-paths.sh +++ b/t/t9147-git-svn-include-paths.sh @@ -45,7 +45,7 @@ test_expect_success 'init+fetch an SVN repository with included qqq directory' ' test_expect_success 'verify include-paths config saved by clone' ' ( cd g && - git config --get svn-remote.svn.include-paths | fgrep "qqq" + git config --get svn-remote.svn.include-paths | grep qqq ) ' @@ -55,7 +55,7 @@ test_expect_success 'SVN-side change outside of www' ' echo b >> qqq/test_qqq.txt && svn_cmd commit -m "SVN-side change outside of www" && svn_cmd up && - svn_cmd log -v | fgrep "SVN-side change outside of www" + svn_cmd log -v | grep "SVN-side change outside of www" ) ' @@ -87,7 +87,7 @@ test_expect_success 'SVN-side change inside of ignored www' ' echo zaq >> www/test_www.txt && svn_cmd commit -m "SVN-side change inside of www/test_www.txt" && svn_cmd up && - svn_cmd log -v | fgrep "SVN-side change inside of www/test_www.txt" + svn_cmd log -v | grep "SVN-side change inside of www/test_www.txt" ) ' @@ -120,7 +120,7 @@ test_expect_success 'SVN-side change in and out of included qqq' ' echo ygg >> qqq/test_qqq.txt && svn_cmd commit -m "SVN-side change in and out of ignored www" && svn_cmd up && - svn_cmd log -v | fgrep "SVN-side change in and out of ignored www" + svn_cmd log -v | grep "SVN-side change in and out of ignored www" ) ' diff --git a/t/t9162-git-svn-dcommit-interactive.sh b/t/t9162-git-svn-dcommit-interactive.sh index e2aa8ed88a..b3ce033a0d 100755 --- a/t/t9162-git-svn-dcommit-interactive.sh +++ b/t/t9162-git-svn-dcommit-interactive.sh @@ -4,7 +4,6 @@ test_description='git svn dcommit --interactive series' -TEST_FAILS_SANITIZE_LEAK=true . ./lib-git-svn.sh test_expect_success 'initialize repo' ' diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh new file mode 100755 index 0000000000..be51a8bb7a --- /dev/null +++ b/t/t9210-scalar.sh @@ -0,0 +1,213 @@ +#!/bin/sh + +test_description='test the `scalar` command' + +. ./test-lib.sh + +GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt,launchctl:true,schtasks:true" +export GIT_TEST_MAINT_SCHEDULER + +test_expect_success 'scalar shows a usage' ' + test_expect_code 129 scalar -h +' + +test_expect_success 'scalar invoked on enlistment root' ' + test_when_finished rm -rf test src deeper && + + for enlistment_root in test src deeper/test + do + git init ${enlistment_root}/src && + + # Register + scalar register ${enlistment_root} && + scalar list >out && + grep "$(pwd)/${enlistment_root}/src\$" out && + + # Delete (including enlistment root) + scalar delete $enlistment_root && + test_path_is_missing $enlistment_root && + scalar list >out && + ! grep "^$(pwd)/${enlistment_root}/src\$" out || return 1 + done +' + +test_expect_success 'scalar invoked on enlistment src repo' ' + test_when_finished rm -rf test src deeper && + + for enlistment_root in test src deeper/test + do + git init ${enlistment_root}/src && + + # Register + scalar register ${enlistment_root}/src && + scalar list >out && + grep "$(pwd)/${enlistment_root}/src\$" out && + + # Delete (will not include enlistment root) + scalar delete ${enlistment_root}/src && + test_path_is_dir $enlistment_root && + scalar list >out && + ! grep "^$(pwd)/${enlistment_root}/src\$" out || return 1 + done +' + +test_expect_success 'scalar invoked when enlistment root and repo are the same' ' + test_when_finished rm -rf test src deeper && + + for enlistment_root in test src deeper/test + do + git init ${enlistment_root} && + + # Register + scalar register ${enlistment_root} && + scalar list >out && + grep "$(pwd)/${enlistment_root}\$" out && + + # Delete (will not include enlistment root) + scalar delete ${enlistment_root} && + test_path_is_missing $enlistment_root && + scalar list >out && + ! grep "^$(pwd)/${enlistment_root}\$" out && + + # Make sure we did not accidentally delete the trash dir + test_path_is_dir "$TRASH_DIRECTORY" || return 1 + done +' + +test_expect_success 'scalar repo search respects GIT_CEILING_DIRECTORIES' ' + test_when_finished rm -rf test && + + git init test/src && + mkdir -p test/src/deep && + GIT_CEILING_DIRECTORIES="$(pwd)/test/src" && + ! scalar register test/src/deep 2>err && + grep "not a git repository" err +' + +test_expect_success 'scalar enlistments need a worktree' ' + test_when_finished rm -rf bare test && + + git init --bare bare/src && + ! scalar register bare/src 2>err && + grep "Scalar enlistments require a worktree" err && + + git init test/src && + ! scalar register test/src/.git 2>err && + grep "Scalar enlistments require a worktree" err +' + +test_expect_success FSMONITOR_DAEMON 'scalar register starts fsmon daemon' ' + git init test/src && + test_must_fail git -C test/src fsmonitor--daemon status && + scalar register test/src && + git -C test/src fsmonitor--daemon status && + test_cmp_config -C test/src true core.fsmonitor +' + +test_expect_success 'scalar unregister' ' + git init vanish/src && + scalar register vanish/src && + git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + grep -F "$(pwd)/vanish/src" scalar.repos && + rm -rf vanish/src/.git && + scalar unregister vanish && + test_must_fail git config --get --global --fixed-value \ + maintenance.repo "$(pwd)/vanish/src" && + scalar list >scalar.repos && + ! grep -F "$(pwd)/vanish/src" scalar.repos && + + # scalar unregister should be idempotent + scalar unregister vanish +' + +test_expect_success 'set up repository to clone' ' + test_commit first && + test_commit second && + test_commit third && + git switch -c parallel first && + mkdir -p 1/2 && + test_commit 1/2/3 && + git config uploadPack.allowFilter true && + git config uploadPack.allowAnySHA1InWant true +' + +test_expect_success 'scalar clone' ' + second=$(git rev-parse --verify second:second.t) && + scalar clone "file://$(pwd)" cloned --single-branch && + ( + cd cloned/src && + + git config --get --global --fixed-value maintenance.repo \ + "$(pwd)" && + + git for-each-ref --format="%(refname)" refs/remotes/origin/ >actual && + echo "refs/remotes/origin/parallel" >expect && + test_cmp expect actual && + + test_path_is_missing 1/2 && + test_must_fail git rev-list --missing=print $second && + git rev-list $second && + git cat-file blob $second >actual && + echo "second" >expect && + test_cmp expect actual + ) +' + +test_expect_success 'scalar reconfigure' ' + git init one/src && + scalar register one && + git -C one/src config core.preloadIndex false && + scalar reconfigure one && + test true = "$(git -C one/src config core.preloadIndex)" && + git -C one/src config core.preloadIndex false && + scalar reconfigure -a && + test true = "$(git -C one/src config core.preloadIndex)" +' + +test_expect_success 'scalar delete without enlistment shows a usage' ' + test_expect_code 129 scalar delete +' + +test_expect_success 'scalar delete with enlistment' ' + scalar delete cloned && + test_path_is_missing cloned +' + +test_expect_success 'scalar supports -c/-C' ' + test_when_finished "scalar delete sub" && + git init sub && + scalar -C sub -c status.aheadBehind=bogus register && + test -z "$(git -C sub config --local status.aheadBehind)" && + test true = "$(git -C sub config core.preloadIndex)" +' + +test_expect_success '`scalar [...] <dir>` errors out when dir is missing' ' + ! scalar run config cloned 2>err && + grep "cloned. does not exist" err +' + +SQ="'" +test_expect_success UNZIP 'scalar diagnose' ' + scalar clone "file://$(pwd)" cloned --single-branch && + git repack && + echo "$(pwd)/.git/objects/" >>cloned/src/.git/objects/info/alternates && + test_commit -C cloned/src loose && + scalar diagnose cloned >out 2>err && + grep "Available space" out && + sed -n "s/.*$SQ\\(.*\\.zip\\)$SQ.*/\\1/p" <err >zip_path && + zip_path=$(cat zip_path) && + test -n "$zip_path" && + "$GIT_UNZIP" -v "$zip_path" && + folder=${zip_path%.zip} && + test_path_is_missing "$folder" && + "$GIT_UNZIP" -p "$zip_path" diagnostics.log >out && + test_file_not_empty out && + "$GIT_UNZIP" -p "$zip_path" packs-local.txt >out && + grep "$(pwd)/.git/objects" out && + "$GIT_UNZIP" -p "$zip_path" objects-local.txt >out && + grep "^Total: [1-9]" out +' + +test_done diff --git a/t/t9211-scalar-clone.sh b/t/t9211-scalar-clone.sh new file mode 100755 index 0000000000..dd33d87e9b --- /dev/null +++ b/t/t9211-scalar-clone.sh @@ -0,0 +1,151 @@ +#!/bin/sh + +test_description='test the `scalar clone` subcommand' + +. ./test-lib.sh + +GIT_TEST_MAINT_SCHEDULER="crontab:test-tool crontab cron.txt,launchctl:true,schtasks:true" +export GIT_TEST_MAINT_SCHEDULER + +test_expect_success 'set up repository to clone' ' + rm -rf .git && + git init to-clone && + ( + cd to-clone && + git branch -m base && + + test_commit first && + test_commit second && + test_commit third && + + git switch -c parallel first && + mkdir -p 1/2 && + test_commit 1/2/3 && + + git switch base && + + # By default, permit + git config uploadpack.allowfilter true && + git config uploadpack.allowanysha1inwant true + ) +' + +cleanup_clone () { + rm -rf "$1" +} + +test_expect_success 'creates content in enlistment root' ' + enlistment=cloned && + + scalar clone "file://$(pwd)/to-clone" $enlistment && + ls -A $enlistment >enlistment-root && + test_line_count = 1 enlistment-root && + test_path_is_dir $enlistment/src && + test_path_is_dir $enlistment/src/.git && + + cleanup_clone $enlistment +' + +test_expect_success 'with spaces' ' + enlistment="cloned with space" && + + scalar clone "file://$(pwd)/to-clone" "$enlistment" && + test_path_is_dir "$enlistment" && + test_path_is_dir "$enlistment/src" && + test_path_is_dir "$enlistment/src/.git" && + + cleanup_clone "$enlistment" +' + +test_expect_success 'partial clone if supported by server' ' + enlistment=partial-clone && + + scalar clone "file://$(pwd)/to-clone" $enlistment && + + ( + cd $enlistment/src && + + # Two promisor packs: one for refs, the other for blobs + ls .git/objects/pack/pack-*.promisor >promisorlist && + test_line_count = 2 promisorlist + ) && + + cleanup_clone $enlistment +' + +test_expect_success 'fall back on full clone if partial unsupported' ' + enlistment=no-partial-support && + + test_config -C to-clone uploadpack.allowfilter false && + test_config -C to-clone uploadpack.allowanysha1inwant false && + + scalar clone "file://$(pwd)/to-clone" $enlistment 2>err && + grep "filtering not recognized by server, ignoring" err && + + ( + cd $enlistment/src && + + # Still get a refs promisor file, but none for blobs + ls .git/objects/pack/pack-*.promisor >promisorlist && + test_line_count = 1 promisorlist + ) && + + cleanup_clone $enlistment +' + +test_expect_success 'initializes sparse-checkout by default' ' + enlistment=sparse && + + scalar clone "file://$(pwd)/to-clone" $enlistment && + ( + cd $enlistment/src && + test_cmp_config true core.sparseCheckout && + test_cmp_config true core.sparseCheckoutCone + ) && + + cleanup_clone $enlistment +' + +test_expect_success '--full-clone does not create sparse-checkout' ' + enlistment=full-clone && + + scalar clone --full-clone "file://$(pwd)/to-clone" $enlistment && + ( + cd $enlistment/src && + test_cmp_config "" --default "" core.sparseCheckout && + test_cmp_config "" --default "" core.sparseCheckoutCone + ) && + + cleanup_clone $enlistment +' + +test_expect_success '--single-branch clones HEAD only' ' + enlistment=single-branch && + + scalar clone --single-branch "file://$(pwd)/to-clone" $enlistment && + ( + cd $enlistment/src && + git for-each-ref refs/remotes/origin >out && + test_line_count = 1 out && + grep "refs/remotes/origin/base" out + ) && + + cleanup_clone $enlistment +' + +test_expect_success '--no-single-branch clones all branches' ' + enlistment=no-single-branch && + + scalar clone --no-single-branch "file://$(pwd)/to-clone" $enlistment && + ( + cd $enlistment/src && + git for-each-ref refs/remotes/origin >out && + test_line_count = 2 out && + grep "refs/remotes/origin/base" out && + grep "refs/remotes/origin/parallel" out + ) && + + cleanup_clone $enlistment +' + +test_done diff --git a/t/t9304-fast-import-marks.sh b/t/t9304-fast-import-marks.sh index bed01c99ea..a98ef032d9 100755 --- a/t/t9304-fast-import-marks.sh +++ b/t/t9304-fast-import-marks.sh @@ -25,6 +25,7 @@ test_expect_success 'import with large marks file' ' ' test_expect_success 'setup dump with submodule' ' + test_config_global protocol.file.allow always && git submodule add "$PWD" sub && git commit -m "add submodule" && git fast-export HEAD >dump diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index fc99703fc5..ff21a12ee6 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -268,6 +268,7 @@ test_expect_success 'signed-tags=warn-strip' ' test_expect_success 'setup submodule' ' + test_config_global protocol.file.allow always && git checkout -f main && mkdir sub && ( @@ -293,6 +294,7 @@ test_expect_success 'setup submodule' ' test_expect_success 'submodule fast-export | fast-import' ' + test_config_global protocol.file.allow always && SUBENT1=$(git ls-tree main^ sub) && SUBENT2=$(git ls-tree main sub) && rm -rf new && diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh index 102c133112..b105d6d9d5 100755 --- a/t/t9700-perl-git.sh +++ b/t/t9700-perl-git.sh @@ -4,17 +4,12 @@ # test_description='perl interface (Git.pm)' -. ./test-lib.sh -if ! test_have_prereq PERL; then - skip_all='skipping perl interface tests, perl not available' - test_done -fi +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-perl.sh -perl -MTest::More -e 0 2>/dev/null || { - skip_all="Perl Test::More unavailable, skipping test" - test_done -} +skip_all_if_no_Test_More # set up test repository @@ -50,11 +45,13 @@ test_expect_success \ git config --add test.pathmulti bar ' -# The external test will outputs its own plan -test_external_has_tap=1 +test_expect_success 'set up bare repository' ' + git init --bare bare.git +' -test_external_without_stderr \ - 'Perl API' \ - perl "$TEST_DIRECTORY"/t9700/test.pl +test_expect_success 'use t9700/test.pl to test Git.pm' ' + "$PERL_PATH" "$TEST_DIRECTORY"/t9700/test.pl 2>stderr && + test_must_be_empty stderr +' test_done diff --git a/t/t9700/test.pl b/t/t9700/test.pl index e046f7db76..6d753708d2 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -30,6 +30,18 @@ BEGIN { use_ok('Git') } # set up our $abs_repo_dir = cwd(); ok(our $r = Git->repository(Directory => "."), "open repository"); +{ + local $ENV{GIT_TEST_ASSUME_DIFFERENT_OWNER} = 1; + my $failed; + + $failed = eval { Git->repository(Directory => $abs_repo_dir) }; + ok(!$failed, "reject unsafe non-bare repository"); + like($@, qr/not a git repository/i, "unsafe error message"); + + $failed = eval { Git->repository(Directory => "$abs_repo_dir/bare.git") }; + ok(!$failed, "reject unsafe bare repository"); + like($@, qr/not a git repository/i, "unsafe error message"); +} # config is($r->config("test.string"), "value", "config scalar: string"); diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh index 468767cbf4..2a9838f37f 100755 --- a/t/t9814-git-p4-rename.sh +++ b/t/t9814-git-p4-rename.sh @@ -216,7 +216,7 @@ test_expect_success 'detect copies' ' # variable exists, which allows admins to disable the "p4 move" command. test_lazy_prereq P4D_HAVE_CONFIGURABLE_RUN_MOVE_ALLOW ' p4 configure show run.move.allow >out && - egrep ^run.move.allow: out + grep -E ^run.move.allow: out ' # If move can be disabled, turn it off and test p4 move handling diff --git a/t/t9815-git-p4-submit-fail.sh b/t/t9815-git-p4-submit-fail.sh index 9779dc0d11..0ca9937de6 100755 --- a/t/t9815-git-p4-submit-fail.sh +++ b/t/t9815-git-p4-submit-fail.sh @@ -417,8 +417,8 @@ test_expect_success 'cleanup chmod after submit cancel' ' ! p4 fstat -T action text && test_path_is_file text+x && ! p4 fstat -T action text+x && - ls -l text | egrep ^-r-- && - ls -l text+x | egrep ^-r-x + ls -l text | grep -E ^-r-- && + ls -l text+x | grep -E ^-r-x ) ' diff --git a/t/t9850-shell.sh b/t/t9850-shell.sh new file mode 100755 index 0000000000..cfc71c3bd4 --- /dev/null +++ b/t/t9850-shell.sh @@ -0,0 +1,37 @@ +#!/bin/sh + +test_description='git shell tests' +. ./test-lib.sh + +test_expect_success 'shell allows upload-pack' ' + printf 0000 >input && + git upload-pack . <input >expect && + git shell -c "git-upload-pack $SQ.$SQ" <input >actual && + test_cmp expect actual +' + +test_expect_success 'shell forbids other commands' ' + test_must_fail git shell -c "git config foo.bar baz" +' + +test_expect_success 'shell forbids interactive use by default' ' + test_must_fail git shell +' + +test_expect_success 'shell allows interactive command' ' + mkdir git-shell-commands && + write_script git-shell-commands/ping <<-\EOF && + echo pong + EOF + echo pong >expect && + echo ping | git shell >actual && + test_cmp expect actual +' + +test_expect_success 'shell complains of overlong commands' ' + perl -e "print \"a\" x 2**12 for (0..2**19)" | + test_must_fail git shell 2>err && + grep "too long" err +' + +test_done diff --git a/t/t9901-git-web--browse.sh b/t/t9901-git-web--browse.sh index de7152f827..19f56e5680 100755 --- a/t/t9901-git-web--browse.sh +++ b/t/t9901-git-web--browse.sh @@ -5,6 +5,7 @@ test_description='git web--browse basic tests This test checks that git web--browse can handle various valid URLs.' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_web_browse () { diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 6a30f5719c..d459fae655 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -759,4 +759,20 @@ test_expect_success 'prompt - hide if pwd ignored - inside gitdir' ' test_cmp expected "$actual" ' +test_expect_success 'prompt - conflict indicator' ' + printf " (main|CONFLICT)" >expected && + echo "stash" >file && + git stash && + test_when_finished "git stash drop" && + echo "commit" >file && + git commit -m "commit" file && + test_when_finished "git reset --hard HEAD~" && + test_must_fail git stash apply && + ( + GIT_PS1_SHOWCONFLICTSTATE="yes" && + __git_ps1 >"$actual" + ) && + test_cmp expected "$actual" +' + test_done diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 8c44856eae..29d914a12b 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -273,13 +273,13 @@ debug () { # <file>, <contents>, and <tag> all default to <message>. test_commit () { - notick= && - echo=echo && - append= && - author= && - signoff= && - indir= && - tag=light && + local notick= && + local echo=echo && + local append= && + local author= && + local signoff= && + local indir= && + local tag=light && while test $# != 0 do case "$1" in @@ -322,7 +322,7 @@ test_commit () { shift done && indir=${indir:+"$indir"/} && - file=${2:-"$1.t"} && + local file=${2:-"$1.t"} && if test -n "$append" then $echo "${3-$1}" >>"$indir$file" @@ -633,7 +633,7 @@ test_hook () { # - Explicitly using test_have_prereq. # # - Implicitly by specifying the prerequisite tag in the calls to -# test_expect_{success,failure} and test_external{,_without_stderr}. +# test_expect_{success,failure} # # The single parameter is the prerequisite tag (a simple word, in all # capital letters by convention). @@ -835,93 +835,6 @@ test_expect_success () { test_finish_ } -# test_external runs external test scripts that provide continuous -# test output about their progress, and succeeds/fails on -# zero/non-zero exit code. It outputs the test output on stdout even -# in non-verbose mode, and announces the external script with "# run -# <n>: ..." before running it. When providing relative paths, keep in -# mind that all scripts run in "trash directory". -# Usage: test_external description command arguments... -# Example: test_external 'Perl API' perl ../path/to/test.pl -test_external () { - test "$#" = 4 && { test_prereq=$1; shift; } || test_prereq= - test "$#" = 3 || - BUG "not 3 or 4 parameters to test_external" - descr="$1" - shift - test_verify_prereq - export test_prereq - if ! test_skip "$descr" "$@" - then - # Announce the script to reduce confusion about the - # test output that follows. - say_color "" "# run $test_count: $descr ($*)" - # Export TEST_DIRECTORY, TRASH_DIRECTORY and GIT_TEST_LONG - # to be able to use them in script - export TEST_DIRECTORY TRASH_DIRECTORY GIT_TEST_LONG - # Run command; redirect its stderr to &4 as in - # test_run_, but keep its stdout on our stdout even in - # non-verbose mode. - "$@" 2>&4 - if test "$?" = 0 - then - if test $test_external_has_tap -eq 0; then - test_ok_ "$descr" - else - say_color "" "# test_external test $descr was ok" - test_success=$(($test_success + 1)) - fi - else - if test $test_external_has_tap -eq 0; then - test_failure_ "$descr" "$@" - else - say_color error "# test_external test $descr failed: $@" - test_failure=$(($test_failure + 1)) - fi - fi - fi -} - -# Like test_external, but in addition tests that the command generated -# no output on stderr. -test_external_without_stderr () { - # The temporary file has no (and must have no) security - # implications. - tmp=${TMPDIR:-/tmp} - stderr="$tmp/git-external-stderr.$$.tmp" - test_external "$@" 4> "$stderr" - test -f "$stderr" || error "Internal error: $stderr disappeared." - descr="no stderr: $1" - shift - say >&3 "# expecting no stderr from previous command" - if test ! -s "$stderr" - then - rm "$stderr" - - if test $test_external_has_tap -eq 0; then - test_ok_ "$descr" - else - say_color "" "# test_external_without_stderr test $descr was ok" - test_success=$(($test_success + 1)) - fi - else - if test "$verbose" = t - then - output=$(echo; echo "# Stderr is:"; cat "$stderr") - else - output= - fi - # rm first in case test_failure exits. - rm "$stderr" - if test $test_external_has_tap -eq 0; then - test_failure_ "$descr" "$@" "$output" - else - say_color error "# test_external_without_stderr test $descr failed: $@: $output" - test_failure=$(($test_failure + 1)) - fi - fi -} - # debugging-friendly alternatives to "test [-f|-d|-e]" # The commands test the existence or non-existence of $1 test_path_is_file () { @@ -984,7 +897,7 @@ test_path_is_symlink () { test_dir_is_empty () { test "$#" -ne 1 && BUG "1 param" test_path_is_dir "$1" && - if test -n "$(ls -a1 "$1" | egrep -v '^\.\.?$')" + if test -n "$(ls -a1 "$1" | grep -E -v '^\.\.?$')" then echo "Directory '$1' is not empty, it contains:" ls -la "$1" @@ -1955,3 +1868,14 @@ test_is_magic_mtime () { rm -f .git/test-mtime-actual return $ret } + +# Given two filenames, parse both using 'git config --list --file' +# and compare the sorted output of those commands. Useful when +# wanting to ignore whitespace differences and sorting concerns. +test_cmp_config_output () { + git config --list --file="$1" >config-expect && + git config --list --file="$2" >config-actual && + sort config-expect >sorted-expect && + sort config-actual >sorted-actual && + test_cmp sorted-expect sorted-actual +} diff --git a/t/test-lib.sh b/t/test-lib.sh index 7726d1da88..6db377f68b 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -47,6 +47,16 @@ then echo "PANIC: Running in a $TEST_DIRECTORY that doesn't end in '/t'?" >&2 exit 1 fi +if test -f "$GIT_BUILD_DIR/GIT-BUILD-DIR" +then + GIT_BUILD_DIR="$(cat "$GIT_BUILD_DIR/GIT-BUILD-DIR")" || exit 1 + # On Windows, we must convert Windows paths lest they contain a colon + case "$(uname -s)" in + *MINGW*) + GIT_BUILD_DIR="$(cygpath -au "$GIT_BUILD_DIR")" + ;; + esac +fi # Prepend a string to a VAR using an arbitrary ":" delimiter, not # adding the delimiter if VAR or VALUE is empty. I.e. a generalized: @@ -238,6 +248,9 @@ parse_option () { ;; esac ;; + --invert-exit-code) + invert_exit_code=t + ;; *) echo "error: unknown test option '$opt'" >&2; exit 1 ;; esac @@ -302,6 +315,11 @@ TEST_NUMBER="${TEST_NAME%%-*}" TEST_NUMBER="${TEST_NUMBER#t}" TEST_RESULTS_DIR="$TEST_OUTPUT_DIRECTORY/test-results" TEST_RESULTS_BASE="$TEST_RESULTS_DIR/$TEST_NAME$TEST_STRESS_JOB_SFX" +TEST_RESULTS_SAN_FILE_PFX=trace +TEST_RESULTS_SAN_DIR_SFX=leak +TEST_RESULTS_SAN_FILE= +TEST_RESULTS_SAN_DIR="$TEST_RESULTS_DIR/$TEST_NAME.$TEST_RESULTS_SAN_DIR_SFX" +TEST_RESULTS_SAN_DIR_NR_LEAKS_STARTUP= TRASH_DIRECTORY="trash directory.$TEST_NAME$TEST_STRESS_JOB_SFX" test -n "$root" && TRASH_DIRECTORY="$root/$TRASH_DIRECTORY" case "$TRASH_DIRECTORY" in @@ -309,6 +327,16 @@ case "$TRASH_DIRECTORY" in *) TRASH_DIRECTORY="$TEST_OUTPUT_DIRECTORY/$TRASH_DIRECTORY" ;; esac +# Utility functions using $TEST_RESULTS_* variables +nr_san_dir_leaks_ () { + # stderr piped to /dev/null because the directory may have + # been "rmdir"'d already. + find "$TEST_RESULTS_SAN_DIR" \ + -type f \ + -name "$TEST_RESULTS_SAN_FILE_PFX.*" 2>/dev/null | + wc -l +} + # If --stress was passed, run this test repeatedly in several parallel loops. if test "$GIT_TEST_STRESS_STARTED" = "done" then @@ -545,9 +573,11 @@ case $GIT_TEST_FSYNC in esac # Add libc MALLOC and MALLOC_PERTURB test only if we are not executing -# the test with valgrind and have not compiled with SANITIZE=address. +# the test with valgrind and have not compiled with conflict SANITIZE +# options. if test -n "$valgrind" || test -n "$SANITIZE_ADDRESS" || + test -n "$SANITIZE_LEAK" || test -n "$TEST_NO_MALLOC_CHECK" then setup_malloc_check () { @@ -557,14 +587,19 @@ then : nothing } else + _USE_GLIBC_TUNABLES= + if _GLIBC_VERSION=$(getconf GNU_LIBC_VERSION 2>/dev/null) && + _GLIBC_VERSION=${_GLIBC_VERSION#"glibc "} && + expr 2.34 \<= "$_GLIBC_VERSION" >/dev/null + then + _USE_GLIBC_TUNABLES=YesPlease + fi setup_malloc_check () { local g local t MALLOC_CHECK_=3 MALLOC_PERTURB_=165 export MALLOC_CHECK_ MALLOC_PERTURB_ - if _GLIBC_VERSION=$(getconf GNU_LIBC_VERSION 2>/dev/null) && - _GLIBC_VERSION=${_GLIBC_VERSION#"glibc "} && - expr 2.34 \<= "$_GLIBC_VERSION" >/dev/null + if test -n "$_USE_GLIBC_TUNABLES" then g= LD_PRELOAD="libc_malloc_debug.so.0" @@ -788,15 +823,31 @@ test_ok_ () { finalize_test_case_output ok "$@" } +_invert_exit_code_failure_end_blurb () { + say_color warn "# faked up failures as TODO & now exiting with 0 due to --invert-exit-code" +} + test_failure_ () { failure_label=$1 test_failure=$(($test_failure + 1)) - say_color error "not ok $test_count - $1" + local pfx="" + if test -n "$invert_exit_code" # && test -n "$HARNESS_ACTIVE" + then + pfx="# TODO induced breakage (--invert-exit-code):" + fi + say_color error "not ok $test_count - ${pfx:+$pfx }$1" shift printf '%s\n' "$*" | sed -e 's/^/# /' if test -n "$immediate" then say_color error "1..$test_count" + if test -n "$invert_exit_code" + then + finalize_test_output + _invert_exit_code_failure_end_blurb + GIT_EXIT_OK=t + exit 0 + fi _error_exit fi finalize_test_case_output failure "$failure_label" "$@" @@ -804,14 +855,14 @@ test_failure_ () { test_known_broken_ok_ () { test_fixed=$(($test_fixed+1)) - say_color error "ok $test_count - $@ # TODO known breakage vanished" - finalize_test_case_output fixed "$@" + say_color error "ok $test_count - $1 # TODO known breakage vanished" + finalize_test_case_output fixed "$1" } test_known_broken_failure_ () { test_broken=$(($test_broken+1)) - say_color warn "not ok $test_count - $@ # TODO known breakage" - finalize_test_case_output broken "$@" + say_color warn "not ok $test_count - $1 # TODO known breakage" + finalize_test_case_output broken "$1" } test_debug () { @@ -1052,11 +1103,7 @@ test_run_ () { trace= # 117 is magic because it is unlikely to match the exit # code of other programs - if test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)" || - { - test "${GIT_TEST_CHAIN_LINT_HARDER:-${GIT_TEST_CHAIN_LINT_HARDER_DEFAULT:-1}}" != 0 && - $(printf '%s\n' "$1" | sed -f "$GIT_BUILD_DIR/t/chainlint.sed" | grep -q '?![A-Z][A-Z]*?!') - } + if test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)" then BUG "broken &&-chain or run-away HERE-DOC: $1" fi @@ -1168,9 +1215,67 @@ test_atexit_handler () { teardown_malloc_check } -test_done () { - GIT_EXIT_OK=t +sanitize_leak_log_message_ () { + local new="$1" && + local old="$2" && + local file="$3" && + + printf "With SANITIZE=leak at exit we have %d leak logs, but started with %d + +This means that we have a blindspot where git is leaking but we're +losing the exit code somewhere, or not propagating it appropriately +upwards! + +See the logs at \"%s.*\"; +those logs are reproduced below." \ + "$new" "$old" "$file" +} +check_test_results_san_file_ () { + if test -z "$TEST_RESULTS_SAN_FILE" + then + return + fi && + local old="$TEST_RESULTS_SAN_DIR_NR_LEAKS_STARTUP" && + local new="$(nr_san_dir_leaks_)" && + + if test $new -le $old + then + return + fi && + local out="$(sanitize_leak_log_message_ "$new" "$old" "$TEST_RESULTS_SAN_FILE")" && + say_color error "$out" && + if test "$old" != 0 + then + echo && + say_color error "The logs include output from past runs to avoid" && + say_color error "that remove 'test-results' between runs." + fi && + say_color error "$(cat "$TEST_RESULTS_SAN_FILE".*)" && + + if test -n "$passes_sanitize_leak" && test "$test_failure" = 0 + then + say "As TEST_PASSES_SANITIZE_LEAK=true and our logs show we're leaking, exit non-zero!" && + invert_exit_code=t + elif test -n "$passes_sanitize_leak" + then + say "As TEST_PASSES_SANITIZE_LEAK=true and our logs show we're leaking, and we're failing for other reasons too..." && + invert_exit_code= + elif test -n "$sanitize_leak_check" && test "$test_failure" = 0 + then + say "As TEST_PASSES_SANITIZE_LEAK=true isn't set the above leak is 'ok' with GIT_TEST_PASSING_SANITIZE_LEAK=check" && + invert_exit_code= + elif test -n "$sanitize_leak_check" + then + say "As TEST_PASSES_SANITIZE_LEAK=true isn't set the above leak is 'ok' with GIT_TEST_PASSING_SANITIZE_LEAK=check" && + invert_exit_code=t + else + say "With GIT_TEST_SANITIZE_LEAK_LOG=true our logs revealed a memory leak, exit non-zero!" && + invert_exit_code=t + fi +} + +test_done () { # Run the atexit commands _before_ the trash directory is # removed, so the commands can access pidfiles and socket files. test_atexit_handler @@ -1210,28 +1315,32 @@ test_done () { fi case "$test_failure" in 0) - if test $test_external_has_tap -eq 0 + if test $test_remaining -gt 0 then - if test $test_remaining -gt 0 - then - say_color pass "# passed all $msg" - fi - - # Maybe print SKIP message - test -z "$skip_all" || skip_all="# SKIP $skip_all" - case "$test_count" in - 0) - say "1..$test_count${skip_all:+ $skip_all}" - ;; - *) - test -z "$skip_all" || - say_color warn "$skip_all" - say "1..$test_count" - ;; - esac + say_color pass "# passed all $msg" fi - if test -z "$debug" && test -n "$remove_trash" + # Maybe print SKIP message + test -z "$skip_all" || skip_all="# SKIP $skip_all" + case "$test_count" in + 0) + say "1..$test_count${skip_all:+ $skip_all}" + ;; + *) + test -z "$skip_all" || + say_color warn "$skip_all" + say "1..$test_count" + ;; + esac + + if test -n "$stress" && test -n "$invert_exit_code" + then + # We're about to move our "$TRASH_DIRECTORY" + # to "$TRASH_DIRECTORY.stress-failed" if + # --stress is combined with + # --invert-exit-code. + say "with --stress and --invert-exit-code we're not removing '$TRASH_DIRECTORY'" + elif test -z "$debug" && test -n "$remove_trash" then test -d "$TRASH_DIRECTORY" || error "Tests passed but trash directory already removed before test cleanup; aborting" @@ -1244,17 +1353,35 @@ test_done () { } || error "Tests passed but test cleanup failed; aborting" fi + + check_test_results_san_file_ "$test_failure" + + if test -z "$skip_all" && test -n "$invert_exit_code" + then + say_color warn "# faking up non-zero exit with --invert-exit-code" + GIT_EXIT_OK=t + exit 1 + fi + test_at_end_hook_ + GIT_EXIT_OK=t exit 0 ;; *) - if test $test_external_has_tap -eq 0 + say_color error "# failed $test_failure among $msg" + say "1..$test_count" + + check_test_results_san_file_ "$test_failure" + + if test -n "$invert_exit_code" then - say_color error "# failed $test_failure among $msg" - say "1..$test_count" + _invert_exit_code_failure_end_blurb + GIT_EXIT_OK=t + exit 0 fi + GIT_EXIT_OK=t exit 1 ;; esac @@ -1387,14 +1514,12 @@ fi GITPERLLIB="$GIT_BUILD_DIR"/perl/build/lib export GITPERLLIB test -d "$GIT_BUILD_DIR"/templates/blt || { - error "You haven't built things yet, have you?" + BAIL_OUT "You haven't built things yet, have you?" } if ! test -x "$GIT_BUILD_DIR"/t/helper/test-tool$X then - echo >&2 'You need to build test-tool:' - echo >&2 'Run "make t/helper/test-tool" in the source (toplevel) directory' - exit 1 + BAIL_OUT 'You need to build test-tool; Run "make t/helper/test-tool" in the source (toplevel) directory' fi # Are we running this test at all? @@ -1408,24 +1533,76 @@ then test_done fi -# skip non-whitelisted tests when compiled with SANITIZE=leak +BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK () { + BAIL_OUT "$1 has no effect except when compiled with SANITIZE=leak" +} + if test -n "$SANITIZE_LEAK" then - if test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false + # Normalize with test_bool_env + passes_sanitize_leak= + + # We need to see TEST_PASSES_SANITIZE_LEAK in "git + # env--helper" (via test_bool_env) + export TEST_PASSES_SANITIZE_LEAK + if test_bool_env TEST_PASSES_SANITIZE_LEAK false then - # We need to see it in "git env--helper" (via - # test_bool_env) - export TEST_PASSES_SANITIZE_LEAK + passes_sanitize_leak=t + fi + + if test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check" + then + sanitize_leak_check=t + if test -n "$invert_exit_code" + then + BAIL_OUT "cannot use --invert-exit-code under GIT_TEST_PASSING_SANITIZE_LEAK=check" + fi - if ! test_bool_env TEST_PASSES_SANITIZE_LEAK false + if test -z "$passes_sanitize_leak" then - skip_all="skipping $this_test under GIT_TEST_PASSING_SANITIZE_LEAK=true" - test_done + say "in GIT_TEST_PASSING_SANITIZE_LEAK=check mode, setting --invert-exit-code for TEST_PASSES_SANITIZE_LEAK != true" + invert_exit_code=t fi + elif test -z "$passes_sanitize_leak" && + test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false + then + skip_all="skipping $this_test under GIT_TEST_PASSING_SANITIZE_LEAK=true" + test_done + fi + + if test_bool_env GIT_TEST_SANITIZE_LEAK_LOG false + then + if ! mkdir -p "$TEST_RESULTS_SAN_DIR" + then + BAIL_OUT "cannot create $TEST_RESULTS_SAN_DIR" + fi && + TEST_RESULTS_SAN_FILE="$TEST_RESULTS_SAN_DIR/$TEST_RESULTS_SAN_FILE_PFX" + + # In case "test-results" is left over from a previous + # run: Only report if new leaks show up. + TEST_RESULTS_SAN_DIR_NR_LEAKS_STARTUP=$(nr_san_dir_leaks_) + + # Don't litter *.leak dirs if there was nothing to report + test_atexit "rmdir \"$TEST_RESULTS_SAN_DIR\" 2>/dev/null || :" + + prepend_var LSAN_OPTIONS : dedup_token_length=9999 + prepend_var LSAN_OPTIONS : log_exe_name=1 + prepend_var LSAN_OPTIONS : log_path=\"$TEST_RESULTS_SAN_FILE\" + export LSAN_OPTIONS fi -elif test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false +elif test "$GIT_TEST_PASSING_SANITIZE_LEAK" = "check" || + test_bool_env GIT_TEST_PASSING_SANITIZE_LEAK false +then + BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK "GIT_TEST_PASSING_SANITIZE_LEAK=true" +elif test_bool_env GIT_TEST_SANITIZE_LEAK_LOG false then - BAIL_OUT "GIT_TEST_PASSING_SANITIZE_LEAK=true has no effect except when compiled with SANITIZE=leak" + BAIL_OUT_ENV_NEEDS_SANITIZE_LEAK "GIT_TEST_SANITIZE_LEAK_LOG=true" +fi + +if test "${GIT_TEST_CHAIN_LINT:-1}" != 0 +then + "$PERL_PATH" "$TEST_DIRECTORY/chainlint.pl" "$0" || + BUG "lint error (see '?!...!? annotations above)" fi # Last-minute variable setup @@ -1448,9 +1625,7 @@ remove_trash_directory () { # Test repository remove_trash_directory "$TRASH_DIRECTORY" || { - GIT_EXIT_OK=t - echo >&5 "FATAL: Cannot prepare test area" - exit 1 + BAIL_OUT 'cannot prepare test area' } remove_trash=t @@ -1466,7 +1641,7 @@ fi # Use -P to resolve symlinks in our working directory so that the cwd # in subprocesses like git equals our $PWD (for pathname comparisons). -cd -P "$TRASH_DIRECTORY" || exit 1 +cd -P "$TRASH_DIRECTORY" || BAIL_OUT "cannot cd -P to \"$TRASH_DIRECTORY\"" start_test_output "$0" |
