diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-20 05:14:36 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-20 05:14:36 +0000 |
commit | 037de004c68d704abf839eebe075c58c9603f8f3 (patch) | |
tree | 7ac13a7fbb70193e7d04fc193f75de839e914d45 /t | |
parent | Adding upstream version 1:2.43.0. (diff) | |
download | git-037de004c68d704abf839eebe075c58c9603f8f3.tar.xz git-037de004c68d704abf839eebe075c58c9603f8f3.zip |
Adding upstream version 1:2.45.1.upstream/1%2.45.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
333 files changed, 10690 insertions, 3003 deletions
@@ -1,3 +1,6 @@ +# The default target of this Makefile is... +all:: + # Import tree-wide shared Makefile behavior and libraries include ../shared.mak @@ -6,6 +9,7 @@ include ../shared.mak # Copyright (c) 2005 Junio C Hamano # +-include ../config.mak.uname -include ../config.mak.autogen -include ../config.mak @@ -17,6 +21,7 @@ TAR ?= $(TAR) RM ?= rm -f PROVE ?= prove DEFAULT_TEST_TARGET ?= test +DEFAULT_UNIT_TEST_TARGET ?= unit-tests-raw TEST_LINT ?= test-lint ifdef TEST_OUTPUT_DIRECTORY @@ -41,13 +46,16 @@ 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 = '$(PERL_PATH_SQ)' chainlint.pl +UNIT_TEST_SOURCES = $(wildcard unit-tests/t-*.c) +UNIT_TEST_PROGRAMS = $(patsubst unit-tests/%.c,unit-tests/bin/%$(X),$(UNIT_TEST_SOURCES)) +UNIT_TESTS = $(sort $(filter-out unit-tests/bin/t-basic%,$(UNIT_TEST_PROGRAMS))) # `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 run the external "chainlint.pl" script themselves CHAINLINTSUPPRESS = GIT_TEST_EXT_CHAIN_LINT=0 && export GIT_TEST_EXT_CHAIN_LINT && -all: $(DEFAULT_TEST_TARGET) +all:: $(DEFAULT_TEST_TARGET) test: pre-clean check-chainlint $(TEST_LINT) $(CHAINLINTSUPPRESS) $(MAKE) aggregate-results-and-cleanup @@ -65,6 +73,17 @@ prove: pre-clean check-chainlint $(TEST_LINT) $(T): @echo "*** $@ ***"; '$(TEST_SHELL_PATH_SQ)' $@ $(GIT_TEST_OPTS) +$(UNIT_TESTS): + @echo "*** $@ ***"; $@ + +.PHONY: unit-tests unit-tests-raw unit-tests-prove +unit-tests: $(DEFAULT_UNIT_TEST_TARGET) + +unit-tests-raw: $(UNIT_TESTS) + +unit-tests-prove: + @echo "*** prove - unit tests ***"; $(PROVE) $(GIT_PROVE_OPTS) $(UNIT_TESTS) + pre-clean: $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' @@ -90,20 +109,12 @@ check-chainlint: echo "# chainlint: $(CHAINLINTTMP_SQ)/tests" && \ for i in $(CHAINLINTTESTS); do \ echo "# chainlint: $$i" && \ - sed -e '/^[ ]*$$/d' chainlint/$$i.expect; \ + cat chainlint/$$i.expect; \ done \ } >'$(CHAINLINTTMP_SQ)'/expect && \ $(CHAINLINT) --emit-all '$(CHAINLINTTMP_SQ)'/tests | \ - sed -e 's/^[1-9][0-9]* //;/^[ ]*$$/d' >'$(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 + sed -e 's/^[1-9][0-9]* //' >'$(CHAINLINTTMP_SQ)'/actual && \ + diff -u '$(CHAINLINTTMP_SQ)'/expect '$(CHAINLINTTMP_SQ)'/actual test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax \ test-lint-filenames @@ -149,4 +160,4 @@ perf: $(MAKE) -C perf/ all .PHONY: pre-clean $(T) aggregate-results clean valgrind perf \ - check-chainlint clean-chainlint test-chainlint + check-chainlint clean-chainlint test-chainlint $(UNIT_TESTS) @@ -32,7 +32,14 @@ the tests. ok 2 - plain with GIT_WORK_TREE ok 3 - plain bare -Since the tests all output TAP (see http://testanything.org) they can +t/Makefile defines a target for each test file, such that you can also use +shell pattern matching to run a subset of the tests: + + make *checkout* + +will run all tests with 'checkout' in their filename. + +Since the tests all output TAP (see https://testanything.org) they can be run with any TAP harness. Here's an example of parallel testing powered by a recent version of prove(1): @@ -479,6 +486,9 @@ GIT_TEST_DEFAULT_HASH=<hash-algo> specifies which hash algorithm to use in the test scripts. Recognized values for <hash-algo> are "sha1" and "sha256". +GIT_TEST_DEFAULT_REF_FORMAT=<format> specifies which ref storage format +to use in the test scripts. Recognized values for <format> are "files". + GIT_TEST_NO_WRITE_REV_INDEX=<boolean>, when true disables the 'pack.writeReverseIndex' setting. @@ -721,6 +731,26 @@ The "do's:" Note that we still &&-chain the loop to propagate failures from earlier commands. + - Repeat tests with slightly different arguments in a loop. + + In some cases it may make sense to re-run the same set of tests with + different options or commands to ensure that the command behaves + despite the different parameters. This can be achieved by looping + around a specific parameter: + + for arg in '' "--foo" + do + test_expect_success "test command ${arg:-without arguments}" ' + command $arg + ' + done + + Note that while the test title uses double quotes ("), the test body + should continue to use single quotes (') to avoid breakage in case the + values contain e.g. quoting characters. The loop variable will be + accessible regardless of the single quotes as the test body is passed + to `eval`. + And here are the "don'ts:" @@ -1278,7 +1308,7 @@ Devel::Cover module. To install it do: sudo aptitude install libdevel-cover-perl # From the CPAN with cpanminus - curl -L http://cpanmin.us | perl - --sudo --self-upgrade + curl -L https://cpanmin.us/ | perl - --sudo --self-upgrade cpanm --sudo Devel::Cover Then, at the top-level: diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index 5e21e84..8757245 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -532,7 +532,7 @@ test_expect_success 'blame -L :funcname with userdiff driver' ' "$(cat file.template)" && test_commit --author "B <B@test.git>" \ "change" "$fortran_file" \ - "$(cat file.template | sed -e s/ChangeMe/IWasChanged/)" && + "$(sed -e s/ChangeMe/IWasChanged/ file.template)" && check_count -f "$fortran_file" -L:RIGHT A 3 B 1 ' diff --git a/t/chainlint/blank-line-before-esac.expect b/t/chainlint/blank-line-before-esac.expect index 48ed4eb..056e030 100644 --- a/t/chainlint/blank-line-before-esac.expect +++ b/t/chainlint/blank-line-before-esac.expect @@ -1,11 +1,11 @@ -test_done ( ) { +test_done () { case "$test_failure" in - 0 ) + 0) test_at_end_hook_ exit 0 ;; - * ) + *) if test $test_external_has_tap -eq 0 then say_color error "# failed $test_failure among $msg" @@ -14,5 +14,5 @@ test_done ( ) { exit 1 ;; - esac + esac } diff --git a/t/chainlint/blank-line.expect b/t/chainlint/blank-line.expect index f76fde1..b47827d 100644 --- a/t/chainlint/blank-line.expect +++ b/t/chainlint/blank-line.expect @@ -1,4 +1,8 @@ ( + nothing && + something + + ) diff --git a/t/chainlint/block.expect b/t/chainlint/block.expect index a3bcea4..1c87326 100644 --- a/t/chainlint/block.expect +++ b/t/chainlint/block.expect @@ -12,9 +12,9 @@ ) && { - echo a ; ?!AMP?! echo b + echo a; ?!AMP?! echo b } && -{ echo a ; ?!AMP?! echo b ; } && +{ echo a; ?!AMP?! echo b; } && { echo "${var}9" && diff --git a/t/chainlint/chain-break-background.expect b/t/chainlint/chain-break-background.expect index 28f9114..20d0bb5 100644 --- a/t/chainlint/chain-break-background.expect +++ b/t/chainlint/chain-break-background.expect @@ -1,9 +1,9 @@ JGIT_DAEMON_PID= && git init --bare empty.git && -> empty.git/git-daemon-export-ok && +>empty.git/git-daemon-export-ok && mkfifo jgit_daemon_output && { - jgit daemon --port="$JGIT_DAEMON_PORT" . > 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-return-exit.expect b/t/chainlint/chain-break-return-exit.expect index 1732d22..4cd18e2 100644 --- a/t/chainlint/chain-break-return-exit.expect +++ b/t/chainlint/chain-break-return-exit.expect @@ -1,16 +1,16 @@ case "$(git ls-files)" in -one ) echo pass one ;; -* ) echo bad one ; return 1 ;; +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 + 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 ;; +dir/two"$LF"one) echo pass both ;; +*) echo bad; return 1 ;; esac && for i in 1 2 3 4 ; do diff --git a/t/chainlint/chain-break-status.expect b/t/chainlint/chain-break-status.expect index f4bada9..e6b3b21 100644 --- a/t/chainlint/chain-break-status.expect +++ b/t/chainlint/chain-break-status.expect @@ -1,7 +1,7 @@ -OUT=$(( ( large_git ; echo $? 1 >& 3 ) | : ) 3 >& 1) && +OUT=$( ((large_git; echo $? 1>&3) | :) 3>&1 ) && test_match_signal 13 "$OUT" && -{ test-tool sigchain > actual ; ret=$? ; } && +{ test-tool sigchain >actual; ret=$?; } && { test_match_signal 15 "$ret" || test "$ret" = 3 diff --git a/t/chainlint/chained-subshell.expect b/t/chainlint/chained-subshell.expect index af0369d..83810ea 100644 --- a/t/chainlint/chained-subshell.expect +++ b/t/chainlint/chained-subshell.expect @@ -4,7 +4,7 @@ mkdir sub && ( nuff said ) && -cut "-d " -f actual | ( read s1 s2 s3 && +cut "-d " -f actual | (read s1 s2 s3 && test -f $s1 ?!AMP?! test $(cat $s2) = tree2path1 && -test $(cat $s3) = tree3path1 ) +test $(cat $s3) = tree3path1) diff --git a/t/chainlint/command-substitution-subsubshell.expect b/t/chainlint/command-substitution-subsubshell.expect index ab2f79e..ec42f2c 100644 --- a/t/chainlint/command-substitution-subsubshell.expect +++ b/t/chainlint/command-substitution-subsubshell.expect @@ -1,2 +1,2 @@ -OUT=$(( ( large_git 1 >& 3 ) | : ) 3 >& 1) && +OUT=$( ((large_git 1>&3) | :) 3>&1 ) && test_match_signal 13 "$OUT" diff --git a/t/chainlint/dqstring-line-splice.expect b/t/chainlint/dqstring-line-splice.expect index bf9ced6..37eab80 100644 --- a/t/chainlint/dqstring-line-splice.expect +++ b/t/chainlint/dqstring-line-splice.expect @@ -1,3 +1,5 @@ -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 && + +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 index 1072498..087eda1 100644 --- a/t/chainlint/dqstring-no-interpolate.expect +++ b/t/chainlint/dqstring-no-interpolate.expect @@ -6,6 +6,7 @@ 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 && +) >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 index e8733c9..8507721 100644 --- a/t/chainlint/empty-here-doc.expect +++ b/t/chainlint/empty-here-doc.expect @@ -1,4 +1,4 @@ -git ls-tree $tree path > current && -cat > expected <<\EOF && +git ls-tree $tree path >current && +cat >expected <<\EOF && EOF test_output diff --git a/t/chainlint/exclamation.expect b/t/chainlint/exclamation.expect index 2d961a5..765a35b 100644 --- a/t/chainlint/exclamation.expect +++ b/t/chainlint/exclamation.expect @@ -1,4 +1,4 @@ -if ! condition ; then echo nope ; else yep ; fi && +if ! condition; then echo nope; else yep; fi && test_prerequisite !MINGW && mail uucp!address && echo !whatever! diff --git a/t/chainlint/for-loop-abbreviated.expect b/t/chainlint/for-loop-abbreviated.expect index a21007a..02c0d15 100644 --- a/t/chainlint/for-loop-abbreviated.expect +++ b/t/chainlint/for-loop-abbreviated.expect @@ -1,5 +1,5 @@ for it do - path=$(expr "$it" : ( [^:]*) ) && + 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 d65c821..d2237f1 100644 --- a/t/chainlint/for-loop.expect +++ b/t/chainlint/for-loop.expect @@ -6,6 +6,7 @@ bar EOF done ?!AMP?! + for i in a b c; do echo $i && cat $i ?!LOOP?! diff --git a/t/chainlint/function.expect b/t/chainlint/function.expect index a14388e..dd7c997 100644 --- a/t/chainlint/function.expect +++ b/t/chainlint/function.expect @@ -1,8 +1,8 @@ -sha1_file ( ) { +sha1_file() { echo "$*" | sed "s#..#.git/objects/&/#" } && -remove_object ( ) { +remove_object() { file=$(sha1_file "$*") && test -e "$file" ?!AMP?! rm -f "$file" diff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect index 1df3f78..91b9612 100644 --- a/t/chainlint/here-doc.expect +++ b/t/chainlint/here-doc.expect @@ -1,6 +1,6 @@ boodle wobba \ - gorgo snoot \ - wafta snurb <<EOF && + gorgo snoot \ + wafta snurb <<EOF && quoth the raven, nevermore... EOF diff --git a/t/chainlint/loop-detect-status.expect b/t/chainlint/loop-detect-status.expect index 24da9e8..7ce3a34 100644 --- a/t/chainlint/loop-detect-status.expect +++ b/t/chainlint/loop-detect-status.expect @@ -1,18 +1,18 @@ -( while test $i -le $blobcount -do - printf "Generating blob $i/$blobcount\r" >& 2 && +(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 ) | + 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/nested-cuddled-subshell.expect b/t/chainlint/nested-cuddled-subshell.expect index 2a86885..3836049 100644 --- a/t/chainlint/nested-cuddled-subshell.expect +++ b/t/chainlint/nested-cuddled-subshell.expect @@ -2,18 +2,24 @@ (cd foo && bar ) && + (cd foo && bar ) ?!AMP?! + ( cd foo && bar) && + ( cd foo && bar) ?!AMP?! + (cd foo && bar) && + (cd foo && bar) ?!AMP?! + foobar ) diff --git a/t/chainlint/nested-loop-detect-failure.expect b/t/chainlint/nested-loop-detect-failure.expect index 4793a0e..3461df4 100644 --- a/t/chainlint/nested-loop-detect-failure.expect +++ b/t/chainlint/nested-loop-detect-failure.expect @@ -1,31 +1,31 @@ -for i in 0 1 2 3 4 5 6 7 8 9 ; +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 ; + for j in 0 1 2 3 4 5 6 7 8 9; do - echo "$i$j" > "path$i$j" ?!LOOP?! + echo "$i$j" >"path$i$j" ?!LOOP?! done ?!LOOP?! done && -for i in 0 1 2 3 4 5 6 7 8 9 ; +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 ; + for j in 0 1 2 3 4 5 6 7 8 9; do - echo "$i$j" > "path$i$j" || return 1 + echo "$i$j" >"path$i$j" || return 1 done done && -for i in 0 1 2 3 4 5 6 7 8 9 ; +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 ; + for j in 0 1 2 3 4 5 6 7 8 9; do - echo "$i$j" > "path$i$j" ?!LOOP?! + echo "$i$j" >"path$i$j" ?!LOOP?! done || return 1 done && -for i in 0 1 2 3 4 5 6 7 8 9 ; +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 ; + for j in 0 1 2 3 4 5 6 7 8 9; do - echo "$i$j" > "path$i$j" || return 1 + 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 02e0a9f..73ff285 100644 --- a/t/chainlint/nested-subshell.expect +++ b/t/chainlint/nested-subshell.expect @@ -4,6 +4,7 @@ echo a && echo b ) >file && + cd foo && ( echo a ?!AMP?! diff --git a/t/chainlint/pipe.expect b/t/chainlint/pipe.expect index 2cfc028..811971b 100644 --- a/t/chainlint/pipe.expect +++ b/t/chainlint/pipe.expect @@ -2,7 +2,9 @@ foo | bar | baz && + fish | cow ?!AMP?! + sunder ) diff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect index 5278927..75d6f60 100644 --- a/t/chainlint/subshell-here-doc.expect +++ b/t/chainlint/subshell-here-doc.expect @@ -1,7 +1,7 @@ ( echo wobba \ - gorgo snoot \ - wafta snurb <<-EOF && + gorgo snoot \ + wafta snurb <<-EOF && quoth the raven, nevermore... EOF diff --git a/t/chainlint/subshell-one-liner.expect b/t/chainlint/subshell-one-liner.expect index b701536..8f69499 100644 --- a/t/chainlint/subshell-one-liner.expect +++ b/t/chainlint/subshell-one-liner.expect @@ -2,13 +2,18 @@ (foo && bar) && (foo && bar) | (foo && bar) >baz && + (foo; ?!AMP?! bar) && (foo; ?!AMP?! bar) | (foo; ?!AMP?! bar) >baz && + (foo || exit 1) && (foo || exit 1) | (foo || exit 1) >baz && + (foo && bar) ?!AMP?! + (foo && bar; ?!AMP?! baz) ?!AMP?! + foobar ) diff --git a/t/chainlint/t7900-subtree.expect b/t/chainlint/t7900-subtree.expect index 71b3b3b..02f3129 100644 --- a/t/chainlint/t7900-subtree.expect +++ b/t/chainlint/t7900-subtree.expect @@ -15,6 +15,7 @@ main-sub4" && $chkms TXT ) && + subfiles=$(git ls-files) && check_equal "$subfiles" "$chkms $chks" diff --git a/t/chainlint/token-pasting.expect b/t/chainlint/token-pasting.expect index 342360b..6a38791 100644 --- a/t/chainlint/token-pasting.expect +++ b/t/chainlint/token-pasting.expect @@ -4,22 +4,22 @@ git config filter.rot13.clean ./rot13.sh && { echo "*.t filter=rot13" ?!AMP?! echo "*.i ident" -} > .gitattributes && +} >.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 && +} >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?! +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" | diff --git a/t/chainlint/while-loop.expect b/t/chainlint/while-loop.expect index 1f5eaea..06c1567 100644 --- a/t/chainlint/while-loop.expect +++ b/t/chainlint/while-loop.expect @@ -6,6 +6,7 @@ bar EOF done ?!AMP?! + while true; do echo foo && cat bar ?!LOOP?! diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index dd8107c..b2b28c2 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -47,6 +47,8 @@ while (<>) { /\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)'; + /\blocal\s+[A-Za-z0-9_]*=\$([A-Za-z0-9_{]|[(][^(])/ and + err q(quote "$val" in 'local var=$val'); /^\s*([A-Z0-9_]+=(\w*|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and err '"FOO=bar shell_func" assignment extends beyond "shell_func"'; $line = ''; diff --git a/t/helper/test-bundle-uri.c b/t/helper/test-bundle-uri.c index 4750585..09dc787 100644 --- a/t/helper/test-bundle-uri.c +++ b/t/helper/test-bundle-uri.c @@ -5,9 +5,7 @@ #include "strbuf.h" #include "string-list.h" #include "transport.h" -#include "ref-filter.h" #include "remote.h" -#include "refs.h" enum input_mode { KEY_VALUE_PAIRS, diff --git a/t/helper/test-ctype.c b/t/helper/test-ctype.c deleted file mode 100644 index e5659df..0000000 --- a/t/helper/test-ctype.c +++ /dev/null @@ -1,70 +0,0 @@ -#include "test-tool.h" - -static int rc; - -static void report_error(const char *class, int ch) -{ - printf("%s classifies char %d (0x%02x) wrongly\n", class, ch, ch); - rc = 1; -} - -static int is_in(const char *s, int ch) -{ - /* - * We can't find NUL using strchr. Accept it as the first - * character in the spec -- there are no empty classes. - */ - if (ch == '\0') - return ch == *s; - if (*s == '\0') - s++; - return !!strchr(s, ch); -} - -#define TEST_CLASS(t,s) { \ - int i; \ - for (i = 0; i < 256; i++) { \ - if (is_in(s, i) != t(i)) \ - report_error(#t, i); \ - } \ - if (t(EOF)) \ - report_error(#t, EOF); \ -} - -#define DIGIT "0123456789" -#define LOWER "abcdefghijklmnopqrstuvwxyz" -#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -#define PUNCT "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" -#define ASCII \ - "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \ - "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \ - "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" \ - "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" \ - "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f" \ - "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" \ - "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f" \ - "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f" -#define CNTRL \ - "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \ - "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \ - "\x7f" - -int cmd__ctype(int argc UNUSED, const char **argv UNUSED) -{ - TEST_CLASS(isdigit, DIGIT); - TEST_CLASS(isspace, " \n\r\t"); - TEST_CLASS(isalpha, LOWER UPPER); - TEST_CLASS(isalnum, LOWER UPPER DIGIT); - TEST_CLASS(is_glob_special, "*?[\\"); - TEST_CLASS(is_regex_special, "$()*+.?[\\^{|"); - TEST_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~"); - TEST_CLASS(isascii, ASCII); - TEST_CLASS(islower, LOWER); - TEST_CLASS(isupper, UPPER); - TEST_CLASS(iscntrl, CNTRL); - TEST_CLASS(ispunct, PUNCT); - TEST_CLASS(isxdigit, DIGIT "abcdefABCDEF"); - TEST_CLASS(isprint, LOWER UPPER DIGIT PUNCT " "); - - return rc; -} diff --git a/t/helper/test-date.c b/t/helper/test-date.c index 0683d46..f25512d 100644 --- a/t/helper/test-date.c +++ b/t/helper/test-date.c @@ -52,7 +52,7 @@ static void show_dates(const char **argv, const char *format) arg++; tz = atoi(arg); - printf("%s -> %s\n", *argv, show_date(t, tz, &mode)); + printf("%s -> %s\n", *argv, show_date(t, tz, mode)); } date_mode_release(&mode); diff --git a/t/helper/test-delete-gpgsig.c b/t/helper/test-delete-gpgsig.c new file mode 100644 index 0000000..e36831a --- /dev/null +++ b/t/helper/test-delete-gpgsig.c @@ -0,0 +1,62 @@ +#include "test-tool.h" +#include "gpg-interface.h" +#include "strbuf.h" + + +int cmd__delete_gpgsig(int argc, const char **argv) +{ + struct strbuf buf = STRBUF_INIT; + const char *pattern = "gpgsig"; + const char *bufptr, *tail, *eol; + int deleting = 0; + size_t plen; + + if (argc >= 2) { + pattern = argv[1]; + argv++; + argc--; + } + + plen = strlen(pattern); + strbuf_read(&buf, 0, 0); + + if (!strcmp(pattern, "trailer")) { + size_t payload_size = parse_signed_buffer(buf.buf, buf.len); + fwrite(buf.buf, 1, payload_size, stdout); + fflush(stdout); + return 0; + } + + bufptr = buf.buf; + tail = bufptr + buf.len; + + while (bufptr < tail) { + /* Find the end of the line */ + eol = memchr(bufptr, '\n', tail - bufptr); + if (!eol) + eol = tail; + + /* Drop continuation lines */ + if (deleting && (bufptr < eol) && (bufptr[0] == ' ')) { + bufptr = eol + 1; + continue; + } + deleting = 0; + + /* Does the line match the prefix? */ + if (((bufptr + plen) < eol) && + !memcmp(bufptr, pattern, plen) && + (bufptr[plen] == ' ')) { + deleting = 1; + bufptr = eol + 1; + continue; + } + + /* Print all other lines */ + fwrite(bufptr, 1, (eol - bufptr) + 1, stdout); + bufptr = eol + 1; + } + fflush(stdout); + + return 0; +} diff --git a/t/helper/test-fast-rebase.c b/t/helper/test-fast-rebase.c deleted file mode 100644 index cac20a7..0000000 --- a/t/helper/test-fast-rebase.c +++ /dev/null @@ -1,241 +0,0 @@ -/* - * "git fast-rebase" builtin command - * - * FAST: Forking Any Subprocesses (is) Taboo - * - * This is meant SOLELY as a demo of what is possible. sequencer.c and - * rebase.c should be refactored to use the ideas here, rather than attempting - * to extend this file to replace those (unless Phillip or Dscho say that - * refactoring is too hard and we need a clean slate, but I'm guessing that - * refactoring is the better route). - */ - -#define USE_THE_INDEX_VARIABLE -#include "test-tool.h" -#include "cache-tree.h" -#include "commit.h" -#include "environment.h" -#include "gettext.h" -#include "hash.h" -#include "hex.h" -#include "lockfile.h" -#include "merge-ort.h" -#include "object-name.h" -#include "read-cache-ll.h" -#include "refs.h" -#include "revision.h" -#include "sequencer.h" -#include "setup.h" -#include "strvec.h" -#include "tree.h" - -static const char *short_commit_name(struct commit *commit) -{ - return repo_find_unique_abbrev(the_repository, &commit->object.oid, - DEFAULT_ABBREV); -} - -static struct commit *peel_committish(const char *name) -{ - struct object *obj; - struct object_id oid; - - if (repo_get_oid(the_repository, name, &oid)) - return NULL; - obj = parse_object(the_repository, &oid); - return (struct commit *)repo_peel_to_type(the_repository, name, 0, obj, - OBJ_COMMIT); -} - -static char *get_author(const char *message) -{ - size_t len; - const char *a; - - a = find_commit_header(message, "author", &len); - if (a) - return xmemdupz(a, len); - - return NULL; -} - -static struct commit *create_commit(struct tree *tree, - struct commit *based_on, - struct commit *parent) -{ - struct object_id ret; - struct object *obj; - struct commit_list *parents = NULL; - char *author; - char *sign_commit = NULL; - struct commit_extra_header *extra; - struct strbuf msg = STRBUF_INIT; - const char *out_enc = get_commit_output_encoding(); - const char *message = repo_logmsg_reencode(the_repository, based_on, - NULL, out_enc); - const char *orig_message = NULL; - const char *exclude_gpgsig[] = { "gpgsig", NULL }; - - commit_list_insert(parent, &parents); - extra = read_commit_extra_headers(based_on, exclude_gpgsig); - find_commit_subject(message, &orig_message); - strbuf_addstr(&msg, orig_message); - author = get_author(message); - reset_ident_date(); - if (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents, - &ret, author, NULL, sign_commit, extra)) { - error(_("failed to write commit object")); - return NULL; - } - free(author); - strbuf_release(&msg); - - obj = parse_object(the_repository, &ret); - return (struct commit *)obj; -} - -int cmd__fast_rebase(int argc, const char **argv) -{ - struct commit *onto; - struct commit *last_commit = NULL, *last_picked_commit = NULL; - struct object_id head; - struct lock_file lock = LOCK_INIT; - struct strvec rev_walk_args = STRVEC_INIT; - struct rev_info revs; - struct commit *commit; - struct merge_options merge_opt; - struct tree *next_tree, *base_tree, *head_tree; - struct merge_result result; - struct strbuf reflog_msg = STRBUF_INIT; - struct strbuf branch_name = STRBUF_INIT; - int ret = 0; - - /* - * test-tool stuff doesn't set up the git directory by default; need to - * do that manually. - */ - setup_git_directory(); - - if (argc == 2 && !strcmp(argv[1], "-h")) { - printf("Sorry, I am not a psychiatrist; I can not give you the help you need. Oh, you meant usage...\n"); - exit(129); - } - - if (argc != 5 || strcmp(argv[1], "--onto")) - die("usage: read the code, figure out how to use it, then do so"); - - onto = peel_committish(argv[2]); - strbuf_addf(&branch_name, "refs/heads/%s", argv[4]); - - /* Sanity check */ - if (repo_get_oid(the_repository, "HEAD", &head)) - die(_("Cannot read HEAD")); - assert(oideq(&onto->object.oid, &head)); - - repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR); - if (repo_read_index(the_repository) < 0) - BUG("Could not read index"); - - repo_init_revisions(the_repository, &revs, NULL); - revs.verbose_header = 1; - revs.max_parents = 1; - revs.cherry_mark = 1; - revs.limited = 1; - revs.reverse = 1; - revs.right_only = 1; - revs.sort_order = REV_SORT_IN_GRAPH_ORDER; - revs.topo_order = 1; - strvec_pushl(&rev_walk_args, "", argv[4], "--not", argv[3], NULL); - - if (setup_revisions(rev_walk_args.nr, rev_walk_args.v, &revs, NULL) > 1) { - ret = error(_("unhandled options")); - goto cleanup; - } - - strvec_clear(&rev_walk_args); - - if (prepare_revision_walk(&revs) < 0) { - ret = error(_("error preparing revisions")); - goto cleanup; - } - - init_merge_options(&merge_opt, the_repository); - memset(&result, 0, sizeof(result)); - merge_opt.show_rename_progress = 1; - merge_opt.branch1 = "HEAD"; - head_tree = repo_get_commit_tree(the_repository, onto); - result.tree = head_tree; - last_commit = onto; - while ((commit = get_revision(&revs))) { - struct commit *base; - - fprintf(stderr, "Rebasing %s...\r", - oid_to_hex(&commit->object.oid)); - assert(commit->parents && !commit->parents->next); - base = commit->parents->item; - - next_tree = repo_get_commit_tree(the_repository, commit); - base_tree = repo_get_commit_tree(the_repository, base); - - merge_opt.branch2 = short_commit_name(commit); - merge_opt.ancestor = xstrfmt("parent of %s", merge_opt.branch2); - - merge_incore_nonrecursive(&merge_opt, - base_tree, - result.tree, - next_tree, - &result); - - free((char*)merge_opt.ancestor); - merge_opt.ancestor = NULL; - if (!result.clean) - break; - last_picked_commit = commit; - last_commit = create_commit(result.tree, commit, last_commit); - } - - merge_switch_to_result(&merge_opt, head_tree, &result, 1, !result.clean); - - if (result.clean < 0) - exit(128); - - if (result.clean) { - fprintf(stderr, "\nDone.\n"); - strbuf_addf(&reflog_msg, "finish rebase %s onto %s", - oid_to_hex(&last_picked_commit->object.oid), - oid_to_hex(&last_commit->object.oid)); - if (update_ref(reflog_msg.buf, branch_name.buf, - &last_commit->object.oid, - &last_picked_commit->object.oid, - REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) { - error(_("could not update %s"), argv[4]); - die("Failed to update %s", argv[4]); - } - if (create_symref("HEAD", branch_name.buf, reflog_msg.buf) < 0) - die(_("unable to update HEAD")); - - prime_cache_tree(the_repository, the_repository->index, - result.tree); - } else { - fprintf(stderr, "\nAborting: Hit a conflict.\n"); - strbuf_addf(&reflog_msg, "rebase progress up to %s", - oid_to_hex(&last_picked_commit->object.oid)); - if (update_ref(reflog_msg.buf, "HEAD", - &last_commit->object.oid, - &head, - REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR)) { - error(_("could not update %s"), argv[4]); - die("Failed to update %s", argv[4]); - } - } - if (write_locked_index(&the_index, &lock, - COMMIT_LOCK | SKIP_IF_UNCHANGED)) - die(_("unable to write %s"), get_index_file()); - - ret = (result.clean == 0); -cleanup: - strbuf_release(&reflog_msg); - strbuf_release(&branch_name); - release_revisions(&revs); - return ret; -} diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index 70396fa..023ed2e 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -7,6 +7,7 @@ #include "string-list.h" #include "trace.h" #include "utf8.h" +#include "copy.h" /* * A "string_list_each_func_t" function that normalizes an entry from @@ -500,6 +501,16 @@ int cmd__path_utils(int argc, const char **argv) return !!res; } + if (argc == 4 && !strcmp(argv[1], "do_files_match")) { + int ret = do_files_match(argv[2], argv[3]); + + if (ret) + printf("equal\n"); + else + printf("different\n"); + return !ret; + } + fprintf(stderr, "%s: unknown function name: %s\n", argv[0], argv[1] ? argv[1] : "(there was none)"); return 1; diff --git a/t/helper/test-pkt-line.c b/t/helper/test-pkt-line.c index f4d134a..4daa82f 100644 --- a/t/helper/test-pkt-line.c +++ b/t/helper/test-pkt-line.c @@ -1,7 +1,9 @@ #include "git-compat-util.h" #include "test-tool.h" #include "pkt-line.h" +#include "sideband.h" #include "write-or-die.h" +#include "parse-options.h" static void pack_line(const char *line) { @@ -64,12 +66,33 @@ static void unpack(void) } } -static void unpack_sideband(void) +static void unpack_sideband(int argc, const char **argv) { struct packet_reader reader; - packet_reader_init(&reader, 0, NULL, 0, - PACKET_READ_GENTLE_ON_EOF | - PACKET_READ_CHOMP_NEWLINE); + int options = PACKET_READ_GENTLE_ON_EOF; + int chomp_newline = 1; + int reader_use_sideband = 0; + const char *const unpack_sideband_usage[] = { + "test_tool unpack_sideband [options...]", NULL + }; + struct option cmd_options[] = { + OPT_BOOL(0, "reader-use-sideband", &reader_use_sideband, + "set use_sideband bit for packet reader (Default: off)"), + OPT_BOOL(0, "chomp-newline", &chomp_newline, + "chomp newline in packet (Default: on)"), + OPT_END() + }; + + argc = parse_options(argc, argv, "", cmd_options, unpack_sideband_usage, + 0); + if (argc > 0) + usage_msg_opt(_("too many arguments"), unpack_sideband_usage, + cmd_options); + + if (chomp_newline) + options |= PACKET_READ_CHOMP_NEWLINE; + packet_reader_init(&reader, 0, NULL, 0, options); + reader.use_sideband = reader_use_sideband; while (packet_reader_read(&reader) != PACKET_READ_EOF) { int band; @@ -79,6 +102,17 @@ static void unpack_sideband(void) case PACKET_READ_EOF: break; case PACKET_READ_NORMAL: + /* + * When the "use_sideband" field of the reader is turned + * on, sideband packets other than the payload have been + * parsed and consumed in packet_reader_read(), and only + * the payload arrives here. + */ + if (reader.use_sideband) { + write_or_die(1, reader.line, reader.pktlen - 1); + break; + } + band = reader.line[0] & 0xff; if (band < 1 || band > 2) continue; /* skip non-sideband packets */ @@ -97,15 +131,31 @@ static void unpack_sideband(void) static int send_split_sideband(void) { + const char *foo = "Foo.\n"; + const char *bar = "Bar.\n"; const char *part1 = "Hello,"; const char *primary = "\001primary: regular output\n"; const char *part2 = " world!\n"; + /* Each sideband message has a trailing newline character. */ + send_sideband(1, 2, foo, strlen(foo), LARGE_PACKET_MAX); + send_sideband(1, 2, bar, strlen(bar), LARGE_PACKET_MAX); + + /* + * One sideband message is divided into part1 and part2 + * by the primary message. + */ send_sideband(1, 2, part1, strlen(part1), LARGE_PACKET_MAX); packet_write(1, primary, strlen(primary)); send_sideband(1, 2, part2, strlen(part2), LARGE_PACKET_MAX); packet_response_end(1); + /* + * We use unpack_sideband() to consume packets. A flush packet + * is required to end parsing. + */ + packet_flush(1); + return 0; } @@ -126,7 +176,7 @@ int cmd__pkt_line(int argc, const char **argv) else if (!strcmp(argv[1], "unpack")) unpack(); else if (!strcmp(argv[1], "unpack-sideband")) - unpack_sideband(); + unpack_sideband(argc - 1, argv + 1); else if (!strcmp(argv[1], "send-split-sideband")) send_split_sideband(); else if (!strcmp(argv[1], "receive-sideband")) diff --git a/t/helper/test-prio-queue.c b/t/helper/test-prio-queue.c deleted file mode 100644 index f0bf255..0000000 --- a/t/helper/test-prio-queue.c +++ /dev/null @@ -1,51 +0,0 @@ -#include "test-tool.h" -#include "prio-queue.h" - -static int intcmp(const void *va, const void *vb, void *data UNUSED) -{ - const int *a = va, *b = vb; - return *a - *b; -} - -static void show(int *v) -{ - if (!v) - printf("NULL\n"); - else - printf("%d\n", *v); - free(v); -} - -int cmd__prio_queue(int argc UNUSED, const char **argv) -{ - struct prio_queue pq = { intcmp }; - - while (*++argv) { - if (!strcmp(*argv, "get")) { - void *peek = prio_queue_peek(&pq); - void *get = prio_queue_get(&pq); - if (peek != get) - BUG("peek and get results do not match"); - show(get); - } else if (!strcmp(*argv, "dump")) { - void *peek; - void *get; - while ((peek = prio_queue_peek(&pq))) { - get = prio_queue_get(&pq); - if (peek != get) - BUG("peek and get results do not match"); - show(get); - } - } else if (!strcmp(*argv, "stack")) { - pq.compare = NULL; - } else { - int *v = xmalloc(sizeof(*v)); - *v = atoi(*argv); - prio_queue_put(&pq, v); - } - } - - clear_prio_queue(&pq); - - return 0; -} diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c index 3e17339..1e3b431 100644 --- a/t/helper/test-reach.c +++ b/t/helper/test-reach.c @@ -1,11 +1,9 @@ #include "test-tool.h" #include "commit.h" #include "commit-reach.h" -#include "config.h" #include "gettext.h" #include "hex.h" #include "object-name.h" -#include "parse-options.h" #include "ref-filter.h" #include "setup.h" #include "string-list.h" @@ -113,13 +111,16 @@ int cmd__reach(int ac, const char **av) repo_in_merge_bases(the_repository, A, B)); else if (!strcmp(av[1], "in_merge_bases_many")) printf("%s(A,X):%d\n", av[1], - repo_in_merge_bases_many(the_repository, A, X_nr, X_array)); + repo_in_merge_bases_many(the_repository, A, X_nr, X_array, 0)); else if (!strcmp(av[1], "is_descendant_of")) printf("%s(A,X):%d\n", av[1], repo_is_descendant_of(r, A, X)); else if (!strcmp(av[1], "get_merge_bases_many")) { - struct commit_list *list = repo_get_merge_bases_many(the_repository, - A, X_nr, - X_array); + struct commit_list *list = NULL; + if (repo_get_merge_bases_many(the_repository, + A, X_nr, + X_array, + &list) < 0) + exit(128); printf("%s(A,X):\n", av[1]); print_sorted_commit_ids(list); } else if (!strcmp(av[1], "reduce_heads")) { diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c index e9a444d..4acae41 100644 --- a/t/helper/test-read-midx.c +++ b/t/helper/test-read-midx.c @@ -6,6 +6,7 @@ #include "pack-bitmap.h" #include "packfile.h" #include "setup.h" +#include "gettext.h" static int read_midx_file(const char *object_dir, int show_objects) { @@ -79,7 +80,7 @@ static int read_midx_checksum(const char *object_dir) static int read_midx_preferred_pack(const char *object_dir) { struct multi_pack_index *midx = NULL; - struct bitmap_index *bitmap = NULL; + uint32_t preferred_pack; setup_git_directory(); @@ -87,23 +88,45 @@ static int read_midx_preferred_pack(const char *object_dir) if (!midx) return 1; - bitmap = prepare_bitmap_git(the_repository); - if (!bitmap) + if (midx_preferred_pack(midx, &preferred_pack) < 0) { + warning(_("could not determine MIDX preferred pack")); return 1; - if (!bitmap_is_midx(bitmap)) { - free_bitmap_index(bitmap); + } + + printf("%s\n", midx->pack_names[preferred_pack]); + return 0; +} + +static int read_midx_bitmapped_packs(const char *object_dir) +{ + struct multi_pack_index *midx = NULL; + struct bitmapped_pack pack; + uint32_t i; + + setup_git_directory(); + + midx = load_multi_pack_index(object_dir, 1); + if (!midx) return 1; + + for (i = 0; i < midx->num_packs; i++) { + if (nth_bitmapped_pack(the_repository, midx, &pack, i) < 0) + return 1; + + printf("%s\n", pack_basename(pack.p)); + printf(" bitmap_pos: %"PRIuMAX"\n", (uintmax_t)pack.bitmap_pos); + printf(" bitmap_nr: %"PRIuMAX"\n", (uintmax_t)pack.bitmap_nr); } - printf("%s\n", midx->pack_names[midx_preferred_pack(bitmap)]); - free_bitmap_index(bitmap); + close_midx(midx); + return 0; } int cmd__read_midx(int argc, const char **argv) { if (!(argc == 2 || argc == 3)) - usage("read-midx [--show-objects|--checksum|--preferred-pack] <object-dir>"); + usage("read-midx [--show-objects|--checksum|--preferred-pack|--bitmap] <object-dir>"); if (!strcmp(argv[1], "--show-objects")) return read_midx_file(argv[2], 1); @@ -111,5 +134,7 @@ int cmd__read_midx(int argc, const char **argv) return read_midx_checksum(argv[2]); else if (!strcmp(argv[1], "--preferred-pack")) return read_midx_preferred_pack(argv[2]); + else if (!strcmp(argv[1], "--bitmap")) + return read_midx_bitmapped_packs(argv[2]); return read_midx_file(argv[1], 0); } diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 48552e6..82bbf6e 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -112,25 +112,6 @@ static const char **get_store(const char **argv, struct ref_store **refs) return argv + 1; } -static struct flag_definition pack_flags[] = { FLAG_DEF(PACK_REFS_PRUNE), - FLAG_DEF(PACK_REFS_ALL), - { NULL, 0 } }; - -static int cmd_pack_refs(struct ref_store *refs, const char **argv) -{ - unsigned int flags = arg_flags(*argv++, "flags", pack_flags); - static struct ref_exclusions exclusions = REF_EXCLUSIONS_INIT; - static struct string_list included_refs = STRING_LIST_INIT_NODUP; - struct pack_refs_opts pack_opts = { .flags = flags, - .exclusions = &exclusions, - .includes = &included_refs }; - - if (pack_opts.flags & PACK_REFS_ALL) - string_list_append(pack_opts.includes, "*"); - - return refs_pack_refs(refs, &pack_opts); -} - static int cmd_create_symref(struct ref_store *refs, const char **argv) { const char *refname = notnull(*argv++, "refname"); @@ -221,15 +202,21 @@ static int cmd_verify_ref(struct ref_store *refs, const char **argv) return ret; } +static int each_reflog(const char *refname, void *cb_data UNUSED) +{ + printf("%s\n", refname); + return 0; +} + static int cmd_for_each_reflog(struct ref_store *refs, const char **argv UNUSED) { - return refs_for_each_reflog(refs, each_ref, NULL); + return refs_for_each_reflog(refs, each_reflog, NULL); } -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 UNUSED) +static int each_reflog_ent(struct object_id *old_oid, struct object_id *new_oid, + const char *committer, timestamp_t timestamp, + 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, @@ -241,14 +228,14 @@ static int cmd_for_each_reflog_ent(struct ref_store *refs, const char **argv) { const char *refname = notnull(*argv++, "refname"); - return refs_for_each_reflog_ent(refs, refname, each_reflog, refs); + return refs_for_each_reflog_ent(refs, refname, each_reflog_ent, refs); } static int cmd_for_each_reflog_ent_reverse(struct ref_store *refs, const char **argv) { const char *refname = notnull(*argv++, "refname"); - return refs_for_each_reflog_ent_reverse(refs, refname, each_reflog, refs); + return refs_for_each_reflog_ent_reverse(refs, refname, each_reflog_ent, refs); } static int cmd_reflog_exists(struct ref_store *refs, const char **argv) @@ -298,16 +285,19 @@ static int cmd_update_ref(struct ref_store *refs, const char **argv) const char *new_sha1_buf = notnull(*argv++, "new-sha1"); const char *old_sha1_buf = notnull(*argv++, "old-sha1"); unsigned int flags = arg_flags(*argv++, "flags", transaction_flags); - struct object_id old_oid; + struct object_id old_oid, *old_oid_ptr = NULL; struct object_id new_oid; - if (get_oid_hex(old_sha1_buf, &old_oid)) - die("cannot parse %s as %s", old_sha1_buf, the_hash_algo->name); + if (*old_sha1_buf) { + if (get_oid_hex(old_sha1_buf, &old_oid)) + die("cannot parse %s as %s", old_sha1_buf, the_hash_algo->name); + old_oid_ptr = &old_oid; + } if (get_oid_hex(new_sha1_buf, &new_oid)) die("cannot parse %s as %s", new_sha1_buf, the_hash_algo->name); return refs_update_ref(refs, msg, refname, - &new_oid, &old_oid, + &new_oid, old_oid_ptr, flags, UPDATE_REFS_DIE_ON_ERR); } @@ -317,7 +307,6 @@ struct command { }; static struct command commands[] = { - { "pack-refs", cmd_pack_refs }, { "create-symref", cmd_create_symref }, { "delete-refs", cmd_delete_refs }, { "rename-ref", cmd_rename_ref }, diff --git a/t/helper/test-regex.c b/t/helper/test-regex.c index bd871a7..80042ea 100644 --- a/t/helper/test-regex.c +++ b/t/helper/test-regex.c @@ -30,7 +30,7 @@ static int test_regex_bug(void) if (regexec(&r, str, 1, m, 0)) die("no match of pattern '%s' to string '%s'", pat, str); - /* http://sourceware.org/bugzilla/show_bug.cgi?id=3957 */ + /* https://sourceware.org/bugzilla/show_bug.cgi?id=3957 */ if (m[0].rm_so == 3) /* matches '\n' when it should not */ die("regex bug confirmed: re-build git with NO_REGEX=1"); diff --git a/t/helper/test-repository.c b/t/helper/test-repository.c index 4cd8a95..0c7c5aa 100644 --- a/t/helper/test-repository.c +++ b/t/helper/test-repository.c @@ -1,10 +1,8 @@ #include "test-tool.h" #include "commit-graph.h" #include "commit.h" -#include "config.h" #include "environment.h" #include "hex.h" -#include "object-store-ll.h" #include "object.h" #include "repository.h" #include "setup.h" diff --git a/t/helper/test-simple-ipc.c b/t/helper/test-simple-ipc.c index 941ae7e..fb59277 100644 --- a/t/helper/test-simple-ipc.c +++ b/t/helper/test-simple-ipc.c @@ -4,7 +4,6 @@ #include "test-tool.h" #include "gettext.h" -#include "strbuf.h" #include "simple-ipc.h" #include "parse-options.h" #include "thread-utils.h" diff --git a/t/helper/test-submodule.c b/t/helper/test-submodule.c index 356e0a2..7197969 100644 --- a/t/helper/test-submodule.c +++ b/t/helper/test-submodule.c @@ -4,16 +4,24 @@ #include "remote.h" #include "repository.h" #include "setup.h" +#include "strbuf.h" #include "submodule-config.h" #include "submodule.h" #define TEST_TOOL_CHECK_NAME_USAGE \ - "test-tool submodule check-name <name>" + "test-tool submodule check-name" static const char *submodule_check_name_usage[] = { TEST_TOOL_CHECK_NAME_USAGE, NULL }; +#define TEST_TOOL_CHECK_URL_USAGE \ + "test-tool submodule check-url" +static const char *submodule_check_url_usage[] = { + TEST_TOOL_CHECK_URL_USAGE, + NULL +}; + #define TEST_TOOL_IS_ACTIVE_USAGE \ "test-tool submodule is-active <name>" static const char *submodule_is_active_usage[] = { @@ -30,31 +38,26 @@ static const char *submodule_resolve_relative_url_usage[] = { static const char *submodule_usage[] = { TEST_TOOL_CHECK_NAME_USAGE, + TEST_TOOL_CHECK_URL_USAGE, TEST_TOOL_IS_ACTIVE_USAGE, TEST_TOOL_RESOLVE_RELATIVE_URL_USAGE, NULL }; +typedef int (*check_fn_t)(const char *); + /* - * 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). + * Apply 'check_fn' to each line of stdin, printing values that pass the check + * to stdout. */ -static int check_name(int argc, const char **argv) +static int check_submodule(check_fn_t check_fn) { - 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); + struct strbuf buf = STRBUF_INIT; + while (strbuf_getline(&buf, stdin) != EOF) { + if (!check_fn(buf.buf)) + printf("%s\n", buf.buf); } + strbuf_release(&buf); return 0; } @@ -68,7 +71,20 @@ static int cmd__submodule_check_name(int argc, const char **argv) if (argc) usage_with_options(submodule_check_name_usage, options); - return check_name(argc, argv); + return check_submodule(check_submodule_name); +} + +static int cmd__submodule_check_url(int argc, const char **argv) +{ + struct option options[] = { + OPT_END() + }; + argc = parse_options(argc, argv, "test-tools", options, + submodule_check_url_usage, 0); + if (argc) + usage_with_options(submodule_check_url_usage, options); + + return check_submodule(check_submodule_url); } static int cmd__submodule_is_active(int argc, const char **argv) @@ -194,6 +210,7 @@ static int cmd__submodule_config_writeable(int argc, const char **argv UNUSED) static struct test_cmd cmds[] = { { "check-name", cmd__submodule_check_name }, + { "check-url", cmd__submodule_check_url }, { "is-active", cmd__submodule_is_active }, { "resolve-relative-url", cmd__submodule_resolve_relative_url}, { "config-list", cmd__submodule_config_list }, diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 876cd2d..80a946b 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -19,8 +19,8 @@ static struct test_cmd cmds[] = { { "config", cmd__config }, { "crontab", cmd__crontab }, { "csprng", cmd__csprng }, - { "ctype", cmd__ctype }, { "date", cmd__date }, + { "delete-gpgsig", cmd__delete_gpgsig }, { "delta", cmd__delta }, { "dir-iterator", cmd__dir_iterator }, { "drop-caches", cmd__drop_caches }, @@ -30,7 +30,6 @@ static struct test_cmd cmds[] = { { "dump-untracked-cache", cmd__dump_untracked_cache }, { "env-helper", cmd__env_helper }, { "example-decorate", cmd__example_decorate }, - { "fast-rebase", cmd__fast_rebase }, { "find-pack", cmd__find_pack }, { "fsmonitor-client", cmd__fsmonitor_client }, { "genrandom", cmd__genrandom }, @@ -57,7 +56,6 @@ static struct test_cmd cmds[] = { { "path-utils", cmd__path_utils }, { "pcre2-config", cmd__pcre2_config }, { "pkt-line", cmd__pkt_line }, - { "prio-queue", cmd__prio_queue }, { "proc-receive", cmd__proc_receive }, { "progress", cmd__progress }, { "reach", cmd__reach }, diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 70dd4eb..2808b92 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -12,9 +12,9 @@ int cmd__chmtime(int argc, const char **argv); int cmd__config(int argc, const char **argv); int cmd__crontab(int argc, const char **argv); int cmd__csprng(int argc, const char **argv); -int cmd__ctype(int argc, const char **argv); int cmd__date(int argc, const char **argv); int cmd__delta(int argc, const char **argv); +int cmd__delete_gpgsig(int argc, const char **argv); int cmd__dir_iterator(int argc, const char **argv); int cmd__drop_caches(int argc, const char **argv); int cmd__dump_cache_tree(int argc, const char **argv); @@ -24,7 +24,6 @@ int cmd__dump_untracked_cache(int argc, const char **argv); int cmd__dump_reftable(int argc, const char **argv); int cmd__env_helper(int argc, const char **argv); int cmd__example_decorate(int argc, const char **argv); -int cmd__fast_rebase(int argc, const char **argv); int cmd__find_pack(int argc, const char **argv); int cmd__fsmonitor_client(int argc, const char **argv); int cmd__genrandom(int argc, const char **argv); @@ -50,7 +49,6 @@ 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); int cmd__pkt_line(int argc, const char **argv); -int cmd__prio_queue(int argc, const char **argv); int cmd__proc_receive(int argc, const char **argv); int cmd__progress(int argc, const char **argv); int cmd__reach(int argc, const char **argv); diff --git a/t/helper/test-trace2.c b/t/helper/test-trace2.c index d5ca004..1adac29 100644 --- a/t/helper/test-trace2.c +++ b/t/helper/test-trace2.c @@ -412,6 +412,56 @@ static int ut_201counter(int argc, const char **argv) return 0; } +static int ut_300redact_start(int argc, const char **argv) +{ + if (!argc) + die("expect <argv...>"); + + trace2_cmd_start(argv); + + return 0; +} + +static int ut_301redact_child_start(int argc, const char **argv) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + int k; + + if (!argc) + die("expect <argv...>"); + + for (k = 0; argv[k]; k++) + strvec_push(&cmd.args, argv[k]); + + trace2_child_start(&cmd); + + strvec_clear(&cmd.args); + + return 0; +} + +static int ut_302redact_exec(int argc, const char **argv) +{ + if (!argc) + die("expect <exe> <argv...>"); + + trace2_exec(argv[0], &argv[1]); + + return 0; +} + +static int ut_303redact_def_param(int argc, const char **argv) +{ + struct key_value_info kvi = KVI_INIT; + + if (argc < 2) + die("expect <key> <value>"); + + trace2_def_param(argv[0], argv[1], &kvi); + + return 0; +} + /* * Usage: * test-tool trace2 <ut_name_1> <ut_usage_1> @@ -438,6 +488,11 @@ static struct unit_test ut_table[] = { { ut_200counter, "200counter", "<v1> [<v2> [<v3> [...]]]" }, { ut_201counter, "201counter", "<v1> <v2> <threads>" }, + + { ut_300redact_start, "300redact_start", "<argv...>" }, + { ut_301redact_child_start, "301redact_child_start", "<argv...>" }, + { ut_302redact_exec, "302redact_exec", "<exe> <argv...>" }, + { ut_303redact_def_param, "303redact_def_param", "<key> <value>" }, }; /* clang-format on */ diff --git a/t/lib-credential.sh b/t/lib-credential.sh index 15fc9a3..44799c0 100644 --- a/t/lib-credential.sh +++ b/t/lib-credential.sh @@ -50,6 +50,7 @@ helper_test_clean() { reject $1 https example.com user-overwrite reject $1 https example.com user-erase1 reject $1 https example.com user-erase2 + reject $1 https victim.example.com user reject $1 http path.tld user reject $1 https timeout.tld user reject $1 https sso.tld diff --git a/t/lib-cvs.sh b/t/lib-cvs.sh index 32b3473..57b9b2d 100644 --- a/t/lib-cvs.sh +++ b/t/lib-cvs.sh @@ -71,8 +71,8 @@ test_cmp_branch_tree () { find . -type d -name .git -prune -o -type f -print ) | sort >module-git-"$1".list && test_cmp module-cvs-"$1".list module-git-"$1".list && - cat module-cvs-"$1".list | while read f + while read f do test_cmp_branch_file "$1" "$f" || return 1 - done + done <module-cvs-"$1".list } diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index 83b83c9..add11e8 100644 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -13,7 +13,7 @@ test_lazy_prereq GPG ' gpg_version=$(gpg --version 2>&1) test $? != 127 || exit 1 - # As said here: http://www.gnupg.org/documentation/faqs.html#q6.19 + # As said here: https://web.archive.org/web/20130212022238/https://www.gnupg.org/faq/gnupg-faq.html#why-does-gnupg-1.0.6-bail-out-on-keyrings-used-with-1.0.7 # the gpg version 1.0.6 did not parse trust packets correctly, so for # that version, creation of signed tags using the generated key fails. case "$gpg_version" in diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 5fe3c8a..d83bafe 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -55,21 +55,31 @@ fi HTTPD_PARA="" -for DEFAULT_HTTPD_PATH in '/usr/sbin/httpd' '/usr/sbin/apache2' +for DEFAULT_HTTPD_PATH in '/usr/sbin/httpd' \ + '/usr/sbin/apache2' \ + "$(command -v httpd)" \ + "$(command -v apache2)" do - if test -x "$DEFAULT_HTTPD_PATH" + if test -n "$DEFAULT_HTTPD_PATH" && test -x "$DEFAULT_HTTPD_PATH" then break fi done +if test -x "$DEFAULT_HTTPD_PATH" +then + DETECTED_HTTPD_ROOT="$("$DEFAULT_HTTPD_PATH" -V 2>/dev/null | sed -n 's/^ -D HTTPD_ROOT="\(.*\)"$/\1/p')" +fi + for DEFAULT_HTTPD_MODULE_PATH in '/usr/libexec/apache2' \ '/usr/lib/apache2/modules' \ '/usr/lib64/httpd/modules' \ '/usr/lib/httpd/modules' \ - '/usr/libexec/httpd' + '/usr/libexec/httpd' \ + '/usr/lib/apache2' \ + "${DETECTED_HTTPD_ROOT:+${DETECTED_HTTPD_ROOT}/modules}" do - if test -d "$DEFAULT_HTTPD_MODULE_PATH" + if test -n "$DEFAULT_HTTPD_MODULE_PATH" && test -d "$DEFAULT_HTTPD_MODULE_PATH" then break fi @@ -127,6 +137,20 @@ else "Could not identify web server at '$LIB_HTTPD_PATH'" fi +if test -n "$LIB_HTTPD_DAV" && test -f /etc/os-release +then + case "$(grep "^ID=" /etc/os-release | cut -d= -f2-)" in + alpine) + # The WebDAV module in Alpine Linux is broken at least up to + # Alpine v3.16 as the default DBM driver is missing. + # + # https://gitlab.alpinelinux.org/alpine/aports/-/issues/13112 + test_skip_or_die GIT_TEST_HTTPD \ + "Apache WebDAV module does not have default DBM backend driver" + ;; + esac +fi + install_script () { write_script "$HTTPD_ROOT_PATH/$1" <"$TEST_PATH/$1" } diff --git a/t/lib-httpd/passwd b/t/lib-httpd/passwd index 99a34d6..d9c122f 100644 --- a/t/lib-httpd/passwd +++ b/t/lib-httpd/passwd @@ -1 +1 @@ -user@host:xb4E8pqD81KQs +user@host:$apr1$LGPmCZWj$9vxEwj5Z5GzQLBMxp3mCx1 diff --git a/t/lib-httpd/proxy-passwd b/t/lib-httpd/proxy-passwd index 77c2513..2ad7705 100644 --- a/t/lib-httpd/proxy-passwd +++ b/t/lib-httpd/proxy-passwd @@ -1 +1 @@ -proxuser:2x7tAukjAED5M +proxuser:$apr1$RxS6MLkD$DYsqQdflheq4GPNxzJpx5. diff --git a/t/lib-parallel-checkout.sh b/t/lib-parallel-checkout.sh index acaee9c..8324d6c 100644 --- a/t/lib-parallel-checkout.sh +++ b/t/lib-parallel-checkout.sh @@ -20,7 +20,7 @@ test_checkout_workers () { BUG "too few arguments to test_checkout_workers" fi && - local expected_workers=$1 && + local expected_workers="$1" && shift && local trace_file=trace-test-checkout-workers && diff --git a/t/oid-info/hash-info b/t/oid-info/hash-info index d0736dd..b8a5bcb 100644 --- a/t/oid-info/hash-info +++ b/t/oid-info/hash-info @@ -15,3 +15,15 @@ empty_blob sha256:473a0f4c3be8a93681a267e3b1e9a7dcda1185436fe141f7749120a3037218 empty_tree sha1:4b825dc642cb6eb9a060e54bf8d69288fbee4904 empty_tree sha256:6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321 + +blob17_1 sha1:263 +blob17_1 sha256:34 + +blob17_2 sha1:410 +blob17_2 sha256:174 + +blob17_3 sha1:523 +blob17_3 sha256:313 + +blob17_4 sha1:790 +blob17_4 sha256:481 diff --git a/t/perf/p5332-multi-pack-reuse.sh b/t/perf/p5332-multi-pack-reuse.sh new file mode 100755 index 0000000..5c6c575 --- /dev/null +++ b/t/perf/p5332-multi-pack-reuse.sh @@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='tests pack performance with multi-pack reuse' + +. ./perf-lib.sh +. "${TEST_DIRECTORY}/perf/lib-pack.sh" + +packdir=.git/objects/pack + +test_perf_large_repo + +find_pack () { + for idx in $packdir/pack-*.idx + do + if git show-index <$idx | grep -q "$1" + then + basename $idx + fi || return 1 + done +} + +repack_into_n_chunks () { + git repack -adk && + + test "$1" -eq 1 && return || + + find $packdir -type f | sort >packs.before && + + # partition the repository into $1 chunks of consecutive commits, and + # then create $1 packs with the objects reachable from each chunk + # (excluding any objects reachable from the previous chunks) + sz="$(($(git rev-list --count --all) / $1))" + for rev in $(git rev-list --all | awk "NR % $sz == 0" | tac) + do + pack="$(echo "$rev" | git pack-objects --revs \ + --honor-pack-keep --delta-base-offset $packdir/pack)" && + touch $packdir/pack-$pack.keep || return 1 + done + + # grab any remaining objects not packed by the previous step(s) + git pack-objects --revs --all --honor-pack-keep --delta-base-offset \ + $packdir/pack && + + find $packdir -type f | sort >packs.after && + + # and install the whole thing + for f in $(comm -12 packs.before packs.after) + do + rm -f "$f" || return 1 + done + rm -fr $packdir/*.keep +} + +for nr_packs in 1 10 100 +do + test_expect_success "create $nr_packs-pack scenario" ' + repack_into_n_chunks $nr_packs + ' + + test_expect_success "setup bitmaps for $nr_packs-pack scenario" ' + find $packdir -type f -name "*.idx" | sed -e "s/.*\/\(.*\)$/+\1/g" | + git multi-pack-index write --stdin-packs --bitmap \ + --preferred-pack="$(find_pack $(git rev-parse HEAD))" + ' + + for reuse in single multi + do + test_perf "clone for $nr_packs-pack scenario ($reuse-pack reuse)" " + git for-each-ref --format='%(objectname)' refs/heads refs/tags >in && + git -c pack.allowPackReuse=$reuse pack-objects \ + --revs --delta-base-offset --use-bitmap-index \ + --stdout <in >result + " + + test_size "clone size for $nr_packs-pack scenario ($reuse-pack reuse)" ' + wc -c <result + ' + done +done + +test_done diff --git a/t/perf/p6300-for-each-ref.sh b/t/perf/p6300-for-each-ref.sh new file mode 100755 index 0000000..fa7289c --- /dev/null +++ b/t/perf/p6300-for-each-ref.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +test_description='performance of for-each-ref' +. ./perf-lib.sh + +test_perf_fresh_repo + +ref_count_per_type=10000 +test_iteration_count=10 + +test_expect_success "setup" ' + test_commit_bulk $(( 1 + $ref_count_per_type )) && + + # Create refs + test_seq $ref_count_per_type | + sed "s,.*,update refs/heads/branch_& HEAD~&\nupdate refs/custom/special_& HEAD~&," | + git update-ref --stdin && + + # Create annotated tags + for i in $(test_seq $ref_count_per_type) + do + # Base tags + echo "tag tag_$i" && + echo "mark :$i" && + echo "from HEAD~$i" && + printf "tagger %s <%s> %s\n" \ + "$GIT_COMMITTER_NAME" \ + "$GIT_COMMITTER_EMAIL" \ + "$GIT_COMMITTER_DATE" && + echo "data <<EOF" && + echo "tag $i" && + echo "EOF" && + + # Nested tags + echo "tag nested_$i" && + echo "from :$i" && + printf "tagger %s <%s> %s\n" \ + "$GIT_COMMITTER_NAME" \ + "$GIT_COMMITTER_EMAIL" \ + "$GIT_COMMITTER_DATE" && + echo "data <<EOF" && + echo "nested tag $i" && + echo "EOF" || return 1 + done | git fast-import +' + +test_for_each_ref () { + title="for-each-ref" + if test $# -gt 0; then + title="$title ($1)" + shift + fi + args="$@" + + test_perf "$title" " + for i in \$(test_seq $test_iteration_count); do + git for-each-ref $args >/dev/null + done + " +} + +run_tests () { + test_for_each_ref "$1" + test_for_each_ref "$1, no sort" --no-sort + test_for_each_ref "$1, --count=1" --count=1 + test_for_each_ref "$1, --count=1, no sort" --no-sort --count=1 + test_for_each_ref "$1, tags" refs/tags/ + test_for_each_ref "$1, tags, no sort" --no-sort refs/tags/ + test_for_each_ref "$1, tags, dereferenced" '--format="%(refname) %(objectname) %(*objectname)"' refs/tags/ + test_for_each_ref "$1, tags, dereferenced, no sort" --no-sort '--format="%(refname) %(objectname) %(*objectname)"' refs/tags/ + + test_perf "for-each-ref ($1, tags) + cat-file --batch-check (dereferenced)" " + for i in \$(test_seq $test_iteration_count); do + git for-each-ref --format='%(objectname)^{} %(refname) %(objectname)' refs/tags/ | \ + git cat-file --batch-check='%(objectname) %(rest)' >/dev/null + done + " +} + +run_tests "loose" + +test_expect_success 'pack refs' ' + git pack-refs --all +' +run_tests "packed" + +test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index e778677..ab0c763 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -15,7 +15,7 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ . +# along with this program. If not, see https://www.gnu.org/licenses/ . # These variables must be set before the inclusion of test-lib.sh below, # because it will change our working directory. @@ -31,7 +31,7 @@ unset GIT_CONFIG_NOSYSTEM GIT_CONFIG_SYSTEM="$TEST_DIRECTORY/perf/config" export GIT_CONFIG_SYSTEM -if test -n "$GIT_TEST_INSTALLED" -a -z "$PERF_SET_GIT_TEST_INSTALLED" +if test -n "$GIT_TEST_INSTALLED" && test -z "$PERF_SET_GIT_TEST_INSTALLED" then error "Do not use GIT_TEST_INSTALLED with the perf tests. diff --git a/t/perf/repos/inflate-repo.sh b/t/perf/repos/inflate-repo.sh index fcfc992..412e4b4 100755 --- a/t/perf/repos/inflate-repo.sh +++ b/t/perf/repos/inflate-repo.sh @@ -33,7 +33,7 @@ do done git ls-tree -r HEAD >GEN_src_list -nr_src_files=$(cat GEN_src_list | wc -l) +nr_src_files=$(wc -l <GEN_src_list) src_branch=$(git symbolic-ref --short HEAD) @@ -91,10 +91,10 @@ set_git_test_installed () { run_dirs_helper () { mydir=${1%/} shift - while test $# -gt 0 -a "$1" != -- -a ! -f "$1"; do + while test $# -gt 0 && test "$1" != -- && test ! -f "$1"; do shift done - if test $# -gt 0 -a "$1" = --; then + if test $# -gt 0 && test "$1" = --; then shift fi @@ -124,7 +124,7 @@ run_dirs_helper () { } run_dirs () { - while test $# -gt 0 -a "$1" != -- -a ! -f "$1"; do + while test $# -gt 0 && test "$1" != -- && test ! -f "$1"; do run_dirs_helper "$@" shift done @@ -180,7 +180,8 @@ run_subsection () { GIT_PERF_AGGREGATING_LATER=t export GIT_PERF_AGGREGATING_LATER - if test $# = 0 -o "$1" = -- -o -f "$1"; then + if test $# = 0 || test "$1" = -- || test -f "$1" + then set -- . "$@" fi diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 6e300be..98b81e4 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -1201,6 +1201,34 @@ test_expect_success 'very long name in the index handled sanely' ' test $len = 4098 ' +# D/F conflict checking uses an optimization when adding to the end. +# make sure it does not get confused by `a-` sorting _between_ +# `a` and `a/`. +test_expect_success 'more update-index D/F conflicts' ' + # empty the index to make sure our entry is last + git read-tree --empty && + cacheinfo=100644,$(test_oid empty_blob) && + git update-index --add --cacheinfo $cacheinfo,path5/a && + + test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/file && + test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/file && + test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/c/file && + + # "a-" sorts between "a" and "a/" + git update-index --add --cacheinfo $cacheinfo,path5/a- && + + test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/file && + test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/file && + test_must_fail git update-index --add --cacheinfo $cacheinfo,path5/a/b/c/file && + + cat >expected <<-\EOF && + path5/a + path5/a- + EOF + git ls-files >actual && + test_cmp expected actual +' + test_expect_success 'test_must_fail on a failing git command' ' test_must_fail git notacommand ' diff --git a/t/t0001-init.sh b/t/t0001-init.sh index 2b78e3b..b131d66 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -532,6 +532,76 @@ test_expect_success 'init rejects attempts to initialize with different hash' ' test_must_fail git -C sha256 init --object-format=sha1 ' +test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage is not allowed with repo version 0' ' + test_when_finished "rm -rf refstorage" && + git init refstorage && + git -C refstorage config extensions.refStorage files && + test_must_fail git -C refstorage rev-parse 2>err && + grep "repo version is 0, but v1-only extension found" err +' + +test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with files backend' ' + test_when_finished "rm -rf refstorage" && + git init refstorage && + git -C refstorage config core.repositoryformatversion 1 && + git -C refstorage config extensions.refStorage files && + test_commit -C refstorage A && + git -C refstorage rev-parse --verify HEAD +' + +test_expect_success DEFAULT_REPO_FORMAT 'extensions.refStorage with unknown backend' ' + test_when_finished "rm -rf refstorage" && + git init refstorage && + git -C refstorage config core.repositoryformatversion 1 && + git -C refstorage config extensions.refStorage garbage && + test_must_fail git -C refstorage rev-parse 2>err && + grep "invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}garbage${SQ}" err +' + +test_expect_success DEFAULT_REPO_FORMAT 'init with GIT_DEFAULT_REF_FORMAT=files' ' + test_when_finished "rm -rf refformat" && + GIT_DEFAULT_REF_FORMAT=files git init refformat && + echo 0 >expect && + git -C refformat config core.repositoryformatversion >actual && + test_cmp expect actual && + test_must_fail git -C refformat config extensions.refstorage +' + +test_expect_success 'init with GIT_DEFAULT_REF_FORMAT=garbage' ' + test_when_finished "rm -rf refformat" && + cat >expect <<-EOF && + fatal: unknown ref storage format ${SQ}garbage${SQ} + EOF + test_must_fail env GIT_DEFAULT_REF_FORMAT=garbage git init refformat 2>err && + test_cmp expect err +' + +test_expect_success 'init with --ref-format=files' ' + test_when_finished "rm -rf refformat" && + git init --ref-format=files refformat && + echo files >expect && + git -C refformat rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 're-init with same format' ' + test_when_finished "rm -rf refformat" && + git init --ref-format=files refformat && + git init --ref-format=files refformat && + echo files >expect && + git -C refformat rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'init with --ref-format=garbage' ' + test_when_finished "rm -rf refformat" && + cat >expect <<-EOF && + fatal: unknown ref storage format ${SQ}garbage${SQ} + EOF + test_must_fail git init --ref-format=garbage refformat 2>err && + test_cmp expect err +' + test_expect_success MINGW 'core.hidedotfiles = false' ' git config --global core.hidedotfiles false && rm -rf newdir && diff --git a/t/t0002-gitfile.sh b/t/t0002-gitfile.sh index 736516c..bf3bf60 100755 --- a/t/t0002-gitfile.sh +++ b/t/t0002-gitfile.sh @@ -40,7 +40,7 @@ test_expect_success 'final setup + check rev-parse --git-dir' ' test_expect_success 'check hash-object' ' echo "foo" >bar && - SHA=$(cat bar | git hash-object -w --stdin) && + SHA=$(git hash-object -w --stdin <bar) && test_path_is_file "$REAL/objects/$(objpath $SHA)" ' diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index aee2298..774b52c 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -19,6 +19,20 @@ attr_check () { test_must_be_empty err } +attr_check_object_mode_basic () { + path="$1" && + expect="$2" && + check_opts="$3" && + git check-attr $check_opts builtin_objectmode -- "$path" >actual 2>err && + echo "$path: builtin_objectmode: $expect" >expect && + test_cmp expect actual +} + +attr_check_object_mode () { + attr_check_object_mode_basic "$@" && + test_must_be_empty err +} + attr_check_quote () { path="$1" quoted_path="$2" expect="$3" && @@ -558,4 +572,66 @@ test_expect_success EXPENSIVE 'large attributes file ignored in index' ' test_cmp expect err ' +test_expect_success 'builtin object mode attributes work (dir and regular paths)' ' + >normal && + attr_check_object_mode normal 100644 && + mkdir dir && + attr_check_object_mode dir 040000 +' + +test_expect_success POSIXPERM 'builtin object mode attributes work (executable)' ' + >exec && + chmod +x exec && + attr_check_object_mode exec 100755 +' + +test_expect_success SYMLINKS 'builtin object mode attributes work (symlinks)' ' + ln -s to_sym sym && + attr_check_object_mode sym 120000 +' + +test_expect_success 'native object mode attributes work with --cached' ' + >normal && + git add normal && + empty_blob=$(git rev-parse :normal) && + git update-index --index-info <<-EOF && + 100755 $empty_blob 0 exec + 120000 $empty_blob 0 symlink + EOF + attr_check_object_mode normal 100644 --cached && + attr_check_object_mode exec 100755 --cached && + attr_check_object_mode symlink 120000 --cached +' + +test_expect_success 'check object mode attributes work for submodules' ' + mkdir sub && + ( + cd sub && + git init && + mv .git .real && + echo "gitdir: .real" >.git && + test_commit first + ) && + attr_check_object_mode sub 160000 && + attr_check_object_mode sub unspecified --cached && + git add sub && + attr_check_object_mode sub 160000 --cached +' + +test_expect_success 'we do not allow user defined builtin_* attributes' ' + echo "foo* builtin_foo" >.gitattributes && + git add .gitattributes 2>actual && + echo "builtin_foo is not a valid attribute name: .gitattributes:1" >expect && + test_cmp expect actual +' + +test_expect_success 'user defined builtin_objectmode values are ignored' ' + echo "foo* builtin_objectmode=12345" >.gitattributes && + git add .gitattributes && + >foo_1 && + attr_check_object_mode_basic foo_1 100644 && + echo "builtin_objectmode is not a valid attribute name: .gitattributes:1" >expect && + test_cmp expect err +' + test_done diff --git a/t/t0006-date.sh b/t/t0006-date.sh index e18b160..3031256 100755 --- a/t/t0006-date.sh +++ b/t/t0006-date.sh @@ -46,6 +46,7 @@ check_show () { TIME='1466000000 +0200' check_show iso8601 "$TIME" '2016-06-15 16:13:20 +0200' check_show iso8601-strict "$TIME" '2016-06-15T16:13:20+02:00' +check_show iso8601-strict "$(echo "$TIME" | sed 's/+0200$/+0000/')" '2016-06-15T14:13:20Z' check_show rfc2822 "$TIME" 'Wed, 15 Jun 2016 16:13:20 +0200' check_show short "$TIME" '2016-06-15' check_show default "$TIME" 'Wed Jun 15 16:13:20 2016 +0200' @@ -69,6 +70,14 @@ check_show 'format:%s' '123456789 +1234' 123456789 check_show 'format:%s' '123456789 -1234' 123456789 check_show 'format-local:%s' '123456789 -1234' 123456789 +# negative TZ offset +TIME='1466000000 -0200' +check_show iso8601 "$TIME" '2016-06-15 12:13:20 -0200' +check_show iso8601-strict "$TIME" '2016-06-15T12:13:20-02:00' +check_show rfc2822 "$TIME" 'Wed, 15 Jun 2016 12:13:20 -0200' +check_show default "$TIME" 'Wed Jun 15 12:13:20 2016 -0200' +check_show raw "$TIME" '1466000000 -0200' + # arbitrary time absurdly far in the future FUTURE="5758122296 -0400" check_show iso "$FUTURE" "2152-06-19 18:24:56 -0400" TIME_IS_64BIT,TIME_T_IS_64BIT diff --git a/t/t0009-prio-queue.sh b/t/t0009-prio-queue.sh deleted file mode 100755 index eea9910..0000000 --- a/t/t0009-prio-queue.sh +++ /dev/null @@ -1,66 +0,0 @@ -#!/bin/sh - -test_description='basic tests for priority queue implementation' - -TEST_PASSES_SANITIZE_LEAK=true -. ./test-lib.sh - -cat >expect <<'EOF' -1 -2 -3 -4 -5 -5 -6 -7 -8 -9 -10 -EOF -test_expect_success 'basic ordering' ' - test-tool prio-queue 2 6 3 10 9 5 7 4 5 8 1 dump >actual && - test_cmp expect actual -' - -cat >expect <<'EOF' -2 -3 -4 -1 -5 -6 -EOF -test_expect_success 'mixed put and get' ' - test-tool prio-queue 6 2 4 get 5 3 get get 1 dump >actual && - test_cmp expect actual -' - -cat >expect <<'EOF' -1 -2 -NULL -1 -2 -NULL -EOF -test_expect_success 'notice empty queue' ' - test-tool prio-queue 1 2 get get get 1 2 get get get >actual && - test_cmp expect actual -' - -cat >expect <<'EOF' -3 -2 -6 -4 -5 -1 -8 -EOF -test_expect_success 'stack order' ' - test-tool prio-queue stack 8 1 5 4 6 2 3 dump >actual && - test_cmp expect actual -' - -test_done diff --git a/t/t0010-racy-git.sh b/t/t0010-racy-git.sh index 837c8b7..84172a3 100755 --- a/t/t0010-racy-git.sh +++ b/t/t0010-racy-git.sh @@ -10,25 +10,24 @@ TEST_PASSES_SANITIZE_LEAK=true for trial in 0 1 2 3 4 do - rm -f .git/index - echo frotz >infocom - git update-index --add infocom - echo xyzzy >infocom - - files=$(git diff-files -p) - test_expect_success \ - "Racy GIT trial #$trial part A" \ - 'test "" != "$files"' - + test_expect_success "Racy git trial #$trial part A" ' + rm -f .git/index && + echo frotz >infocom && + git update-index --add infocom && + echo xyzzy >infocom && + + git diff-files -p >out && + test_file_not_empty out + ' sleep 1 - echo xyzzy >cornerstone - git update-index --add cornerstone - files=$(git diff-files -p) - test_expect_success \ - "Racy GIT trial #$trial part B" \ - 'test "" != "$files"' + test_expect_success "Racy git trial #$trial part B" ' + echo xyzzy >cornerstone && + git update-index --add cornerstone && + git diff-files -p >out && + test_file_not_empty out + ' done test_done diff --git a/t/t0011-hashmap.sh b/t/t0011-hashmap.sh index 1cb6aa6..46e74ad 100755 --- a/t/t0011-hashmap.sh +++ b/t/t0011-hashmap.sh @@ -239,7 +239,7 @@ test_expect_success 'grow / shrink' ' echo value40 >> expect && echo size >> in && echo 64 39 >> expect && - cat in | test-tool hashmap > out && + test-tool hashmap <in >out && test_cmp expect out ' diff --git a/t/t0018-advice.sh b/t/t0018-advice.sh index c13057a..0dcfb76 100755 --- a/t/t0018-advice.sh +++ b/t/t0018-advice.sh @@ -17,7 +17,6 @@ test_expect_success 'advice should be printed when config variable is unset' ' test_expect_success 'advice should be printed when config variable is set to true' ' cat >expect <<-\EOF && hint: This is a piece of advice - hint: Disable this message with "git config advice.nestedTag false" EOF test_config advice.nestedTag true && test-tool advise "This is a piece of advice" 2>actual && diff --git a/t/t0024-crlf-archive.sh b/t/t0024-crlf-archive.sh index a34de56..a7f4de4 100755 --- a/t/t0024-crlf-archive.sh +++ b/t/t0024-crlf-archive.sh @@ -9,7 +9,7 @@ test_expect_success setup ' git config core.autocrlf true && - printf "CRLF line ending\r\nAnd another\r\n" > sample && + printf "CRLF line ending\r\nAnd another\r\n" >sample && git add sample && test_tick && @@ -19,8 +19,9 @@ test_expect_success setup ' test_expect_success 'tar archive' ' - git archive --format=tar HEAD | - ( mkdir untarred && cd untarred && "$TAR" -xf - ) && + git archive --format=tar HEAD >test.tar && + mkdir untarred && + "$TAR" xf test.tar -C untarred && test_cmp sample untarred/sample @@ -30,7 +31,11 @@ test_expect_success UNZIP 'zip archive' ' git archive --format=zip HEAD >test.zip && - ( mkdir unzipped && cd unzipped && "$GIT_UNZIP" ../test.zip ) && + mkdir unzipped && + ( + cd unzipped && + "$GIT_UNZIP" ../test.zip + ) && test_cmp sample unzipped/sample diff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh index 1b55f59..ad151a3 100755 --- a/t/t0028-working-tree-encoding.sh +++ b/t/t0028-working-tree-encoding.sh @@ -131,8 +131,8 @@ do test_when_finished "rm -f crlf.utf${i}.raw lf.utf${i}.raw" && test_when_finished "git reset --hard HEAD^" && - cat lf.utf8.raw | write_utf${i} >lf.utf${i}.raw && - cat crlf.utf8.raw | write_utf${i} >crlf.utf${i}.raw && + write_utf${i} <lf.utf8.raw >lf.utf${i}.raw && + write_utf${i} <crlf.utf8.raw >crlf.utf${i}.raw && cp crlf.utf${i}.raw eol.utf${i} && cat >expectIndexLF <<-EOF && diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index d1b3be8..f10f42f 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -401,6 +401,21 @@ test_expect_success 'strip comments with changed comment char' ' test -z "$(echo "; comment" | git -c core.commentchar=";" stripspace -s)" ' +test_expect_success 'strip comments with changed comment string' ' + test ! -z "$(echo "// comment" | git -c core.commentchar=// stripspace)" && + test -z "$(echo "// comment" | git -c core.commentchar="//" stripspace -s)" +' + +test_expect_success 'newline as commentchar is forbidden' ' + test_must_fail git -c core.commentChar="$LF" stripspace -s 2>err && + grep "core.commentchar cannot contain newline" err +' + +test_expect_success 'empty commentchar is forbidden' ' + test_must_fail git -c core.commentchar= stripspace -s 2>err && + grep "core.commentchar must have at least one character" err +' + test_expect_success '-c with single line' ' printf "# foo\n" >expect && printf "foo" | git stripspace -c >actual && diff --git a/t/t0033-safe-directory.sh b/t/t0033-safe-directory.sh index dc34968..11c3e8f 100755 --- a/t/t0033-safe-directory.sh +++ b/t/t0033-safe-directory.sh @@ -80,4 +80,28 @@ test_expect_success 'safe.directory in included file' ' git status ' +test_expect_success 'local clone of unowned repo refused in unsafe directory' ' + test_when_finished "rm -rf source" && + git init source && + ( + sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER && + test_commit -C source initial + ) && + test_must_fail git clone --local source target && + test_path_is_missing target +' + +test_expect_success 'local clone of unowned repo accepted in safe directory' ' + test_when_finished "rm -rf source" && + git init source && + ( + sane_unset GIT_TEST_ASSUME_DIFFERENT_OWNER && + test_commit -C source initial + ) && + test_must_fail git clone --local source target && + git config --global --add safe.directory "$(pwd)/source/.git" && + git clone --local source target && + test_path_is_dir target +' + test_done diff --git a/t/t0035-safe-bare-repository.sh b/t/t0035-safe-bare-repository.sh index 038b8b7..d3cb2a1 100755 --- a/t/t0035-safe-bare-repository.sh +++ b/t/t0035-safe-bare-repository.sh @@ -29,9 +29,20 @@ expect_rejected () { grep -F "implicit-bare-repository:$pwd" "$pwd/trace.perf" } -test_expect_success 'setup bare repo in worktree' ' +test_expect_success 'setup an embedded bare repo, secondary worktree and submodule' ' git init outer-repo && - git init --bare outer-repo/bare-repo + git init --bare --initial-branch=main outer-repo/bare-repo && + git -C outer-repo worktree add ../outer-secondary && + test_path_is_dir outer-secondary && + ( + cd outer-repo && + test_commit A && + git push bare-repo +HEAD:refs/heads/main && + git -c protocol.file.allow=always \ + submodule add --name subn -- ./bare-repo subd + ) && + test_path_is_dir outer-repo/.git/worktrees/outer-secondary && + test_path_is_dir outer-repo/.git/modules/subn ' test_expect_success 'safe.bareRepository unset' ' @@ -53,8 +64,7 @@ test_expect_success 'safe.bareRepository in the repository' ' # safe.bareRepository must not be "explicit", otherwise # git config fails with "fatal: not in a git directory" (like # safe.directory) - test_config -C outer-repo/bare-repo safe.bareRepository \ - all && + test_config -C outer-repo/bare-repo safe.bareRepository all && test_config_global safe.bareRepository explicit && expect_rejected -C outer-repo/bare-repo ' @@ -78,4 +88,20 @@ test_expect_success 'no trace when GIT_DIR is explicitly provided' ' expect_accepted_explicit "$pwd/outer-repo/bare-repo" ' +test_expect_success 'no trace when "bare repository" is .git' ' + expect_accepted_implicit -C outer-repo/.git +' + +test_expect_success 'no trace when "bare repository" is a subdir of .git' ' + expect_accepted_implicit -C outer-repo/.git/objects +' + +test_expect_success 'no trace in $GIT_DIR of secondary worktree' ' + expect_accepted_implicit -C outer-repo/.git/worktrees/outer-secondary +' + +test_expect_success 'no trace in $GIT_DIR of a submodule' ' + expect_accepted_implicit -C outer-repo/.git/modules/subn +' + test_done diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 8fdef88..8bb2a8b 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -210,6 +210,22 @@ test_expect_success 'superfluous value provided: boolean' ' test_cmp expect actual ' +test_expect_success 'superfluous value provided: boolean, abbreviated' ' + cat >expect <<-\EOF && + error: option `yes'\'' takes no value + EOF + test_expect_code 129 env GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \ + test-tool parse-options --ye=hi 2>actual && + test_cmp expect actual && + + cat >expect <<-\EOF && + error: option `no-yes'\'' takes no value + EOF + test_expect_code 129 env GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \ + test-tool parse-options --no-ye=hi 2>actual && + test_cmp expect actual +' + test_expect_success 'superfluous value provided: cmdmode' ' cat >expect <<-\EOF && error: option `mode1'\'' takes no value @@ -376,7 +392,7 @@ test_expect_success 'OPT_CMDMODE() detects incompatibility (1)' ' test_must_be_empty output && test_grep "mode1" output.err && test_grep "mode2" output.err && - test_grep "is incompatible with" output.err + test_grep "cannot be used together" output.err ' test_expect_success 'OPT_CMDMODE() detects incompatibility (2)' ' @@ -384,7 +400,7 @@ test_expect_success 'OPT_CMDMODE() detects incompatibility (2)' ' test_must_be_empty output && test_grep "mode2" output.err && test_grep "set23" output.err && - test_grep "is incompatible with" output.err + test_grep "cannot be used together" output.err ' test_expect_success 'OPT_CMDMODE() detects incompatibility (3)' ' @@ -392,7 +408,7 @@ test_expect_success 'OPT_CMDMODE() detects incompatibility (3)' ' test_must_be_empty output && test_grep "mode2" output.err && test_grep "set23" output.err && - test_grep "is incompatible with" output.err + test_grep "cannot be used together" output.err ' test_expect_success 'OPT_CMDMODE() detects incompatibility (4)' ' @@ -401,7 +417,7 @@ test_expect_success 'OPT_CMDMODE() detects incompatibility (4)' ' test_must_be_empty output && test_grep "mode2" output.err && test_grep "mode34.3" output.err && - test_grep "is incompatible with" output.err + test_grep "cannot be used together" output.err ' test_expect_success 'OPT_COUNTUP() with PARSE_OPT_NODASH works' ' diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index 0afa3d0..85686ee 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -610,4 +610,45 @@ test_expect_success !VALGRIND,RUNTIME_PREFIX,CAN_EXEC_IN_PWD '%(prefix)/ works' test_cmp expect actual ' +test_expect_success 'do_files_match()' ' + test_seq 0 10 >0-10.txt && + test_seq -1 10 >-1-10.txt && + test_seq 1 10 >1-10.txt && + test_seq 1 9 >1-9.txt && + test_seq 0 8 >0-8.txt && + + test-tool path-utils do_files_match 0-10.txt 0-10.txt >out && + + assert_fails() { + test_must_fail \ + test-tool path-utils do_files_match "$1" "$2" >out && + grep different out + } && + + assert_fails 0-8.txt 1-9.txt && + assert_fails -1-10.txt 0-10.txt && + assert_fails 1-10.txt 1-9.txt && + assert_fails 1-10.txt .git && + assert_fails does-not-exist 1-10.txt && + + if test_have_prereq FILEMODE + then + cp 0-10.txt 0-10.x && + chmod a+x 0-10.x && + assert_fails 0-10.txt 0-10.x + fi && + + if test_have_prereq SYMLINKS + then + ln -sf 0-10.txt symlink && + ln -s 0-10.txt another-symlink && + ln -s over-the-ocean yet-another-symlink && + ln -s "$PWD/0-10.txt" absolute-symlink && + assert_fails 0-10.txt symlink && + test-tool path-utils do_files_match symlink another-symlink && + assert_fails symlink yet-another-symlink && + assert_fails symlink absolute-symlink + fi +' + test_done diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh index 487bc8d..0ecec2b 100755 --- a/t/t0070-fundamental.sh +++ b/t/t0070-fundamental.sh @@ -9,10 +9,6 @@ Verify wrappers and compatibility functions. TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh -test_expect_success 'character classes (isspace, isalpha etc.)' ' - test-tool ctype -' - test_expect_success 'mktemp to nonexistent directory prints filename' ' test_must_fail test-tool mktemp doesnotexist/testXXXXXX 2>err && grep "doesnotexist/test" err @@ -53,4 +49,62 @@ test_expect_success 'missing sideband designator is reported' ' test_grep "missing sideband" err ' +test_expect_success 'unpack-sideband: --no-chomp-newline' ' + test_when_finished "rm -f expect-out expect-err" && + test-tool pkt-line send-split-sideband >split-sideband && + test-tool pkt-line unpack-sideband \ + --no-chomp-newline <split-sideband >out 2>err && + cat >expect-out <<-EOF && + primary: regular output + EOF + cat >expect-err <<-EOF && + Foo. + Bar. + Hello, world! + EOF + test_cmp expect-out out && + test_cmp expect-err err +' + +test_expect_success 'unpack-sideband: --chomp-newline (default)' ' + test_when_finished "rm -f expect-out expect-err" && + test-tool pkt-line send-split-sideband >split-sideband && + test-tool pkt-line unpack-sideband \ + --chomp-newline <split-sideband >out 2>err && + printf "primary: regular output" >expect-out && + printf "Foo.Bar.Hello, world!" >expect-err && + test_cmp expect-out out && + test_cmp expect-err err +' + +test_expect_success 'unpack-sideband: packet_reader_read() consumes sideband, no chomp payload' ' + test_when_finished "rm -f expect-out expect-err" && + test-tool pkt-line send-split-sideband >split-sideband && + test-tool pkt-line unpack-sideband \ + --reader-use-sideband \ + --no-chomp-newline <split-sideband >out 2>err && + cat >expect-out <<-EOF && + primary: regular output + EOF + printf "remote: Foo. \n" >expect-err && + printf "remote: Bar. \n" >>expect-err && + printf "remote: Hello, world! \n" >>expect-err && + test_cmp expect-out out && + test_cmp expect-err err +' + +test_expect_success 'unpack-sideband: packet_reader_read() consumes sideband, chomp payload' ' + test_when_finished "rm -f expect-out expect-err" && + test-tool pkt-line send-split-sideband >split-sideband && + test-tool pkt-line unpack-sideband \ + --reader-use-sideband \ + --chomp-newline <split-sideband >out 2>err && + printf "primary: regular output" >expect-out && + printf "remote: Foo. \n" >expect-err && + printf "remote: Bar. \n" >>expect-err && + printf "remote: Hello, world! \n" >>expect-err && + test_cmp expect-out out && + test_cmp expect-err err +' + test_done diff --git a/t/t0080-unit-test-output.sh b/t/t0080-unit-test-output.sh new file mode 100755 index 0000000..6657c11 --- /dev/null +++ b/t/t0080-unit-test-output.sh @@ -0,0 +1,59 @@ +#!/bin/sh + +test_description='Test the output of the unit test framework' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +test_expect_success 'TAP output from unit tests' ' + cat >expect <<-EOF && + ok 1 - passing test + ok 2 - passing test and assertion return 1 + # check "1 == 2" failed at t/unit-tests/t-basic.c:76 + # left: 1 + # right: 2 + not ok 3 - failing test + ok 4 - failing test and assertion return 0 + not ok 5 - passing TEST_TODO() # TODO + ok 6 - passing TEST_TODO() returns 1 + # todo check ${SQ}check(x)${SQ} succeeded at t/unit-tests/t-basic.c:25 + not ok 7 - failing TEST_TODO() + ok 8 - failing TEST_TODO() returns 0 + # check "0" failed at t/unit-tests/t-basic.c:30 + # skipping test - missing prerequisite + # skipping check ${SQ}1${SQ} at t/unit-tests/t-basic.c:32 + ok 9 - test_skip() # SKIP + ok 10 - skipped test returns 1 + # skipping test - missing prerequisite + ok 11 - test_skip() inside TEST_TODO() # SKIP + ok 12 - test_skip() inside TEST_TODO() returns 1 + # check "0" failed at t/unit-tests/t-basic.c:48 + not ok 13 - TEST_TODO() after failing check + ok 14 - TEST_TODO() after failing check returns 0 + # check "0" failed at t/unit-tests/t-basic.c:56 + not ok 15 - failing check after TEST_TODO() + ok 16 - failing check after TEST_TODO() returns 0 + # check "!strcmp("\thello\\\\", "there\"\n")" failed at t/unit-tests/t-basic.c:61 + # left: "\011hello\\\\" + # right: "there\"\012" + # check "!strcmp("NULL", NULL)" failed at t/unit-tests/t-basic.c:62 + # left: "NULL" + # right: NULL + # check "${SQ}a${SQ} == ${SQ}\n${SQ}" failed at t/unit-tests/t-basic.c:63 + # left: ${SQ}a${SQ} + # right: ${SQ}\012${SQ} + # check "${SQ}\\\\${SQ} == ${SQ}\\${SQ}${SQ}" failed at t/unit-tests/t-basic.c:64 + # left: ${SQ}\\\\${SQ} + # right: ${SQ}\\${SQ}${SQ} + not ok 17 - messages from failing string and char comparison + # BUG: test has no checks at t/unit-tests/t-basic.c:91 + not ok 18 - test with no checks + ok 19 - test with no checks returns 0 + 1..19 + EOF + + ! "$GIT_BUILD_DIR"/t/unit-tests/bin/t-basic >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t0091-bugreport.sh b/t/t0091-bugreport.sh index 8798fee..fca3904 100755 --- a/t/t0091-bugreport.sh +++ b/t/t0091-bugreport.sh @@ -39,9 +39,9 @@ test_expect_success 'sanity check "System Info" section' ' sed -ne "/^\[System Info\]$/,/^$/p" <git-bugreport-format.txt >system && - # The beginning should match "git version --build-info" verbatim, + # The beginning should match "git version --build-options" verbatim, # but rather than checking bit-for-bit equality, just test some basics. - grep "git version [0-9]." system && + grep "git version " system && grep "shell-path: ." system && # After the version, there should be some more info. diff --git a/t/t0202/test.pl b/t/t0202/test.pl index 2cbf7b9..47d96a2 100755 --- a/t/t0202/test.pl +++ b/t/t0202/test.pl @@ -1,5 +1,5 @@ #!/usr/bin/perl -use 5.008; +use 5.008001; use lib (split(/:/, $ENV{GITPERLLIB})); use strict; use warnings; diff --git a/t/t0204-gettext-reencode-sanity.sh b/t/t0204-gettext-reencode-sanity.sh index 4f2e0dc..310a450 100755 --- a/t/t0204-gettext-reencode-sanity.sh +++ b/t/t0204-gettext-reencode-sanity.sh @@ -82,7 +82,7 @@ test_expect_success GETTEXT_ISO_LOCALE 'gettext.c: git init UTF-8 -> ISO-8859-1' printf "Bjó til tóma Git lind" >expect && LANGUAGE=is LC_ALL="$is_IS_iso_locale" git init repo >actual && test_when_finished "rm -rf repo" && - grep "^$(cat expect | iconv -f UTF-8 -t ISO8859-1) " actual + grep "^$(iconv -f UTF-8 -t ISO8859-1 <expect) " actual ' test_done diff --git a/t/t0210-trace2-normal.sh b/t/t0210-trace2-normal.sh index 80e76a4..c312657 100755 --- a/t/t0210-trace2-normal.sh +++ b/t/t0210-trace2-normal.sh @@ -2,7 +2,7 @@ test_description='test trace2 facility (normal target)' -TEST_PASSES_SANITIZE_LEAK=true +TEST_PASSES_SANITIZE_LEAK=false . ./test-lib.sh # Turn off any inherited trace2 settings for this test. @@ -283,4 +283,22 @@ test_expect_success 'using global config with include' ' test_cmp expect actual ' +test_expect_success 'unsafe URLs are redacted by default' ' + test_when_finished \ + "rm -r trace.normal unredacted.normal clone clone2" && + + test_config_global \ + "url.$(pwd).insteadOf" https://user:pwd@example.com/ && + test_config_global trace2.configParams "core.*,remote.*.url" && + + GIT_TRACE2="$(pwd)/trace.normal" \ + git clone https://user:pwd@example.com/ clone && + ! grep user:pwd trace.normal && + + GIT_TRACE2_REDACT=0 GIT_TRACE2="$(pwd)/unredacted.normal" \ + git clone https://user:pwd@example.com/ clone2 && + grep "start .* clone https://user:pwd@example.com" unredacted.normal && + grep "remote.origin.url=https://user:pwd@example.com" unredacted.normal +' + test_done diff --git a/t/t0211-trace2-perf.sh b/t/t0211-trace2-perf.sh index cfba686..13ef69b 100755 --- a/t/t0211-trace2-perf.sh +++ b/t/t0211-trace2-perf.sh @@ -2,7 +2,7 @@ test_description='test trace2 facility (perf target)' -TEST_PASSES_SANITIZE_LEAK=true +TEST_PASSES_SANITIZE_LEAK=false . ./test-lib.sh # Turn off any inherited trace2 settings for this test. @@ -268,4 +268,254 @@ test_expect_success PTHREADS 'global counter test/test2' ' have_counter_event "main" "counter" "test" "test2" 60 actual ' +test_expect_success 'unsafe URLs are redacted by default' ' + test_when_finished \ + "rm -r actual trace.perf unredacted.perf clone clone2" && + + test_config_global \ + "url.$(pwd).insteadOf" https://user:pwd@example.com/ && + test_config_global trace2.configParams "core.*,remote.*.url" && + + GIT_TRACE2_PERF="$(pwd)/trace.perf" \ + git clone https://user:pwd@example.com/ clone && + ! grep user:pwd trace.perf && + + GIT_TRACE2_REDACT=0 GIT_TRACE2_PERF="$(pwd)/unredacted.perf" \ + git clone https://user:pwd@example.com/ clone2 && + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <unredacted.perf >actual && + grep "d0|main|start|.* clone https://user:pwd@example.com" actual && + grep "d0|main|def_param|.*|remote.origin.url:https://user:pwd@example.com" actual +' + +# Confirm that the requested command produces a "cmd_name" and a +# set of "def_param" events. +# +try_simple () { + test_when_finished "rm prop.perf actual" && + + cmd=$1 && + cmd_name=$2 && + + test_config_global "trace2.configParams" "cfg.prop.*" && + test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" && + + test_config_global "cfg.prop.foo" "red" && + + ENV_PROP_FOO=blue \ + GIT_TRACE2_PERF="$(pwd)/prop.perf" \ + $cmd && + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual && + grep "d0|main|cmd_name|.*|$cmd_name" actual && + grep "d0|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual +} + +# Representative mainstream builtin Git command dispatched +# in run_builtin() in git.c +# +test_expect_success 'expect def_params for normal builtin command' ' + try_simple "git version" "version" +' + +# Representative query command dispatched in handle_options() +# in git.c +# +test_expect_success 'expect def_params for query command' ' + try_simple "git --man-path" "_query_" +' + +# remote-curl.c does not use the builtin setup in git.c, so confirm +# that executables built from remote-curl.c emit def_params. +# +# Also tests the dashed-command handling where "git foo" silently +# spawns "git-foo". Make sure that both commands should emit +# def_params. +# +# Pass bogus arguments to remote-https and allow the command to fail +# because we don't actually have a remote to fetch from. We just want +# to see the run-dashed code run an executable built from +# remote-curl.c rather than git.c. Confirm that we get def_param +# events from both layers. +# +test_expect_success 'expect def_params for remote-curl and _run_dashed_' ' + test_when_finished "rm prop.perf actual" && + + test_config_global "trace2.configParams" "cfg.prop.*" && + test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" && + + test_config_global "cfg.prop.foo" "red" && + + test_might_fail env \ + ENV_PROP_FOO=blue \ + GIT_TRACE2_PERF="$(pwd)/prop.perf" \ + git remote-http x y && + + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual && + + grep "d0|main|cmd_name|.*|_run_dashed_" actual && + grep "d0|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual && + + grep "d1|main|cmd_name|.*|remote-curl" actual && + grep "d1|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual +' + +# Similarly, `git-http-fetch` is not built from git.c so do a +# trivial fetch so that the main git.c run-dashed code spawns +# an executable built from http-fetch.c. Confirm that we get +# def_param events from both layers. +# +test_expect_success 'expect def_params for http-fetch and _run_dashed_' ' + test_when_finished "rm prop.perf actual" && + + test_config_global "trace2.configParams" "cfg.prop.*" && + test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" && + + test_config_global "cfg.prop.foo" "red" && + + test_might_fail env \ + ENV_PROP_FOO=blue \ + GIT_TRACE2_PERF="$(pwd)/prop.perf" \ + git http-fetch --stdin file:/// <<-EOF && + EOF + + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual && + + grep "d0|main|cmd_name|.*|_run_dashed_" actual && + grep "d0|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual && + + grep "d1|main|cmd_name|.*|http-fetch" actual && + grep "d1|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual +' + +# Historically, alias expansion explicitly emitted the def_param +# events (independent of whether the command was a builtin, a Git +# command or arbitrary shell command) so that it wasn't dependent +# upon the unpeeling of the alias. Let's make sure that we preserve +# the net effect. +# +test_expect_success 'expect def_params during git alias expansion' ' + test_when_finished "rm prop.perf actual" && + + test_config_global "trace2.configParams" "cfg.prop.*" && + test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" && + + test_config_global "cfg.prop.foo" "red" && + + test_config_global "alias.xxx" "version" && + + ENV_PROP_FOO=blue \ + GIT_TRACE2_PERF="$(pwd)/prop.perf" \ + git xxx && + + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual && + + # "git xxx" is first mapped to "git-xxx" and the child will fail. + grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual && + + # We unpeel that and substitute "version" into "xxx" (giving + # "git version") and update the cmd_name event. + grep "d0|main|cmd_name|.*|_run_git_alias_ (_run_dashed_/_run_git_alias_)" actual && + + # These def_param events could be associated with either of the + # above cmd_name events. It does not matter. + grep "d0|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual && + + # The "git version" child sees a different cmd_name hierarchy. + # Also test the def_param (only for completeness). + grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_git_alias_/version)" actual && + grep "d1|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual +' + +test_expect_success 'expect def_params during shell alias expansion' ' + test_when_finished "rm prop.perf actual" && + + test_config_global "trace2.configParams" "cfg.prop.*" && + test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" && + + test_config_global "cfg.prop.foo" "red" && + + test_config_global "alias.xxx" "!git version" && + + ENV_PROP_FOO=blue \ + GIT_TRACE2_PERF="$(pwd)/prop.perf" \ + git xxx && + + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual && + + # "git xxx" is first mapped to "git-xxx" and the child will fail. + grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual && + + # We unpeel that and substitute "git version" for "git xxx" (as a + # shell command. Another cmd_name event is emitted as we unpeel. + grep "d0|main|cmd_name|.*|_run_shell_alias_ (_run_dashed_/_run_shell_alias_)" actual && + + # These def_param events could be associated with either of the + # above cmd_name events. It does not matter. + grep "d0|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual && + + # We get the following only because we used a git command for the + # shell command. In general, it could have been a shell script and + # we would see nothing. + # + # The child knows the cmd_name hierarchy so it includes it. + grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_shell_alias_/version)" actual && + grep "d1|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual +' + +test_expect_success 'expect def_params during nested git alias expansion' ' + test_when_finished "rm prop.perf actual" && + + test_config_global "trace2.configParams" "cfg.prop.*" && + test_config_global "trace2.envvars" "ENV_PROP_FOO,ENV_PROP_BAR" && + + test_config_global "cfg.prop.foo" "red" && + + test_config_global "alias.xxx" "yyy" && + test_config_global "alias.yyy" "version" && + + ENV_PROP_FOO=blue \ + GIT_TRACE2_PERF="$(pwd)/prop.perf" \ + git xxx && + + perl "$TEST_DIRECTORY/t0211/scrub_perf.perl" <prop.perf >actual && + + # "git xxx" is first mapped to "git-xxx" and try to spawn "git-xxx" + # and the child will fail. + grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_)" actual && + grep "d0|main|child_start|.*|.* class:dashed argv:\[git-xxx\]" actual && + + # We unpeel that and substitute "yyy" into "xxx" (giving "git yyy") + # and spawn "git-yyy" and the child will fail. + grep "d0|main|alias|.*|alias:xxx argv:\[yyy\]" actual && + grep "d0|main|cmd_name|.*|_run_dashed_ (_run_dashed_/_run_dashed_)" actual && + grep "d0|main|child_start|.*|.* class:dashed argv:\[git-yyy\]" actual && + + # We unpeel that and substitute "version" into "xxx" (giving + # "git version") and update the cmd_name event. + grep "d0|main|alias|.*|alias:yyy argv:\[version\]" actual && + grep "d0|main|cmd_name|.*|_run_git_alias_ (_run_dashed_/_run_dashed_/_run_git_alias_)" actual && + + # These def_param events could be associated with any of the + # above cmd_name events. It does not matter. + grep "d0|main|def_param|.*|cfg.prop.foo:red" actual >actual.matches && + grep "d0|main|def_param|.*|ENV_PROP_FOO:blue" actual && + + # However, we do not want them repeated each time we unpeel. + test_line_count = 1 actual.matches && + + # The "git version" child sees a different cmd_name hierarchy. + # Also test the def_param (only for completeness). + grep "d1|main|cmd_name|.*|version (_run_dashed_/_run_dashed_/_run_git_alias_/version)" actual && + grep "d1|main|def_param|.*|cfg.prop.foo:red" actual && + grep "d1|main|def_param|.*|ENV_PROP_FOO:blue" actual +' + test_done diff --git a/t/t0212-trace2-event.sh b/t/t0212-trace2-event.sh index 6d3374f..147643d 100755 --- a/t/t0212-trace2-event.sh +++ b/t/t0212-trace2-event.sh @@ -323,4 +323,44 @@ test_expect_success 'discard traces when there are too many files' ' head -n2 trace_target_dir/git-trace2-discard | tail -n1 | grep \"event\":\"too_many_files\" ' +# In the following "...redact..." tests, skip testing the GIT_TRACE2_REDACT=0 +# case because we would need to exactly model the full JSON event stream like +# we did in the basic tests above and I do not think it is worth it. + +test_expect_success 'unsafe URLs are redacted by default in cmd_start events' ' + test_when_finished \ + "rm -r trace.event" && + + GIT_TRACE2_EVENT="$(pwd)/trace.event" \ + test-tool trace2 300redact_start git clone https://user:pwd@example.com/ clone2 && + ! grep user:pwd trace.event +' + +test_expect_success 'unsafe URLs are redacted by default in child_start events' ' + test_when_finished \ + "rm -r trace.event" && + + GIT_TRACE2_EVENT="$(pwd)/trace.event" \ + test-tool trace2 301redact_child_start git clone https://user:pwd@example.com/ clone2 && + ! grep user:pwd trace.event +' + +test_expect_success 'unsafe URLs are redacted by default in exec events' ' + test_when_finished \ + "rm -r trace.event" && + + GIT_TRACE2_EVENT="$(pwd)/trace.event" \ + test-tool trace2 302redact_exec git clone https://user:pwd@example.com/ clone2 && + ! grep user:pwd trace.event +' + +test_expect_success 'unsafe URLs are redacted by default in def_param events' ' + test_when_finished \ + "rm -r trace.event" && + + GIT_TRACE2_EVENT="$(pwd)/trace.event" \ + test-tool trace2 303redact_def_param url https://user:pwd@example.com/ && + ! grep user:pwd trace.event +' + test_done diff --git a/t/t0301-credential-cache.sh b/t/t0301-credential-cache.sh index 8300faa..f2c146f 100755 --- a/t/t0301-credential-cache.sh +++ b/t/t0301-credential-cache.sh @@ -8,6 +8,14 @@ test -z "$NO_UNIX_SOCKETS" || { skip_all='skipping credential-cache tests, unix sockets not available' test_done } +if test_have_prereq MINGW +then + service_running=$(sc query afunix | grep "4 RUNNING") + test -z "$service_running" || { + skip_all='skipping credential-cache tests, unix sockets not available' + test_done + } +fi uname_s=$(uname -s) case $uname_s in diff --git a/t/t0303-credential-external.sh b/t/t0303-credential-external.sh index 095574b..72ae405 100755 --- a/t/t0303-credential-external.sh +++ b/t/t0303-credential-external.sh @@ -32,9 +32,24 @@ commands. . ./test-lib.sh . "$TEST_DIRECTORY"/lib-credential.sh +# If we're not given a specific external helper to run against, +# there isn't much to test. But we can still run through our +# battery of tests with a fake helper and check that the +# test themselves are self-consistent and clean up after +# themselves. +# +# We'll use the "store" helper, since we can easily inspect +# its state by looking at the on-disk file. But since it doesn't +# implement any caching or expiry logic, we'll cheat and override +# the "check" function to just report all results as OK. if test -z "$GIT_TEST_CREDENTIAL_HELPER"; then - skip_all="used to test external credential helpers" - test_done + GIT_TEST_CREDENTIAL_HELPER=store + GIT_TEST_CREDENTIAL_HELPER_TIMEOUT=store + check () { + test "$1" = "approve" || return 0 + git -c credential.helper=store credential approve + } + check_cleanup=t fi test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" || @@ -59,4 +74,11 @@ fi # might be long-term system storage helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER" +if test "$check_cleanup" = "t" +then + test_expect_success 'test cleanup removes everything' ' + test_must_be_empty "$HOME/.git-credentials" + ' +fi + test_done diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index 5b7bee8..88a66f0 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -49,7 +49,7 @@ test_expect_success 'convert shallow clone to partial clone' ' test_cmp_config -C client 1 core.repositoryformatversion ' -test_expect_success SHA1 'convert to partial clone with noop extension' ' +test_expect_success DEFAULT_REPO_FORMAT 'convert to partial clone with noop extension' ' rm -fr server client && test_create_repo server && test_commit -C server my_commit 1 && @@ -60,7 +60,7 @@ test_expect_success SHA1 'convert to partial clone with noop extension' ' git -C client fetch --unshallow --filter="blob:none" ' -test_expect_success SHA1 'converting to partial clone fails with unrecognized extension' ' +test_expect_success DEFAULT_REPO_FORMAT 'converting to partial clone fails with unrecognized extension' ' rm -fr server client && test_create_repo server && test_commit -C server my_commit 1 && @@ -665,6 +665,21 @@ test_expect_success 'lazy-fetch when accessing object not in the_repository' ' git -C partial.git rev-list --objects --missing=print HEAD >out && grep "[?]$FILE_HASH" out && + # The no-lazy-fetch mechanism prevents Git from fetching + test_must_fail env GIT_NO_LAZY_FETCH=1 \ + git -C partial.git cat-file -e "$FILE_HASH" && + + # The same with command line option to "git" + test_must_fail git --no-lazy-fetch -C partial.git cat-file -e "$FILE_HASH" && + + # The same, forcing a subprocess via an alias + test_must_fail git --no-lazy-fetch -C partial.git \ + -c alias.foo="!git cat-file" foo -e "$FILE_HASH" && + + # Sanity check that the file is still missing + git -C partial.git rev-list --objects --missing=print HEAD >out && + grep "[?]$FILE_HASH" out && + git -C full cat-file -s "$FILE_HASH" >expect && test-tool partial-clone object-info partial.git "$FILE_HASH" >actual && test_cmp expect actual && diff --git a/t/t0411-clone-from-partial.sh b/t/t0411-clone-from-partial.sh new file mode 100755 index 0000000..c98d501 --- /dev/null +++ b/t/t0411-clone-from-partial.sh @@ -0,0 +1,78 @@ +#!/bin/sh + +test_description='check that local clone does not fetch from promisor remotes' + +. ./test-lib.sh + +test_expect_success 'create evil repo' ' + git init tmp && + test_commit -C tmp a && + git -C tmp config uploadpack.allowfilter 1 && + git clone --filter=blob:none --no-local --no-checkout tmp evil && + rm -rf tmp && + + git -C evil config remote.origin.uploadpack \"\$TRASH_DIRECTORY/fake-upload-pack\" && + write_script fake-upload-pack <<-\EOF && + echo >&2 "fake-upload-pack running" + >"$TRASH_DIRECTORY/script-executed" + exit 1 + EOF + export TRASH_DIRECTORY && + + # empty shallow file disables local clone optimization + >evil/.git/shallow +' + +test_expect_success 'local clone must not fetch from promisor remote and execute script' ' + rm -f script-executed && + test_must_fail git clone \ + --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ + evil clone1 2>err && + test_grep "detected dubious ownership" err && + test_grep ! "fake-upload-pack running" err && + test_path_is_missing script-executed +' + +test_expect_success 'clone from file://... must not fetch from promisor remote and execute script' ' + rm -f script-executed && + test_must_fail git clone \ + --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ + "file://$(pwd)/evil" clone2 2>err && + test_grep "detected dubious ownership" err && + test_grep ! "fake-upload-pack running" err && + test_path_is_missing script-executed +' + +test_expect_success 'fetch from file://... must not fetch from promisor remote and execute script' ' + rm -f script-executed && + test_must_fail git fetch \ + --upload-pack="GIT_TEST_ASSUME_DIFFERENT_OWNER=true git-upload-pack" \ + "file://$(pwd)/evil" 2>err && + test_grep "detected dubious ownership" err && + test_grep ! "fake-upload-pack running" err && + test_path_is_missing script-executed +' + +test_expect_success 'pack-objects should fetch from promisor remote and execute script' ' + rm -f script-executed && + echo "HEAD" | test_must_fail git -C evil pack-objects --revs --stdout >/dev/null 2>err && + test_grep "fake-upload-pack running" err && + test_path_is_file script-executed +' + +test_expect_success 'clone from promisor remote does not lazy-fetch by default' ' + rm -f script-executed && + test_must_fail git clone evil no-lazy 2>err && + test_grep "lazy fetching disabled" err && + test_path_is_missing script-executed +' + +test_expect_success 'promisor lazy-fetching can be re-enabled' ' + rm -f script-executed && + test_must_fail env GIT_NO_LAZY_FETCH=0 \ + git clone evil lazy-ok 2>err && + test_grep "fake-upload-pack running" err && + test_path_is_file script-executed +' + +test_done diff --git a/t/t0450-txt-doc-vs-help.sh b/t/t0450-txt-doc-vs-help.sh index cd3969e..69917d7 100755 --- a/t/t0450-txt-doc-vs-help.sh +++ b/t/t0450-txt-doc-vs-help.sh @@ -59,7 +59,9 @@ txt_to_synopsis () { -e '/^\[verse\]$/,/^$/ { /^$/d; /^\[verse\]$/d; - + s/_//g; + s/++//g; + s/`//g; s/{litdd}/--/g; s/'\''\(git[ a-z-]*\)'\''/\1/g; diff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh new file mode 100755 index 0000000..6421434 --- /dev/null +++ b/t/t0600-reffiles-backend.sh @@ -0,0 +1,475 @@ +#!/bin/sh + +test_description='Test reffiles backend' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh + +if ! test_have_prereq REFFILES +then + skip_all='skipping reffiles specific tests' + test_done +fi + +test_expect_success 'setup' ' + git commit --allow-empty -m Initial && + C=$(git rev-parse HEAD) && + git commit --allow-empty -m Second && + D=$(git rev-parse HEAD) && + git commit --allow-empty -m Third && + E=$(git rev-parse HEAD) +' + +test_expect_success 'empty directory should not fool rev-parse' ' + prefix=refs/e-rev-parse && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + echo "$C" >expected && + git rev-parse $prefix/foo >actual && + test_cmp expected actual +' + +test_expect_success 'empty directory should not fool for-each-ref' ' + prefix=refs/e-for-each-ref && + git update-ref $prefix/foo $C && + git for-each-ref $prefix >expected && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + git for-each-ref $prefix >actual && + test_cmp expected actual +' + +test_expect_success 'empty directory should not fool create' ' + prefix=refs/e-create && + mkdir -p .git/$prefix/foo/bar/baz && + printf "create %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool verify' ' + prefix=refs/e-verify && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "verify %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 1-arg update' ' + prefix=refs/e-update-1 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "update %s $D\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 2-arg update' ' + prefix=refs/e-update-2 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "update %s $D $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 0-arg delete' ' + prefix=refs/e-delete-0 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "delete %s\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'empty directory should not fool 1-arg delete' ' + prefix=refs/e-delete-1 && + git update-ref $prefix/foo $C && + git pack-refs --all && + mkdir -p .git/$prefix/foo/bar/baz && + printf "delete %s $C\n" $prefix/foo | + git update-ref --stdin +' + +test_expect_success 'non-empty directory blocks create' ' + prefix=refs/ne-create && + mkdir -p .git/$prefix/foo/bar && + : >.git/$prefix/foo/bar/baz.lock && + test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/foo$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ + EOF + printf "%s\n" "update $prefix/foo $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ + EOF + printf "%s\n" "update $prefix/foo $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'broken reference blocks create' ' + prefix=refs/broken-create && + mkdir -p .git/$prefix && + echo "gobbledigook" >.git/$prefix/foo && + test_when_finished "rm -f .git/$prefix/foo" && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken + EOF + printf "%s\n" "update $prefix/foo $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken + EOF + printf "%s\n" "update $prefix/foo $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'non-empty directory blocks indirect create' ' + prefix=refs/ne-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + mkdir -p .git/$prefix/foo/bar && + : >.git/$prefix/foo/bar/baz.lock && + test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/symref$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ + EOF + printf "%s\n" "update $prefix/symref $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ + EOF + printf "%s\n" "update $prefix/symref $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'broken reference blocks indirect create' ' + prefix=refs/broken-indirect-create && + git symbolic-ref $prefix/symref $prefix/foo && + echo "gobbledigook" >.git/$prefix/foo && + test_when_finished "rm -f .git/$prefix/foo" && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken + EOF + printf "%s\n" "update $prefix/symref $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err && + cat >expected <<-EOF && + fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken + EOF + printf "%s\n" "update $prefix/symref $D $C" | + test_must_fail git update-ref --stdin 2>output.err && + test_cmp expected output.err +' + +test_expect_success 'no bogus intermediate values during delete' ' + prefix=refs/slow-transaction && + # Set up a reference with differing loose and packed versions: + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + # Now try to update the reference, but hold the `packed-refs` lock + # for a while to see what happens while the process is blocked: + : >.git/packed-refs.lock && + test_when_finished "rm -f .git/packed-refs.lock" && + { + # Note: the following command is intentionally run in the + # background. We increase the timeout so that `update-ref` + # attempts to acquire the `packed-refs` lock for much longer + # than it takes for us to do the check then delete it: + git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo & + } && + pid2=$! && + # Give update-ref plenty of time to get to the point where it tries + # to lock packed-refs: + sleep 1 && + # Make sure that update-ref did not complete despite the lock: + kill -0 $pid2 && + # Verify that the reference still has its old value: + sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) && + case "$sha1" in + $D) + # This is what we hope for; it means that nothing + # user-visible has changed yet. + : ;; + undefined) + # This is not correct; it means the deletion has happened + # already even though update-ref should not have been + # able to acquire the lock yet. + echo "$prefix/foo deleted prematurely" && + break + ;; + $C) + # This value should never be seen. Probably the loose + # reference has been deleted but the packed reference + # is still there: + echo "$prefix/foo incorrectly observed to be C" && + break + ;; + *) + # WTF? + echo "unexpected value observed for $prefix/foo: $sha1" && + break + ;; + esac >out && + rm -f .git/packed-refs.lock && + wait $pid2 && + test_must_be_empty out && + test_must_fail git rev-parse --verify --quiet $prefix/foo +' + +test_expect_success 'delete fails cleanly if packed-refs file is locked' ' + prefix=refs/locked-packed-refs && + # Set up a reference with differing loose and packed versions: + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + git for-each-ref $prefix >unchanged && + # Now try to delete it while the `packed-refs` lock is held: + : >.git/packed-refs.lock && + test_when_finished "rm -f .git/packed-refs.lock" && + test_must_fail git update-ref -d $prefix/foo >out 2>err && + git for-each-ref $prefix >actual && + test_grep "Unable to create $SQ.*packed-refs.lock$SQ: " err && + test_cmp unchanged actual +' + +test_expect_success 'delete fails cleanly if packed-refs.new write fails' ' + # Setup and expectations are similar to the test above. + prefix=refs/failed-packed-refs && + git update-ref $prefix/foo $C && + git pack-refs --all && + git update-ref $prefix/foo $D && + git for-each-ref $prefix >unchanged && + # This should not happen in practice, but it is an easy way to get a + # reliable error (we open with create_tempfile(), which uses O_EXCL). + : >.git/packed-refs.new && + test_when_finished "rm -f .git/packed-refs.new" && + test_must_fail git update-ref -d $prefix/foo && + git for-each-ref $prefix >actual && + test_cmp unchanged actual +' + +RWT="test-tool ref-store worktree:wt" +RMAIN="test-tool ref-store worktree:main" + +test_expect_success 'setup worktree' ' + test_commit first && + git worktree add -b wt-main wt && + ( + cd wt && + test_commit second + ) +' + +# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should +# only appear in the for-each-reflog output if it is called from the correct +# worktree, which is exercised in this test. This test is poorly written for +# mulitple reasons: 1) it creates invalidly formatted log entres. 2) it uses +# direct FS access for creating the reflogs. 3) PSEUDO-WT and refs/bisect/random +# do not create reflogs by default, so it is not testing a realistic scenario. +test_expect_success 'for_each_reflog()' ' + echo $ZERO_OID >.git/logs/PSEUDO_MAIN_HEAD && + mkdir -p .git/logs/refs/bisect && + echo $ZERO_OID >.git/logs/refs/bisect/random && + + echo $ZERO_OID >.git/worktrees/wt/logs/PSEUDO_WT_HEAD && + mkdir -p .git/worktrees/wt/logs/refs/bisect && + echo $ZERO_OID >.git/worktrees/wt/logs/refs/bisect/wt-random && + + $RWT for-each-reflog >actual && + cat >expected <<-\EOF && + HEAD + PSEUDO_WT_HEAD + refs/bisect/wt-random + refs/heads/main + refs/heads/wt-main + EOF + test_cmp expected actual && + + $RMAIN for-each-reflog >actual && + cat >expected <<-\EOF && + HEAD + PSEUDO_MAIN_HEAD + refs/bisect/random + refs/heads/main + refs/heads/wt-main + EOF + test_cmp expected actual +' + +# Triggering the bug detected by this test requires a newline to fall +# exactly BUFSIZ-1 bytes from the end of the file. We don't know +# what that value is, since it's platform dependent. However, if +# we choose some value N, we also catch any D which divides N evenly +# (since we will read backwards in chunks of D). So we choose 8K, +# which catches glibc (with an 8K BUFSIZ) and *BSD (1K). +# +# Each line is 114 characters, so we need 75 to still have a few before the +# last 8K. The 89-character padding on the final entry lines up our +# newline exactly. +test_expect_success SHA1 'parsing reverse reflogs at BUFSIZ boundaries' ' + git checkout -b reflogskip && + zf=$(test_oid zero_2) && + ident="abc <xyz> 0000000001 +0000" && + for i in $(test_seq 1 75); do + printf "$zf%02d $zf%02d %s\t" $i $(($i+1)) "$ident" && + if test $i = 75; then + for j in $(test_seq 1 89); do + printf X || return 1 + done + else + printf X + fi && + printf "\n" || return 1 + done >.git/logs/refs/heads/reflogskip && + git rev-parse reflogskip@{73} >actual && + echo ${zf}03 >expect && + test_cmp expect actual +' + +# This test takes a lock on an individual ref; this is not supported in +# reftable. +test_expect_success 'reflog expire operates on symref not referrent' ' + git branch --create-reflog the_symref && + git branch --create-reflog referrent && + git update-ref referrent HEAD && + git symbolic-ref refs/heads/the_symref refs/heads/referrent && + test_when_finished "rm -f .git/refs/heads/referrent.lock" && + touch .git/refs/heads/referrent.lock && + git reflog expire --expire=all the_symref +' + +test_expect_success 'empty reflog' ' + test_when_finished "rm -rf empty" && + git init empty && + test_commit -C empty A && + >empty/.git/logs/refs/heads/foo && + git -C empty reflog expire --all 2>err && + test_must_be_empty err +' + +test_expect_success SYMLINKS 'ref resolution not confused by broken symlinks' ' + ln -s does-not-exist .git/refs/heads/broken && + test_must_fail git rev-parse --verify broken +' + +test_expect_success 'log diagnoses bogus HEAD hash' ' + git init empty && + test_when_finished "rm -rf empty" && + echo 1234abcd >empty/.git/refs/heads/main && + test_must_fail git -C empty log 2>stderr && + test_grep broken stderr +' + +test_expect_success 'log diagnoses bogus HEAD symref' ' + git init empty && + test-tool -C empty ref-store main create-symref HEAD refs/heads/invalid.lock && + test_must_fail git -C empty log 2>stderr && + test_grep broken stderr && + test_must_fail git -C empty log --default totally-bogus 2>stderr && + test_grep broken stderr +' + +test_expect_success 'empty directory removal' ' + git branch d1/d2/r1 HEAD && + git branch d1/r2 HEAD && + test_path_is_file .git/refs/heads/d1/d2/r1 && + test_path_is_file .git/logs/refs/heads/d1/d2/r1 && + git branch -d d1/d2/r1 && + test_must_fail git show-ref --verify -q refs/heads/d1/d2 && + test_must_fail git show-ref --verify -q logs/refs/heads/d1/d2 && + test_path_is_file .git/refs/heads/d1/r2 && + test_path_is_file .git/logs/refs/heads/d1/r2 +' + +test_expect_success 'symref empty directory removal' ' + git branch e1/e2/r1 HEAD && + git branch e1/r2 HEAD && + git checkout e1/e2/r1 && + test_when_finished "git checkout main" && + test_path_is_file .git/refs/heads/e1/e2/r1 && + test_path_is_file .git/logs/refs/heads/e1/e2/r1 && + git update-ref -d HEAD && + test_must_fail git show-ref --verify -q refs/heads/e1/e2 && + test_must_fail git show-ref --verify -q logs/refs/heads/e1/e2 && + test_path_is_file .git/refs/heads/e1/r2 && + test_path_is_file .git/logs/refs/heads/e1/r2 && + test_path_is_file .git/logs/HEAD +' + +test_expect_success 'directory not created deleting packed ref' ' + git branch d1/d2/r1 HEAD && + git pack-refs --all && + test_path_is_missing .git/refs/heads/d1/d2 && + git update-ref -d refs/heads/d1/d2/r1 && + test_path_is_missing .git/refs/heads/d1/d2 && + test_path_is_missing .git/refs/heads/d1 +' + +test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' ' + git branch --create-reflog u && + mv .git/logs/refs/heads/u real-u && + ln -s real-u .git/logs/refs/heads/u && + test_must_fail git branch -m u v +' + +test_expect_success SYMLINKS 'git branch -m with symlinked .git/refs' ' + test_when_finished "rm -rf subdir" && + git init --bare subdir && + + rm -rfv subdir/refs subdir/objects subdir/packed-refs && + ln -s ../.git/refs subdir/refs && + ln -s ../.git/objects subdir/objects && + ln -s ../.git/packed-refs subdir/packed-refs && + + git -C subdir rev-parse --absolute-git-dir >subdir.dir && + git rev-parse --absolute-git-dir >our.dir && + ! test_cmp subdir.dir our.dir && + + git -C subdir log && + git -C subdir branch rename-src && + git rev-parse rename-src >expect && + git -C subdir branch -m rename-src rename-dest && + git rev-parse rename-dest >actual && + test_cmp expect actual && + git branch -D rename-dest +' + +test_expect_success MINGW,SYMLINKS_WINDOWS 'rebase when .git/logs is a symlink' ' + git checkout main && + mv .git/logs actual_logs && + cmd //c "mklink /D .git\logs ..\actual_logs" && + git rebase -f HEAD^ && + test -L .git/logs && + rm .git/logs && + mv actual_logs .git/logs +' + +test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' ' + umask 077 && + git config core.sharedRepository group && + git reflog expire --all && + actual="$(ls -l .git/logs/refs/heads/main)" && + case "$actual" in + -rw-rw-*) + : happy + ;; + *) + echo Ooops, .git/logs/refs/heads/main is not 066x [$actual] + false + ;; + esac +' + +test_done diff --git a/t/t3210-pack-refs.sh b/t/t0601-reffiles-pack-refs.sh index 7f4e98d..7d4ab0b 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t0601-reffiles-pack-refs.sh @@ -15,6 +15,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh +if ! test_have_prereq REFFILES +then + skip_all='skipping reffiles specific tests' + test_done +fi + test_expect_success 'enable reflogs' ' git config core.logallrefupdates true ' @@ -26,6 +32,19 @@ test_expect_success 'prepare a trivial repository' ' HEAD=$(git rev-parse --verify HEAD) ' +test_expect_success 'pack-refs --prune --all' ' + test_path_is_missing .git/packed-refs && + git pack-refs --no-prune --all && + test_path_is_file .git/packed-refs && + N=$(find .git/refs -type f | wc -l) && + test "$N" != 0 && + + git pack-refs --prune --all && + test_path_is_file .git/packed-refs && + N=$(find .git/refs -type f) && + test -z "$N" +' + SHA1= test_expect_success 'see if git show-ref works as expected' ' @@ -145,6 +164,13 @@ test_expect_success 'test --exclude takes precedence over --include' ' git pack-refs --include "refs/heads/pack*" --exclude "refs/heads/pack*" && test -f .git/refs/heads/dont_pack5' +test_expect_success '--auto packs and prunes refs as usual' ' + git branch auto && + test_path_is_file .git/refs/heads/auto && + git pack-refs --auto --all && + test_path_is_missing .git/refs/heads/auto +' + test_expect_success 'see if up-to-date packed refs are preserved' ' git branch q && git pack-refs --all --prune && @@ -294,4 +320,64 @@ test_expect_success SYMLINKS 'pack symlinked packed-refs' ' test "$(test_readlink .git/packed-refs)" = "my-deviant-packed-refs" ' +# The 'packed-refs' file is stored directly in .git/. This means it is global +# to the repository, and can only contain refs that are shared across all +# worktrees. +test_expect_success 'refs/worktree must not be packed' ' + test_commit initial && + test_commit wt1 && + test_commit wt2 && + git worktree add wt1 wt1 && + git worktree add wt2 wt2 && + git checkout initial && + git update-ref refs/worktree/foo HEAD && + git -C wt1 update-ref refs/worktree/foo HEAD && + git -C wt2 update-ref refs/worktree/foo HEAD && + git pack-refs --all && + test_path_is_missing .git/refs/tags/wt1 && + test_path_is_file .git/refs/worktree/foo && + test_path_is_file .git/worktrees/wt1/refs/worktree/foo && + test_path_is_file .git/worktrees/wt2/refs/worktree/foo +' + +# we do not want to count on running pack-refs to +# actually pack it, as it is perfectly reasonable to +# skip processing a broken ref +test_expect_success 'create packed-refs file with broken ref' ' + test_tick && git commit --allow-empty -m one && + recoverable=$(git rev-parse HEAD) && + test_tick && git commit --allow-empty -m two && + missing=$(git rev-parse HEAD) && + rm -f .git/refs/heads/main && + cat >.git/packed-refs <<-EOF && + $missing refs/heads/main + $recoverable refs/heads/other + EOF + echo $missing >expect && + git rev-parse refs/heads/main >actual && + test_cmp expect actual +' + +test_expect_success 'pack-refs does not silently delete broken packed ref' ' + git pack-refs --all --prune && + git rev-parse refs/heads/main >actual && + test_cmp expect actual +' + +test_expect_success 'pack-refs does not drop broken refs during deletion' ' + git update-ref -d refs/heads/other && + git rev-parse refs/heads/main >actual && + test_cmp expect actual +' + +test_expect_success 'maintenance --auto unconditionally packs loose refs' ' + git update-ref refs/heads/something HEAD && + test_path_is_file .git/refs/heads/something && + git rev-parse refs/heads/something >expect && + git maintenance run --task=pack-refs --auto && + test_path_is_missing .git/refs/heads/something && + git rev-parse refs/heads/something >actual && + test_cmp expect actual +' + test_done diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh new file mode 100755 index 0000000..178791e --- /dev/null +++ b/t/t0610-reftable-basics.sh @@ -0,0 +1,1032 @@ +#!/bin/sh +# +# Copyright (c) 2020 Google LLC +# + +test_description='reftable basics' +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +if ! test_have_prereq REFTABLE +then + skip_all='skipping reftable tests; set GIT_TEST_DEFAULT_REF_FORMAT=reftable' + test_done +fi + +INVALID_OID=$(test_oid 001) + +test_expect_success 'init: creates basic reftable structures' ' + test_when_finished "rm -rf repo" && + git init repo && + test_path_is_dir repo/.git/reftable && + test_path_is_file repo/.git/reftable/tables.list && + echo reftable >expect && + git -C repo rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'init: sha256 object format via environment variable' ' + test_when_finished "rm -rf repo" && + GIT_DEFAULT_HASH=sha256 git init repo && + cat >expect <<-EOF && + sha256 + reftable + EOF + git -C repo rev-parse --show-object-format --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'init: sha256 object format via option' ' + test_when_finished "rm -rf repo" && + git init --object-format=sha256 repo && + cat >expect <<-EOF && + sha256 + reftable + EOF + git -C repo rev-parse --show-object-format --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'init: reinitializing reftable backend succeeds' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo A && + + git -C repo for-each-ref >expect && + git init --ref-format=reftable repo && + git -C repo for-each-ref >actual && + test_cmp expect actual +' + +test_expect_success 'init: reinitializing files with reftable backend fails' ' + test_when_finished "rm -rf repo" && + git init --ref-format=files repo && + test_commit -C repo file && + + cp repo/.git/HEAD expect && + test_must_fail git init --ref-format=reftable repo && + test_cmp expect repo/.git/HEAD +' + +test_expect_success 'init: reinitializing reftable with files backend fails' ' + test_when_finished "rm -rf repo" && + git init --ref-format=reftable repo && + test_commit -C repo file && + + cp repo/.git/HEAD expect && + test_must_fail git init --ref-format=files repo && + test_cmp expect repo/.git/HEAD +' + +test_expect_perms () { + local perms="$1" + local file="$2" + local actual="$(ls -l "$file")" && + + case "$actual" in + $perms*) + : happy + ;; + *) + echo "$(basename $2) is not $perms but $actual" + false + ;; + esac +} + +test_expect_reftable_perms () { + local umask="$1" + local shared="$2" + local expect="$3" + + test_expect_success POSIXPERM "init: honors --shared=$shared with umask $umask" ' + test_when_finished "rm -rf repo" && + ( + umask $umask && + git init --shared=$shared repo + ) && + test_expect_perms "$expect" repo/.git/reftable/tables.list && + for table in repo/.git/reftable/*.ref + do + test_expect_perms "$expect" "$table" || + return 1 + done + ' + + test_expect_success POSIXPERM "pack-refs: honors --shared=$shared with umask $umask" ' + test_when_finished "rm -rf repo" && + ( + umask $umask && + git init --shared=$shared repo && + test_commit -C repo A && + test_line_count = 2 repo/.git/reftable/tables.list && + git -C repo pack-refs + ) && + test_expect_perms "$expect" repo/.git/reftable/tables.list && + for table in repo/.git/reftable/*.ref + do + test_expect_perms "$expect" "$table" || + return 1 + done + ' +} + +test_expect_reftable_perms 002 umask "-rw-rw-r--" +test_expect_reftable_perms 022 umask "-rw-r--r--" +test_expect_reftable_perms 027 umask "-rw-r-----" + +test_expect_reftable_perms 002 group "-rw-rw-r--" +test_expect_reftable_perms 022 group "-rw-rw-r--" +test_expect_reftable_perms 027 group "-rw-rw----" + +test_expect_reftable_perms 002 world "-rw-rw-r--" +test_expect_reftable_perms 022 world "-rw-rw-r--" +test_expect_reftable_perms 027 world "-rw-rw-r--" + +test_expect_success 'clone: can clone reftable repository' ' + test_when_finished "rm -rf repo clone" && + git init repo && + test_commit -C repo message1 file1 && + + git clone repo cloned && + echo reftable >expect && + git -C cloned rev-parse --show-ref-format >actual && + test_cmp expect actual && + test_path_is_file cloned/file1 +' + +test_expect_success 'clone: can clone reffiles into reftable repository' ' + test_when_finished "rm -rf reffiles reftable" && + git init --ref-format=files reffiles && + test_commit -C reffiles A && + git clone --ref-format=reftable ./reffiles reftable && + + git -C reffiles rev-parse HEAD >expect && + git -C reftable rev-parse HEAD >actual && + test_cmp expect actual && + + git -C reftable rev-parse --show-ref-format >actual && + echo reftable >expect && + test_cmp expect actual && + + git -C reffiles rev-parse --show-ref-format >actual && + echo files >expect && + test_cmp expect actual +' + +test_expect_success 'clone: can clone reftable into reffiles repository' ' + test_when_finished "rm -rf reffiles reftable" && + git init --ref-format=reftable reftable && + test_commit -C reftable A && + git clone --ref-format=files ./reftable reffiles && + + git -C reftable rev-parse HEAD >expect && + git -C reffiles rev-parse HEAD >actual && + test_cmp expect actual && + + git -C reftable rev-parse --show-ref-format >actual && + echo reftable >expect && + test_cmp expect actual && + + git -C reffiles rev-parse --show-ref-format >actual && + echo files >expect && + test_cmp expect actual +' + +test_expect_success 'ref transaction: corrupted tables cause failure' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit file1 && + for f in .git/reftable/*.ref + do + : >"$f" || return 1 + done && + test_must_fail git update-ref refs/heads/main HEAD + ) +' + +test_expect_success 'ref transaction: corrupted tables.list cause failure' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit file1 && + echo garbage >.git/reftable/tables.list && + test_must_fail git update-ref refs/heads/main HEAD + ) +' + +test_expect_success 'ref transaction: refuses to write ref causing F/D conflict' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo file && + test_must_fail git -C repo update-ref refs/heads/main/forbidden +' + +test_expect_success 'ref transaction: deleting ref with invalid name fails' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo file && + test_must_fail git -C repo update-ref -d ../../my-private-file +' + +test_expect_success 'ref transaction: can skip object ID verification' ' + test_when_finished "rm -rf repo" && + git init repo && + test_must_fail test-tool -C repo ref-store main update-ref msg refs/heads/branch $INVALID_OID $ZERO_OID 0 && + test-tool -C repo ref-store main update-ref msg refs/heads/branch $INVALID_OID $ZERO_OID REF_SKIP_OID_VERIFICATION +' + +test_expect_success 'ref transaction: updating same ref multiple times fails' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo A && + cat >updates <<-EOF && + update refs/heads/main $A + update refs/heads/main $A + EOF + cat >expect <<-EOF && + fatal: multiple updates for ref ${SQ}refs/heads/main${SQ} not allowed + EOF + test_must_fail git -C repo update-ref --stdin <updates 2>err && + test_cmp expect err +' + +test_expect_success 'ref transaction: can delete symbolic self-reference with git-symbolic-ref(1)' ' + test_when_finished "rm -rf repo" && + git init repo && + git -C repo symbolic-ref refs/heads/self refs/heads/self && + git -C repo symbolic-ref -d refs/heads/self +' + +test_expect_success 'ref transaction: deleting symbolic self-reference without --no-deref fails' ' + test_when_finished "rm -rf repo" && + git init repo && + git -C repo symbolic-ref refs/heads/self refs/heads/self && + cat >expect <<-EOF && + error: multiple updates for ${SQ}refs/heads/self${SQ} (including one via symref ${SQ}refs/heads/self${SQ}) are not allowed + EOF + test_must_fail git -C repo update-ref -d refs/heads/self 2>err && + test_cmp expect err +' + +test_expect_success 'ref transaction: deleting symbolic self-reference with --no-deref succeeds' ' + test_when_finished "rm -rf repo" && + git init repo && + git -C repo symbolic-ref refs/heads/self refs/heads/self && + git -C repo update-ref -d --no-deref refs/heads/self +' + +test_expect_success 'ref transaction: creating symbolic ref fails with F/D conflict' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo A && + cat >expect <<-EOF && + error: unable to write symref for refs/heads: file/directory conflict + EOF + test_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err && + test_cmp expect err +' + +test_expect_success 'ref transaction: ref deletion' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit file && + HEAD_OID=$(git show-ref -s --verify HEAD) && + cat >expect <<-EOF && + $HEAD_OID refs/heads/main + $HEAD_OID refs/tags/file + EOF + git show-ref >actual && + test_cmp expect actual && + + test_must_fail git update-ref -d refs/tags/file $INVALID_OID && + git show-ref >actual && + test_cmp expect actual && + + git update-ref -d refs/tags/file $HEAD_OID && + echo "$HEAD_OID refs/heads/main" >expect && + git show-ref >actual && + test_cmp expect actual + ) +' + +test_expect_success 'ref transaction: writes cause auto-compaction' ' + test_when_finished "rm -rf repo" && + + git init repo && + test_line_count = 1 repo/.git/reftable/tables.list && + + test_commit -C repo --no-tag A && + test_line_count = 1 repo/.git/reftable/tables.list && + + test_commit -C repo --no-tag B && + test_line_count = 1 repo/.git/reftable/tables.list +' + +test_expect_success 'ref transaction: env var disables compaction' ' + test_when_finished "rm -rf repo" && + + git init repo && + test_commit -C repo A && + + start=$(wc -l <repo/.git/reftable/tables.list) && + iterations=5 && + expected=$((start + iterations)) && + + for i in $(test_seq $iterations) + do + GIT_TEST_REFTABLE_AUTOCOMPACTION=false \ + git -C repo update-ref branch-$i HEAD || return 1 + done && + test_line_count = $expected repo/.git/reftable/tables.list && + + git -C repo update-ref foo HEAD && + test_line_count -lt $expected repo/.git/reftable/tables.list +' + +test_expect_success 'ref transaction: alternating table sizes are compacted' ' + test_when_finished "rm -rf repo" && + + git init repo && + test_commit -C repo A && + for i in $(test_seq 5) + do + git -C repo branch -f foo && + git -C repo branch -d foo || return 1 + done && + test_line_count = 2 repo/.git/reftable/tables.list +' + +check_fsync_events () { + local trace="$1" && + shift && + + cat >expect && + sed -n \ + -e '/^{"event":"counter",.*"category":"fsync",/ { + s/.*"category":"fsync",//; + s/}$//; + p; + }' \ + <"$trace" >actual && + test_cmp expect actual +} + +test_expect_success 'ref transaction: writes are synced' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo initial && + + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + GIT_TEST_FSYNC=true \ + git -C repo -c core.fsync=reference \ + -c core.fsyncMethod=fsync update-ref refs/heads/branch HEAD && + check_fsync_events trace2.txt <<-EOF + "name":"hardware-flush","count":4 + EOF +' + +test_expect_success 'ref transaction: empty transaction in empty repo' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo --no-tag A && + git -C repo update-ref -d refs/heads/main && + test-tool -C repo ref-store main delete-refs REF_NO_DEREF msg HEAD && + git -C repo update-ref --stdin <<-EOF + prepare + commit + EOF +' + +test_expect_success 'ref transaction: fails gracefully when auto compaction fails' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit A && + for i in $(test_seq 10) + do + git branch branch-$i && + for table in .git/reftable/*.ref + do + touch "$table.lock" || exit 1 + done || + exit 1 + done && + test_line_count = 10 .git/reftable/tables.list + ) +' + +test_expect_success 'pack-refs: compacts tables' ' + test_when_finished "rm -rf repo" && + git init repo && + + test_commit -C repo A && + ls -1 repo/.git/reftable >table-files && + test_line_count = 3 table-files && + test_line_count = 2 repo/.git/reftable/tables.list && + + git -C repo pack-refs && + ls -1 repo/.git/reftable >table-files && + test_line_count = 2 table-files && + test_line_count = 1 repo/.git/reftable/tables.list +' + +test_expect_success 'pack-refs: compaction raises locking errors' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo A && + touch repo/.git/reftable/tables.list.lock && + cat >expect <<-EOF && + error: unable to compact stack: data is locked + EOF + test_must_fail git -C repo pack-refs 2>err && + test_cmp expect err +' + +for command in pack-refs gc "maintenance run --task=pack-refs" +do +test_expect_success "$command: auto compaction" ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit A && + + # We need a bit of setup to ensure that git-gc(1) actually + # triggers, and that it does not write anything to the refdb. + git config gc.auto 1 && + git config gc.autoDetach 0 && + git config gc.reflogExpire never && + git config gc.reflogExpireUnreachable never && + test_oid blob17_1 | git hash-object -w --stdin && + + # The tables should have been auto-compacted, and thus auto + # compaction should not have to do anything. + ls -1 .git/reftable >tables-expect && + test_line_count = 3 tables-expect && + git $command --auto && + ls -1 .git/reftable >tables-actual && + test_cmp tables-expect tables-actual && + + test_oid blob17_2 | git hash-object -w --stdin && + + # Lock all tables write some refs. Auto-compaction will be + # unable to compact tables and thus fails gracefully, leaving + # the stack in a sub-optimal state. + ls .git/reftable/*.ref | + while read table + do + touch "$table.lock" || exit 1 + done && + git branch B && + git branch C && + rm .git/reftable/*.lock && + test_line_count = 4 .git/reftable/tables.list && + + git $command --auto && + test_line_count = 1 .git/reftable/tables.list + ) +' +done + +test_expect_success 'pack-refs: prunes stale tables' ' + test_when_finished "rm -rf repo" && + git init repo && + touch repo/.git/reftable/stale-table.ref && + git -C repo pack-refs && + test_path_is_missing repo/.git/reftable/stable-ref.ref +' + +test_expect_success 'pack-refs: does not prune non-table files' ' + test_when_finished "rm -rf repo" && + git init repo && + touch repo/.git/reftable/garbage && + git -C repo pack-refs && + test_path_is_file repo/.git/reftable/garbage +' + +test_expect_success 'packed-refs: writes are synced' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo initial && + test_line_count = 2 table-files && + + : >trace2.txt && + GIT_TRACE2_EVENT="$(pwd)/trace2.txt" \ + GIT_TEST_FSYNC=true \ + git -C repo -c core.fsync=reference \ + -c core.fsyncMethod=fsync pack-refs && + check_fsync_events trace2.txt <<-EOF + "name":"hardware-flush","count":2 + EOF +' + +test_expect_success 'ref iterator: bogus names are flagged' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit --no-tag file && + test-tool ref-store main update-ref msg "refs/heads/bogus..name" $(git rev-parse HEAD) $ZERO_OID REF_SKIP_REFNAME_VERIFICATION && + + cat >expect <<-EOF && + $ZERO_OID refs/heads/bogus..name 0xc + $(git rev-parse HEAD) refs/heads/main 0x0 + EOF + test-tool ref-store main for-each-ref "" >actual && + test_cmp expect actual + ) +' + +test_expect_success 'ref iterator: missing object IDs are not flagged' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test-tool ref-store main update-ref msg "refs/heads/broken-hash" $INVALID_OID $ZERO_OID REF_SKIP_OID_VERIFICATION && + + cat >expect <<-EOF && + $INVALID_OID refs/heads/broken-hash 0x0 + EOF + test-tool ref-store main for-each-ref "" >actual && + test_cmp expect actual + ) +' + +test_expect_success 'basic: commit and list refs' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo file && + test_write_lines refs/heads/main refs/tags/file >expect && + git -C repo for-each-ref --format="%(refname)" >actual && + test_cmp actual expect +' + +test_expect_success 'basic: can write large commit message' ' + test_when_finished "rm -rf repo" && + git init repo && + perl -e " + print \"this is a long commit message\" x 50000 + " >commit-msg && + git -C repo commit --allow-empty --file=../commit-msg +' + +test_expect_success 'basic: show-ref fails with empty repository' ' + test_when_finished "rm -rf repo" && + git init repo && + test_must_fail git -C repo show-ref >actual && + test_must_be_empty actual +' + +test_expect_success 'basic: can check out unborn branch' ' + test_when_finished "rm -rf repo" && + git init repo && + git -C repo checkout -b main +' + +test_expect_success 'basic: peeled tags are stored' ' + test_when_finished "rm -rf repo" && + git init repo && + test_commit -C repo file && + git -C repo tag -m "annotated tag" test_tag HEAD && + for ref in refs/heads/main refs/tags/file refs/tags/test_tag refs/tags/test_tag^{} + do + echo "$(git -C repo rev-parse "$ref") $ref" || return 1 + done >expect && + git -C repo show-ref -d >actual && + test_cmp expect actual +' + +test_expect_success 'basic: for-each-ref can print symrefs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit file && + git branch && + git symbolic-ref refs/heads/sym refs/heads/main && + cat >expected <<-EOF && + refs/heads/main + EOF + git for-each-ref --format="%(symref)" refs/heads/sym >actual && + test_cmp expected actual + ) +' + +test_expect_success 'basic: notes' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + write_script fake_editor <<-\EOF && + echo "$MSG" >"$1" + echo "$MSG" >&2 + EOF + + test_commit 1st && + test_commit 2nd && + GIT_EDITOR=./fake_editor MSG=b4 git notes add && + GIT_EDITOR=./fake_editor MSG=b3 git notes edit && + echo b4 >expect && + git notes --ref commits@{1} show >actual && + test_cmp expect actual + ) +' + +test_expect_success 'basic: stash' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit file && + git stash list >expect && + test_line_count = 0 expect && + + echo hoi >>file.t && + git stash push -m stashed && + git stash list >expect && + test_line_count = 1 expect && + + git stash clear && + git stash list >expect && + test_line_count = 0 expect + ) +' + +test_expect_success 'basic: cherry-pick' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit message1 file1 && + test_commit message2 file2 && + git branch source && + git checkout HEAD^ && + test_commit message3 file3 && + git cherry-pick source && + test_path_is_file file2 + ) +' + +test_expect_success 'basic: rebase' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit message1 file1 && + test_commit message2 file2 && + git branch source && + git checkout HEAD^ && + test_commit message3 file3 && + git rebase source && + test_path_is_file file2 + ) +' + +test_expect_success 'reflog: can delete separate reflog entries' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit file && + test_commit file2 && + test_commit file3 && + test_commit file4 && + git reflog >actual && + grep file3 actual && + + git reflog delete HEAD@{1} && + git reflog >actual && + ! grep file3 actual + ) +' + +test_expect_success 'reflog: can switch to previous branch' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit file1 && + git checkout -b branch1 && + test_commit file2 && + git checkout -b branch2 && + git switch - && + git rev-parse --symbolic-full-name HEAD >actual && + echo refs/heads/branch1 >expect && + test_cmp actual expect + ) +' + +test_expect_success 'reflog: copying branch writes reflog entry' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit file1 && + test_commit file2 && + oid=$(git rev-parse --short HEAD) && + git branch src && + cat >expect <<-EOF && + ${oid} dst@{0}: Branch: copied refs/heads/src to refs/heads/dst + ${oid} dst@{1}: branch: Created from main + EOF + git branch -c src dst && + git reflog dst >actual && + test_cmp expect actual + ) +' + +test_expect_success 'reflog: renaming branch writes reflog entry' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git symbolic-ref HEAD refs/heads/before && + test_commit file && + git show-ref >expected.refs && + sed s/before/after/g <expected.refs >expected && + git branch -M after && + git show-ref >actual && + test_cmp expected actual && + echo refs/heads/after >expected && + git symbolic-ref HEAD >actual && + test_cmp expected actual + ) +' + +test_expect_success 'reflog: can store empty logs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_must_fail test-tool ref-store main reflog-exists refs/heads/branch && + test-tool ref-store main create-reflog refs/heads/branch && + test-tool ref-store main reflog-exists refs/heads/branch && + test-tool ref-store main for-each-reflog-ent-reverse refs/heads/branch >actual && + test_must_be_empty actual + ) +' + +test_expect_success 'reflog: expiry empties reflog' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit initial && + git checkout -b branch && + test_commit fileA && + test_commit fileB && + + cat >expect <<-EOF && + commit: fileB + commit: fileA + branch: Created from HEAD + EOF + git reflog show --format="%gs" refs/heads/branch >actual && + test_cmp expect actual && + + git reflog expire branch --expire=all && + git reflog show --format="%gs" refs/heads/branch >actual && + test_must_be_empty actual && + test-tool ref-store main reflog-exists refs/heads/branch + ) +' + +test_expect_success 'reflog: can be deleted' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit initial && + test-tool ref-store main reflog-exists refs/heads/main && + test-tool ref-store main delete-reflog refs/heads/main && + test_must_fail test-tool ref-store main reflog-exists refs/heads/main + ) +' + +test_expect_success 'reflog: garbage collection deletes reflog entries' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + for count in $(test_seq 1 10) + do + test_commit "number $count" file.t $count number-$count || + return 1 + done && + git reflog refs/heads/main >actual && + test_line_count = 10 actual && + grep "commit (initial): number 1" actual && + grep "commit: number 10" actual && + + git gc && + git reflog refs/heads/main >actual && + test_line_count = 0 actual + ) +' + +test_expect_success 'reflog: updates via HEAD update HEAD reflog' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit main-one && + git checkout -b new-branch && + test_commit new-one && + test_commit new-two && + + echo new-one >expect && + git log -1 --format=%s HEAD@{1} >actual && + test_cmp expect actual + ) +' + +test_expect_success 'worktree: adding worktree creates separate stack' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + test_commit -C repo A && + + git -C repo worktree add ../worktree && + test_path_is_file repo/.git/worktrees/worktree/refs/heads && + echo "ref: refs/heads/.invalid" >expect && + test_cmp expect repo/.git/worktrees/worktree/HEAD && + test_path_is_dir repo/.git/worktrees/worktree/reftable && + test_path_is_file repo/.git/worktrees/worktree/reftable/tables.list +' + +test_expect_success 'worktree: pack-refs in main repo packs main refs' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + test_commit -C repo A && + + GIT_TEST_REFTABLE_AUTOCOMPACTION=false \ + git -C repo worktree add ../worktree && + GIT_TEST_REFTABLE_AUTOCOMPACTION=false \ + git -C worktree update-ref refs/worktree/per-worktree HEAD && + + test_line_count = 4 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 3 repo/.git/reftable/tables.list && + git -C repo pack-refs && + test_line_count = 4 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 1 repo/.git/reftable/tables.list +' + +test_expect_success 'worktree: pack-refs in worktree packs worktree refs' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + test_commit -C repo A && + + GIT_TEST_REFTABLE_AUTOCOMPACTION=false \ + git -C repo worktree add ../worktree && + GIT_TEST_REFTABLE_AUTOCOMPACTION=false \ + git -C worktree update-ref refs/worktree/per-worktree HEAD && + + test_line_count = 4 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 3 repo/.git/reftable/tables.list && + git -C worktree pack-refs && + test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 3 repo/.git/reftable/tables.list +' + +test_expect_success 'worktree: creating shared ref updates main stack' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + test_commit -C repo A && + + git -C repo worktree add ../worktree && + git -C repo pack-refs && + git -C worktree pack-refs && + test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 1 repo/.git/reftable/tables.list && + + GIT_TEST_REFTABLE_AUTOCOMPACTION=false \ + git -C worktree update-ref refs/heads/shared HEAD && + test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 2 repo/.git/reftable/tables.list +' + +test_expect_success 'worktree: creating per-worktree ref updates worktree stack' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + test_commit -C repo A && + + git -C repo worktree add ../worktree && + git -C repo pack-refs && + git -C worktree pack-refs && + test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 1 repo/.git/reftable/tables.list && + + git -C worktree update-ref refs/bisect/per-worktree HEAD && + test_line_count = 2 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 1 repo/.git/reftable/tables.list +' + +test_expect_success 'worktree: creating per-worktree ref from main repo' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + test_commit -C repo A && + + git -C repo worktree add ../worktree && + git -C repo pack-refs && + git -C worktree pack-refs && + test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 1 repo/.git/reftable/tables.list && + + git -C repo update-ref worktrees/worktree/refs/bisect/per-worktree HEAD && + test_line_count = 2 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 1 repo/.git/reftable/tables.list +' + +test_expect_success 'worktree: creating per-worktree ref from second worktree' ' + test_when_finished "rm -rf repo wt1 wt2" && + git init repo && + test_commit -C repo A && + + git -C repo worktree add ../wt1 && + git -C repo worktree add ../wt2 && + git -C repo pack-refs && + git -C wt1 pack-refs && + git -C wt2 pack-refs && + test_line_count = 1 repo/.git/worktrees/wt1/reftable/tables.list && + test_line_count = 1 repo/.git/worktrees/wt2/reftable/tables.list && + test_line_count = 1 repo/.git/reftable/tables.list && + + git -C wt1 update-ref worktrees/wt2/refs/bisect/per-worktree HEAD && + test_line_count = 1 repo/.git/worktrees/wt1/reftable/tables.list && + test_line_count = 2 repo/.git/worktrees/wt2/reftable/tables.list && + test_line_count = 1 repo/.git/reftable/tables.list +' + +test_expect_success 'worktree: can create shared and per-worktree ref in one transaction' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + test_commit -C repo A && + + git -C repo worktree add ../worktree && + git -C repo pack-refs && + git -C worktree pack-refs && + test_line_count = 1 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 1 repo/.git/reftable/tables.list && + + cat >stdin <<-EOF && + create worktrees/worktree/refs/bisect/per-worktree HEAD + create refs/branches/shared HEAD + EOF + git -C repo update-ref --stdin <stdin && + test_line_count = 2 repo/.git/worktrees/worktree/reftable/tables.list && + test_line_count = 2 repo/.git/reftable/tables.list +' + +test_expect_success 'worktree: can access common refs' ' + test_when_finished "rm -rf repo worktree" && + git init repo && + test_commit -C repo file1 && + git -C repo branch branch1 && + git -C repo worktree add ../worktree && + + echo refs/heads/worktree >expect && + git -C worktree symbolic-ref HEAD >actual && + test_cmp expect actual && + git -C worktree checkout branch1 +' + +test_expect_success 'worktree: adds worktree with detached HEAD' ' + test_when_finished "rm -rf repo worktree" && + + git init repo && + test_commit -C repo A && + git -C repo rev-parse main >expect && + + git -C repo worktree add --detach ../worktree main && + git -C worktree rev-parse HEAD >actual && + test_cmp expect actual +' + +test_expect_success 'fetch: accessing FETCH_HEAD special ref works' ' + test_when_finished "rm -rf repo sub" && + + git init sub && + test_commit -C sub two && + git -C sub rev-parse HEAD >expect && + + git init repo && + test_commit -C repo one && + git -C repo fetch ../sub && + git -C repo rev-parse FETCH_HEAD >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t0611-reftable-httpd.sh b/t/t0611-reftable-httpd.sh new file mode 100755 index 0000000..5e05b9c --- /dev/null +++ b/t/t0611-reftable-httpd.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +test_description='reftable HTTPD tests' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-httpd.sh + +start_httpd + +REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo" + +test_expect_success 'serving ls-remote' ' + git init --ref-format=reftable -b main "$REPO" && + cd "$REPO" && + test_commit m1 && + >.git/git-daemon-export-ok && + git ls-remote "http://127.0.0.1:$LIB_HTTPD_PORT/smart/repo" | cut -f 2-2 -d " " >actual && + cat >expect <<-EOF && + HEAD + refs/heads/main + refs/tags/m1 + EOF + test_cmp actual expect +' + +test_done diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index d73a0be..e12b221 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -6,7 +6,7 @@ test_description='git cat-file' test_cmdmode_usage () { test_expect_code 129 "$@" 2>err && - grep "^error:.*is incompatible with" err + grep "^error: .* cannot be used together" err } for switches in \ @@ -112,65 +112,65 @@ strlen () { run_tests () { type=$1 - sha1=$2 + oid=$2 size=$3 content=$4 pretty_content=$5 - batch_output="$sha1 $type $size + batch_output="$oid $type $size $content" test_expect_success "$type exists" ' - git cat-file -e $sha1 + git cat-file -e $oid ' test_expect_success "Type of $type is correct" ' echo $type >expect && - git cat-file -t $sha1 >actual && + git cat-file -t $oid >actual && test_cmp expect actual ' test_expect_success "Size of $type is correct" ' echo $size >expect && - git cat-file -s $sha1 >actual && + git cat-file -s $oid >actual && test_cmp expect actual ' test_expect_success "Type of $type is correct using --allow-unknown-type" ' echo $type >expect && - git cat-file -t --allow-unknown-type $sha1 >actual && + git cat-file -t --allow-unknown-type $oid >actual && test_cmp expect actual ' test_expect_success "Size of $type is correct using --allow-unknown-type" ' echo $size >expect && - git cat-file -s --allow-unknown-type $sha1 >actual && + git cat-file -s --allow-unknown-type $oid >actual && test_cmp expect actual ' test -z "$content" || test_expect_success "Content of $type is correct" ' echo_without_newline "$content" >expect && - git cat-file $type $sha1 >actual && + git cat-file $type $oid >actual && test_cmp expect actual ' test_expect_success "Pretty content of $type is correct" ' echo_without_newline "$pretty_content" >expect && - git cat-file -p $sha1 >actual && + git cat-file -p $oid >actual && test_cmp expect actual ' test -z "$content" || test_expect_success "--batch output of $type is correct" ' echo "$batch_output" >expect && - echo $sha1 | git cat-file --batch >actual && + echo $oid | git cat-file --batch >actual && test_cmp expect actual ' test_expect_success "--batch-check output of $type is correct" ' - echo "$sha1 $type $size" >expect && - echo_without_newline $sha1 | git cat-file --batch-check >actual && + echo "$oid $type $size" >expect && + echo_without_newline $oid | git cat-file --batch-check >actual && test_cmp expect actual ' @@ -179,33 +179,33 @@ $content" test -z "$content" || test_expect_success "--batch-command $opt output of $type content is correct" ' echo "$batch_output" >expect && - test_write_lines "contents $sha1" | git cat-file --batch-command $opt >actual && + test_write_lines "contents $oid" | git cat-file --batch-command $opt >actual && test_cmp expect actual ' test_expect_success "--batch-command $opt output of $type info is correct" ' - echo "$sha1 $type $size" >expect && - test_write_lines "info $sha1" | + echo "$oid $type $size" >expect && + test_write_lines "info $oid" | git cat-file --batch-command $opt >actual && test_cmp expect actual ' done test_expect_success "custom --batch-check format" ' - echo "$type $sha1" >expect && - echo $sha1 | git cat-file --batch-check="%(objecttype) %(objectname)" >actual && + echo "$type $oid" >expect && + echo $oid | git cat-file --batch-check="%(objecttype) %(objectname)" >actual && test_cmp expect actual ' test_expect_success "custom --batch-command format" ' - echo "$type $sha1" >expect && - echo "info $sha1" | git cat-file --batch-command="%(objecttype) %(objectname)" >actual && + echo "$type $oid" >expect && + echo "info $oid" | git cat-file --batch-command="%(objecttype) %(objectname)" >actual && test_cmp expect actual ' test_expect_success '--batch-check with %(rest)' ' echo "$type this is some extra content" >expect && - echo "$sha1 this is some extra content" | + echo "$oid this is some extra content" | git cat-file --batch-check="%(objecttype) %(rest)" >actual && test_cmp expect actual ' @@ -216,7 +216,7 @@ $content" echo "$size" && echo "$content" } >expect && - echo $sha1 | git cat-file --batch="%(objectsize)" >actual && + echo $oid | git cat-file --batch="%(objectsize)" >actual && test_cmp expect actual ' @@ -226,114 +226,154 @@ $content" echo "$type" && echo "$content" } >expect && - echo $sha1 | git cat-file --batch="%(objecttype)" >actual && + echo $oid | git cat-file --batch="%(objecttype)" >actual && test_cmp expect actual ' } hello_content="Hello World" hello_size=$(strlen "$hello_content") -hello_sha1=$(echo_without_newline "$hello_content" | git hash-object --stdin) +hello_oid=$(echo_without_newline "$hello_content" | git hash-object --stdin) test_expect_success "setup" ' + git config core.repositoryformatversion 1 && + git config extensions.objectformat $test_hash_algo && + git config extensions.compatobjectformat $test_compat_hash_algo && echo_without_newline "$hello_content" > hello && git update-index --add hello ' -run_tests 'blob' $hello_sha1 $hello_size "$hello_content" "$hello_content" +run_blob_tests () { + oid=$1 -test_expect_success '--batch-command --buffer with flush for blob info' ' - echo "$hello_sha1 blob $hello_size" >expect && - test_write_lines "info $hello_sha1" "flush" | + run_tests 'blob' $oid $hello_size "$hello_content" "$hello_content" + + test_expect_success '--batch-command --buffer with flush for blob info' ' + echo "$oid blob $hello_size" >expect && + test_write_lines "info $oid" "flush" | GIT_TEST_CAT_FILE_NO_FLUSH_ON_EXIT=1 \ git cat-file --batch-command --buffer >actual && test_cmp expect actual -' + ' -test_expect_success '--batch-command --buffer without flush for blob info' ' + test_expect_success '--batch-command --buffer without flush for blob info' ' touch output && - test_write_lines "info $hello_sha1" | + test_write_lines "info $oid" | GIT_TEST_CAT_FILE_NO_FLUSH_ON_EXIT=1 \ git cat-file --batch-command --buffer >>output && test_must_be_empty output -' + ' +} + +hello_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $hello_oid) +run_blob_tests $hello_oid +run_blob_tests $hello_compat_oid test_expect_success '--batch-check without %(rest) considers whole line' ' - echo "$hello_sha1 blob $hello_size" >expect && - git update-index --add --cacheinfo 100644 $hello_sha1 "white space" && + echo "$hello_oid blob $hello_size" >expect && + git update-index --add --cacheinfo 100644 $hello_oid "white space" && test_when_finished "git update-index --remove \"white space\"" && echo ":white space" | git cat-file --batch-check >actual && test_cmp expect actual ' -tree_sha1=$(git write-tree) +tree_oid=$(git write-tree) +tree_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tree_oid) tree_size=$(($(test_oid rawsz) + 13)) -tree_pretty_content="100644 blob $hello_sha1 hello${LF}" +tree_compat_size=$(($(test_oid --hash=compat rawsz) + 13)) +tree_pretty_content="100644 blob $hello_oid hello${LF}" +tree_compat_pretty_content="100644 blob $hello_compat_oid hello${LF}" -run_tests 'tree' $tree_sha1 $tree_size "" "$tree_pretty_content" +run_tests 'tree' $tree_oid $tree_size "" "$tree_pretty_content" +run_tests 'tree' $tree_compat_oid $tree_compat_size "" "$tree_compat_pretty_content" commit_message="Initial commit" -commit_sha1=$(echo_without_newline "$commit_message" | git commit-tree $tree_sha1) +commit_oid=$(echo_without_newline "$commit_message" | git commit-tree $tree_oid) +commit_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $commit_oid) commit_size=$(($(test_oid hexsz) + 137)) -commit_content="tree $tree_sha1 +commit_compat_size=$(($(test_oid --hash=compat hexsz) + 137)) +commit_content="tree $tree_oid +author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE +committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + +$commit_message" + +commit_compat_content="tree $tree_compat_oid author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE $commit_message" -run_tests 'commit' $commit_sha1 $commit_size "$commit_content" "$commit_content" +run_tests 'commit' $commit_oid $commit_size "$commit_content" "$commit_content" +run_tests 'commit' $commit_compat_oid $commit_compat_size "$commit_compat_content" "$commit_compat_content" -tag_header_without_timestamp="object $hello_sha1 -type blob +tag_header_without_oid="type blob tag hellotag tagger $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" +tag_header_without_timestamp="object $hello_oid +$tag_header_without_oid" +tag_compat_header_without_timestamp="object $hello_compat_oid +$tag_header_without_oid" tag_description="This is a tag" tag_content="$tag_header_without_timestamp 0 +0000 $tag_description" +tag_compat_content="$tag_compat_header_without_timestamp 0 +0000 -tag_sha1=$(echo_without_newline "$tag_content" | git hash-object -t tag --stdin -w) +$tag_description" + +tag_oid=$(echo_without_newline "$tag_content" | git hash-object -t tag --stdin -w) tag_size=$(strlen "$tag_content") -run_tests 'tag' $tag_sha1 $tag_size "$tag_content" "$tag_content" +tag_compat_oid=$(git rev-parse --output-object-format=$test_compat_hash_algo $tag_oid) +tag_compat_size=$(strlen "$tag_compat_content") + +run_tests 'tag' $tag_oid $tag_size "$tag_content" "$tag_content" +run_tests 'tag' $tag_compat_oid $tag_compat_size "$tag_compat_content" "$tag_compat_content" test_expect_success "Reach a blob from a tag pointing to it" ' echo_without_newline "$hello_content" >expect && - git cat-file blob $tag_sha1 >actual && + git cat-file blob $tag_oid >actual && test_cmp expect actual ' -for batch in batch batch-check batch-command +for oid in $hello_oid $hello_compat_oid do - for opt in t s e p + for batch in batch batch-check batch-command do + for opt in t s e p + do test_expect_success "Passing -$opt with --$batch fails" ' - test_must_fail git cat-file --$batch -$opt $hello_sha1 + test_must_fail git cat-file --$batch -$opt $oid ' test_expect_success "Passing --$batch with -$opt fails" ' - test_must_fail git cat-file -$opt --$batch $hello_sha1 + test_must_fail git cat-file -$opt --$batch $oid ' - done + done - test_expect_success "Passing <type> with --$batch fails" ' - test_must_fail git cat-file --$batch blob $hello_sha1 - ' + test_expect_success "Passing <type> with --$batch fails" ' + test_must_fail git cat-file --$batch blob $oid + ' - test_expect_success "Passing --$batch with <type> fails" ' - test_must_fail git cat-file blob --$batch $hello_sha1 - ' + test_expect_success "Passing --$batch with <type> fails" ' + test_must_fail git cat-file blob --$batch $oid + ' - test_expect_success "Passing sha1 with --$batch fails" ' - test_must_fail git cat-file --$batch $hello_sha1 - ' + test_expect_success "Passing oid with --$batch fails" ' + test_must_fail git cat-file --$batch $oid + ' + done done -for opt in t s e p +for oid in $hello_oid $hello_compat_oid do - test_expect_success "Passing -$opt with --follow-symlinks fails" ' - test_must_fail git cat-file --follow-symlinks -$opt $hello_sha1 + for opt in t s e p + do + test_expect_success "Passing -$opt with --follow-symlinks fails" ' + test_must_fail git cat-file --follow-symlinks -$opt $oid ' + done done test_expect_success "--batch-check for a non-existent named object" ' @@ -360,12 +400,12 @@ test_expect_success "--batch-check for a non-existent hash" ' test_expect_success "--batch for an existent and a non-existent hash" ' cat >expect <<-EOF && - $tag_sha1 tag $tag_size + $tag_oid tag $tag_size $tag_content 0000000000000000000000000000000000000000 missing EOF - printf "$tag_sha1\n0000000000000000000000000000000000000000" >in && + printf "$tag_oid\n0000000000000000000000000000000000000000" >in && git cat-file --batch <in >actual && test_cmp expect actual ' @@ -386,112 +426,102 @@ test_expect_success 'empty --batch-check notices missing object' ' test_cmp expect actual ' -batch_input="$hello_sha1 -$commit_sha1 -$tag_sha1 +batch_tests () { + boid=$1 + loid=$2 + lsize=$3 + coid=$4 + csize=$5 + ccontent=$6 + toid=$7 + tsize=$8 + tcontent=$9 + + batch_input="$boid +$coid +$toid deadbeef " -printf "%s\0" \ - "$hello_sha1 blob $hello_size" \ + printf "%s\0" \ + "$boid blob $hello_size" \ "$hello_content" \ - "$commit_sha1 commit $commit_size" \ - "$commit_content" \ - "$tag_sha1 tag $tag_size" \ - "$tag_content" \ + "$coid commit $csize" \ + "$ccontent" \ + "$toid tag $tsize" \ + "$tcontent" \ "deadbeef missing" \ " missing" >batch_output -test_expect_success '--batch with multiple sha1s gives correct format' ' + test_expect_success '--batch with multiple oids gives correct format' ' tr "\0" "\n" <batch_output >expect && echo_without_newline "$batch_input" >in && git cat-file --batch <in >actual && test_cmp expect actual -' + ' -test_expect_success '--batch, -z with multiple sha1s gives correct format' ' + test_expect_success '--batch, -z with multiple oids gives correct format' ' echo_without_newline_nul "$batch_input" >in && tr "\0" "\n" <batch_output >expect && git cat-file --batch -z <in >actual && test_cmp expect actual -' + ' -test_expect_success '--batch, -Z with multiple sha1s gives correct format' ' + test_expect_success '--batch, -Z with multiple oids gives correct format' ' echo_without_newline_nul "$batch_input" >in && git cat-file --batch -Z <in >actual && test_cmp batch_output actual -' + ' -batch_check_input="$hello_sha1 -$tree_sha1 -$commit_sha1 -$tag_sha1 +batch_check_input="$boid +$loid +$coid +$toid deadbeef " -printf "%s\0" \ - "$hello_sha1 blob $hello_size" \ - "$tree_sha1 tree $tree_size" \ - "$commit_sha1 commit $commit_size" \ - "$tag_sha1 tag $tag_size" \ + printf "%s\0" \ + "$boid blob $hello_size" \ + "$loid tree $lsize" \ + "$coid commit $csize" \ + "$toid tag $tsize" \ "deadbeef missing" \ " missing" >batch_check_output -test_expect_success "--batch-check with multiple sha1s gives correct format" ' + test_expect_success "--batch-check with multiple oids gives correct format" ' tr "\0" "\n" <batch_check_output >expect && echo_without_newline "$batch_check_input" >in && git cat-file --batch-check <in >actual && test_cmp expect actual -' + ' -test_expect_success "--batch-check, -z with multiple sha1s gives correct format" ' + test_expect_success "--batch-check, -z with multiple oids gives correct format" ' tr "\0" "\n" <batch_check_output >expect && echo_without_newline_nul "$batch_check_input" >in && git cat-file --batch-check -z <in >actual && test_cmp expect actual -' + ' -test_expect_success "--batch-check, -Z with multiple sha1s gives correct format" ' + test_expect_success "--batch-check, -Z with multiple oids gives correct format" ' echo_without_newline_nul "$batch_check_input" >in && git cat-file --batch-check -Z <in >actual && test_cmp batch_check_output actual -' - -test_expect_success FUNNYNAMES 'setup 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 -' - -test_expect_success FUNNYNAMES '--batch-check, -z with newline in input' ' - git cat-file --batch-check -z <in >actual && - echo "$(git rev-parse "HEAD:newline${LF}embedded") blob 0" >expect && - test_cmp expect actual -' - -test_expect_success FUNNYNAMES '--batch-check, -Z with newline in input' ' - git cat-file --batch-check -Z <in >actual && - printf "%s\0" "$(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 +batch_command_multiple_info="info $boid +info $loid +info $coid +info $toid info deadbeef" -test_expect_success '--batch-command with multiple info calls gives correct format' ' + test_expect_success '--batch-command with multiple info calls gives correct format' ' cat >expect <<-EOF && - $hello_sha1 blob $hello_size - $tree_sha1 tree $tree_size - $commit_sha1 commit $commit_size - $tag_sha1 tag $tag_size + $boid blob $hello_size + $loid tree $lsize + $coid commit $csize + $toid tag $tsize deadbeef missing EOF @@ -510,22 +540,22 @@ test_expect_success '--batch-command with multiple info calls gives correct form git cat-file --batch-command --buffer -Z <in >actual && test_cmp expect_nul actual -' + ' -batch_command_multiple_contents="contents $hello_sha1 -contents $commit_sha1 -contents $tag_sha1 +batch_command_multiple_contents="contents $boid +contents $coid +contents $toid contents deadbeef flush" -test_expect_success '--batch-command with multiple command calls gives correct format' ' + test_expect_success '--batch-command with multiple command calls gives correct format' ' printf "%s\0" \ - "$hello_sha1 blob $hello_size" \ + "$boid blob $hello_size" \ "$hello_content" \ - "$commit_sha1 commit $commit_size" \ - "$commit_content" \ - "$tag_sha1 tag $tag_size" \ - "$tag_content" \ + "$coid commit $csize" \ + "$ccontent" \ + "$toid tag $tsize" \ + "$tcontent" \ "deadbeef missing" >expect_nul && tr "\0" "\n" <expect_nul >expect && @@ -543,6 +573,33 @@ test_expect_success '--batch-command with multiple command calls gives correct f git cat-file --batch-command --buffer -Z <in >actual && test_cmp expect_nul actual + ' + +} + +batch_tests $hello_oid $tree_oid $tree_size $commit_oid $commit_size "$commit_content" $tag_oid $tag_size "$tag_content" +batch_tests $hello_compat_oid $tree_compat_oid $tree_compat_size $commit_compat_oid $commit_compat_size "$commit_compat_content" $tag_compat_oid $tag_compat_size "$tag_compat_content" + + +test_expect_success FUNNYNAMES 'setup 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 +' + +test_expect_success FUNNYNAMES '--batch-check, -z with newline in input' ' + git cat-file --batch-check -z <in >actual && + echo "$(git rev-parse "HEAD:newline${LF}embedded") blob 0" >expect && + test_cmp expect actual +' + +test_expect_success FUNNYNAMES '--batch-check, -Z with newline in input' ' + git cat-file --batch-check -Z <in >actual && + printf "%s\0" "$(git rev-parse "HEAD:newline${LF}embedded") blob 0" >expect && + test_cmp expect actual ' test_expect_success 'setup blobs which are likely to delta' ' @@ -569,7 +626,7 @@ test_expect_success 'confirm that neither loose blob is a delta' ' # we will check only that one of the two objects is a delta # against the other, but not the order. We can do so by just # asking for the base of both, and checking whether either -# sha1 appears in the output. +# oid appears in the output. test_expect_success '%(deltabase) reports packed delta bases' ' git repack -ad && git cat-file --batch-check="%(deltabase)" <blobs >actual && @@ -583,12 +640,12 @@ test_expect_success 'setup bogus data' ' bogus_short_type="bogus" && bogus_short_content="bogus" && bogus_short_size=$(strlen "$bogus_short_content") && - bogus_short_sha1=$(echo_without_newline "$bogus_short_content" | git hash-object -t $bogus_short_type --literally -w --stdin) && + bogus_short_oid=$(echo_without_newline "$bogus_short_content" | git hash-object -t $bogus_short_type --literally -w --stdin) && bogus_long_type="abcdefghijklmnopqrstuvwxyz1234679" && bogus_long_content="bogus" && bogus_long_size=$(strlen "$bogus_long_content") && - bogus_long_sha1=$(echo_without_newline "$bogus_long_content" | git hash-object -t $bogus_long_type --literally -w --stdin) + bogus_long_oid=$(echo_without_newline "$bogus_long_content" | git hash-object -t $bogus_long_type --literally -w --stdin) ' for arg1 in '' --allow-unknown-type @@ -608,9 +665,9 @@ do if test "$arg1" = "--allow-unknown-type" then - git cat-file $arg1 $arg2 $bogus_short_sha1 + git cat-file $arg1 $arg2 $bogus_short_oid else - test_must_fail git cat-file $arg1 $arg2 $bogus_short_sha1 >out 2>actual && + test_must_fail git cat-file $arg1 $arg2 $bogus_short_oid >out 2>actual && test_must_be_empty out && test_cmp expect actual fi @@ -620,21 +677,21 @@ do if test "$arg2" = "-p" then cat >expect <<-EOF - error: header for $bogus_long_sha1 too long, exceeds 32 bytes - fatal: Not a valid object name $bogus_long_sha1 + error: header for $bogus_long_oid too long, exceeds 32 bytes + fatal: Not a valid object name $bogus_long_oid EOF else cat >expect <<-EOF - error: header for $bogus_long_sha1 too long, exceeds 32 bytes + error: header for $bogus_long_oid too long, exceeds 32 bytes fatal: git cat-file: could not get object info EOF fi && if test "$arg1" = "--allow-unknown-type" then - git cat-file $arg1 $arg2 $bogus_short_sha1 + git cat-file $arg1 $arg2 $bogus_short_oid else - test_must_fail git cat-file $arg1 $arg2 $bogus_long_sha1 >out 2>actual && + test_must_fail git cat-file $arg1 $arg2 $bogus_long_oid >out 2>actual && test_must_be_empty out && test_cmp expect actual fi @@ -668,28 +725,28 @@ do done test_expect_success '-e is OK with a broken object without --allow-unknown-type' ' - git cat-file -e $bogus_short_sha1 + git cat-file -e $bogus_short_oid ' test_expect_success '-e can not be combined with --allow-unknown-type' ' - test_expect_code 128 git cat-file -e --allow-unknown-type $bogus_short_sha1 + test_expect_code 128 git cat-file -e --allow-unknown-type $bogus_short_oid ' test_expect_success '-p cannot print a broken object even with --allow-unknown-type' ' - test_must_fail git cat-file -p $bogus_short_sha1 && - test_expect_code 128 git cat-file -p --allow-unknown-type $bogus_short_sha1 + test_must_fail git cat-file -p $bogus_short_oid && + test_expect_code 128 git cat-file -p --allow-unknown-type $bogus_short_oid ' test_expect_success '<type> <hash> does not work with objects of broken types' ' cat >err.expect <<-\EOF && fatal: invalid object type "bogus" EOF - test_must_fail git cat-file $bogus_short_type $bogus_short_sha1 2>err.actual && + test_must_fail git cat-file $bogus_short_type $bogus_short_oid 2>err.actual && test_cmp err.expect err.actual ' test_expect_success 'broken types combined with --batch and --batch-check' ' - echo $bogus_short_sha1 >bogus-oid && + echo $bogus_short_oid >bogus-oid && cat >err.expect <<-\EOF && fatal: invalid object type @@ -711,52 +768,52 @@ test_expect_success 'the --allow-unknown-type option does not consider replaceme cat >expect <<-EOF && $bogus_short_type EOF - git cat-file -t --allow-unknown-type $bogus_short_sha1 >actual && + git cat-file -t --allow-unknown-type $bogus_short_oid >actual && test_cmp expect actual && # Create it manually, as "git replace" will die on bogus # types. head=$(git rev-parse --verify HEAD) && - test_when_finished "test-tool ref-store main delete-refs 0 msg refs/replace/$bogus_short_sha1" && - test-tool ref-store main update-ref msg "refs/replace/$bogus_short_sha1" $head $ZERO_OID REF_SKIP_OID_VERIFICATION && + test_when_finished "test-tool ref-store main delete-refs 0 msg refs/replace/$bogus_short_oid" && + test-tool ref-store main update-ref msg "refs/replace/$bogus_short_oid" $head $ZERO_OID REF_SKIP_OID_VERIFICATION && cat >expect <<-EOF && commit EOF - git cat-file -t --allow-unknown-type $bogus_short_sha1 >actual && + git cat-file -t --allow-unknown-type $bogus_short_oid >actual && test_cmp expect actual ' test_expect_success "Type of broken object is correct" ' echo $bogus_short_type >expect && - git cat-file -t --allow-unknown-type $bogus_short_sha1 >actual && + git cat-file -t --allow-unknown-type $bogus_short_oid >actual && test_cmp expect actual ' test_expect_success "Size of broken object is correct" ' echo $bogus_short_size >expect && - git cat-file -s --allow-unknown-type $bogus_short_sha1 >actual && + git cat-file -s --allow-unknown-type $bogus_short_oid >actual && test_cmp expect actual ' test_expect_success 'clean up broken object' ' - rm .git/objects/$(test_oid_to_path $bogus_short_sha1) + rm .git/objects/$(test_oid_to_path $bogus_short_oid) ' test_expect_success "Type of broken object is correct when type is large" ' echo $bogus_long_type >expect && - git cat-file -t --allow-unknown-type $bogus_long_sha1 >actual && + git cat-file -t --allow-unknown-type $bogus_long_oid >actual && test_cmp expect actual ' test_expect_success "Size of large broken object is correct when type is large" ' echo $bogus_long_size >expect && - git cat-file -s --allow-unknown-type $bogus_long_sha1 >actual && + git cat-file -s --allow-unknown-type $bogus_long_oid >actual && test_cmp expect actual ' test_expect_success 'clean up broken object' ' - rm .git/objects/$(test_oid_to_path $bogus_long_sha1) + rm .git/objects/$(test_oid_to_path $bogus_long_oid) ' test_expect_success 'cat-file -t and -s on corrupt loose object' ' @@ -853,7 +910,7 @@ test_expect_success 'prep for symlink tests' ' test_ln_s_add loop2 loop1 && git add morx dir/subdir/ind2 dir/ind1 && git commit -am "test" && - echo $hello_sha1 blob $hello_size >found + echo $hello_oid blob $hello_size >found ' test_expect_success 'git cat-file --batch-check --follow-symlinks works for non-links' ' @@ -941,7 +998,7 @@ test_expect_success 'git cat-file --batch-check --follow-symlinks works for dir/ echo HEAD:dirlink/morx >>expect && echo HEAD:dirlink/morx | git cat-file --batch-check --follow-symlinks >actual && test_cmp expect actual && - echo $hello_sha1 blob $hello_size >expect && + echo $hello_oid blob $hello_size >expect && echo HEAD:dirlink/ind1 | git cat-file --batch-check --follow-symlinks >actual && test_cmp expect actual ' @@ -1100,6 +1157,42 @@ test_expect_success 'cat-file --batch="batman" with --batch-all-objects will wor cmp expect actual ' +test_expect_success 'cat-file %(objectsize:disk) with --batch-all-objects' ' + # our state has both loose and packed objects, + # so find both for our expected output + { + find .git/objects/?? -type f | + awk -F/ "{ print \$0, \$3\$4 }" | + while read path oid + do + size=$(test_file_size "$path") && + echo "$oid $size" || + return 1 + done && + rawsz=$(test_oid rawsz) && + find .git/objects/pack -name "*.idx" | + while read idx + do + git show-index <"$idx" >idx.raw && + sort -nr <idx.raw >idx.sorted && + packsz=$(test_file_size "${idx%.idx}.pack") && + end=$((packsz - rawsz)) && + while read start oid rest + do + size=$((end - start)) && + end=$start && + echo "$oid $size" || + return 1 + done <idx.sorted || + return 1 + done + } >expect.raw && + sort <expect.raw >expect && + git cat-file --batch-all-objects \ + --batch-check="%(objectname) %(objectsize:disk)" >actual && + test_cmp expect actual +' + test_expect_success 'set up replacement object' ' orig=$(git rev-parse HEAD) && git cat-file commit $orig >orig && diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index ac3d173..64aea38 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -124,8 +124,8 @@ test_expect_success 'check that appropriate filter is invoke when --path is used path0_sha=$(git hash-object --path=file0 file1) && test "$file0_sha" = "$path0_sha" && test "$file1_sha" = "$path1_sha" && - path1_sha=$(cat file0 | git hash-object --path=file1 --stdin) && - path0_sha=$(cat file1 | git hash-object --path=file0 --stdin) && + path1_sha=$(git hash-object --path=file1 --stdin <file0) && + path0_sha=$(git hash-object --path=file0 --stdin <file1) && test "$file0_sha" = "$path0_sha" && test "$file1_sha" = "$path1_sha" ' @@ -154,7 +154,7 @@ test_expect_success '--path works in a subdirectory' ' test_expect_success 'check that --no-filters option works' ' nofilters_file1=$(git hash-object --no-filters file1) && test "$file0_sha" = "$nofilters_file1" && - nofilters_file1=$(cat file1 | git hash-object --stdin) && + nofilters_file1=$(git hash-object --stdin <file1) && test "$file0_sha" = "$nofilters_file1" ' diff --git a/t/t1016-compatObjectFormat.sh b/t/t1016-compatObjectFormat.sh new file mode 100755 index 0000000..be3206a --- /dev/null +++ b/t/t1016-compatObjectFormat.sh @@ -0,0 +1,281 @@ +#!/bin/sh +# +# Copyright (c) 2023 Eric Biederman +# + +test_description='Test how well compatObjectFormat works' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-gpg.sh + +# All of the follow variables must be defined in the environment: +# GIT_AUTHOR_NAME +# GIT_AUTHOR_EMAIL +# GIT_AUTHOR_DATE +# GIT_COMMITTER_NAME +# GIT_COMMITTER_EMAIL +# GIT_COMMITTER_DATE +# +# The test relies on these variables being set so that the two +# different commits in two different repositories encoded with two +# different hash functions result in the same content in the commits. +# This means that when the commit is translated between hash functions +# the commit is identical to the commit in the other repository. + +compat_hash () { + case "$1" in + "sha1") + echo "sha256" + ;; + "sha256") + echo "sha1" + ;; + esac +} + +hello_oid () { + case "$1" in + "sha1") + echo "$hello_sha1_oid" + ;; + "sha256") + echo "$hello_sha256_oid" + ;; + esac +} + +tree_oid () { + case "$1" in + "sha1") + echo "$tree_sha1_oid" + ;; + "sha256") + echo "$tree_sha256_oid" + ;; + esac +} + +commit_oid () { + case "$1" in + "sha1") + echo "$commit_sha1_oid" + ;; + "sha256") + echo "$commit_sha256_oid" + ;; + esac +} + +commit2_oid () { + case "$1" in + "sha1") + echo "$commit2_sha1_oid" + ;; + "sha256") + echo "$commit2_sha256_oid" + ;; + esac +} + +del_sigcommit () { + local delete="$1" + + if test "$delete" = "sha256" ; then + local pattern="gpgsig-sha256" + else + local pattern="gpgsig" + fi + test-tool delete-gpgsig "$pattern" +} + + +del_sigtag () { + local storage="$1" + local delete="$2" + + if test "$storage" = "$delete" ; then + local pattern="trailer" + elif test "$storage" = "sha256" ; then + local pattern="gpgsig" + else + local pattern="gpgsig-sha256" + fi + test-tool delete-gpgsig "$pattern" +} + +base=$(pwd) +for hash in sha1 sha256 +do + cd "$base" + mkdir -p repo-$hash + cd repo-$hash + + test_expect_success "setup $hash repository" ' + git init --object-format=$hash && + git config core.repositoryformatversion 1 && + git config extensions.objectformat $hash && + git config extensions.compatobjectformat $(compat_hash $hash) && + git config gpg.program $TEST_DIRECTORY/t1016/gpg && + echo "Hellow World!" > hello && + eval hello_${hash}_oid=$(git hash-object hello) && + git update-index --add hello && + git commit -m "Initial commit" && + eval commit_${hash}_oid=$(git rev-parse HEAD) && + eval tree_${hash}_oid=$(git rev-parse HEAD^{tree}) + ' + test_expect_success "create a $hash tagged blob" ' + git tag --no-sign -m "This is a tag" hellotag $(hello_oid $hash) && + eval hellotag_${hash}_oid=$(git rev-parse hellotag) + ' + test_expect_success "create a $hash tagged tree" ' + git tag --no-sign -m "This is a tag" treetag $(tree_oid $hash) && + eval treetag_${hash}_oid=$(git rev-parse treetag) + ' + test_expect_success "create a $hash tagged commit" ' + git tag --no-sign -m "This is a tag" committag $(commit_oid $hash) && + eval committag_${hash}_oid=$(git rev-parse committag) + ' + test_expect_success GPG2 "create a $hash signed commit" ' + git commit --gpg-sign --allow-empty -m "This is a signed commit" && + eval signedcommit_${hash}_oid=$(git rev-parse HEAD) + ' + test_expect_success GPG2 "create a $hash signed tag" ' + git tag -s -m "This is a signed tag" signedtag HEAD && + eval signedtag_${hash}_oid=$(git rev-parse signedtag) + ' + test_expect_success "create a $hash branch" ' + git checkout -b branch $(commit_oid $hash) && + echo "More more more give me more!" > more && + eval more_${hash}_oid=$(git hash-object more) && + echo "Another and another and another" > another && + eval another_${hash}_oid=$(git hash-object another) && + git update-index --add more another && + git commit -m "Add more files!" && + eval commit2_${hash}_oid=$(git rev-parse HEAD) && + eval tree2_${hash}_oid=$(git rev-parse HEAD^{tree}) + ' + test_expect_success GPG2 "create another $hash signed tag" ' + git tag -s -m "This is another signed tag" signedtag2 $(commit2_oid $hash) && + eval signedtag2_${hash}_oid=$(git rev-parse signedtag2) + ' + test_expect_success GPG2 "merge the $hash branches together" ' + git merge -S -m "merge some signed tags together" signedtag signedtag2 && + eval signedcommit2_${hash}_oid=$(git rev-parse HEAD) + ' + test_expect_success GPG2 "create additional $hash signed commits" ' + git commit --gpg-sign --allow-empty -m "This is an additional signed commit" && + git cat-file commit HEAD | del_sigcommit sha256 > "../${hash}_signedcommit3" && + git cat-file commit HEAD | del_sigcommit sha1 > "../${hash}_signedcommit4" && + eval signedcommit3_${hash}_oid=$(git hash-object -t commit -w ../${hash}_signedcommit3) && + eval signedcommit4_${hash}_oid=$(git hash-object -t commit -w ../${hash}_signedcommit4) + ' + test_expect_success GPG2 "create additional $hash signed tags" ' + git tag -s -m "This is an additional signed tag" signedtag34 HEAD && + git cat-file tag signedtag34 | del_sigtag "${hash}" sha256 > ../${hash}_signedtag3 && + git cat-file tag signedtag34 | del_sigtag "${hash}" sha1 > ../${hash}_signedtag4 && + eval signedtag3_${hash}_oid=$(git hash-object -t tag -w ../${hash}_signedtag3) && + eval signedtag4_${hash}_oid=$(git hash-object -t tag -w ../${hash}_signedtag4) + ' +done +cd "$base" + +compare_oids () { + test "$#" = 5 && { local PREREQ="$1"; shift; } || PREREQ= + local type="$1" + local name="$2" + local sha1_oid="$3" + local sha256_oid="$4" + + echo ${sha1_oid} > ${name}_sha1_expected + echo ${sha256_oid} > ${name}_sha256_expected + echo ${type} > ${name}_type_expected + + git --git-dir=repo-sha1/.git rev-parse --output-object-format=sha256 ${sha1_oid} > ${name}_sha1_sha256_found + git --git-dir=repo-sha256/.git rev-parse --output-object-format=sha1 ${sha256_oid} > ${name}_sha256_sha1_found + local sha1_sha256_oid="$(cat ${name}_sha1_sha256_found)" + local sha256_sha1_oid="$(cat ${name}_sha256_sha1_found)" + + test_expect_success $PREREQ "Verify ${type} ${name}'s sha1 oid" ' + git --git-dir=repo-sha256/.git rev-parse --output-object-format=sha1 ${sha256_oid} > ${name}_sha1 && + test_cmp ${name}_sha1 ${name}_sha1_expected +' + + test_expect_success $PREREQ "Verify ${type} ${name}'s sha256 oid" ' + git --git-dir=repo-sha1/.git rev-parse --output-object-format=sha256 ${sha1_oid} > ${name}_sha256 && + test_cmp ${name}_sha256 ${name}_sha256_expected +' + + test_expect_success $PREREQ "Verify ${name}'s sha1 type" ' + git --git-dir=repo-sha1/.git cat-file -t ${sha1_oid} > ${name}_type1 && + git --git-dir=repo-sha256/.git cat-file -t ${sha256_sha1_oid} > ${name}_type2 && + test_cmp ${name}_type1 ${name}_type2 && + test_cmp ${name}_type1 ${name}_type_expected +' + + test_expect_success $PREREQ "Verify ${name}'s sha256 type" ' + git --git-dir=repo-sha256/.git cat-file -t ${sha256_oid} > ${name}_type3 && + git --git-dir=repo-sha1/.git cat-file -t ${sha1_sha256_oid} > ${name}_type4 && + test_cmp ${name}_type3 ${name}_type4 && + test_cmp ${name}_type3 ${name}_type_expected +' + + test_expect_success $PREREQ "Verify ${name}'s sha1 size" ' + git --git-dir=repo-sha1/.git cat-file -s ${sha1_oid} > ${name}_size1 && + git --git-dir=repo-sha256/.git cat-file -s ${sha256_sha1_oid} > ${name}_size2 && + test_cmp ${name}_size1 ${name}_size2 +' + + test_expect_success $PREREQ "Verify ${name}'s sha256 size" ' + git --git-dir=repo-sha256/.git cat-file -s ${sha256_oid} > ${name}_size3 && + git --git-dir=repo-sha1/.git cat-file -s ${sha1_sha256_oid} > ${name}_size4 && + test_cmp ${name}_size3 ${name}_size4 +' + + test_expect_success $PREREQ "Verify ${name}'s sha1 pretty content" ' + git --git-dir=repo-sha1/.git cat-file -p ${sha1_oid} > ${name}_content1 && + git --git-dir=repo-sha256/.git cat-file -p ${sha256_sha1_oid} > ${name}_content2 && + test_cmp ${name}_content1 ${name}_content2 +' + + test_expect_success $PREREQ "Verify ${name}'s sha256 pretty content" ' + git --git-dir=repo-sha256/.git cat-file -p ${sha256_oid} > ${name}_content3 && + git --git-dir=repo-sha1/.git cat-file -p ${sha1_sha256_oid} > ${name}_content4 && + test_cmp ${name}_content3 ${name}_content4 +' + + test_expect_success $PREREQ "Verify ${name}'s sha1 content" ' + git --git-dir=repo-sha1/.git cat-file ${type} ${sha1_oid} > ${name}_content5 && + git --git-dir=repo-sha256/.git cat-file ${type} ${sha256_sha1_oid} > ${name}_content6 && + test_cmp ${name}_content5 ${name}_content6 +' + + test_expect_success $PREREQ "Verify ${name}'s sha256 content" ' + git --git-dir=repo-sha256/.git cat-file ${type} ${sha256_oid} > ${name}_content7 && + git --git-dir=repo-sha1/.git cat-file ${type} ${sha1_sha256_oid} > ${name}_content8 && + test_cmp ${name}_content7 ${name}_content8 +' + +} + +compare_oids 'blob' hello "$hello_sha1_oid" "$hello_sha256_oid" +compare_oids 'tree' tree "$tree_sha1_oid" "$tree_sha256_oid" +compare_oids 'commit' commit "$commit_sha1_oid" "$commit_sha256_oid" +compare_oids GPG2 'commit' signedcommit "$signedcommit_sha1_oid" "$signedcommit_sha256_oid" +compare_oids 'tag' hellotag "$hellotag_sha1_oid" "$hellotag_sha256_oid" +compare_oids 'tag' treetag "$treetag_sha1_oid" "$treetag_sha256_oid" +compare_oids 'tag' committag "$committag_sha1_oid" "$committag_sha256_oid" +compare_oids GPG2 'tag' signedtag "$signedtag_sha1_oid" "$signedtag_sha256_oid" + +compare_oids 'blob' more "$more_sha1_oid" "$more_sha256_oid" +compare_oids 'blob' another "$another_sha1_oid" "$another_sha256_oid" +compare_oids 'tree' tree2 "$tree2_sha1_oid" "$tree2_sha256_oid" +compare_oids 'commit' commit2 "$commit2_sha1_oid" "$commit2_sha256_oid" +compare_oids GPG2 'tag' signedtag2 "$signedtag2_sha1_oid" "$signedtag2_sha256_oid" +compare_oids GPG2 'commit' signedcommit2 "$signedcommit2_sha1_oid" "$signedcommit2_sha256_oid" +compare_oids GPG2 'commit' signedcommit3 "$signedcommit3_sha1_oid" "$signedcommit3_sha256_oid" +compare_oids GPG2 'commit' signedcommit4 "$signedcommit4_sha1_oid" "$signedcommit4_sha256_oid" +compare_oids GPG2 'tag' signedtag3 "$signedtag3_sha1_oid" "$signedtag3_sha256_oid" +compare_oids GPG2 'tag' signedtag4 "$signedtag4_sha1_oid" "$signedtag4_sha256_oid" + +test_done diff --git a/t/t1016/gpg b/t/t1016/gpg new file mode 100755 index 0000000..2601cb1 --- /dev/null +++ b/t/t1016/gpg @@ -0,0 +1,2 @@ +#!/bin/sh +exec gpg --faked-system-time "20230918T154812" "$@" diff --git a/t/t1091-sparse-checkout-builtin.sh b/t/t1091-sparse-checkout-builtin.sh index f67611d..ab3a105 100755 --- a/t/t1091-sparse-checkout-builtin.sh +++ b/t/t1091-sparse-checkout-builtin.sh @@ -334,7 +334,7 @@ test_expect_success 'cone mode: set with nested folders' ' test_expect_success 'cone mode: add independent path' ' git -C repo sparse-checkout set deep/deeper1 && - git -C repo sparse-checkout add folder1 && + git -C repo sparse-checkout add --end-of-options folder1 && cat >expect <<-\EOF && /* !/*/ @@ -886,6 +886,12 @@ test_expect_success 'by default, cone mode will error out when passed files' ' grep ".gitignore.*is not a directory" error ' +test_expect_success 'error on mistyped command line options' ' + test_must_fail git -C repo sparse-checkout add --sikp-checks .gitignore 2>error && + + grep "unknown option.*sikp-checks" error +' + test_expect_success 'by default, non-cone mode will warn on individual files' ' git -C repo sparse-checkout reapply --no-cone && git -C repo sparse-checkout add .gitignore 2>warning && @@ -962,7 +968,7 @@ test_expect_success 'check-rules non-cone mode' ' git -C bare sparse-checkout check-rules --no-cone --rules-file ../rules\ >check-rules-file <all-files && - cat rules | git -C repo sparse-checkout set --no-cone --stdin && + git -C repo sparse-checkout set --no-cone --stdin <rules && git -C repo ls-files -t >out && sed -n "/^S /!s/^. //p" out >ls-files && diff --git a/t/t1300-config.sh b/t/t1300-config.sh index f4e2752..9b65d9e 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -11,6 +11,98 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh +test_expect_success 'setup whitespace config' ' + sed -e "s/^|//" \ + -e "s/[$]$//" \ + -e "s/X/ /g" >.git/config <<-\EOF + [section] + | solid = rock + | sparse = big XX blue + | sparseAndTail = big XX blue $ + | sparseAndTailQuoted = "big XX blue " + | sparseAndBiggerTail = big XX blue X X + | sparseAndBiggerTailQuoted = "big XX blue X X" + | sparseAndBiggerTailQuotedPlus = "big XX blue X X"X $ + | headAndTail = Xbig blue $ + | headAndTailQuoted = "Xbig blue " + | headAndTailQuotedPlus = "Xbig blue " $ + | annotated = big blueX# to be discarded + | annotatedQuoted = "big blue"X# to be discarded + EOF +' + +test_expect_success 'no internal whitespace' ' + echo "rock" >expect && + git config --get section.solid >actual && + test_cmp expect actual +' + +test_expect_success 'internal whitespace' ' + echo "big QQ blue" | q_to_tab >expect && + git config --get section.sparse >actual && + test_cmp expect actual +' + +test_expect_success 'internal and trailing whitespace' ' + echo "big QQ blue" | q_to_tab >expect && + git config --get section.sparseAndTail >actual && + test_cmp expect actual +' + +test_expect_success 'internal and trailing whitespace, all quoted' ' + echo "big QQ blue " | q_to_tab >expect && + git config --get section.sparseAndTailQuoted >actual && + test_cmp expect actual +' + +test_expect_success 'internal and more trailing whitespace' ' + echo "big QQ blue" | q_to_tab >expect && + git config --get section.sparseAndBiggerTail >actual && + test_cmp expect actual +' + +test_expect_success 'internal and more trailing whitespace, all quoted' ' + echo "big QQ blue Q Q" | q_to_tab >expect && + git config --get section.sparseAndBiggerTailQuoted >actual && + test_cmp expect actual +' + +test_expect_success 'internal and more trailing whitespace, not all quoted' ' + echo "big QQ blue Q Q" | q_to_tab >expect && + git config --get section.sparseAndBiggerTailQuotedPlus >actual && + test_cmp expect actual +' + +test_expect_success 'leading and trailing whitespace' ' + echo "big blue" >expect && + git config --get section.headAndTail >actual && + test_cmp expect actual +' + +test_expect_success 'leading and trailing whitespace, all quoted' ' + echo "Qbig blue " | q_to_tab >expect && + git config --get section.headAndTailQuoted >actual && + test_cmp expect actual +' + +test_expect_success 'leading and trailing whitespace, not all quoted' ' + echo "Qbig blue " | q_to_tab >expect && + git config --get section.headAndTailQuotedPlus >actual && + test_cmp expect actual +' + +test_expect_success 'inline comment' ' + echo "big blue" >expect && + git config --get section.annotated >actual && + test_cmp expect actual +' + +test_expect_success 'inline comment, quoted' ' + echo "big blue" >expect && + git config --get section.annotatedQuoted >actual && + test_cmp expect actual +' + test_expect_success 'clear default config' ' rm -f .git/config ' @@ -69,14 +161,32 @@ test_expect_success 'replace with non-match (actually matching)' ' cat > expect << EOF [section] - penguin = very blue Movie = BadPhysics UPPERCASE = true - penguin = kingpin + penguin = gentoo # Pygoscelis papua + disposition = peckish # find fish + foo = bar #abc + spsp = value # and comment + htsp = value # and comment [Sections] WhatEver = Second EOF +test_expect_success 'append comments' ' + git config --replace-all --comment="Pygoscelis papua" section.penguin gentoo && + git config --comment="find fish" section.disposition peckish && + git config --comment="#abc" section.foo bar && + + git config --comment="and comment" section.spsp value && + git config --comment=" # and comment" section.htsp value && + + test_cmp expect .git/config +' + +test_expect_success 'Prohibited LF in comment' ' + test_must_fail git config --comment="a${LF}b" section.k v +' + test_expect_success 'non-match result' 'test_cmp expect .git/config' test_expect_success 'find mixed-case key by canonical name' ' @@ -1066,9 +1176,25 @@ test_expect_success '--null --get-regexp' ' test_cmp expect result ' -test_expect_success 'inner whitespace kept verbatim' ' - git config section.val "foo bar" && - test_cmp_config "foo bar" section.val +test_expect_success 'inner whitespace kept verbatim, spaces only' ' + echo "foo bar" >expect && + git config section.val "foo bar" && + git config --get section.val >actual && + test_cmp expect actual +' + +test_expect_success 'inner whitespace kept verbatim, horizontal tabs only' ' + echo "fooQQbar" | q_to_tab >expect && + git config section.val "$(cat expect)" && + git config --get section.val >actual && + test_cmp expect actual +' + +test_expect_success 'inner whitespace kept verbatim, horizontal tabs and spaces' ' + echo "foo Q bar" | q_to_tab >expect && + git config section.val "$(cat expect)" && + git config --get section.val >actual && + test_cmp expect actual ' test_expect_success SYMLINKS 'symlinked configuration' ' @@ -1098,15 +1224,20 @@ test_expect_success SYMLINKS 'symlink to nonexistent configuration' ' test_must_fail git config --file=linktolinktonada --list ' -test_expect_success 'check split_cmdline return' " - git config alias.split-cmdline-fix 'echo \"' && - test_must_fail git split-cmdline-fix && - echo foo > foo && - git add foo && - git commit -m 'initial commit' && - git config branch.main.mergeoptions 'echo \"' && - test_must_fail git merge main -" +test_expect_success 'check split_cmdline return' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git config alias.split-cmdline-fix "echo \"" && + test_must_fail git split-cmdline-fix && + echo foo >foo && + git add foo && + git commit -m "initial commit" && + git config branch.main.mergeoptions "echo \"" && + test_must_fail git merge main + ) +' test_expect_success 'git -c "key=value" support' ' cat >expect <<-\EOF && @@ -1157,10 +1288,16 @@ test_expect_success 'git -c works with aliases of builtins' ' ' test_expect_success 'aliases can be CamelCased' ' - test_config alias.CamelCased "rev-parse HEAD" && - git CamelCased >out && - git rev-parse HEAD >expect && - test_cmp expect out + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + git config alias.CamelCased "rev-parse HEAD" && + git CamelCased >out && + git rev-parse HEAD >expect && + test_cmp expect out + ) ' test_expect_success 'git -c does not split values on equals' ' @@ -2009,11 +2146,11 @@ test_expect_success '--show-origin getting a single key' ' ' test_expect_success 'set up custom config file' ' - CUSTOM_CONFIG_FILE="custom.conf" && - cat >"$CUSTOM_CONFIG_FILE" <<-\EOF + cat >"custom.conf" <<-\EOF && [user] custom = true EOF + CUSTOM_CONFIG_FILE="$(test-tool path-utils real_path custom.conf)" ' test_expect_success !MINGW 'set up custom config file with special name characters' ' @@ -2052,22 +2189,33 @@ test_expect_success '--show-origin stdin with file include' ' ' test_expect_success '--show-origin blob' ' - blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") && - cat >expect <<-EOF && - blob:$blob user.custom=true - EOF - git config --blob=$blob --show-origin --list >output && - test_cmp expect output + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + blob=$(git hash-object -w "$CUSTOM_CONFIG_FILE") && + cat >expect <<-EOF && + blob:$blob user.custom=true + EOF + git config --blob=$blob --show-origin --list >output && + test_cmp expect output + ) ' test_expect_success '--show-origin blob ref' ' - cat >expect <<-\EOF && - blob:main:custom.conf user.custom=true - EOF - git add "$CUSTOM_CONFIG_FILE" && - git commit -m "new config file" && - git config --blob=main:"$CUSTOM_CONFIG_FILE" --show-origin --list >output && - test_cmp expect output + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + cat >expect <<-\EOF && + blob:main:custom.conf user.custom=true + EOF + cp "$CUSTOM_CONFIG_FILE" custom.conf && + git add custom.conf && + git commit -m "new config file" && + git config --blob=main:custom.conf --show-origin --list >output && + test_cmp expect output + ) ' test_expect_success '--show-origin with --default' ' diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index e5a0d65..29cf8a9 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -52,7 +52,7 @@ test_expect_success 'shared=all' ' test 2 = $(git config core.sharedrepository) ' -test_expect_failure 'template can set core.bare' ' +test_expect_success 'template cannot set core.bare' ' test_when_finished "rm -rf subdir" && test_when_finished "rm -rf templates" && test_config core.bare true && @@ -60,18 +60,7 @@ test_expect_failure 'template can set core.bare' ' mkdir -p templates/ && cp .git/config templates/config && git init --template=templates subdir && - test_path_exists subdir/HEAD -' - -test_expect_success 'template can set core.bare but overridden by command line' ' - test_when_finished "rm -rf subdir" && - test_when_finished "rm -rf templates" && - test_config core.bare true && - umask 0022 && - mkdir -p templates/ && - cp .git/config templates/config && - git init --no-bare --template=templates subdir && - test_path_exists subdir/.git/HEAD + test_path_is_missing subdir/HEAD ' test_expect_success POSIXPERM 'update-server-info honors core.sharedRepository' ' @@ -137,22 +126,6 @@ test_expect_success POSIXPERM 'info/refs respects umask in unshared repo' ' test_cmp expect actual ' -test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' ' - umask 077 && - git config core.sharedRepository group && - git reflog expire --all && - actual="$(ls -l .git/logs/refs/heads/main)" && - case "$actual" in - -rw-rw-*) - : happy - ;; - *) - echo Ooops, .git/logs/refs/heads/main is not 066x [$actual] - false - ;; - esac -' - test_expect_success POSIXPERM 'forced modes' ' test_when_finished "rm -rf new" && mkdir -p templates/hooks && diff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh index 179474f..42caa0d 100755 --- a/t/t1302-repo-version.sh +++ b/t/t1302-repo-version.sh @@ -9,10 +9,6 @@ TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' - test_oid_cache <<-\EOF && - version sha1:0 - version sha256:1 - EOF cat >test.patch <<-\EOF && diff --git a/test.txt b/test.txt new file mode 100644 @@ -28,7 +24,12 @@ test_expect_success 'setup' ' ' test_expect_success 'gitdir selection on normal repos' ' - test_oid version >expect && + if test_have_prereq DEFAULT_REPO_FORMAT + then + echo 0 + else + echo 1 + fi >expect && git config core.repositoryformatversion >actual && git -C test config core.repositoryformatversion >actual2 && test_cmp expect actual && @@ -79,8 +80,13 @@ mkconfig () { while read outcome version extensions; do test_expect_success "$outcome version=$version $extensions" " - mkconfig $version $extensions >.git/config && - check_${outcome} + test_when_finished 'rm -rf extensions' && + git init extensions && + ( + cd extensions && + mkconfig $version $extensions >.git/config && + check_${outcome} + ) " done <<\EOF allow 0 @@ -94,7 +100,8 @@ allow 1 noop-v1 EOF test_expect_success 'precious-objects allowed' ' - mkconfig 1 preciousObjects >.git/config && + git config core.repositoryFormatVersion 1 && + git config extensions.preciousObjects 1 && check_allow ' diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 9ac4b70..ec3443c 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -9,8 +9,6 @@ test_description='Test git update-ref and basic ref logging' Z=$ZERO_OID m=refs/heads/main -n_dir=refs/heads/gu -n=$n_dir/fixes outside=refs/foo bare=bare-repo @@ -62,10 +60,10 @@ test_expect_success "delete $m without oldvalue verification" ' test_must_fail git show-ref --verify -q $m ' -test_expect_success "fail to create $n" ' - test_when_finished "rm -f .git/$n_dir" && - touch .git/$n_dir && - test_must_fail git update-ref $n $A +test_expect_success "fail to create $n due to file/directory conflict" ' + test_when_finished "git update-ref -d refs/heads/gu" && + git update-ref refs/heads/gu $A && + test_must_fail git update-ref refs/heads/gu/fixes $A ' test_expect_success "create $m (by HEAD)" ' @@ -92,7 +90,8 @@ test_expect_success "deleting current branch adds message to HEAD's log" ' git symbolic-ref HEAD $m && git update-ref -m delete-$m -d $m && test_must_fail git show-ref --verify -q $m && - grep "delete-$m$" .git/logs/HEAD + test-tool ref-store main for-each-reflog-ent HEAD >actual && + grep "delete-$m$" actual ' test_expect_success "deleting by HEAD adds message to HEAD's log" ' @@ -101,7 +100,8 @@ test_expect_success "deleting by HEAD adds message to HEAD's log" ' git symbolic-ref HEAD $m && git update-ref -m delete-by-head -d HEAD && test_must_fail git show-ref --verify -q $m && - grep "delete-by-head$" .git/logs/HEAD + test-tool ref-store main for-each-reflog-ent HEAD >actual && + grep "delete-by-head$" actual ' test_expect_success 'update-ref does not create reflogs by default' ' @@ -132,7 +132,7 @@ test_expect_success 'creates no reflog in bare repository' ' test_expect_success 'core.logAllRefUpdates=true creates reflog in bare repository' ' test_when_finished "git -C $bare config --unset core.logAllRefUpdates && \ - rm $bare/logs/$m" && + test-tool ref-store main delete-reflog $m" && git -C $bare config core.logAllRefUpdates true && git -C $bare update-ref $m $bareB && git -C $bare rev-parse $bareB >expect && @@ -221,27 +221,27 @@ test_expect_success 'delete symref without dereference when the referred ref is ' test_expect_success 'update-ref -d is not confused by self-reference' ' + test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF refs/heads/self" && git symbolic-ref refs/heads/self refs/heads/self && - test_when_finished "rm -f .git/refs/heads/self" && - test_path_is_file .git/refs/heads/self && + git symbolic-ref --no-recurse refs/heads/self && test_must_fail git update-ref -d refs/heads/self && - test_path_is_file .git/refs/heads/self + git symbolic-ref --no-recurse refs/heads/self ' test_expect_success 'update-ref --no-deref -d can delete self-reference' ' + test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF refs/heads/self" && git symbolic-ref refs/heads/self refs/heads/self && - test_when_finished "rm -f .git/refs/heads/self" && - test_path_is_file .git/refs/heads/self && + git symbolic-ref --no-recurse refs/heads/self && git update-ref --no-deref -d refs/heads/self && test_must_fail git show-ref --verify -q refs/heads/self ' -test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' ' +test_expect_success REFFILES 'update-ref --no-deref -d can delete reference to bad ref' ' >.git/refs/heads/bad && test_when_finished "rm -f .git/refs/heads/bad" && git symbolic-ref refs/heads/ref-to-bad refs/heads/bad && test_when_finished "git update-ref -d refs/heads/ref-to-bad" && - test_path_is_file .git/refs/heads/ref-to-bad && + git symbolic-ref --no-recurse refs/heads/ref-to-bad && git update-ref --no-deref -d refs/heads/ref-to-bad && test_must_fail git show-ref --verify -q refs/heads/ref-to-bad ' @@ -265,7 +265,10 @@ test_expect_success "(not) changed .git/$m" ' ! test $B = $(git show-ref -s --verify $m) ' -rm -f .git/logs/refs/heads/main +test_expect_success "clean up reflog" ' + test-tool ref-store main delete-reflog $m +' + test_expect_success "create $m (logged by touch)" ' test_config core.logAllRefUpdates false && GIT_COMMITTER_DATE="2005-05-26 23:30" \ @@ -285,40 +288,13 @@ test_expect_success "set $m (logged by touch)" ' test $A = $(git show-ref -s --verify $m) ' -test_expect_success 'empty directory removal' ' - git branch d1/d2/r1 HEAD && - git branch d1/r2 HEAD && - test_path_is_file .git/refs/heads/d1/d2/r1 && - test_path_is_file .git/logs/refs/heads/d1/d2/r1 && - git branch -d d1/d2/r1 && - test_must_fail git show-ref --verify -q refs/heads/d1/d2 && - test_must_fail git show-ref --verify -q logs/refs/heads/d1/d2 && - test_path_is_file .git/refs/heads/d1/r2 && - test_path_is_file .git/logs/refs/heads/d1/r2 -' - -test_expect_success 'symref empty directory removal' ' - git branch e1/e2/r1 HEAD && - git branch e1/r2 HEAD && - git checkout e1/e2/r1 && - test_when_finished "git checkout main" && - test_path_is_file .git/refs/heads/e1/e2/r1 && - test_path_is_file .git/logs/refs/heads/e1/e2/r1 && - git update-ref -d HEAD && - test_must_fail git show-ref --verify -q refs/heads/e1/e2 && - test_must_fail git show-ref --verify -q logs/refs/heads/e1/e2 && - test_path_is_file .git/refs/heads/e1/r2 && - test_path_is_file .git/logs/refs/heads/e1/r2 && - test_path_is_file .git/logs/HEAD -' - cat >expect <<EOF $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 Initial Creation $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000 Switch $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150860 +0000 EOF test_expect_success "verifying $m's log (logged by touch)" ' - test_when_finished "git update-ref -d $m && rm -rf .git/logs actual expect" && + test_when_finished "git update-ref -d $m && git reflog expire --expire=all --all && rm -rf actual expect" && test-tool ref-store main for-each-reflog-ent $m >actual && test_cmp actual expect ' @@ -348,20 +324,34 @@ $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 +0000 Switch $B $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 +0000 EOF test_expect_success "verifying $m's log (logged by config)" ' - test_when_finished "git update-ref -d $m && rm -rf .git/logs actual expect" && + test_when_finished "git update-ref -d $m && git reflog expire --expire=all --all && rm -rf actual expect" && test-tool ref-store main for-each-reflog-ent $m >actual && test_cmp actual expect ' test_expect_success 'set up for querying the reflog' ' + git update-ref -d $m && + test-tool ref-store main delete-reflog $m && + + GIT_COMMITTER_DATE="1117150320 -0500" git update-ref $m $C && + GIT_COMMITTER_DATE="1117150350 -0500" git update-ref $m $A && + GIT_COMMITTER_DATE="1117150380 -0500" git update-ref $m $B && + GIT_COMMITTER_DATE="1117150680 -0500" git update-ref $m $F && + GIT_COMMITTER_DATE="1117150980 -0500" git update-ref $m $E && git update-ref $m $D && - cat >.git/logs/$m <<-EOF + # Delete the last reflog entry so that the tip of m and the reflog for + # it disagree. + git reflog delete $m@{0} && + + cat >expect <<-EOF && $Z $C $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150320 -0500 $C $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150350 -0500 $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150380 -0500 - $F $Z $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500 - $Z $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500 + $B $F $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150680 -0500 + $F $E $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150980 -0500 EOF + test-tool ref-store main for-each-reflog-ent $m >actual && + test_cmp expect actual ' ed="Thu, 26 May 2005 18:32:00 -0500" @@ -409,13 +399,12 @@ test_expect_success 'Query "main@{2005-05-26 23:33:01}" (middle of history with test_when_finished "rm -f o e" && git rev-parse --verify "main@{2005-05-26 23:33:01}" >o 2>e && echo "$B" >expect && - test_cmp expect o && - test_grep -F "warning: log for ref $m has gap after $gd" e + test_cmp expect o ' test_expect_success 'Query "main@{2005-05-26 23:38:00}" (middle of history)' ' test_when_finished "rm -f o e" && git rev-parse --verify "main@{2005-05-26 23:38:00}" >o 2>e && - echo "$Z" >expect && + echo "$F" >expect && test_cmp expect o && test_must_be_empty e ' @@ -434,7 +423,24 @@ test_expect_success 'Query "main@{2005-05-28}" (past end of history)' ' test_grep -F "warning: log for ref $m unexpectedly ended on $ld" e ' -rm -f .git/$m .git/logs/$m expect +rm -f expect +git update-ref -d $m + +test_expect_success 'query reflog with gap' ' + test_when_finished "git update-ref -d $m" && + + GIT_COMMITTER_DATE="1117150320 -0500" git update-ref $m $A && + GIT_COMMITTER_DATE="1117150380 -0500" git update-ref $m $B && + GIT_COMMITTER_DATE="1117150480 -0500" git update-ref $m $C && + GIT_COMMITTER_DATE="1117150580 -0500" git update-ref $m $D && + GIT_COMMITTER_DATE="1117150680 -0500" git update-ref $m $F && + git reflog delete $m@{2} && + + git rev-parse --verify "main@{2005-05-26 23:33:01}" >actual 2>stderr && + echo "$B" >expect && + test_cmp expect actual && + test_grep -F "warning: log for ref $m has gap after $gd" stderr +' test_expect_success 'creating initial files' ' test_when_finished rm -f M && @@ -491,51 +497,51 @@ test_expect_success 'given old value for missing pseudoref, do not create' ' test_expect_success 'create pseudoref' ' git update-ref PSEUDOREF $A && - test $A = $(git rev-parse PSEUDOREF) + test $A = $(git show-ref -s --verify PSEUDOREF) ' test_expect_success 'overwrite pseudoref with no old value given' ' git update-ref PSEUDOREF $B && - test $B = $(git rev-parse PSEUDOREF) + test $B = $(git show-ref -s --verify PSEUDOREF) ' test_expect_success 'overwrite pseudoref with correct old value' ' git update-ref PSEUDOREF $C $B && - test $C = $(git rev-parse PSEUDOREF) + test $C = $(git show-ref -s --verify PSEUDOREF) ' test_expect_success 'do not overwrite pseudoref with wrong old value' ' test_must_fail git update-ref PSEUDOREF $D $E 2>err && - test $C = $(git rev-parse PSEUDOREF) && + test $C = $(git show-ref -s --verify PSEUDOREF) && test_grep "cannot lock ref.*expected" err ' test_expect_success 'delete pseudoref' ' git update-ref -d PSEUDOREF && - test_must_fail git rev-parse PSEUDOREF + test_must_fail git show-ref -s --verify PSEUDOREF ' test_expect_success 'do not delete pseudoref with wrong old value' ' git update-ref PSEUDOREF $A && test_must_fail git update-ref -d PSEUDOREF $B 2>err && - test $A = $(git rev-parse PSEUDOREF) && + test $A = $(git show-ref -s --verify PSEUDOREF) && test_grep "cannot lock ref.*expected" err ' test_expect_success 'delete pseudoref with correct old value' ' git update-ref -d PSEUDOREF $A && - test_must_fail git rev-parse PSEUDOREF + test_must_fail git show-ref -s --verify PSEUDOREF ' test_expect_success 'create pseudoref with old OID zero' ' git update-ref PSEUDOREF $A $Z && - test $A = $(git rev-parse PSEUDOREF) + test $A = $(git show-ref -s --verify PSEUDOREF) ' test_expect_success 'do not overwrite pseudoref with old OID zero' ' test_when_finished git update-ref -d PSEUDOREF && test_must_fail git update-ref PSEUDOREF $B $Z 2>err && - test $A = $(git rev-parse PSEUDOREF) && + test $A = $(git show-ref -s --verify PSEUDOREF) && test_grep "already exists" err ' @@ -616,7 +622,7 @@ test_expect_success 'stdin fails create with no ref' ' test_expect_success 'stdin fails create with no new value' ' echo "create $a" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: create $a: missing <newvalue>" err + grep "fatal: create $a: missing <new-oid>" err ' test_expect_success 'stdin fails create with too many arguments' ' @@ -634,7 +640,7 @@ test_expect_success 'stdin fails update with no ref' ' test_expect_success 'stdin fails update with no new value' ' echo "update $a" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: update $a: missing <newvalue>" err + grep "fatal: update $a: missing <new-oid>" err ' test_expect_success 'stdin fails update with too many arguments' ' @@ -759,21 +765,21 @@ test_expect_success 'stdin update ref fails with wrong old value' ' test_expect_success 'stdin update ref fails with bad old value' ' echo "update $c $m does-not-exist" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err && + grep "fatal: update $c: invalid <old-oid>: does-not-exist" err && test_must_fail git rev-parse --verify -q $c ' test_expect_success 'stdin create ref fails with bad new value' ' echo "create $c does-not-exist" >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: create $c: invalid <newvalue>: does-not-exist" err && + grep "fatal: create $c: invalid <new-oid>: does-not-exist" err && test_must_fail git rev-parse --verify -q $c ' test_expect_success 'stdin create ref fails with zero new value' ' echo "create $c " >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: create $c: zero <newvalue>" err && + grep "fatal: create $c: zero <new-oid>" err && test_must_fail git rev-parse --verify -q $c ' @@ -797,7 +803,7 @@ test_expect_success 'stdin delete ref fails with wrong old value' ' test_expect_success 'stdin delete ref fails with zero old value' ' echo "delete $a " >stdin && test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: delete $a: zero <oldvalue>" err && + grep "fatal: delete $a: zero <old-oid>" err && git rev-parse $m >expect && git rev-parse $a >actual && test_cmp expect actual @@ -1021,7 +1027,7 @@ test_expect_success 'stdin -z fails create with no ref' ' test_expect_success 'stdin -z fails create with no new value' ' printf $F "create $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: create $a: unexpected end of input when reading <newvalue>" err + grep "fatal: create $a: unexpected end of input when reading <new-oid>" err ' test_expect_success 'stdin -z fails create with too many arguments' ' @@ -1039,27 +1045,27 @@ test_expect_success 'stdin -z fails update with no ref' ' test_expect_success 'stdin -z fails update with too few args' ' printf $F "update $a" "$m" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err + grep "fatal: update $a: unexpected end of input when reading <old-oid>" err ' test_expect_success 'stdin -z emits warning with empty new value' ' git update-ref $a $m && printf $F "update $a" "" "" >stdin && git update-ref -z --stdin <stdin 2>err && - grep "warning: update $a: missing <newvalue>, treating as zero" err && + grep "warning: update $a: missing <new-oid>, treating as zero" err && test_must_fail git rev-parse --verify -q $a ' test_expect_success 'stdin -z fails update with no new value' ' printf $F "update $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: update $a: unexpected end of input when reading <newvalue>" err + grep "fatal: update $a: unexpected end of input when reading <new-oid>" err ' test_expect_success 'stdin -z fails update with no old value' ' printf $F "update $a" "$m" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err + grep "fatal: update $a: unexpected end of input when reading <old-oid>" err ' test_expect_success 'stdin -z fails update with too many arguments' ' @@ -1077,7 +1083,7 @@ test_expect_success 'stdin -z fails delete with no ref' ' test_expect_success 'stdin -z fails delete with no old value' ' printf $F "delete $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: delete $a: unexpected end of input when reading <oldvalue>" err + grep "fatal: delete $a: unexpected end of input when reading <old-oid>" err ' test_expect_success 'stdin -z fails delete with too many arguments' ' @@ -1095,7 +1101,7 @@ test_expect_success 'stdin -z fails verify with too many arguments' ' test_expect_success 'stdin -z fails verify with no old value' ' printf $F "verify $a" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: verify $a: unexpected end of input when reading <oldvalue>" err + grep "fatal: verify $a: unexpected end of input when reading <old-oid>" err ' test_expect_success 'stdin -z fails option with unknown name' ' @@ -1154,7 +1160,7 @@ test_expect_success 'stdin -z update ref fails with wrong old value' ' test_expect_success 'stdin -z update ref fails with bad old value' ' printf $F "update $c" "$m" "does-not-exist" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err && + grep "fatal: update $c: invalid <old-oid>: does-not-exist" err && test_must_fail git rev-parse --verify -q $c ' @@ -1172,14 +1178,14 @@ test_expect_success 'stdin -z create ref fails with bad new value' ' git update-ref -d "$c" && printf $F "create $c" "does-not-exist" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: create $c: invalid <newvalue>: does-not-exist" err && + grep "fatal: create $c: invalid <new-oid>: does-not-exist" err && test_must_fail git rev-parse --verify -q $c ' test_expect_success 'stdin -z create ref fails with empty new value' ' printf $F "create $c" "" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: create $c: missing <newvalue>" err && + grep "fatal: create $c: missing <new-oid>" err && test_must_fail git rev-parse --verify -q $c ' @@ -1203,7 +1209,7 @@ test_expect_success 'stdin -z delete ref fails with wrong old value' ' test_expect_success 'stdin -z delete ref fails with zero old value' ' printf $F "delete $a" "$Z" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: delete $a: zero <oldvalue>" err && + grep "fatal: delete $a: zero <old-oid>" err && git rev-parse $m >expect && git rev-parse $a >actual && test_cmp expect actual @@ -1635,13 +1641,4 @@ test_expect_success PIPE 'transaction flushes status updates' ' test_cmp expected actual ' -test_expect_success 'directory not created deleting packed ref' ' - git branch d1/d2/r1 HEAD && - git pack-refs --all && - test_path_is_missing .git/refs/heads/d1/d2 && - git update-ref -d refs/heads/d1/d2/r1 && - test_path_is_missing .git/refs/heads/d1/d2 && - test_path_is_missing .git/refs/heads/d1 -' - test_done diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index c7745e1..5c60d6f 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -106,9 +106,8 @@ test_expect_success LONG_REF 'we can parse long symbolic ref' ' ' test_expect_success 'symbolic-ref reports failure in exit code' ' - test_when_finished "rm -f .git/HEAD.lock" && - >.git/HEAD.lock && - test_must_fail git symbolic-ref HEAD refs/heads/whatever + # Create d/f conflict to simulate failure. + test_must_fail git symbolic-ref refs/heads refs/heads/foo ' test_expect_success 'symbolic-ref writes reflog entry' ' @@ -171,8 +170,8 @@ test_expect_success 'symbolic-ref refuses invalid target for non-HEAD' ' ' 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 && + git symbolic-ref refs/heads/top-level ORIG_HEAD && + git update-ref ORIG_HEAD HEAD && test_cmp_rev top-level HEAD ' diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh index b50ae6f..33fb7a3 100755 --- a/t/t1403-show-ref.sh +++ b/t/t1403-show-ref.sh @@ -174,6 +174,14 @@ test_expect_success 'show-ref --verify HEAD' ' test_must_be_empty actual ' +test_expect_success 'show-ref --verify pseudorefs' ' + git update-ref CHERRY_PICK_HEAD HEAD $ZERO_OID && + test_when_finished "git update-ref -d CHERRY_PICK_HEAD" && + git show-ref -s --verify HEAD >actual && + git show-ref -s --verify CHERRY_PICK_HEAD >expect && + test_cmp actual expect +' + test_expect_success 'show-ref --verify with dangling ref' ' sha1_file() { echo "$*" | sed "s#..#.git/objects/&/#" @@ -197,18 +205,20 @@ test_expect_success 'show-ref --verify with dangling ref' ' ' test_expect_success 'show-ref sub-modes are mutually exclusive' ' - cat >expect <<-EOF && - fatal: only one of ${SQ}--exclude-existing${SQ}, ${SQ}--verify${SQ} or ${SQ}--exists${SQ} can be given - EOF - test_must_fail git show-ref --verify --exclude-existing 2>err && - test_cmp expect err && + grep "verify" err && + grep "exclude-existing" err && + grep "cannot be used together" err && test_must_fail git show-ref --verify --exists 2>err && - test_cmp expect err && + grep "verify" err && + grep "exists" err && + grep "cannot be used together" err && test_must_fail git show-ref --exclude-existing --exists 2>err && - test_cmp expect err + grep "exclude-existing" err && + grep "exists" err && + grep "cannot be used together" err ' test_expect_success '--exists with existing reference' ' @@ -260,10 +270,20 @@ test_expect_success '--exists with non-commit object' ' test_expect_success '--exists with directory fails with generic error' ' cat >expect <<-EOF && - error: failed to look up reference: Is a directory + error: reference does not exist EOF - test_expect_code 1 git show-ref --exists refs/heads 2>err && + test_expect_code 2 git show-ref --exists refs/heads 2>err && test_cmp expect err ' +test_expect_success '--exists with non-existent special ref' ' + test_expect_code 2 git show-ref --exists FETCH_HEAD +' + +test_expect_success '--exists with existing special ref' ' + test_when_finished "rm .git/FETCH_HEAD" && + git rev-parse HEAD >.git/FETCH_HEAD && + git show-ref --exists FETCH_HEAD +' + test_done diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh index 0369bee..98e9158 100755 --- a/t/t1404-update-ref-errors.sh +++ b/t/t1404-update-ref-errors.sh @@ -92,9 +92,6 @@ df_test() { else delname="$delref" fi && - cat >expected-err <<-EOF && - fatal: cannot lock ref $SQ$addname$SQ: $SQ$delref$SQ exists; cannot create $SQ$addref$SQ - EOF $pack && if $add_del then @@ -103,7 +100,7 @@ df_test() { printf "%s\n" "delete $delname" "create $addname $D" fi >commands && test_must_fail git update-ref --stdin <commands 2>output.err && - test_cmp expected-err output.err && + grep "fatal:\( cannot lock ref $SQ$addname$SQ:\)\? $SQ$delref$SQ exists; cannot create $SQ$addref$SQ" output.err && printf "%s\n" "$C $delref" >expected-refs && git for-each-ref --format="%(objectname) %(refname)" $prefix/r >actual-refs && test_cmp expected-refs actual-refs @@ -191,141 +188,69 @@ test_expect_success 'one new ref is a simple prefix of another' ' ' -test_expect_success REFFILES 'empty directory should not fool rev-parse' ' - prefix=refs/e-rev-parse && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - echo "$C" >expected && - git rev-parse $prefix/foo >actual && - test_cmp expected actual -' - -test_expect_success REFFILES 'empty directory should not fool for-each-ref' ' - prefix=refs/e-for-each-ref && - git update-ref $prefix/foo $C && - git for-each-ref $prefix >expected && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - git for-each-ref $prefix >actual && - test_cmp expected actual -' - -test_expect_success REFFILES 'empty directory should not fool create' ' - prefix=refs/e-create && - mkdir -p .git/$prefix/foo/bar/baz && - printf "create %s $C\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool verify' ' - prefix=refs/e-verify && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "verify %s $C\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool 1-arg update' ' - prefix=refs/e-update-1 && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "update %s $D\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool 2-arg update' ' - prefix=refs/e-update-2 && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "update %s $D $C\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool 0-arg delete' ' - prefix=refs/e-delete-0 && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "delete %s\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'empty directory should not fool 1-arg delete' ' - prefix=refs/e-delete-1 && - git update-ref $prefix/foo $C && - git pack-refs --all && - mkdir -p .git/$prefix/foo/bar/baz && - printf "delete %s $C\n" $prefix/foo | - git update-ref --stdin -' - -test_expect_success REFFILES 'D/F conflict prevents add long + delete short' ' +test_expect_success 'D/F conflict prevents add long + delete short' ' df_test refs/df-al-ds --add-del foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents add short + delete long' ' +test_expect_success 'D/F conflict prevents add short + delete long' ' df_test refs/df-as-dl --add-del foo foo/bar ' -test_expect_success REFFILES 'D/F conflict prevents delete long + add short' ' +test_expect_success 'D/F conflict prevents delete long + add short' ' df_test refs/df-dl-as --del-add foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents delete short + add long' ' +test_expect_success 'D/F conflict prevents delete short + add long' ' df_test refs/df-ds-al --del-add foo foo/bar ' -test_expect_success REFFILES 'D/F conflict prevents add long + delete short packed' ' +test_expect_success 'D/F conflict prevents add long + delete short packed' ' df_test refs/df-al-dsp --pack --add-del foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents add short + delete long packed' ' +test_expect_success 'D/F conflict prevents add short + delete long packed' ' df_test refs/df-as-dlp --pack --add-del foo foo/bar ' -test_expect_success REFFILES 'D/F conflict prevents delete long packed + add short' ' +test_expect_success 'D/F conflict prevents delete long packed + add short' ' df_test refs/df-dlp-as --pack --del-add foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents delete short packed + add long' ' +test_expect_success 'D/F conflict prevents delete short packed + add long' ' df_test refs/df-dsp-al --pack --del-add foo foo/bar ' # Try some combinations involving symbolic refs... -test_expect_success REFFILES 'D/F conflict prevents indirect add long + delete short' ' +test_expect_success 'D/F conflict prevents indirect add long + delete short' ' df_test refs/df-ial-ds --sym-add --add-del foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents indirect add long + indirect delete short' ' +test_expect_success 'D/F conflict prevents indirect add long + indirect delete short' ' df_test refs/df-ial-ids --sym-add --sym-del --add-del foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents indirect add short + indirect delete long' ' +test_expect_success 'D/F conflict prevents indirect add short + indirect delete long' ' df_test refs/df-ias-idl --sym-add --sym-del --add-del foo foo/bar ' -test_expect_success REFFILES 'D/F conflict prevents indirect delete long + indirect add short' ' +test_expect_success 'D/F conflict prevents indirect delete long + indirect add short' ' df_test refs/df-idl-ias --sym-add --sym-del --del-add foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents indirect add long + delete short packed' ' +test_expect_success 'D/F conflict prevents indirect add long + delete short packed' ' df_test refs/df-ial-dsp --sym-add --pack --add-del foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents indirect add long + indirect delete short packed' ' +test_expect_success 'D/F conflict prevents indirect add long + indirect delete short packed' ' df_test refs/df-ial-idsp --sym-add --sym-del --pack --add-del foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents add long + indirect delete short packed' ' +test_expect_success 'D/F conflict prevents add long + indirect delete short packed' ' df_test refs/df-al-idsp --sym-del --pack --add-del foo/bar foo ' -test_expect_success REFFILES 'D/F conflict prevents indirect delete long packed + indirect add short' ' +test_expect_success 'D/F conflict prevents indirect delete long packed + indirect add short' ' df_test refs/df-idlp-ias --sym-add --sym-del --pack --del-add foo/bar foo ' @@ -468,169 +393,4 @@ test_expect_success 'incorrect old value blocks indirect no-deref delete' ' test_cmp expected output.err ' -test_expect_success REFFILES 'non-empty directory blocks create' ' - prefix=refs/ne-create && - mkdir -p .git/$prefix/foo/bar && - : >.git/$prefix/foo/bar/baz.lock && - test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/foo$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ - EOF - printf "%s\n" "update $prefix/foo $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ - EOF - printf "%s\n" "update $prefix/foo $D $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err -' - -test_expect_success REFFILES 'broken reference blocks create' ' - prefix=refs/broken-create && - mkdir -p .git/$prefix && - echo "gobbledigook" >.git/$prefix/foo && - test_when_finished "rm -f .git/$prefix/foo" && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken - EOF - printf "%s\n" "update $prefix/foo $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/foo$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken - EOF - printf "%s\n" "update $prefix/foo $D $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err -' - -test_expect_success REFFILES 'non-empty directory blocks indirect create' ' - prefix=refs/ne-indirect-create && - git symbolic-ref $prefix/symref $prefix/foo && - mkdir -p .git/$prefix/foo/bar && - : >.git/$prefix/foo/bar/baz.lock && - test_when_finished "rm -f .git/$prefix/foo/bar/baz.lock" && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/symref$SQ: there is a non-empty directory $SQ.git/$prefix/foo$SQ blocking reference $SQ$prefix/foo$SQ - EOF - printf "%s\n" "update $prefix/symref $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ - EOF - printf "%s\n" "update $prefix/symref $D $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err -' - -test_expect_success REFFILES 'broken reference blocks indirect create' ' - prefix=refs/broken-indirect-create && - git symbolic-ref $prefix/symref $prefix/foo && - echo "gobbledigook" >.git/$prefix/foo && - test_when_finished "rm -f .git/$prefix/foo" && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken - EOF - printf "%s\n" "update $prefix/symref $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err && - cat >expected <<-EOF && - fatal: cannot lock ref $SQ$prefix/symref$SQ: unable to resolve reference $SQ$prefix/foo$SQ: reference broken - EOF - printf "%s\n" "update $prefix/symref $D $C" | - test_must_fail git update-ref --stdin 2>output.err && - test_cmp expected output.err -' - -test_expect_success REFFILES 'no bogus intermediate values during delete' ' - prefix=refs/slow-transaction && - # Set up a reference with differing loose and packed versions: - git update-ref $prefix/foo $C && - git pack-refs --all && - git update-ref $prefix/foo $D && - # Now try to update the reference, but hold the `packed-refs` lock - # for a while to see what happens while the process is blocked: - : >.git/packed-refs.lock && - test_when_finished "rm -f .git/packed-refs.lock" && - { - # Note: the following command is intentionally run in the - # background. We increase the timeout so that `update-ref` - # attempts to acquire the `packed-refs` lock for much longer - # than it takes for us to do the check then delete it: - git -c core.packedrefstimeout=30000 update-ref -d $prefix/foo & - } && - pid2=$! && - # Give update-ref plenty of time to get to the point where it tries - # to lock packed-refs: - sleep 1 && - # Make sure that update-ref did not complete despite the lock: - kill -0 $pid2 && - # Verify that the reference still has its old value: - sha1=$(git rev-parse --verify --quiet $prefix/foo || echo undefined) && - case "$sha1" in - $D) - # This is what we hope for; it means that nothing - # user-visible has changed yet. - : ;; - undefined) - # This is not correct; it means the deletion has happened - # already even though update-ref should not have been - # able to acquire the lock yet. - echo "$prefix/foo deleted prematurely" && - break - ;; - $C) - # This value should never be seen. Probably the loose - # reference has been deleted but the packed reference - # is still there: - echo "$prefix/foo incorrectly observed to be C" && - break - ;; - *) - # WTF? - echo "unexpected value observed for $prefix/foo: $sha1" && - break - ;; - esac >out && - rm -f .git/packed-refs.lock && - wait $pid2 && - test_must_be_empty out && - test_must_fail git rev-parse --verify --quiet $prefix/foo -' - -test_expect_success REFFILES 'delete fails cleanly if packed-refs file is locked' ' - prefix=refs/locked-packed-refs && - # Set up a reference with differing loose and packed versions: - git update-ref $prefix/foo $C && - git pack-refs --all && - git update-ref $prefix/foo $D && - git for-each-ref $prefix >unchanged && - # Now try to delete it while the `packed-refs` lock is held: - : >.git/packed-refs.lock && - test_when_finished "rm -f .git/packed-refs.lock" && - test_must_fail git update-ref -d $prefix/foo >out 2>err && - git for-each-ref $prefix >actual && - test_grep "Unable to create $SQ.*packed-refs.lock$SQ: " err && - test_cmp unchanged actual -' - -test_expect_success REFFILES 'delete fails cleanly if packed-refs.new write fails' ' - # Setup and expectations are similar to the test above. - prefix=refs/failed-packed-refs && - git update-ref $prefix/foo $C && - git pack-refs --all && - git update-ref $prefix/foo $D && - git for-each-ref $prefix >unchanged && - # This should not happen in practice, but it is an easy way to get a - # reliable error (we open with create_tempfile(), which uses O_EXCL). - : >.git/packed-refs.new && - test_when_finished "rm -f .git/packed-refs.new" && - test_must_fail git update-ref -d $prefix/foo && - git for-each-ref $prefix >actual && - test_cmp unchanged actual -' - test_done diff --git a/t/t1405-main-ref-store.sh b/t/t1405-main-ref-store.sh index e4627cf..a6bcd62 100755 --- a/t/t1405-main-ref-store.sh +++ b/t/t1405-main-ref-store.sh @@ -15,14 +15,6 @@ test_expect_success 'setup' ' test_commit one ' -test_expect_success REFFILES 'pack_refs(PACK_REFS_ALL | PACK_REFS_PRUNE)' ' - N=`find .git/refs -type f | wc -l` && - test "$N" != 0 && - $RUN pack-refs PACK_REFS_PRUNE,PACK_REFS_ALL && - N=`find .git/refs -type f` && - test -z "$N" -' - test_expect_success 'create_symref(FOO, refs/heads/main)' ' $RUN create-symref FOO refs/heads/main nothing && echo refs/heads/main >expected && @@ -41,12 +33,6 @@ test_expect_success 'delete_refs(FOO, refs/tags/new-tag)' ' test_must_fail git rev-parse refs/tags/new-tag -- ' -# In reftable, we keep the reflogs around for deleted refs. -test_expect_success !REFFILES 'delete-reflog(FOO, refs/tags/new-tag)' ' - $RUN delete-reflog FOO && - $RUN delete-reflog refs/tags/new-tag -' - test_expect_success 'rename_refs(main, new-main)' ' git rev-parse main >expected && $RUN rename-ref refs/heads/main refs/heads/new-main && @@ -82,11 +68,11 @@ test_expect_success 'verify_ref(new-main)' ' ' test_expect_success 'for_each_reflog()' ' - $RUN for-each-reflog | sort -k2 | cut -d" " -f 2- >actual && + $RUN for-each-reflog >actual && cat >expected <<-\EOF && - HEAD 0x1 - refs/heads/main 0x0 - refs/heads/new-main 0x0 + HEAD + refs/heads/main + refs/heads/new-main EOF test_cmp expected actual ' @@ -112,7 +98,7 @@ test_expect_success 'delete_reflog(HEAD)' ' test_must_fail git reflog exists HEAD ' -test_expect_success REFFILES 'create-reflog(HEAD)' ' +test_expect_success 'create-reflog(HEAD)' ' $RUN create-reflog HEAD && git reflog exists HEAD ' diff --git a/t/t1406-submodule-ref-store.sh b/t/t1406-submodule-ref-store.sh index e6a7f73..c01f0f1 100755 --- a/t/t1406-submodule-ref-store.sh +++ b/t/t1406-submodule-ref-store.sh @@ -63,11 +63,11 @@ test_expect_success 'verify_ref(new-main)' ' ' test_expect_success 'for_each_reflog()' ' - $RUN for-each-reflog | sort | cut -d" " -f 2- >actual && + $RUN for-each-reflog >actual && cat >expected <<-\EOF && - HEAD 0x1 - refs/heads/main 0x0 - refs/heads/new-main 0x0 + HEAD + refs/heads/main + refs/heads/new-main EOF test_cmp expected actual ' diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh index 05b1881..48b1c92 100755 --- a/t/t1407-worktree-ref-store.sh +++ b/t/t1407-worktree-ref-store.sh @@ -53,41 +53,4 @@ test_expect_success 'create_symref(FOO, refs/heads/main)' ' test_cmp expected actual ' -# Some refs (refs/bisect/*, pseudorefs) are kept per worktree, so they should -# only appear in the for-each-reflog output if it is called from the correct -# worktree, which is exercised in this test. This test is poorly written (and -# therefore marked REFFILES) for mulitple reasons: 1) it creates invalidly -# formatted log entres. 2) it uses direct FS access for creating the reflogs. 3) -# PSEUDO-WT and refs/bisect/random do not create reflogs by default, so it is -# not testing a realistic scenario. -test_expect_success REFFILES 'for_each_reflog()' ' - echo $ZERO_OID > .git/logs/PSEUDO-MAIN && - mkdir -p .git/logs/refs/bisect && - echo $ZERO_OID > .git/logs/refs/bisect/random && - - echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT && - mkdir -p .git/worktrees/wt/logs/refs/bisect && - echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random && - - $RWT for-each-reflog | cut -d" " -f 2- | sort >actual && - cat >expected <<-\EOF && - HEAD 0x1 - PSEUDO-WT 0x0 - refs/bisect/wt-random 0x0 - refs/heads/main 0x0 - refs/heads/wt-main 0x0 - EOF - test_cmp expected actual && - - $RMAIN for-each-reflog | cut -d" " -f 2- | sort >actual && - cat >expected <<-\EOF && - HEAD 0x1 - PSEUDO-MAIN 0x0 - refs/bisect/random 0x0 - refs/heads/main 0x0 - refs/heads/wt-main 0x0 - EOF - test_cmp expected actual -' - test_done diff --git a/t/t1409-avoid-packing-refs.sh b/t/t1409-avoid-packing-refs.sh index f23c015..7748973 100755 --- a/t/t1409-avoid-packing-refs.sh +++ b/t/t1409-avoid-packing-refs.sh @@ -5,6 +5,12 @@ test_description='avoid rewriting packed-refs unnecessarily' TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh +if test_have_prereq !REFFILES +then + skip_all='skipping files-backend specific pack-refs tests' + test_done +fi + # Add an identifying mark to the packed-refs file header line. This # shouldn't upset readers, and it should be omitted if the file is # ever rewritten. diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index aeddc2f..5bf883f 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -354,36 +354,6 @@ test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' ' test_must_be_empty actual ' -# Triggering the bug detected by this test requires a newline to fall -# exactly BUFSIZ-1 bytes from the end of the file. We don't know -# what that value is, since it's platform dependent. However, if -# we choose some value N, we also catch any D which divides N evenly -# (since we will read backwards in chunks of D). So we choose 8K, -# which catches glibc (with an 8K BUFSIZ) and *BSD (1K). -# -# Each line is 114 characters, so we need 75 to still have a few before the -# last 8K. The 89-character padding on the final entry lines up our -# newline exactly. -test_expect_success REFFILES,SHA1 'parsing reverse reflogs at BUFSIZ boundaries' ' - git checkout -b reflogskip && - zf=$(test_oid zero_2) && - ident="abc <xyz> 0000000001 +0000" && - for i in $(test_seq 1 75); do - printf "$zf%02d $zf%02d %s\t" $i $(($i+1)) "$ident" && - if test $i = 75; then - for j in $(test_seq 1 89); do - printf X || return 1 - done - else - printf X - fi && - printf "\n" || return 1 - done >.git/logs/refs/heads/reflogskip && - git rev-parse reflogskip@{73} >actual && - echo ${zf}03 >expect && - test_cmp expect actual -' - test_expect_success 'no segfaults for reflog containing non-commit sha1s' ' git update-ref --create-reflog -m "Creating ref" \ refs/tests/tree-in-reflog HEAD && @@ -397,18 +367,6 @@ test_expect_failure 'reflog with non-commit entries displays all entries' ' test_line_count = 3 actual ' -# This test takes a lock on an individual ref; this is not supported in -# reftable. -test_expect_success REFFILES 'reflog expire operates on symref not referrent' ' - git branch --create-reflog the_symref && - git branch --create-reflog referrent && - git update-ref referrent HEAD && - git symbolic-ref refs/heads/the_symref refs/heads/referrent && - test_when_finished "rm -f .git/refs/heads/referrent.lock" && - touch .git/refs/heads/referrent.lock && - git reflog expire --expire=all the_symref -' - test_expect_success 'continue walking past root commits' ' git init orphanage && ( @@ -469,13 +427,121 @@ test_expect_success 'expire one of multiple worktrees' ' ) ' -test_expect_success REFFILES 'empty reflog' ' +test_expect_success 'empty reflog' ' test_when_finished "rm -rf empty" && git init empty && test_commit -C empty A && - >empty/.git/logs/refs/heads/foo && + test-tool ref-store main create-reflog refs/heads/foo && git -C empty reflog expire --all 2>err && test_must_be_empty err ' +test_expect_success 'list reflogs' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git reflog list >actual && + test_must_be_empty actual && + + test_commit A && + cat >expect <<-EOF && + HEAD + refs/heads/main + EOF + git reflog list >actual && + test_cmp expect actual && + + git branch b && + cat >expect <<-EOF && + HEAD + refs/heads/b + refs/heads/main + EOF + git reflog list >actual && + test_cmp expect actual + ) +' + +test_expect_success 'list reflogs with worktree' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + + test_commit A && + git worktree add wt && + git -c core.logAllRefUpdates=always \ + update-ref refs/worktree/main HEAD && + git -c core.logAllRefUpdates=always \ + update-ref refs/worktree/per-worktree HEAD && + git -c core.logAllRefUpdates=always -C wt \ + update-ref refs/worktree/per-worktree HEAD && + git -c core.logAllRefUpdates=always -C wt \ + update-ref refs/worktree/worktree HEAD && + + cat >expect <<-EOF && + HEAD + refs/heads/main + refs/heads/wt + refs/worktree/main + refs/worktree/per-worktree + EOF + git reflog list >actual && + test_cmp expect actual && + + cat >expect <<-EOF && + HEAD + refs/heads/main + refs/heads/wt + refs/worktree/per-worktree + refs/worktree/worktree + EOF + git -C wt reflog list >actual && + test_cmp expect actual + ) +' + +test_expect_success 'reflog list returns error with additional args' ' + cat >expect <<-EOF && + error: list does not accept arguments: ${SQ}bogus${SQ} + EOF + test_must_fail git reflog list bogus 2>err && + test_cmp expect err +' + +test_expect_success 'reflog for symref with unborn target can be listed' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + git symbolic-ref HEAD refs/heads/unborn && + cat >expect <<-EOF && + HEAD + refs/heads/main + EOF + git reflog list >actual && + test_cmp expect actual + ) +' + +test_expect_success 'reflog with invalid object ID can be listed' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A && + test-tool ref-store main update-ref msg refs/heads/missing \ + $(test_oid deadbeef) "$ZERO_OID" REF_SKIP_OID_VERIFICATION && + cat >expect <<-EOF && + HEAD + refs/heads/main + refs/heads/missing + EOF + git reflog list >actual && + test_cmp expect actual + ) +' + test_done diff --git a/t/t1414-reflog-walk.sh b/t/t1414-reflog-walk.sh index ea64cec..be6c3f4 100755 --- a/t/t1414-reflog-walk.sh +++ b/t/t1414-reflog-walk.sh @@ -121,13 +121,12 @@ test_expect_success 'min/max age uses entry date to limit' ' # Create a situation where the reflog and ref database disagree about the latest # state of HEAD. -test_expect_success REFFILES 'walk prefers reflog to ref tip' ' +test_expect_success 'walk prefers reflog to ref tip' ' + test_commit A && + test_commit B && + git reflog delete HEAD@{0} && head=$(git rev-parse HEAD) && - one=$(git rev-parse one) && - ident="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE" && - echo "$head $one $ident broken reflog entry" >>.git/logs/HEAD && - - echo $one >expect && + git rev-parse A >expect && git log -g --format=%H -1 >actual && test_cmp expect actual ' diff --git a/t/t1415-worktree-refs.sh b/t/t1415-worktree-refs.sh index 3b53184..eb4eec8 100755 --- a/t/t1415-worktree-refs.sh +++ b/t/t1415-worktree-refs.sh @@ -17,17 +17,6 @@ test_expect_success 'setup' ' git -C wt2 update-ref refs/worktree/foo HEAD ' -# The 'packed-refs' file is stored directly in .git/. This means it is global -# to the repository, and can only contain refs that are shared across all -# worktrees. -test_expect_success REFFILES 'refs/worktree must not be packed' ' - git pack-refs --all && - test_path_is_missing .git/refs/tags/wt1 && - test_path_is_file .git/refs/worktree/foo && - test_path_is_file .git/worktrees/wt1/refs/worktree/foo && - test_path_is_file .git/worktrees/wt2/refs/worktree/foo -' - test_expect_success 'refs/worktree are per-worktree' ' test_cmp_rev worktree/foo initial && ( cd wt1 && test_cmp_rev worktree/foo wt1 ) && diff --git a/t/t1417-reflog-updateref.sh b/t/t1417-reflog-updateref.sh index 14f13b5..0eb5e67 100755 --- a/t/t1417-reflog-updateref.sh +++ b/t/t1417-reflog-updateref.sh @@ -14,9 +14,13 @@ test_expect_success 'setup' ' test_commit B && test_commit C && - cp .git/logs/HEAD HEAD.old && + git reflog HEAD >expect && git reset --hard HEAD~ && - cp HEAD.old .git/logs/HEAD + # Make sure that the reflog does not point to the same commit + # as HEAD. + git reflog delete HEAD@{0} && + git reflog HEAD >actual && + test_cmp expect actual ) ' @@ -25,7 +29,7 @@ test_reflog_updateref () { shift args="$@" - test_expect_success REFFILES "get '$exp' with '$args'" ' + test_expect_success "get '$exp' with '$args'" ' test_when_finished "rm -rf copy" && cp -R repo copy && diff --git a/t/t1419-exclude-refs.sh b/t/t1419-exclude-refs.sh index 5d8c86b..1359574 100755 --- a/t/t1419-exclude-refs.sh +++ b/t/t1419-exclude-refs.sh @@ -8,6 +8,12 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh +if test_have_prereq !REFFILES +then + skip_all='skipping `git for-each-ref --exclude` tests; need files backend' + test_done +fi + for_each_ref__exclude () { GIT_TRACE2_PERF=1 test-tool ref-store main \ for-each-ref--exclude "$@" >actual.raw diff --git a/t/t1430-bad-ref-name.sh b/t/t1430-bad-ref-name.sh index 68cc9e7..0c00118 100755 --- a/t/t1430-bad-ref-name.sh +++ b/t/t1430-bad-ref-name.sh @@ -164,9 +164,9 @@ test_expect_success 'rev-parse skips symref pointing to broken name' ' test_expect_success 'for-each-ref emits warnings for broken names' ' test-tool ref-store main update-ref msg "refs/heads/broken...ref" $main_sha1 $ZERO_OID REF_SKIP_REFNAME_VERIFICATION && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...ref" && - printf "ref: refs/heads/broken...ref\n" >.git/refs/heads/badname && + test-tool ref-store main create-symref refs/heads/badname refs/heads/broken...ref && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/badname" && - printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref && + test-tool ref-store main create-symref refs/heads/broken...symref refs/heads/main && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" && git for-each-ref >output 2>error && ! grep -e "broken\.\.\.ref" output && @@ -257,7 +257,7 @@ test_expect_success 'update-ref -d can delete broken name through symref' ' ' test_expect_success 'update-ref --no-deref -d can delete symref with broken name' ' - printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref && + test-tool ref-store main create-symref refs/heads/broken...symref refs/heads/main && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" && test_ref_exists refs/heads/broken...symref && git update-ref --no-deref -d refs/heads/broken...symref >output 2>error && @@ -267,7 +267,7 @@ test_expect_success 'update-ref --no-deref -d can delete symref with broken name ' test_expect_success 'branch -d can delete symref with broken name' ' - printf "ref: refs/heads/main\n" >.git/refs/heads/broken...symref && + test-tool ref-store main create-symref refs/heads/broken...symref refs/heads/main && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" && test_ref_exists refs/heads/broken...symref && git branch -d broken...symref >output 2>error && @@ -277,7 +277,7 @@ test_expect_success 'branch -d can delete symref with broken name' ' ' test_expect_success 'update-ref --no-deref -d can delete dangling symref with broken name' ' - printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref && + test-tool ref-store main create-symref refs/heads/broken...symref refs/heads/idonotexist && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" && test_ref_exists refs/heads/broken...symref && git update-ref --no-deref -d refs/heads/broken...symref >output 2>error && @@ -287,7 +287,7 @@ test_expect_success 'update-ref --no-deref -d can delete dangling symref with br ' test_expect_success 'branch -d can delete dangling symref with broken name' ' - printf "ref: refs/heads/idonotexist\n" >.git/refs/heads/broken...symref && + test-tool ref-store main create-symref refs/heads/broken...symref refs/heads/idonotexist && test_when_finished "test-tool ref-store main delete-refs REF_NO_DEREF msg refs/heads/broken...symref" && test_ref_exists refs/heads/broken...symref && git branch -d broken...symref >output 2>error && diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index 0e3e87d..173b4fa 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -15,6 +15,7 @@ test_expect_success setup ' git config --unset i18n.commitencoding && git checkout HEAD^0 && test_commit B fileB two && + orig_head=$(git rev-parse HEAD) && git tag -d A B && git reflog expire --expire=now --all ' @@ -115,15 +116,15 @@ test_expect_success 'zlib corrupt loose object output ' ' ' test_expect_success 'branch pointing to non-commit' ' - git rev-parse HEAD^{tree} >.git/refs/heads/invalid && + tree_oid=$(git rev-parse --verify HEAD^{tree}) && test_when_finished "git update-ref -d refs/heads/invalid" && + test-tool ref-store main update-ref msg refs/heads/invalid $tree_oid $ZERO_OID REF_SKIP_OID_VERIFICATION && test_must_fail git fsck 2>out && test_grep "not a commit" out ' -test_expect_success 'HEAD link pointing at a funny object' ' - test_when_finished "mv .git/SAVED_HEAD .git/HEAD" && - mv .git/HEAD .git/SAVED_HEAD && +test_expect_success REFFILES 'HEAD link pointing at a funny object' ' + test_when_finished "git update-ref HEAD $orig_head" && echo $ZERO_OID >.git/HEAD && # avoid corrupt/broken HEAD from interfering with repo discovery test_must_fail env GIT_DIR=.git git fsck 2>out && @@ -131,27 +132,25 @@ test_expect_success 'HEAD link pointing at a funny object' ' ' test_expect_success 'HEAD link pointing at a funny place' ' - test_when_finished "mv .git/SAVED_HEAD .git/HEAD" && - mv .git/HEAD .git/SAVED_HEAD && - echo "ref: refs/funny/place" >.git/HEAD && + test_when_finished "git update-ref --no-deref HEAD $orig_head" && + test-tool ref-store main create-symref HEAD refs/funny/place && # avoid corrupt/broken HEAD from interfering with repo discovery test_must_fail env GIT_DIR=.git git fsck 2>out && test_grep "HEAD points to something strange" out ' -test_expect_success 'HEAD link pointing at a funny object (from different wt)' ' - test_when_finished "mv .git/SAVED_HEAD .git/HEAD" && - test_when_finished "rm -rf .git/worktrees wt" && +test_expect_success REFFILES 'HEAD link pointing at a funny object (from different wt)' ' + test_when_finished "git update-ref HEAD $orig_head" && + test_when_finished "git worktree remove -f wt" && git worktree add wt && - mv .git/HEAD .git/SAVED_HEAD && echo $ZERO_OID >.git/HEAD && # avoid corrupt/broken HEAD from interfering with repo discovery test_must_fail git -C wt fsck 2>out && test_grep "main-worktree/HEAD: detached HEAD points" out ' -test_expect_success 'other worktree HEAD link pointing at a funny object' ' - test_when_finished "rm -rf .git/worktrees other" && +test_expect_success REFFILES 'other worktree HEAD link pointing at a funny object' ' + test_when_finished "git worktree remove -f other" && git worktree add other && echo $ZERO_OID >.git/worktrees/other/HEAD && test_must_fail git fsck 2>out && @@ -159,17 +158,18 @@ test_expect_success 'other worktree HEAD link pointing at a funny object' ' ' test_expect_success 'other worktree HEAD link pointing at missing object' ' - test_when_finished "rm -rf .git/worktrees other" && + test_when_finished "git worktree remove -f other" && git worktree add other && - echo "Contents missing from repo" | git hash-object --stdin >.git/worktrees/other/HEAD && + object_id=$(echo "Contents missing from repo" | git hash-object --stdin) && + test-tool -C other ref-store main update-ref msg HEAD $object_id "" REF_NO_DEREF,REF_SKIP_OID_VERIFICATION && test_must_fail git fsck 2>out && test_grep "worktrees/other/HEAD: invalid sha1 pointer" out ' test_expect_success 'other worktree HEAD link pointing at a funny place' ' - test_when_finished "rm -rf .git/worktrees other" && + test_when_finished "git worktree remove -f other" && git worktree add other && - echo "ref: refs/funny/place" >.git/worktrees/other/HEAD && + git -C other symbolic-ref HEAD refs/funny/place && test_must_fail git fsck 2>out && test_grep "worktrees/other/HEAD points to something strange" out ' @@ -391,7 +391,7 @@ test_expect_success 'tag pointing to nonexistent' ' tag=$(git hash-object -t tag -w --stdin <invalid-tag) && test_when_finished "remove_object $tag" && - echo $tag >.git/refs/tags/invalid && + git update-ref refs/tags/invalid $tag && test_when_finished "git update-ref -d refs/tags/invalid" && test_must_fail git fsck --tags >out && test_grep "broken link" out @@ -411,7 +411,7 @@ test_expect_success 'tag pointing to something else than its type' ' tag=$(git hash-object -t tag -w --stdin <wrong-tag) && test_when_finished "remove_object $tag" && - echo $tag >.git/refs/tags/wrong && + git update-ref refs/tags/wrong $tag && test_when_finished "git update-ref -d refs/tags/wrong" && test_must_fail git fsck --tags ' @@ -428,7 +428,7 @@ test_expect_success 'tag with incorrect tag name & missing tagger' ' tag=$(git hash-object --literally -t tag -w --stdin <wrong-tag) && test_when_finished "remove_object $tag" && - echo $tag >.git/refs/tags/wrong && + git update-ref refs/tags/wrong $tag && test_when_finished "git update-ref -d refs/tags/wrong" && git fsck --tags 2>out && @@ -452,7 +452,7 @@ test_expect_success 'tag with bad tagger' ' tag=$(git hash-object --literally -t tag -w --stdin <wrong-tag) && test_when_finished "remove_object $tag" && - echo $tag >.git/refs/tags/wrong && + git update-ref refs/tags/wrong $tag && test_when_finished "git update-ref -d refs/tags/wrong" && test_must_fail git fsck --tags 2>out && test_grep "error in tag .*: invalid author/committer" out @@ -471,7 +471,7 @@ test_expect_success 'tag with NUL in header' ' tag=$(git hash-object --literally -t tag -w --stdin <tag-NUL-header) && test_when_finished "remove_object $tag" && - echo $tag >.git/refs/tags/wrong && + git update-ref refs/tags/wrong $tag && test_when_finished "git update-ref -d refs/tags/wrong" && test_must_fail git fsck --tags 2>out && test_grep "error in tag $tag.*unterminated header: NUL at offset" out @@ -1060,4 +1060,41 @@ test_expect_success 'fsck reports problems in current worktree index without fil test_cmp expect actual ' +test_expect_success 'fsck warning on symlink target with excessive length' ' + symlink_target=$(printf "pattern %032769d" 1 | git hash-object -w --stdin) && + test_when_finished "remove_object $symlink_target" && + tree=$(printf "120000 blob %s\t%s\n" $symlink_target symlink | git mktree) && + test_when_finished "remove_object $tree" && + cat >expected <<-EOF && + warning in blob $symlink_target: symlinkTargetLength: symlink target too long + EOF + git fsck --no-dangling >actual 2>&1 && + test_cmp expected actual +' + +test_expect_success 'fsck warning on symlink target pointing inside git dir' ' + gitdir=$(printf ".git" | git hash-object -w --stdin) && + ntfs_gitdir=$(printf "GIT~1" | git hash-object -w --stdin) && + hfs_gitdir=$(printf ".${u200c}git" | git hash-object -w --stdin) && + inside_gitdir=$(printf "nested/.git/config" | git hash-object -w --stdin) && + benign_target=$(printf "legit/config" | git hash-object -w --stdin) && + tree=$(printf "120000 blob %s\t%s\n" \ + $benign_target benign_target \ + $gitdir gitdir \ + $hfs_gitdir hfs_gitdir \ + $inside_gitdir inside_gitdir \ + $ntfs_gitdir ntfs_gitdir | + git mktree) && + for o in $gitdir $ntfs_gitdir $hfs_gitdir $inside_gitdir $benign_target $tree + do + test_when_finished "remove_object $o" || return 1 + done && + printf "warning in blob %s: symlinkPointsToGitDir: symlink target points to git dir\n" \ + $gitdir $hfs_gitdir $inside_gitdir $ntfs_gitdir | + sort >expected && + git fsck --no-dangling >actual 2>&1 && + sort actual >actual.sorted && + test_cmp expected actual.sorted +' + test_done diff --git a/t/t1500-rev-parse.sh b/t/t1500-rev-parse.sh index 3f9e7f6..a669e59 100755 --- a/t/t1500-rev-parse.sh +++ b/t/t1500-rev-parse.sh @@ -208,6 +208,23 @@ test_expect_success 'rev-parse --show-object-format in repo' ' grep "unknown mode for --show-object-format: squeamish-ossifrage" err ' +test_expect_success 'rev-parse --show-ref-format' ' + test_detect_ref_format >expect && + git rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'rev-parse --show-ref-format with invalid storage' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + git config extensions.refstorage broken && + test_must_fail git rev-parse --show-ref-format 2>err && + grep "error: invalid value for ${SQ}extensions.refstorage${SQ}: ${SQ}broken${SQ}" err + ) +' + test_expect_success '--show-toplevel from subdir of working tree' ' pwd >expect && git -C sub/dir rev-parse --show-toplevel >actual && diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh index f073759..b754b9f 100755 --- a/t/t1502-rev-parse-parseopt.sh +++ b/t/t1502-rev-parse-parseopt.sh @@ -322,4 +322,15 @@ check_invalid_long_option optionspec-neg --no-positive-only check_invalid_long_option optionspec-neg --negative check_invalid_long_option optionspec-neg --no-no-negative +test_expect_success 'ambiguous: --no matches both --noble and --no-noble' ' + cat >spec <<-\EOF && + some-command [options] + -- + noble The feudal switch. + EOF + test_expect_code 129 env GIT_TEST_DISALLOW_ABBREVIATED_OPTIONS=false \ + git rev-parse --parseopt -- <spec 2>err --no && + grep "error: ambiguous option: no (could be --noble or --no-noble)" err +' + test_done diff --git a/t/t1503-rev-parse-verify.sh b/t/t1503-rev-parse-verify.sh index bc13683..79df65e 100755 --- a/t/t1503-rev-parse-verify.sh +++ b/t/t1503-rev-parse-verify.sh @@ -144,11 +144,6 @@ test_expect_success 'main@{n} for various n' ' test_must_fail git rev-parse --verify main@{$Np1} ' -test_expect_success SYMLINKS,REFFILES 'ref resolution not confused by broken symlinks' ' - ln -s does-not-exist .git/refs/heads/broken && - test_must_fail git rev-parse --verify broken -' - test_expect_success 'options can appear after --verify' ' git rev-parse --verify HEAD >expect && git rev-parse --verify -q HEAD >actual && diff --git a/t/t1509/prepare-chroot.sh b/t/t1509/prepare-chroot.sh index 6d47e2c..dc997e0 100755 --- a/t/t1509/prepare-chroot.sh +++ b/t/t1509/prepare-chroot.sh @@ -43,7 +43,7 @@ rsync --exclude-from t/t1509/excludes -Ha . "$R$(pwd)" # env might slip through, see test-lib.sh, unset.*PERL_PATH sed 's|^PERL_PATH=.*|PERL_PATH=/bin/true|' GIT-BUILD-OPTIONS > "$R$(pwd)/GIT-BUILD-OPTIONS" for cmd in git $BB;do - ldd $cmd | grep '/' | sed 's,.*\s\(/[^ ]*\).*,\1,' | while read i; do + ldd $cmd | sed -n '/\//s,.*\s\(/[^ ]*\).*,\1,p' | while read i; do mkdir -p "$R$(dirname $i)" cp "$i" "$R/$i" done diff --git a/t/t1800-hook.sh b/t/t1800-hook.sh index 8b0234c..1894ebe 100755 --- a/t/t1800-hook.sh +++ b/t/t1800-hook.sh @@ -185,4 +185,19 @@ test_expect_success 'stdin to hooks' ' test_cmp expect actual ' +test_expect_success 'clone protections' ' + test_config core.hooksPath "$(pwd)/my-hooks" && + mkdir -p my-hooks && + write_script my-hooks/test-hook <<-\EOF && + echo Hook ran $1 + EOF + + git hook run test-hook 2>err && + test_grep "Hook ran" err && + test_must_fail env GIT_CLONE_PROTECTION_ACTIVE=true \ + git hook run test-hook 2>err && + test_grep "active .core.hooksPath" err && + test_grep ! "Hook ran" err +' + test_done diff --git a/t/t2011-checkout-invalid-head.sh b/t/t2011-checkout-invalid-head.sh index d9997e7..04f53b1 100755 --- a/t/t2011-checkout-invalid-head.sh +++ b/t/t2011-checkout-invalid-head.sh @@ -18,12 +18,12 @@ test_expect_success 'checkout should not start branch from a tree' ' test_must_fail git checkout -b newbranch main^{tree} ' -test_expect_success 'checkout main from invalid HEAD' ' +test_expect_success REFFILES 'checkout main from invalid HEAD' ' echo $ZERO_OID >.git/HEAD && git checkout main -- ' -test_expect_success 'checkout notices failure to lock HEAD' ' +test_expect_success REFFILES 'checkout notices failure to lock HEAD' ' test_when_finished "rm -f .git/HEAD.lock" && >.git/HEAD.lock && test_must_fail git checkout -b other @@ -31,11 +31,8 @@ test_expect_success 'checkout notices failure to lock HEAD' ' test_expect_success 'create ref directory/file conflict scenario' ' git update-ref refs/heads/outer/inner main && - - # do not rely on symbolic-ref to get a known state, - # as it may use the same code we are testing reset_to_df () { - echo "ref: refs/heads/outer" >.git/HEAD + git symbolic-ref HEAD refs/heads/outer } ' diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh index 747eb55..c40b661 100755 --- a/t/t2016-checkout-patch.sh +++ b/t/t2016-checkout-patch.sh @@ -2,6 +2,7 @@ test_description='git checkout --patch' +TEST_PASSES_SANITIZE_LEAK=true . ./lib-patch-mode.sh test_expect_success 'setup' ' @@ -38,26 +39,32 @@ test_expect_success 'git checkout -p with staged changes' ' verify_state dir/foo index index ' -test_expect_success 'git checkout -p HEAD with NO staged changes: abort' ' - set_and_save_state dir/foo work head && - test_write_lines n y n | git checkout -p HEAD && - verify_saved_state bar && - verify_saved_state dir/foo -' - -test_expect_success 'git checkout -p HEAD with NO staged changes: apply' ' - test_write_lines n y y | git checkout -p HEAD && - verify_saved_state bar && - verify_state dir/foo head head -' - -test_expect_success 'git checkout -p HEAD with change already staged' ' - set_state dir/foo index index && - # the third n is to get out in case it mistakenly does not apply - test_write_lines n y n | git checkout -p HEAD && - verify_saved_state bar && - verify_state dir/foo head head -' +for opt in "HEAD" "@" +do + test_expect_success "git checkout -p $opt with NO staged changes: abort" ' + set_and_save_state dir/foo work head && + test_write_lines n y n | git checkout -p $opt >output && + verify_saved_state bar && + verify_saved_state dir/foo && + test_grep "Discard" output + ' + + test_expect_success "git checkout -p $opt with NO staged changes: apply" ' + test_write_lines n y y | git checkout -p $opt >output && + verify_saved_state bar && + verify_state dir/foo head head && + test_grep "Discard" output + ' + + test_expect_success "git checkout -p $opt with change already staged" ' + set_state dir/foo index index && + # the third n is to get out in case it mistakenly does not apply + test_write_lines n y n | git checkout -p $opt >output && + verify_saved_state bar && + verify_state dir/foo head head && + test_grep "Discard" output + ' +done test_expect_success 'git checkout -p HEAD^...' ' # the third n is to get out in case it mistakenly does not apply diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh index 947d158..a5c7358 100755 --- a/t/t2017-checkout-orphan.sh +++ b/t/t2017-checkout-orphan.sh @@ -86,7 +86,7 @@ test_expect_success '--orphan makes reflog by default' ' git rev-parse --verify delta@{0} ' -test_expect_success REFFILES '--orphan does not make reflog when core.logAllRefUpdates = false' ' +test_expect_success '--orphan does not make reflog when core.logAllRefUpdates = false' ' git checkout main && git config core.logAllRefUpdates false && git checkout --orphan epsilon && diff --git a/t/t2020-checkout-detach.sh b/t/t2020-checkout-detach.sh index 8202ef8..8d90d02 100755 --- a/t/t2020-checkout-detach.sh +++ b/t/t2020-checkout-detach.sh @@ -45,6 +45,18 @@ test_expect_success 'checkout branch does not detach' ' check_not_detached ' +for opt in "HEAD" "@" +do + test_expect_success "checkout $opt no-op/don't detach" ' + reset && + cat .git/HEAD >expect && + git checkout $opt && + cat .git/HEAD >actual && + check_not_detached && + test_cmp expect actual + ' +done + test_expect_success 'checkout tag detaches' ' reset && git checkout tag && @@ -164,7 +176,10 @@ test_expect_success 'tracking count is accurate after orphan check' ' git config branch.child.merge refs/heads/main && git checkout child^ && git checkout child >stdout && - test_cmp expect stdout + test_cmp expect stdout && + + git checkout --detach child >stdout && + test_grep ! "can be fast-forwarded\." stdout ' test_expect_success 'no advice given for explicit detached head state' ' diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh index a97416c..a3b1449 100755 --- a/t/t2024-checkout-dwim.sh +++ b/t/t2024-checkout-dwim.sh @@ -113,7 +113,7 @@ test_expect_success 'checkout of branch from multiple remotes fails with advice' test_grep ! "^hint: " stderr ' -test_expect_success PERL 'checkout -p with multiple remotes does not print advice' ' +test_expect_success 'checkout -p with multiple remotes does not print advice' ' git checkout -B main && test_might_fail git branch -D foo && diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh index e247a47..c91c4db 100755 --- a/t/t2060-switch.sh +++ b/t/t2060-switch.sh @@ -170,8 +170,10 @@ test_expect_success 'switch back when temporarily detached and checked out elsew # we test in both worktrees to ensure that works # as expected with "first" and "next" worktrees test_must_fail git -C wt1 switch shared && + test_must_fail git -C wt1 switch -C shared && git -C wt1 switch --ignore-other-worktrees shared && test_must_fail git -C wt2 switch shared && + test_must_fail git -C wt2 switch -C shared && git -C wt2 switch --ignore-other-worktrees shared ' diff --git a/t/t2070-restore.sh b/t/t2070-restore.sh index 16d6348..ac40494 100755 --- a/t/t2070-restore.sh +++ b/t/t2070-restore.sh @@ -5,6 +5,7 @@ test_description='restore basic functionality' 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/t2071-restore-patch.sh b/t/t2071-restore-patch.sh index b5c5c0f..42d5522 100755 --- a/t/t2071-restore-patch.sh +++ b/t/t2071-restore-patch.sh @@ -2,9 +2,10 @@ test_description='git restore --patch' +TEST_PASSES_SANITIZE_LEAK=true . ./lib-patch-mode.sh -test_expect_success PERL 'setup' ' +test_expect_success 'setup' ' mkdir dir && echo parent >dir/foo && echo dummy >bar && @@ -16,43 +17,47 @@ test_expect_success PERL 'setup' ' save_head ' -test_expect_success PERL 'restore -p without pathspec is fine' ' +test_expect_success 'restore -p without pathspec is fine' ' echo q >cmd && git restore -p <cmd ' # note: bar sorts before dir/foo, so the first 'n' is always to skip 'bar' -test_expect_success PERL 'saying "n" does nothing' ' +test_expect_success 'saying "n" does nothing' ' set_and_save_state dir/foo work head && test_write_lines n n | git restore -p && verify_saved_state bar && verify_saved_state dir/foo ' -test_expect_success PERL 'git restore -p' ' +test_expect_success 'git restore -p' ' set_and_save_state dir/foo work head && test_write_lines n y | git restore -p && verify_saved_state bar && verify_state dir/foo head head ' -test_expect_success PERL 'git restore -p with staged changes' ' +test_expect_success 'git restore -p with staged changes' ' set_state dir/foo work index && test_write_lines n y | git restore -p && verify_saved_state bar && verify_state dir/foo index index ' -test_expect_success PERL 'git restore -p --source=HEAD' ' - set_state dir/foo work index && - # the third n is to get out in case it mistakenly does not apply - test_write_lines n y n | git restore -p --source=HEAD && - verify_saved_state bar && - verify_state dir/foo head index -' - -test_expect_success PERL 'git restore -p --source=HEAD^' ' +for opt in "HEAD" "@" +do + test_expect_success "git restore -p --source=$opt" ' + set_state dir/foo work index && + # the third n is to get out in case it mistakenly does not apply + test_write_lines n y n | git restore -p --source=$opt >output && + verify_saved_state bar && + verify_state dir/foo head index && + test_grep "Discard" output + ' +done + +test_expect_success 'git restore -p --source=HEAD^' ' set_state dir/foo work index && # the third n is to get out in case it mistakenly does not apply test_write_lines n y n | git restore -p --source=HEAD^ && @@ -60,7 +65,7 @@ test_expect_success PERL 'git restore -p --source=HEAD^' ' verify_state dir/foo parent index ' -test_expect_success PERL 'git restore -p --source=HEAD^...' ' +test_expect_success 'git restore -p --source=HEAD^...' ' set_state dir/foo work index && # the third n is to get out in case it mistakenly does not apply test_write_lines n y n | git restore -p --source=HEAD^... && @@ -68,7 +73,7 @@ test_expect_success PERL 'git restore -p --source=HEAD^...' ' verify_state dir/foo parent index ' -test_expect_success PERL 'git restore -p handles deletion' ' +test_expect_success 'git restore -p handles deletion' ' set_state dir/foo work index && rm dir/foo && test_write_lines n y | git restore -p && @@ -81,21 +86,21 @@ test_expect_success PERL 'git restore -p handles deletion' ' # dir/foo. There's always an extra 'n' to reject edits to dir/foo in # the failure case (and thus get out of the loop). -test_expect_success PERL 'path limiting works: dir' ' +test_expect_success 'path limiting works: dir' ' set_state dir/foo work head && test_write_lines y n | git restore -p dir && verify_saved_state bar && verify_state dir/foo head head ' -test_expect_success PERL 'path limiting works: -- dir' ' +test_expect_success 'path limiting works: -- dir' ' set_state dir/foo work head && test_write_lines y n | git restore -p -- dir && verify_saved_state bar && verify_state dir/foo head head ' -test_expect_success PERL 'path limiting works: HEAD^ -- dir' ' +test_expect_success 'path limiting works: HEAD^ -- dir' ' set_state dir/foo work head && # the third n is to get out in case it mistakenly does not apply test_write_lines y n n | git restore -p --source=HEAD^ -- dir && @@ -103,7 +108,7 @@ test_expect_success PERL 'path limiting works: HEAD^ -- dir' ' verify_state dir/foo parent head ' -test_expect_success PERL 'path limiting works: foo inside dir' ' +test_expect_success 'path limiting works: foo inside dir' ' set_state dir/foo work head && # the third n is to get out in case it mistakenly does not apply test_write_lines y n n | (cd dir && git restore -p foo) && @@ -111,7 +116,7 @@ test_expect_success PERL 'path limiting works: foo inside dir' ' verify_state dir/foo head head ' -test_expect_success PERL 'none of this moved HEAD' ' +test_expect_success 'none of this moved HEAD' ' verify_saved_head ' diff --git a/t/t2072-restore-pathspec-file.sh b/t/t2072-restore-pathspec-file.sh index 8198a1e..86c9c88 100755 --- a/t/t2072-restore-pathspec-file.sh +++ b/t/t2072-restore-pathspec-file.sh @@ -2,6 +2,7 @@ test_description='restore --pathspec-from-file' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_tick diff --git a/t/t2104-update-index-skip-worktree.sh b/t/t2104-update-index-skip-worktree.sh index 0bab134..7ec7f30 100755 --- a/t/t2104-update-index-skip-worktree.sh +++ b/t/t2104-update-index-skip-worktree.sh @@ -11,27 +11,27 @@ TEST_PASSES_SANITIZE_LEAK=true sane_unset GIT_TEST_SPLIT_INDEX test_set_index_version () { - GIT_INDEX_VERSION="$1" - export GIT_INDEX_VERSION + GIT_INDEX_VERSION="$1" + export GIT_INDEX_VERSION } test_set_index_version 3 -cat >expect.full <<EOF -H 1 -H 2 -H sub/1 -H sub/2 -EOF +test_expect_success 'setup' ' + cat >expect.full <<-\EOF && + H 1 + H 2 + H sub/1 + H sub/2 + EOF -cat >expect.skip <<EOF -S 1 -H 2 -S sub/1 -H sub/2 -EOF + cat >expect.skip <<-\EOF && + S 1 + H 2 + S sub/1 + H sub/2 + EOF -test_expect_success 'setup' ' mkdir sub && touch ./1 ./2 sub/1 sub/2 && git add 1 2 sub/1 sub/2 && diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index c01492f..df235ac 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -65,6 +65,16 @@ test_expect_success 'update did not touch untracked files' ' test_must_be_empty out ' +test_expect_success 'error out when passing untracked path' ' + git reset --hard && + echo content >>baz && + echo content >>top && + test_must_fail git add -u baz top 2>err && + test_grep -e "error: pathspec .baz. did not match any file(s) known to git" err && + git diff --cached --name-only >actual && + test_must_be_empty actual +' + test_expect_success 'cache tree has not been corrupted' ' git ls-files -s | diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh index df4aff7..ba320dc 100755 --- a/t/t2400-worktree-add.sh +++ b/t/t2400-worktree-add.sh @@ -126,6 +126,28 @@ test_expect_success 'die the same branch is already checked out' ' ) ' +test_expect_success 'refuse to reset a branch in use elsewhere' ' + ( + cd here && + + # we know we are on detached HEAD but just in case ... + git checkout --detach HEAD && + git rev-parse --verify HEAD >old.head && + + git rev-parse --verify refs/heads/newmain >old.branch && + test_must_fail git checkout -B newmain 2>error && + git rev-parse --verify refs/heads/newmain >new.branch && + git rev-parse --verify HEAD >new.head && + + grep "already used by worktree at" error && + test_cmp old.branch new.branch && + test_cmp old.head new.head && + + # and we must be still on the same detached HEAD state + test_must_fail git symbolic-ref HEAD + ) +' + test_expect_success SYMLINKS 'die the same branch is already checked out (symlink)' ' head=$(git -C there rev-parse --git-path HEAD) && ref=$(git -C there symbolic-ref HEAD) && @@ -405,7 +427,7 @@ test_expect_success '"add" worktree with orphan branch, lock, and reason' ' # Note: Quoted arguments containing spaces are not supported. test_wt_add_orphan_hint () { local context="$1" && - local use_branch=$2 && + local use_branch="$2" && shift 2 && local opts="$*" && test_expect_success "'worktree add' show orphan hint in bad/orphan HEAD w/ $context" ' @@ -415,7 +437,7 @@ test_wt_add_orphan_hint () { git -C repo switch --orphan noref && test_must_fail git -C repo worktree add $opts foobar/ 2>actual && ! grep "error: unknown switch" actual && - grep "hint: If you meant to create a worktree containing a new orphan branch" actual && + grep "hint: If you meant to create a worktree containing a new unborn branch" actual && if [ $use_branch -eq 1 ] then grep -E "^hint: +git worktree add --orphan -b [^ ]+ [^ ]+$" actual @@ -436,7 +458,7 @@ test_expect_success "'worktree add' doesn't show orphan hint in bad/orphan HEAD (cd repo && test_commit commit) && test_must_fail git -C repo worktree add --quiet foobar_branch foobar/ 2>actual && ! grep "error: unknown switch" actual && - ! grep "hint: If you meant to create a worktree containing a new orphan branch" actual + ! grep "hint: If you meant to create a worktree containing a new unborn branch" actual ' test_expect_success 'local clone from linked checkout' ' @@ -468,7 +490,8 @@ test_expect_success 'put a worktree under rebase' ' cd under-rebase && set_fake_editor && FAKE_LINES="edit 1" git rebase -i HEAD^ && - git worktree list | grep "under-rebase.*detached HEAD" + git worktree list >actual && + grep "under-rebase.*detached HEAD" actual ) ' @@ -509,7 +532,8 @@ test_expect_success 'checkout a branch under bisect' ' git bisect start && git bisect bad && git bisect good HEAD~2 && - git worktree list | grep "under-bisect.*detached HEAD" && + git worktree list >actual && + grep "under-bisect.*detached HEAD" actual && test_must_fail git worktree add new-bisect under-bisect && ! test -d new-bisect ) @@ -709,9 +733,9 @@ test_expect_success 'git worktree --no-guess-remote option overrides config' ' test_dwim_orphan () { local info_text="No possible source branch, inferring '--orphan'" && local fetch_error_text="fatal: No local or remote refs exist despite at least one remote" && - local orphan_hint="hint: If you meant to create a worktree containing a new orphan branch" && + local orphan_hint="hint: If you meant to create a worktree containing a new unborn branch" && local invalid_ref_regex="^fatal: invalid reference: " && - local bad_combo_regex="^fatal: '[-a-z]*' and '[-a-z]*' cannot be used together" && + local bad_combo_regex="^fatal: options '[-a-z]*' and '[-a-z]*' cannot be used together" && local git_ns="repo" && local dashc_args="-C $git_ns" && diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index c54fd9e..ccfa6a7 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -28,7 +28,7 @@ test_expect_success 'git branch --help should not have created a bogus branch' ' test_ref_missing refs/heads/--help ' -test_expect_success 'branch -h in broken repository' ' +test_expect_success REFFILES 'branch -h in broken repository' ' mkdir broken && ( cd broken && @@ -75,15 +75,15 @@ test_expect_success 'git branch HEAD should fail' ' test_must_fail git branch HEAD ' -cat >expect <<EOF -$ZERO_OID $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from main -EOF test_expect_success 'git branch --create-reflog d/e/f should create a branch and a log' ' GIT_COMMITTER_DATE="2005-05-26 23:30" \ git -c core.logallrefupdates=false branch --create-reflog d/e/f && test_ref_exists refs/heads/d/e/f && - test_path_is_file .git/logs/refs/heads/d/e/f && - test_cmp expect .git/logs/refs/heads/d/e/f + cat >expect <<-EOF && + $HEAD refs/heads/d/e/f@{0}: branch: Created from main + EOF + git reflog show --no-abbrev-commit refs/heads/d/e/f >actual && + test_cmp expect actual ' test_expect_success 'git branch -d d/e/f should delete a branch and a log' ' @@ -203,10 +203,9 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou test $(git rev-parse --abbrev-ref HEAD) = bam ' -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 " $ZERO_OID.*$msg$" .git/logs/HEAD && - grep "^$ZERO_OID.*$msg$" .git/logs/HEAD +test_expect_success 'git branch -M baz bam should add entries to HEAD reflog' ' + git reflog show HEAD >actual && + grep "HEAD@{0}: Branch: renamed refs/heads/baz to refs/heads/bam" actual ' test_expect_success 'git branch -M should leave orphaned HEAD alone' ' @@ -215,17 +214,20 @@ test_expect_success 'git branch -M should leave orphaned HEAD alone' ' cd orphan && test_commit initial && git checkout --orphan lonely && - grep lonely .git/HEAD && + git symbolic-ref HEAD >expect && + echo refs/heads/lonely >actual && + test_cmp expect actual && test_ref_missing refs/head/lonely && git branch -M main mistress && - grep lonely .git/HEAD + git symbolic-ref HEAD >expect && + test_cmp expect actual ) ' test_expect_success 'resulting reflog can be shown by log -g' ' oid=$(git rev-parse HEAD) && cat >expect <<-EOF && - HEAD@{0} $oid $msg + HEAD@{0} $oid Branch: renamed refs/heads/baz to refs/heads/bam HEAD@{2} $oid checkout: moving from foo to baz EOF git log -g --format="%gd %H %gs" -2 HEAD >actual && @@ -243,7 +245,7 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou git worktree prune ' -test_expect_success 'git branch -M fails if updating any linked working tree fails' ' +test_expect_success REFFILES 'git branch -M fails if updating any linked working tree fails' ' git worktree add -b baz bazdir1 && git worktree add -f bazdir2 baz && touch .git/worktrees/bazdir1/HEAD.lock && @@ -438,10 +440,10 @@ test_expect_success 'git branch --list -v with --abbrev' ' test_expect_success 'git branch --column' ' COLUMNS=81 git branch --column=column >actual && - cat >expect <<\EOF && - a/b/c bam foo l * main n o/p r - abc bar j/k m/m mb o/o q topic -EOF + cat >expect <<-\EOF && + a/b/c bam foo l * main n o/p r + abc bar j/k m/m mb o/o q topic + EOF test_cmp expect actual ' @@ -451,25 +453,25 @@ test_expect_success 'git branch --column with an extremely long branch name' ' test_when_finished "git branch -d $long" && git branch $long && COLUMNS=80 git branch --column=column >actual && - cat >expect <<EOF && - a/b/c - abc - bam - bar - foo - j/k - l - m/m -* main - mb - n - o/o - o/p - q - r - topic - $long -EOF + cat >expect <<-EOF && + a/b/c + abc + bam + bar + foo + j/k + l + m/m + * main + mb + n + o/o + o/p + q + r + topic + $long + EOF test_cmp expect actual ' @@ -479,10 +481,10 @@ test_expect_success 'git branch with column.*' ' COLUMNS=80 git branch >actual && git config --unset column.branch && git config --unset column.ui && - cat >expect <<\EOF && - a/b/c bam foo l * main n o/p r - abc bar j/k m/m mb o/o q topic -EOF + cat >expect <<-\EOF && + a/b/c bam foo l * main n o/p r + abc bar j/k m/m mb o/o q topic + EOF test_cmp expect actual ' @@ -494,39 +496,36 @@ test_expect_success 'git branch -v with column.ui ignored' ' git config column.ui column && COLUMNS=80 git branch -v | cut -c -8 | sed "s/ *$//" >actual && git config --unset column.ui && - cat >expect <<\EOF && - a/b/c - abc - bam - bar - foo - j/k - l - m/m -* main - mb - n - o/o - o/p - q - r - topic -EOF + cat >expect <<-\EOF && + a/b/c + abc + bam + bar + foo + j/k + l + m/m + * main + mb + n + o/o + o/p + q + r + topic + EOF test_cmp expect actual ' -mv .git/config .git/config-saved - -test_expect_success SHA1 'git branch -m q q2 without config should succeed' ' +test_expect_success DEFAULT_REPO_FORMAT 'git branch -m q q2 without config should succeed' ' + test_when_finished mv .git/config-saved .git/config && + mv .git/config .git/config-saved && git branch -m q q2 && git branch -m q2 q ' -mv .git/config-saved .git/config - -git config branch.s/s.dummy Hello - test_expect_success 'git branch -m s/s s should work when s/t is deleted' ' + git config branch.s/s.dummy Hello && git branch --create-reflog s/s && git reflog exists refs/heads/s/s && git branch --create-reflog s/t && @@ -577,7 +576,7 @@ EOF # ...and that the comments for those sections are also # preserved. - cat config.branch | sed "s/\"source\"/\"dest\"/" >expect && + sed "s/\"source\"/\"dest\"/" config.branch >expect && sed -n -e "/Note the lack/,\$p" .git/config >actual && test_cmp expect actual ' @@ -699,7 +698,8 @@ test_expect_success 'git branch -C c1 c2 should succeed when c1 is checked out' test_expect_success 'git branch -C c1 c2 should never touch HEAD' ' msg="Branch: copied refs/heads/c1 to refs/heads/c2" && - ! grep "$msg$" .git/logs/HEAD + git reflog HEAD >actual && + ! grep "$msg$" actual ' test_expect_success 'git branch -C main should work when main is checked out' ' @@ -809,7 +809,7 @@ test_expect_success 'deleting a symref' ' test_expect_success 'deleting a dangling symref' ' git symbolic-ref refs/heads/dangling-symref nowhere && - test_path_is_file .git/refs/heads/dangling-symref && + git symbolic-ref --no-recurse refs/heads/dangling-symref && echo "Deleted branch dangling-symref (was nowhere)." >expect && git branch -d dangling-symref >actual && test_ref_missing refs/heads/dangling-symref && @@ -833,35 +833,6 @@ test_expect_success 'renaming a symref is not allowed' ' test_ref_missing refs/heads/new-topic ' -test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' ' - git branch --create-reflog u && - mv .git/logs/refs/heads/u real-u && - ln -s real-u .git/logs/refs/heads/u && - test_must_fail git branch -m u v -' - -test_expect_success SYMLINKS 'git branch -m with symlinked .git/refs' ' - test_when_finished "rm -rf subdir" && - git init --bare subdir && - - rm -rfv subdir/refs subdir/objects subdir/packed-refs && - ln -s ../.git/refs subdir/refs && - ln -s ../.git/objects subdir/objects && - ln -s ../.git/packed-refs subdir/packed-refs && - - git -C subdir rev-parse --absolute-git-dir >subdir.dir && - git rev-parse --absolute-git-dir >our.dir && - ! test_cmp subdir.dir our.dir && - - git -C subdir log && - git -C subdir branch rename-src && - git rev-parse rename-src >expect && - git -C subdir branch -m rename-src rename-dest && - git rev-parse rename-dest >actual && - test_cmp expect actual && - git branch -D rename-dest -' - test_expect_success 'test tracking setup via --track' ' git config remote.local.url . && git config remote.local.fetch refs/heads/*:refs/remotes/local/* && @@ -1138,16 +1109,16 @@ test_expect_success '--set-upstream-to notices an error to set branch as own ups test_cmp expect actual " -# Keep this test last, as it changes the current branch -cat >expect <<EOF -$ZERO_OID $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from main -EOF test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' ' + test_when_finished git checkout main && GIT_COMMITTER_DATE="2005-05-26 23:30" \ git checkout -b g/h/i -l main && test_ref_exists refs/heads/g/h/i && - test_path_is_file .git/logs/refs/heads/g/h/i && - test_cmp expect .git/logs/refs/heads/g/h/i + cat >expect <<-EOF && + $HEAD refs/heads/g/h/i@{0}: branch: Created from main + EOF + git reflog show --no-abbrev-commit refs/heads/g/h/i >actual && + test_cmp expect actual ' test_expect_success 'checkout -b makes reflog by default' ' @@ -1183,9 +1154,9 @@ test_expect_success 'avoid ambiguous track and advise' ' hint: tracking ref '\''refs/heads/main'\'': hint: ambi1 hint: ambi2 - hint: '' + hint: hint: This is typically a configuration error. - hint: '' + hint: hint: To support setting up tracking branches, ensure that hint: different remotes'\'' fetch refspecs map into different hint: tracking namespaces. @@ -1573,9 +1544,10 @@ test_expect_success 'tracking with unexpected .fetch refspec' ' test_expect_success 'configured committerdate sort' ' git init -b main sort && + test_config -C sort branch.sort "committerdate" && + ( cd sort && - git config branch.sort committerdate && test_commit initial && git checkout -b a && test_commit a && @@ -1595,9 +1567,10 @@ test_expect_success 'configured committerdate sort' ' ' test_expect_success 'option override configured sort' ' + test_config -C sort branch.sort "committerdate" && + ( cd sort && - git config branch.sort committerdate && git branch --sort=refname >actual && cat >expect <<-\EOF && a @@ -1609,10 +1582,70 @@ test_expect_success 'option override configured sort' ' ) ' +test_expect_success '--no-sort cancels config sort keys' ' + test_config -C sort branch.sort "-refname" && + + ( + cd sort && + + # objecttype is identical for all of them, so sort falls back on + # default (ascending refname) + git branch \ + --no-sort \ + --sort="objecttype" >actual && + cat >expect <<-\EOF && + a + * b + c + main + EOF + test_cmp expect actual + ) + +' + +test_expect_success '--no-sort cancels command line sort keys' ' + ( + cd sort && + + # objecttype is identical for all of them, so sort falls back on + # default (ascending refname) + git branch \ + --sort="-refname" \ + --no-sort \ + --sort="objecttype" >actual && + cat >expect <<-\EOF && + a + * b + c + main + EOF + test_cmp expect actual + ) +' + +test_expect_success '--no-sort without subsequent --sort prints expected branches' ' + ( + cd sort && + + # Sort the results with `sort` for a consistent comparison + # against expected + git branch --no-sort | sort >actual && + cat >expect <<-\EOF && + a + c + main + * b + EOF + test_cmp expect actual + ) +' + test_expect_success 'invalid sort parameter in configuration' ' + test_config -C sort branch.sort "v:notvalid" && + ( cd sort && - git config branch.sort "v:notvalid" && # this works in the "listing" mode, so bad sort key # is a dying offence. @@ -1660,4 +1693,14 @@ test_expect_success '--track overrides branch.autoSetupMerge' ' test_cmp_config "" --default "" branch.foo5.merge ' +test_expect_success 'errors if given a bad branch name' ' + cat <<-\EOF >expect && + fatal: '\''foo..bar'\'' is not a valid branch name + hint: See `man git check-ref-format` + hint: Disable this message with "git config advice.refSyntax false" + EOF + test_must_fail git branch foo..bar >actual 2>&1 && + test_cmp expect actual +' + test_done diff --git a/t/t3202-show-branch.sh b/t/t3202-show-branch.sh index 6a98b2d..a1139f7 100755 --- a/t/t3202-show-branch.sh +++ b/t/t3202-show-branch.sh @@ -4,9 +4,6 @@ test_description='test show-branch' . ./test-lib.sh -# 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 && @@ -187,18 +184,6 @@ test_expect_success 'show branch --merge-base with N arguments' ' test_cmp expect actual ' -test_expect_success 'show branch --reflog=2' ' - sed "s/^> //" >expect <<-\EOF && - > ! [refs/heads/branch10@{0}] (4 years, 5 months ago) commit: branch10 - > ! [refs/heads/branch10@{1}] (4 years, 5 months ago) commit: branch10 - > -- - > + [refs/heads/branch10@{0}] branch10 - > ++ [refs/heads/branch10@{1}] initial - EOF - git show-branch --reflog=2 >actual && - test_cmp actual expect -' - # incompatible options while read combo do @@ -264,4 +249,38 @@ test_expect_success 'error descriptions on orphan branch' ' test_branch_op_in_wt -c new-branch ' +test_expect_success 'setup reflogs' ' + test_commit base && + git checkout -b branch && + test_commit one && + git reset --hard HEAD^ && + test_commit two && + test_commit three +' + +test_expect_success '--reflog shows reflog entries' ' + cat >expect <<-\EOF && + ! [branch@{0}] (0 seconds ago) commit: three + ! [branch@{1}] (60 seconds ago) commit: two + ! [branch@{2}] (2 minutes ago) reset: moving to HEAD^ + ! [branch@{3}] (2 minutes ago) commit: one + ---- + + [branch@{0}] three + ++ [branch@{1}] two + + [branch@{3}] one + ++++ [branch@{2}] base + EOF + # the output always contains relative timestamps; use + # a known time to get deterministic results + GIT_TEST_DATE_NOW=$test_tick \ + git show-branch --reflog branch >actual && + test_cmp expect actual +' + +test_expect_success '--reflog handles missing reflog' ' + git reflog expire --expire=now branch && + git show-branch --reflog branch >actual && + test_must_be_empty actual +' + test_done diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh index 60d6ed2..597df5e 100755 --- a/t/t3310-notes-merge-manual-resolve.sh +++ b/t/t3310-notes-merge-manual-resolve.sh @@ -561,9 +561,9 @@ y and z notes on 4th commit EOF # Fail to finalize merge test_must_fail git notes merge --commit >output 2>&1 && - # .git/NOTES_MERGE_* must remain - test -f .git/NOTES_MERGE_PARTIAL && - test -f .git/NOTES_MERGE_REF && + # NOTES_MERGE_* refs and .git/NOTES_MERGE_* state files must remain + git rev-parse --verify NOTES_MERGE_PARTIAL && + git rev-parse --verify NOTES_MERGE_REF && test -f .git/NOTES_MERGE_WORKTREE/$commit_sha1 && test -f .git/NOTES_MERGE_WORKTREE/$commit_sha2 && test -f .git/NOTES_MERGE_WORKTREE/$commit_sha3 && diff --git a/t/t3321-notes-stripspace.sh b/t/t3321-notes-stripspace.sh index 088a852..beca346 100755 --- a/t/t3321-notes-stripspace.sh +++ b/t/t3321-notes-stripspace.sh @@ -442,7 +442,7 @@ test_expect_success 'add note by specifying "-C", "--no-stripspace" is the defau ${LF} EOF - cat expect | git hash-object -w --stdin >blob && + git hash-object -w --stdin <expect >blob && git notes add -C $(cat blob) && git notes show >actual && test_cmp expect actual && @@ -468,7 +468,7 @@ test_expect_success 'reuse note by specifying "-C" and "--stripspace"' ' second-line EOF - cat data | git hash-object -w --stdin >blob && + git hash-object -w --stdin <data >blob && git notes add --stripspace -C $(cat blob) && git notes show >actual && test_cmp expect actual @@ -492,7 +492,7 @@ test_expect_success 'reuse with "-C" and add note with "-m", "-m" will stripspac third-line EOF - cat data | git hash-object -w --stdin >blob && + git hash-object -w --stdin <data >blob && git notes add -C $(cat blob) -m "third-line" && git notes show >actual && test_cmp expect actual @@ -511,7 +511,7 @@ test_expect_success 'add note with "-m" and reuse note with "-C", "-C" will not second-line EOF - cat data | git hash-object -w --stdin >blob && + git hash-object -w --stdin <data >blob && git notes add -m "first-line" -C $(cat blob) && git notes show >actual && test_cmp expect actual diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 24a539c..e1c8c5f 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -424,16 +424,6 @@ test_expect_success 'refuse to switch to branch checked out elsewhere' ' test_grep "already used by worktree at" err ' -test_expect_success MINGW,SYMLINKS_WINDOWS 'rebase when .git/logs is a symlink' ' - git checkout main && - mv .git/logs actual_logs && - cmd //c "mklink /D .git\logs ..\actual_logs" && - git rebase -f HEAD^ && - test -L .git/logs && - rm .git/logs && - mv actual_logs .git/logs -' - test_expect_success 'rebase when inside worktree subdirectory' ' git init main-wt && ( diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index c5f3055..d1bead6 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -153,6 +153,18 @@ test_expect_success 'rebase -i with the exec command checks tree cleanness' ' git rebase --continue ' +test_expect_success 'cherry-pick works with rebase --exec' ' + test_when_finished "git cherry-pick --abort; \ + git rebase --abort; \ + git checkout primary" && + echo "exec git cherry-pick G" >todo && + ( + set_replace_editor todo && + test_must_fail git rebase -i D D + ) && + test_cmp_rev G CHERRY_PICK_HEAD +' + test_expect_success 'rebase -x with empty command fails' ' test_when_finished "git rebase --abort ||:" && test_must_fail env git rebase -x "" @ 2>actual && @@ -2160,7 +2172,7 @@ test_expect_success '--update-refs: check failed ref update' ' # recorded in the update-refs file. We will force-update the # "second" ref, but "git branch -f" will not work because of # the lock in the update-refs file. - git rev-parse third >.git/refs/heads/second && + git update-ref refs/heads/second third && test_must_fail git rebase --continue 2>err && grep "update_ref failed for ref '\''refs/heads/second'\''" err && diff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh index ebbaed1..9f49c42 100755 --- a/t/t3407-rebase-abort.sh +++ b/t/t3407-rebase-abort.sh @@ -40,9 +40,24 @@ testrebase() { test_path_is_missing "$state_dir" ' + test_expect_success "pre rebase$type head is marked as reachable" ' + # Clean up the state from the previous one + git checkout -f --detach pre-rebase && + test_tick && + git commit --amend --only -m "reworded" && + orig_head=$(git rev-parse HEAD) && + test_must_fail git rebase$type main && + # Stop ORIG_HEAD marking $state_dir/orig-head as reachable + git update-ref -d ORIG_HEAD && + git reflog expire --expire="$GIT_COMMITTER_DATE" --all && + git prune --expire=now && + git rebase --abort && + test_cmp_rev $orig_head HEAD + ' + test_expect_success "rebase$type --abort after --skip" ' # Clean up the state from the previous one - git reset --hard pre-rebase && + git checkout -B to-rebase pre-rebase && test_must_fail git rebase$type main && test_path_is_dir "$state_dir" && test_must_fail git rebase --skip && diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index a364530..fcc40d6 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -43,7 +43,7 @@ test_auto_fixup () { git tag $1 && test_tick && - git rebase $2 -i HEAD^^^ && + git rebase $2 HEAD^^^ && git log --oneline >actual && if test -n "$no_squash" then @@ -61,15 +61,24 @@ test_auto_fixup () { } test_expect_success 'auto fixup (option)' ' - test_auto_fixup final-fixup-option --autosquash + test_auto_fixup fixup-option --autosquash && + test_auto_fixup fixup-option-i "--autosquash -i" ' -test_expect_success 'auto fixup (config)' ' +test_expect_success 'auto fixup (config true)' ' git config rebase.autosquash true && - test_auto_fixup final-fixup-config-true && + test_auto_fixup ! fixup-config-true && + test_auto_fixup fixup-config-true-i -i && test_auto_fixup ! fixup-config-true-no --no-autosquash && + test_auto_fixup ! fixup-config-true-i-no "-i --no-autosquash" +' + +test_expect_success 'auto fixup (config false)' ' git config rebase.autosquash false && - test_auto_fixup ! final-fixup-config-false + test_auto_fixup ! fixup-config-false && + test_auto_fixup ! fixup-config-false-i -i && + test_auto_fixup fixup-config-false-yes --autosquash && + test_auto_fixup fixup-config-false-i-yes "-i --autosquash" ' test_auto_squash () { @@ -87,7 +96,7 @@ test_auto_squash () { git commit -m "squash! first" -m "extra para for first" && git tag $1 && test_tick && - git rebase $2 -i HEAD^^^ && + git rebase $2 HEAD^^^ && git log --oneline >actual && if test -n "$no_squash" then @@ -105,15 +114,24 @@ test_auto_squash () { } test_expect_success 'auto squash (option)' ' - test_auto_squash final-squash --autosquash + test_auto_squash squash-option --autosquash && + test_auto_squash squash-option-i "--autosquash -i" ' -test_expect_success 'auto squash (config)' ' +test_expect_success 'auto squash (config true)' ' git config rebase.autosquash true && - test_auto_squash final-squash-config-true && + test_auto_squash ! squash-config-true && + test_auto_squash squash-config-true-i -i && test_auto_squash ! squash-config-true-no --no-autosquash && + test_auto_squash ! squash-config-true-i-no "-i --no-autosquash" +' + +test_expect_success 'auto squash (config false)' ' git config rebase.autosquash false && - test_auto_squash ! final-squash-config-false + test_auto_squash ! squash-config-false && + test_auto_squash ! squash-config-false-i -i && + test_auto_squash squash-config-false-yes --autosquash && + test_auto_squash squash-config-false-i-yes "-i --autosquash" ' test_expect_success 'misspelled auto squash' ' diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index 693934e..1a820f1 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -333,4 +333,14 @@ test_expect_success 'never change active branch' ' test_cmp_rev not-the-feature-branch unrelated-onto-branch ' +test_expect_success 'autostash commit is marked as reachable' ' + echo changed >file0 && + git rebase --autostash --exec "git prune --expire=now" \ + feature-branch^ feature-branch && + # git rebase succeeds if the stash cannot be applied so we need to check + # the contents of file0 + echo changed >expect && + test_cmp expect file0 +' + test_done diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh index 2eba00b..b40f262 100755 --- a/t/t3422-rebase-incompatible-options.sh +++ b/t/t3422-rebase-incompatible-options.sh @@ -100,12 +100,6 @@ test_rebase_am_only () { test_must_fail git rebase $opt --root A " - test_expect_success "$opt incompatible with rebase.autosquash" " - git checkout B^0 && - test_must_fail git -c rebase.autosquash=true rebase $opt A 2>err && - grep -e --no-autosquash err - " - test_expect_success "$opt incompatible with rebase.rebaseMerges" " git checkout B^0 && test_must_fail git -c rebase.rebaseMerges=true rebase $opt A 2>err && @@ -118,12 +112,6 @@ test_rebase_am_only () { grep -e --no-update-refs err " - test_expect_success "$opt okay with overridden rebase.autosquash" " - test_when_finished \"git reset --hard B^0\" && - git checkout B^0 && - git -c rebase.autosquash=true rebase --no-autosquash $opt A - " - test_expect_success "$opt okay with overridden rebase.rebaseMerges" " test_when_finished \"git reset --hard B^0\" && git checkout B^0 && diff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh index 5e1045a..1ee6b00 100755 --- a/t/t3424-rebase-empty.sh +++ b/t/t3424-rebase-empty.sh @@ -72,6 +72,17 @@ test_expect_success 'rebase --merge --empty=keep' ' test_cmp expect actual ' +test_expect_success 'rebase --merge --empty=stop' ' + git checkout -B testing localmods && + test_must_fail git rebase --merge --empty=stop upstream && + + git rebase --skip && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + test_expect_success 'rebase --merge --empty=ask' ' git checkout -B testing localmods && test_must_fail git rebase --merge --empty=ask upstream && @@ -101,9 +112,9 @@ test_expect_success 'rebase --interactive --empty=keep' ' test_cmp expect actual ' -test_expect_success 'rebase --interactive --empty=ask' ' +test_expect_success 'rebase --interactive --empty=stop' ' git checkout -B testing localmods && - test_must_fail git rebase --interactive --empty=ask upstream && + test_must_fail git rebase --interactive --empty=stop upstream && git rebase --skip && @@ -112,7 +123,7 @@ test_expect_success 'rebase --interactive --empty=ask' ' test_cmp expect actual ' -test_expect_success 'rebase --interactive uses default of --empty=ask' ' +test_expect_success 'rebase --interactive uses default of --empty=stop' ' git checkout -B testing localmods && test_must_fail git rebase --interactive upstream && @@ -167,4 +178,42 @@ test_expect_success 'rebase --merge does not leave state laying around' ' test_path_is_missing .git/MERGE_MSG ' +test_expect_success 'rebase --exec --empty=drop' ' + git checkout -B testing localmods && + git rebase --exec "true" --empty=drop upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --exec --empty=keep' ' + git checkout -B testing localmods && + git rebase --exec "true" --empty=keep upstream && + + test_write_lines D C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --exec uses default of --empty=keep' ' + git checkout -B testing localmods && + git rebase --exec "true" upstream && + + test_write_lines D C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --exec --empty=stop' ' + git checkout -B testing localmods && + test_must_fail git rebase --exec "true" --empty=stop upstream && + + git rebase --skip && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + test_done diff --git a/t/t3428-rebase-signoff.sh b/t/t3428-rebase-signoff.sh index e1b1e94..1bebd1c 100755 --- a/t/t3428-rebase-signoff.sh +++ b/t/t3428-rebase-signoff.sh @@ -8,47 +8,45 @@ This test runs git rebase --signoff and make sure that it works. TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh -# A simple file to commit -cat >file <<EOF -a -EOF +test_expect_success 'setup' ' + git commit --allow-empty -m "Initial empty commit" && + test_commit first file a && + + ident="$GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" && -# Expected commit message for initial commit after rebase --signoff -cat >expected-initial-signed <<EOF -Initial empty commit + # Expected commit message for initial commit after rebase --signoff + cat >expected-initial-signed <<-EOF && + Initial empty commit -Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") -EOF + Signed-off-by: $ident + EOF -# Expected commit message after rebase --signoff -cat >expected-signed <<EOF -first + # Expected commit message after rebase --signoff + cat >expected-signed <<-EOF && + first -Signed-off-by: $(git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/") -EOF + Signed-off-by: $ident + EOF -# Expected commit message after rebase without --signoff (or with --no-signoff) -cat >expected-unsigned <<EOF -first -EOF + # Expected commit message after rebase without --signoff (or with --no-signoff) + cat >expected-unsigned <<-EOF && + first + EOF + git config alias.rbs "rebase --signoff" +' # We configure an alias to do the rebase --signoff so that # on the next subtest we can show that --no-signoff overrides the alias -test_expect_success 'rebase --signoff adds a sign-off line' ' - git commit --allow-empty -m "Initial empty commit" && - git add file && git commit -m first && - git config alias.rbs "rebase --signoff" && - git rbs HEAD^ && - git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && - test_cmp expected-signed actual +test_expect_success 'rebase --apply --signoff adds a sign-off line' ' + git rbs --apply HEAD^ && + test_commit_message HEAD expected-signed ' test_expect_success 'rebase --no-signoff does not add a sign-off line' ' git commit --amend -m "first" && git rbs --no-signoff HEAD^ && - git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && - test_cmp expected-unsigned actual + test_commit_message HEAD expected-unsigned ' test_expect_success 'rebase --exec --signoff adds a sign-off line' ' @@ -56,30 +54,25 @@ test_expect_success 'rebase --exec --signoff adds a sign-off line' ' git commit --amend -m "first" && git rebase --exec "touch exec" --signoff HEAD^ && test_path_is_file exec && - git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && - test_cmp expected-signed actual + test_commit_message HEAD expected-signed ' test_expect_success 'rebase --root --signoff adds a sign-off line' ' git commit --amend -m "first" && git rebase --root --keep-empty --signoff && - git cat-file commit HEAD^ | sed -e "1,/^\$/d" >actual && - test_cmp expected-initial-signed actual && - git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && - test_cmp expected-signed actual + test_commit_message HEAD^ expected-initial-signed && + test_commit_message HEAD expected-signed ' test_expect_success 'rebase -i --signoff fails' ' git commit --amend -m "first" && git rebase -i --signoff HEAD^ && - git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && - test_cmp expected-signed actual + test_commit_message HEAD expected-signed ' test_expect_success 'rebase -m --signoff fails' ' git commit --amend -m "first" && git rebase -m --signoff HEAD^ && - git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && - test_cmp expected-signed actual + test_commit_message HEAD expected-signed ' test_done diff --git a/t/t3438-rebase-broken-files.sh b/t/t3438-rebase-broken-files.sh index c614c4f..821f08e 100755 --- a/t/t3438-rebase-broken-files.sh +++ b/t/t3438-rebase-broken-files.sh @@ -58,4 +58,13 @@ test_expect_success 'unknown key in author-script' ' check_resolve_fails ' +test_expect_success POSIXPERM,SANITY 'unwritable rebased-patches does not leak' ' + >.git/rebased-patches && + chmod a-w .git/rebased-patches && + + git checkout -b side HEAD^ && + test_commit unrelated && + test_must_fail git rebase --apply --onto tmp HEAD^ +' + test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index aeab689..411027f 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -104,11 +104,19 @@ test_expect_success 'revert forbidden on dirty working tree' ' ' test_expect_success 'cherry-pick on unborn branch' ' - git checkout --orphan unborn && + git switch --orphan unborn && git rm --cached -r . && - rm -rf * && git cherry-pick initial && - git diff --quiet initial && + git diff --exit-code initial && + test_cmp_rev ! initial HEAD +' + +test_expect_success 'cherry-pick on unborn branch with --allow-empty' ' + git checkout --detach && + git branch -D unborn && + git switch --orphan unborn && + git cherry-pick initial --allow-empty && + git diff --exit-code initial && test_cmp_rev ! initial HEAD ' @@ -170,6 +178,7 @@ test_expect_success 'advice from failed revert' ' hint: You can instead skip this commit with "git revert --skip". hint: To abort and get back to the state before "git revert", hint: run "git revert --abort". + hint: Disable this message with "git config advice.mergeConflict false" EOF test_commit --append --no-tag "double-add dream" dream dream && test_must_fail git revert HEAD^ 2>actual && diff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh index eba3c38..9748443 100755 --- a/t/t3505-cherry-pick-empty.sh +++ b/t/t3505-cherry-pick-empty.sh @@ -84,7 +84,7 @@ test_expect_success 'cherry-pick a commit that becomes no-op (prep)' ' git commit -m "add file2 on the side" ' -test_expect_success 'cherry-pick a no-op without --keep-redundant' ' +test_expect_success 'cherry-pick a no-op with neither --keep-redundant nor --empty' ' git reset --hard && git checkout fork^0 && test_must_fail git cherry-pick main @@ -99,4 +99,53 @@ test_expect_success 'cherry-pick a no-op with --keep-redundant' ' test_cmp expect actual ' +test_expect_success '--keep-redundant-commits is incompatible with operations' ' + test_must_fail git cherry-pick HEAD 2>output && + test_grep "The previous cherry-pick is now empty" output && + test_must_fail git cherry-pick --keep-redundant-commits --continue 2>output && + test_grep "fatal: cherry-pick: --keep-redundant-commits cannot be used with --continue" output && + test_must_fail git cherry-pick --keep-redundant-commits --skip 2>output && + test_grep "fatal: cherry-pick: --keep-redundant-commits cannot be used with --skip" output && + test_must_fail git cherry-pick --keep-redundant-commits --abort 2>output && + test_grep "fatal: cherry-pick: --keep-redundant-commits cannot be used with --abort" output && + test_must_fail git cherry-pick --keep-redundant-commits --quit 2>output && + test_grep "fatal: cherry-pick: --keep-redundant-commits cannot be used with --quit" output && + git cherry-pick --abort +' + +test_expect_success '--empty is incompatible with operations' ' + test_must_fail git cherry-pick HEAD 2>output && + test_grep "The previous cherry-pick is now empty" output && + test_must_fail git cherry-pick --empty=stop --continue 2>output && + test_grep "fatal: cherry-pick: --empty cannot be used with --continue" output && + test_must_fail git cherry-pick --empty=stop --skip 2>output && + test_grep "fatal: cherry-pick: --empty cannot be used with --skip" output && + test_must_fail git cherry-pick --empty=stop --abort 2>output && + test_grep "fatal: cherry-pick: --empty cannot be used with --abort" output && + test_must_fail git cherry-pick --empty=stop --quit 2>output && + test_grep "fatal: cherry-pick: --empty cannot be used with --quit" output && + git cherry-pick --abort +' + +test_expect_success 'cherry-pick a no-op with --empty=stop' ' + git reset --hard && + git checkout fork^0 && + test_must_fail git cherry-pick --empty=stop main 2>output && + test_grep "The previous cherry-pick is now empty" output +' + +test_expect_success 'cherry-pick a no-op with --empty=drop' ' + git reset --hard && + git checkout fork^0 && + git cherry-pick --empty=drop main && + test_commit_message HEAD -m "add file2 on the side" +' + +test_expect_success 'cherry-pick a no-op with --empty=keep' ' + git reset --hard && + git checkout fork^0 && + git cherry-pick --empty=keep main && + test_commit_message HEAD -m "add file2 on main" +' + test_done diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index c88d597..f3947b4 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -60,6 +60,7 @@ test_expect_success 'advice from failed cherry-pick' ' hint: You can instead skip this commit with "git cherry-pick --skip". hint: To abort and get back to the state before "git cherry-pick", hint: run "git cherry-pick --abort". + hint: Disable this message with "git config advice.mergeConflict false" EOF test_must_fail git cherry-pick picked 2>actual && @@ -74,6 +75,7 @@ test_expect_success 'advice from failed cherry-pick --no-commit' " error: could not apply \$picked... picked hint: after resolving the conflicts, mark the corrected paths hint: with 'git add <paths>' or 'git rm <paths>' + hint: Disable this message with \"git config advice.mergeConflict false\" EOF test_must_fail git cherry-pick --no-commit picked 2>actual && diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 72020a5..7eb52b1 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -90,6 +90,38 @@ test_expect_success 'cherry-pick persists opts correctly' ' test_cmp expect actual ' +test_expect_success 'cherry-pick persists --empty=stop correctly' ' + pristine_detach yetanotherpick && + # Picking `anotherpick` forces a conflict so that we stop. That + # commit is then skipped, after which we pick `yetanotherpick` + # while already on `yetanotherpick` to cause an empty commit + test_must_fail git cherry-pick --empty=stop anotherpick yetanotherpick && + test_must_fail git cherry-pick --skip 2>msg && + test_grep "The previous cherry-pick is now empty" msg && + rm msg && + git cherry-pick --abort +' + +test_expect_success 'cherry-pick persists --empty=drop correctly' ' + pristine_detach yetanotherpick && + # Picking `anotherpick` forces a conflict so that we stop. That + # commit is then skipped, after which we pick `yetanotherpick` + # while already on `yetanotherpick` to cause an empty commit + test_must_fail git cherry-pick --empty=drop anotherpick yetanotherpick && + git cherry-pick --skip && + test_cmp_rev yetanotherpick HEAD +' + +test_expect_success 'cherry-pick persists --empty=keep correctly' ' + pristine_detach yetanotherpick && + # Picking `anotherpick` forces a conflict so that we stop. That + # commit is then skipped, after which we pick `yetanotherpick` + # while already on `yetanotherpick` to cause an empty commit + test_must_fail git cherry-pick --empty=keep anotherpick yetanotherpick && + git cherry-pick --skip && + test_cmp_rev yetanotherpick HEAD^ +' + test_expect_success 'revert persists opts correctly' ' pristine_detach initial && # to make sure that the session to revert a sequence diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh new file mode 100755 index 0000000..3896702 --- /dev/null +++ b/t/t3650-replay-basics.sh @@ -0,0 +1,198 @@ +#!/bin/sh + +test_description='basic git replay tests' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +GIT_AUTHOR_NAME=author@name +GIT_AUTHOR_EMAIL=bogus@email@address +export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL + +test_expect_success 'setup' ' + test_commit A && + test_commit B && + + git switch -c topic1 && + test_commit C && + git switch -c topic2 && + test_commit D && + test_commit E && + git switch topic1 && + test_commit F && + git switch -c topic3 && + test_commit G && + test_commit H && + git switch -c topic4 main && + test_commit I && + test_commit J && + + git switch -c next main && + test_commit K && + git merge -m "Merge topic1" topic1 && + git merge -m "Merge topic2" topic2 && + git merge -m "Merge topic3" topic3 && + >evil && + git add evil && + git commit --amend && + git merge -m "Merge topic4" topic4 && + + git switch main && + test_commit L && + test_commit M && + + git switch -c conflict B && + test_commit C.conflict C.t conflict +' + +test_expect_success 'setup bare' ' + git clone --bare . bare +' + +test_expect_success 'using replay to rebase two branches, one on top of other' ' + git replay --onto main topic1..topic2 >result && + + test_line_count = 1 result && + + git log --format=%s $(cut -f 3 -d " " result) >actual && + test_write_lines E D M L B A >expect && + test_cmp expect actual && + + printf "update refs/heads/topic2 " >expect && + printf "%s " $(cut -f 3 -d " " result) >>expect && + git rev-parse topic2 >>expect && + + test_cmp expect result +' + +test_expect_success 'using replay on bare repo to rebase two branches, one on top of other' ' + git -C bare replay --onto main topic1..topic2 >result-bare && + test_cmp expect result-bare +' + +test_expect_success 'using replay to rebase with a conflict' ' + test_expect_code 1 git replay --onto topic1 B..conflict +' + +test_expect_success 'using replay on bare repo to rebase with a conflict' ' + test_expect_code 1 git -C bare replay --onto topic1 B..conflict +' + +test_expect_success 'using replay to perform basic cherry-pick' ' + # The differences between this test and previous ones are: + # --advance vs --onto + # 2nd field of result is refs/heads/main vs. refs/heads/topic2 + # 4th field of result is hash for main instead of hash for topic2 + + git replay --advance main topic1..topic2 >result && + + test_line_count = 1 result && + + git log --format=%s $(cut -f 3 -d " " result) >actual && + test_write_lines E D M L B A >expect && + test_cmp expect actual && + + printf "update refs/heads/main " >expect && + printf "%s " $(cut -f 3 -d " " result) >>expect && + git rev-parse main >>expect && + + test_cmp expect result +' + +test_expect_success 'using replay on bare repo to perform basic cherry-pick' ' + git -C bare replay --advance main topic1..topic2 >result-bare && + test_cmp expect result-bare +' + +test_expect_success 'replay on bare repo fails with both --advance and --onto' ' + test_must_fail git -C bare replay --advance main --onto main topic1..topic2 >result-bare +' + +test_expect_success 'replay fails when both --advance and --onto are omitted' ' + test_must_fail git replay topic1..topic2 >result +' + +test_expect_success 'using replay to also rebase a contained branch' ' + git replay --contained --onto main main..topic3 >result && + + test_line_count = 2 result && + cut -f 3 -d " " result >new-branch-tips && + + git log --format=%s $(head -n 1 new-branch-tips) >actual && + test_write_lines F C M L B A >expect && + test_cmp expect actual && + + git log --format=%s $(tail -n 1 new-branch-tips) >actual && + test_write_lines H G F C M L B A >expect && + test_cmp expect actual && + + printf "update refs/heads/topic1 " >expect && + printf "%s " $(head -n 1 new-branch-tips) >>expect && + git rev-parse topic1 >>expect && + printf "update refs/heads/topic3 " >>expect && + printf "%s " $(tail -n 1 new-branch-tips) >>expect && + git rev-parse topic3 >>expect && + + test_cmp expect result +' + +test_expect_success 'using replay on bare repo to also rebase a contained branch' ' + git -C bare replay --contained --onto main main..topic3 >result-bare && + test_cmp expect result-bare +' + +test_expect_success 'using replay to rebase multiple divergent branches' ' + git replay --onto main ^topic1 topic2 topic4 >result && + + test_line_count = 2 result && + cut -f 3 -d " " result >new-branch-tips && + + git log --format=%s $(head -n 1 new-branch-tips) >actual && + test_write_lines E D M L B A >expect && + test_cmp expect actual && + + git log --format=%s $(tail -n 1 new-branch-tips) >actual && + test_write_lines J I M L B A >expect && + test_cmp expect actual && + + printf "update refs/heads/topic2 " >expect && + printf "%s " $(head -n 1 new-branch-tips) >>expect && + git rev-parse topic2 >>expect && + printf "update refs/heads/topic4 " >>expect && + printf "%s " $(tail -n 1 new-branch-tips) >>expect && + git rev-parse topic4 >>expect && + + test_cmp expect result +' + +test_expect_success 'using replay on bare repo to rebase multiple divergent branches, including contained ones' ' + git -C bare replay --contained --onto main ^main topic2 topic3 topic4 >result && + + test_line_count = 4 result && + cut -f 3 -d " " result >new-branch-tips && + + >expect && + for i in 2 1 3 4 + do + printf "update refs/heads/topic$i " >>expect && + printf "%s " $(grep topic$i result | cut -f 3 -d " ") >>expect && + git -C bare rev-parse topic$i >>expect || return 1 + done && + + test_cmp expect result && + + test_write_lines F C M L B A >expect1 && + test_write_lines E D C M L B A >expect2 && + test_write_lines H G F C M L B A >expect3 && + test_write_lines J I M L B A >expect4 && + + for i in 1 2 3 4 + do + git -C bare log --format=%s $(grep topic$i result | cut -f 3 -d " ") >actual && + test_cmp expect$i actual || return 1 + done +' + +test_done diff --git a/t/t3700-add.sh b/t/t3700-add.sh index f23d39f..839c904 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -28,6 +28,16 @@ test_expect_success 'Test of git add' ' touch foo && git add foo ' +test_expect_success 'Test with no pathspecs' ' + cat >expect <<-EOF && + Nothing specified, nothing added. + hint: Maybe you wanted to say ${SQ}git add .${SQ}? + hint: Disable this message with "git config advice.addEmptyPathspec false" + EOF + git add 2>actual && + test_cmp expect actual +' + test_expect_success 'Post-check that foo is in the index' ' git ls-files foo | grep foo ' @@ -339,6 +349,40 @@ test_expect_success '"git add ." in empty repo' ' ) ' +test_expect_success '"git add" a embedded repository' ' + rm -fr outer && git init outer && + ( + cd outer && + for i in 1 2 + do + name=inner$i && + git init $name && + git -C $name commit --allow-empty -m $name || + return 1 + done && + git add . 2>actual && + cat >expect <<-EOF && + warning: adding embedded git repository: inner1 + hint: You${SQ}ve added another git repository inside your current repository. + hint: Clones of the outer repository will not contain the contents of + hint: the embedded repository and will not know how to obtain it. + hint: If you meant to add a submodule, use: + hint: + hint: git submodule add <url> inner1 + hint: + hint: If you added this path by mistake, you can remove it from the + hint: index with: + hint: + hint: git rm --cached inner1 + hint: + hint: See "git help submodule" for more information. + hint: Disable this message with "git config advice.addEmbeddedRepo false" + warning: adding embedded git repository: inner2 + EOF + test_cmp expect actual + ) +' + test_expect_success 'error on a repository with no commits' ' rm -fr empty && git init empty && @@ -370,8 +414,7 @@ cat >expect.err <<\EOF The following paths are ignored by one of your .gitignore files: ignored-file hint: Use -f if you really want to add them. -hint: Turn this message off by running -hint: "git config advice.addIgnoredFile false" +hint: Disable this message with "git config advice.addIgnoredFile false" EOF cat >expect.out <<\EOF add 'track-this' diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 0b5339a..04d8333 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -4,6 +4,7 @@ test_description='add -i basic tests' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-terminal.sh @@ -325,9 +326,9 @@ test_expect_success 'different prompts for mode change/deleted' ' git -c core.filemode=true add -p >actual && sed -n "s/^\(([0-9/]*) Stage .*?\).*/\1/p" actual >actual.filtered && cat >expect <<-\EOF && - (1/1) Stage deletion [y,n,q,a,d,?]? - (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,?]? - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? + (1/1) Stage deletion [y,n,q,a,d,p,?]? + (1/2) Stage mode change [y,n,q,a,d,j,J,g,/,p,?]? + (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? EOF test_cmp expect actual.filtered ' @@ -514,13 +515,13 @@ test_expect_success 'split hunk setup' ' test_expect_success 'goto hunk' ' test_when_finished "git reset" && tr _ " " >expect <<-EOF && - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? + 1: -1,2 +1,3 +15 + (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? + 1: -1,2 +1,3 +15 _ 2: -2,4 +3,8 +21 go to which hunk? @@ -1,2 +1,3 @@ _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ EOF test_write_lines s y g 1 | git add -p >actual && tail -n 7 <actual >actual.trimmed && @@ -530,11 +531,11 @@ test_expect_success 'goto hunk' ' test_expect_success 'navigate to hunk via regex' ' test_when_finished "git reset" && tr _ " " >expect <<-EOF && - (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? @@ -1,2 +1,3 @@ + (2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? @@ -1,2 +1,3 @@ _10 +15 _20 - (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?_ + (1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]?_ EOF test_write_lines s y /1,2 | git add -p >actual && tail -n 5 <actual >actual.trimmed && @@ -715,21 +716,21 @@ test_expect_success 'colors can be overridden' ' <BLUE>+<RESET><BLUE>new<RESET> <CYAN> more-context<RESET> <BLUE>+<RESET><BLUE>another-one<RESET> - <YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? <RESET><BOLD>Split into 2 hunks.<RESET> + <YELLOW>(1/1) Stage this hunk [y,n,q,a,d,s,e,p,?]? <RESET><BOLD>Split into 2 hunks.<RESET> <MAGENTA>@@ -1,3 +1,3 @@<RESET> <CYAN> context<RESET> <BOLD>-old<RESET> <BLUE>+<RESET><BLUE>new<RESET> <CYAN> more-context<RESET> - <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET> + <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET><MAGENTA>@@ -3 +3,2 @@<RESET> <CYAN> more-context<RESET> <BLUE>+<RESET><BLUE>another-one<RESET> - <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> + <YELLOW>(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,p,?]? <RESET><MAGENTA>@@ -1,3 +1,3 @@<RESET> <CYAN> context<RESET> <BOLD>-old<RESET> <BLUE>+new<RESET> <CYAN> more-context<RESET> - <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? <RESET> + <YELLOW>(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,p,?]? <RESET> EOF test_cmp expect actual ' diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index 34faeac..00db82f 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -200,7 +200,7 @@ test_expect_success 'drop stash reflog updates refs/stash' ' test_cmp expect actual ' -test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' ' +test_expect_success 'drop stash reflog updates refs/stash with rewrite' ' git init repo && ( cd repo && @@ -213,16 +213,16 @@ test_expect_success REFFILES 'drop stash reflog updates refs/stash with rewrite' new_oid="$(git -C repo rev-parse stash@{0})" && cat >expect <<-EOF && - $(test_oid zero) $old_oid - $old_oid $new_oid + $new_oid + $old_oid EOF - cut -d" " -f1-2 repo/.git/logs/refs/stash >actual && + git -C repo reflog show refs/stash --format=%H >actual && test_cmp expect actual && git -C repo stash drop stash@{1} && - cut -d" " -f1-2 repo/.git/logs/refs/stash >actual && + git -C repo reflog show refs/stash --format=%H >actual && cat >expect <<-EOF && - $(test_oid zero) $new_oid + $new_oid EOF test_cmp expect actual ' @@ -1516,4 +1516,56 @@ test_expect_success 'restore untracked files even when we hit conflicts' ' ) ' +test_expect_success 'stash create reports a locked index' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A A.file && + echo change >A.file && + touch .git/index.lock && + + cat >expect <<-EOF && + error: could not write index + EOF + test_must_fail git stash create 2>err && + test_cmp expect err + ) +' + +test_expect_success 'stash push reports a locked index' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A A.file && + echo change >A.file && + touch .git/index.lock && + + cat >expect <<-EOF && + error: could not write index + EOF + test_must_fail git stash push 2>err && + test_cmp expect err + ) +' + +test_expect_success 'stash apply reports a locked index' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + test_commit A A.file && + echo change >A.file && + git stash push && + touch .git/index.lock && + + cat >expect <<-EOF && + error: could not write index + EOF + test_must_fail git stash apply 2>err && + test_cmp expect err + ) +' + test_done diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh index accfe38..368fc2a 100755 --- a/t/t3904-stash-patch.sh +++ b/t/t3904-stash-patch.sh @@ -3,12 +3,6 @@ test_description='stash -p' . ./lib-patch-mode.sh -if ! test_have_prereq PERL -then - skip_all='skipping stash -p tests, perl not available' - test_done -fi - test_expect_success 'setup' ' mkdir dir && echo parent > dir/foo && diff --git a/t/t3920-crlf-messages.sh b/t/t3920-crlf-messages.sh index 67fd234..50ae222 100755 --- a/t/t3920-crlf-messages.sh +++ b/t/t3920-crlf-messages.sh @@ -10,7 +10,7 @@ LIB_CRLF_BRANCHES="" create_crlf_ref () { branch="$1" && cat >.crlf-orig-$branch.txt && - cat .crlf-orig-$branch.txt | append_cr >.crlf-message-$branch.txt && + append_cr <.crlf-orig-$branch.txt >.crlf-message-$branch.txt && grep 'Subject' .crlf-orig-$branch.txt | tr '\n' ' ' | sed 's/[ ]*$//' | tr -d '\n' >.crlf-subject-$branch.txt && grep 'Body' .crlf-orig-$branch.txt | append_cr >.crlf-body-$branch.txt && LIB_CRLF_BRANCHES="${LIB_CRLF_BRANCHES} ${branch}" && @@ -97,7 +97,7 @@ test_expect_success 'branch: --verbose works with messages using CRLF' ' git branch -v >tmp && # Remove first two columns, and the line for the currently checked out branch current=$(git branch --show-current) && - grep -v $current <tmp | awk "{\$1=\$2=\"\"}1" >actual && + awk "/$current/ { next } { \$1 = \$2 = \"\" } 1" <tmp >actual && test_cmp expect actual ' diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index 85be136..49c042a 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -286,4 +286,28 @@ test_expect_success 'basename similarity vs best similarity' ' test_cmp expected actual ' +test_expect_success 'last line matters too' ' + { + test_write_lines a 0 1 2 3 4 5 6 7 8 9 && + printf "git ignores final up to 63 characters if not newline terminated" + } >no-final-lf && + git add no-final-lf && + git commit -m "original version of file with no final newline" && + + # Change ONLY the first character of the whole file + { + test_write_lines b 0 1 2 3 4 5 6 7 8 9 && + printf "git ignores final up to 63 characters if not newline terminated" + } >no-final-lf && + git add no-final-lf && + git mv no-final-lf still-absent-final-lf && + git commit -a -m "rename no-final-lf -> still-absent-final-lf" && + git diff-tree -r -M --name-status HEAD^ HEAD >actual && + sed -e "s/^R[0-9]* /R /" actual >actual.munged && + cat >expected <<-\EOF && + R no-final-lf still-absent-final-lf + EOF + test_cmp expected actual.munged +' + test_done diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh index 7afc883..cb33070 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -405,7 +405,7 @@ test_expect_success 'diff-tree -r B A == diff-tree -r -R A B' ' test_expect_success 'diff can read from stdin' ' test_must_fail git diff --no-index -- MN - < NN | - grep -v "^index" | sed "s#/-#/NN#" >.test-a && + sed "/^index/d; s#/-#/NN#" >.test-a && test_must_fail git diff --no-index -- MN NN | grep -v "^index" >.test-b && test_cmp .test-a .test-b diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index d7a5f7a..bc8ba88 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -13,13 +13,13 @@ TEST_PASSES_SANITIZE_LEAK=true # Print the short OID of a symlink with the given name. symlink_oid () { - local oid=$(printf "%s" "$1" | git hash-object --stdin) && + local oid="$(printf "%s" "$1" | git hash-object --stdin)" && git rev-parse --short "$oid" } # Print the short OID of the given file. short_oid () { - local oid=$(git hash-object "$1") && + local oid="$(git hash-object "$1")" && git rev-parse --short "$oid" } diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index 5cc17c2..3855d68 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -178,32 +178,29 @@ process_diffs () { V=$(git version | sed -e 's/^git version //' -e 's/\./\\./g') while read magic cmd do - status=success case "$magic" in '' | '#'*) continue ;; - :*) - magic=${magic#:} + :noellipses) + magic=noellipses label="$magic-$cmd" - case "$magic" in - noellipses) ;; - failure) - status=failure - magic= - label="$cmd" ;; - *) - BUG "unknown magic $magic" ;; - esac ;; + ;; + :*) + BUG "unknown magic $magic" + ;; *) - cmd="$magic $cmd" magic= - label="$cmd" ;; + cmd="$magic $cmd" + magic= + label="$cmd" + ;; esac + test=$(echo "$label" | sed -e 's|[/ ][/ ]*|_|g') pfx=$(printf "%04d" $test_count) expect="$TEST_DIRECTORY/t4013/diff.$test" actual="$pfx-diff.$test" - test_expect_$status "git $cmd # magic is ${magic:-(not used)}" ' + test_expect_success "git $cmd # magic is ${magic:-(not used)}" ' { echo "$ git $cmd" case "$magic" in @@ -522,7 +519,7 @@ test_expect_success 'log -S requires an argument' ' ' test_expect_success 'diff --cached on unborn branch' ' - echo ref: refs/heads/unborn >.git/HEAD && + git symbolic-ref HEAD refs/heads/unborn && git diff --cached >result && process_diffs result >actual && process_diffs "$TEST_DIRECTORY/t4013/diff.diff_--cached" >expected && @@ -636,8 +633,8 @@ check_prefix () { test_cmp expect actual.paths } -test_expect_success 'diff-files does not respect diff.noprefix' ' - git -c diff.noprefix diff-files -p >actual && +test_expect_success 'diff-files does not respect diff.noPrefix' ' + git -c diff.noPrefix diff-files -p >actual && check_prefix actual a/file0 b/file0 ' @@ -646,24 +643,65 @@ test_expect_success 'diff-files respects --no-prefix' ' check_prefix actual file0 file0 ' -test_expect_success 'diff respects diff.noprefix' ' - git -c diff.noprefix diff >actual && +test_expect_success 'diff respects diff.noPrefix' ' + git -c diff.noPrefix diff >actual && check_prefix actual file0 file0 ' -test_expect_success 'diff --default-prefix overrides diff.noprefix' ' - git -c diff.noprefix diff --default-prefix >actual && +test_expect_success 'diff --default-prefix overrides diff.noPrefix' ' + git -c diff.noPrefix diff --default-prefix >actual && check_prefix actual a/file0 b/file0 ' -test_expect_success 'diff respects diff.mnemonicprefix' ' - git -c diff.mnemonicprefix diff >actual && +test_expect_success 'diff respects diff.mnemonicPrefix' ' + git -c diff.mnemonicPrefix diff >actual && + check_prefix actual i/file0 w/file0 +' + +test_expect_success 'diff --default-prefix overrides diff.mnemonicPrefix' ' + git -c diff.mnemonicPrefix diff --default-prefix >actual && + check_prefix actual a/file0 b/file0 +' + +test_expect_success 'diff respects diff.srcPrefix' ' + git -c diff.srcPrefix=x/ diff >actual && + check_prefix actual x/file0 b/file0 +' + +test_expect_success 'diff respects diff.dstPrefix' ' + git -c diff.dstPrefix=y/ diff >actual && + check_prefix actual a/file0 y/file0 +' + +test_expect_success 'diff --src-prefix overrides diff.srcPrefix' ' + git -c diff.srcPrefix=y/ diff --src-prefix=z/ >actual && + check_prefix actual z/file0 b/file0 +' + +test_expect_success 'diff --dst-prefix overrides diff.dstPrefix' ' + git -c diff.dstPrefix=y/ diff --dst-prefix=z/ >actual && + check_prefix actual a/file0 z/file0 +' + +test_expect_success 'diff.{src,dst}Prefix ignored with diff.noPrefix' ' + git -c diff.dstPrefix=y/ -c diff.srcPrefix=x/ -c diff.noPrefix diff >actual && + check_prefix actual file0 file0 +' + +test_expect_success 'diff.{src,dst}Prefix ignored with diff.mnemonicPrefix' ' + git -c diff.dstPrefix=x/ -c diff.srcPrefix=y/ -c diff.mnemonicPrefix diff >actual && check_prefix actual i/file0 w/file0 ' -test_expect_success 'diff --default-prefix overrides diff.mnemonicprefix' ' - git -c diff.mnemonicprefix diff --default-prefix >actual && +test_expect_success 'diff.{src,dst}Prefix ignored with --default-prefix' ' + git -c diff.dstPrefix=x/ -c diff.srcPrefix=y/ diff --default-prefix >actual && check_prefix actual a/file0 b/file0 ' +test_expect_success 'diff --no-renames cannot be abbreviated' ' + test_expect_code 129 git diff --no-rename >actual 2>error && + test_must_be_empty actual && + grep "invalid option: --no-rename" error +' + test_done diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 5ced27e..e37a141 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -1906,6 +1906,16 @@ body" && grep "^body$" actual ' +test_expect_success 'cover letter with --cover-from-description subject (UTF-8 subject line)' ' + test_config branch.rebuild-1.description "Café? + +body" && + git checkout rebuild-1 && + git format-patch --stdout --cover-letter --cover-from-description subject --encode-email-headers main >actual && + grep "^Subject: \[PATCH 0/2\] =?UTF-8?q?Caf=C3=A9=3F?=$" actual && + ! grep "Café" actual +' + test_expect_success 'cover letter with format.coverFromDescription = auto (short subject line)' ' test_config branch.rebuild-1.description "config subject diff --git a/t/t4018/csharp-exclude-assignments b/t/t4018/csharp-exclude-assignments new file mode 100644 index 0000000..239f312 --- /dev/null +++ b/t/t4018/csharp-exclude-assignments @@ -0,0 +1,20 @@ +class Example +{ + string Method(int RIGHT) + { + var constantAssignment = "test"; + var methodAssignment = MethodCall(); + var multiLineMethodAssignment = MethodCall( + ); + var multiLine = "first" + + MethodCall() + + + ( MethodCall() + ) + + MethodCall(); + + return "ChangeMe"; + } + + string MethodCall(int a = 0, int b = 0) => "test"; +} diff --git a/t/t4018/csharp-exclude-control-statements b/t/t4018/csharp-exclude-control-statements new file mode 100644 index 0000000..3a0f404 --- /dev/null +++ b/t/t4018/csharp-exclude-control-statements @@ -0,0 +1,34 @@ +class Example +{ + string Method(int RIGHT) + { + if (false) + { + return "out"; + } + else { } + if (true) MethodCall( + ); + else MethodCall( + ); + switch ("test") + { + case "one": + return MethodCall( + ); + case "two": + break; + } + (int, int) tuple = (1, 4); + switch (tuple) + { + case (1, 4): + MethodCall(); + break; + } + + return "ChangeMe"; + } + + string MethodCall(int a = 0, int b = 0) => "test"; +} diff --git a/t/t4018/csharp-exclude-exceptions b/t/t4018/csharp-exclude-exceptions new file mode 100644 index 0000000..b1e6425 --- /dev/null +++ b/t/t4018/csharp-exclude-exceptions @@ -0,0 +1,29 @@ +using System; + +class Example +{ + string Method(int RIGHT) + { + try + { + throw new Exception("fail"); + } + catch (Exception) + { + } + finally + { + } + try { } catch (Exception) {} + try + { + throw GetException( + ); + } + catch (Exception) { } + + return "ChangeMe"; + } + + Exception GetException() => new Exception("fail"); +} diff --git a/t/t4018/csharp-exclude-generic-method-calls b/t/t4018/csharp-exclude-generic-method-calls new file mode 100644 index 0000000..31af546 --- /dev/null +++ b/t/t4018/csharp-exclude-generic-method-calls @@ -0,0 +1,12 @@ +class Example +{ + string Method(int RIGHT) + { + GenericMethodCall<int, int>( + ); + + return "ChangeMe"; + } + + string GenericMethodCall<T, T2>() => "test"; +} diff --git a/t/t4018/csharp-exclude-init-dispose b/t/t4018/csharp-exclude-init-dispose new file mode 100644 index 0000000..2bc8e19 --- /dev/null +++ b/t/t4018/csharp-exclude-init-dispose @@ -0,0 +1,22 @@ +using System; + +class Example : IDisposable +{ + string Method(int RIGHT) + { + new Example(); + new Example( + ); + new Example { }; + using (this) + { + } + var def = + this is default( + Example); + + return "ChangeMe"; + } + + public void Dispose() {} +} diff --git a/t/t4018/csharp-exclude-iterations b/t/t4018/csharp-exclude-iterations new file mode 100644 index 0000000..960aa18 --- /dev/null +++ b/t/t4018/csharp-exclude-iterations @@ -0,0 +1,26 @@ +using System.Linq; + +class Example +{ + string Method(int RIGHT) + { + do { } while (true); + do MethodCall( + ); while (true); + while (true); + while (true) { + break; + } + for (int i = 0; i < 10; ++i) + { + } + foreach (int i in Enumerable.Range(0, 10)) + { + } + int[] numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0]; + + return "ChangeMe"; + } + + string MethodCall(int a = 0, int b = 0) => "test"; +} diff --git a/t/t4018/csharp-exclude-method-calls b/t/t4018/csharp-exclude-method-calls new file mode 100644 index 0000000..51e2dc2 --- /dev/null +++ b/t/t4018/csharp-exclude-method-calls @@ -0,0 +1,20 @@ +class Example +{ + string Method(int RIGHT) + { + MethodCall(); + MethodCall(1, 2); + MethodCall( + 1, 2); + MethodCall( + 1, 2, + 3); + MethodCall( + 1, MethodCall(), + 2); + + return "ChangeMe"; + } + + int MethodCall(int a = 0, int b = 0, int c = 0) => 42; +} diff --git a/t/t4018/csharp-exclude-other b/t/t4018/csharp-exclude-other new file mode 100644 index 0000000..4d5581c --- /dev/null +++ b/t/t4018/csharp-exclude-other @@ -0,0 +1,18 @@ +class Example +{ + string Method(int RIGHT) + { + lock (this) + { + } + unsafe + { + byte[] bytes = [1, 2, 3]; + fixed (byte* pointerToFirst = bytes) + { + } + } + + return "ChangeMe"; + } +} diff --git a/t/t4018/csharp-method b/t/t4018/csharp-method new file mode 100644 index 0000000..16b367a --- /dev/null +++ b/t/t4018/csharp-method @@ -0,0 +1,10 @@ +class Example +{ + string Method(int RIGHT) + { + // Filler + // Filler + + return "ChangeMe"; + } +} diff --git a/t/t4018/csharp-method-array b/t/t4018/csharp-method-array new file mode 100644 index 0000000..1126de8 --- /dev/null +++ b/t/t4018/csharp-method-array @@ -0,0 +1,10 @@ +class Example +{ + string[] Method(int RIGHT) + { + // Filler + // Filler + + return ["ChangeMe"]; + } +} diff --git a/t/t4018/csharp-method-explicit b/t/t4018/csharp-method-explicit new file mode 100644 index 0000000..5a71011 --- /dev/null +++ b/t/t4018/csharp-method-explicit @@ -0,0 +1,12 @@ +using System; + +class Example : IDisposable +{ + void IDisposable.Dispose() // RIGHT + { + // Filler + // Filler + + // ChangeMe + } +} diff --git a/t/t4018/csharp-method-generics b/t/t4018/csharp-method-generics new file mode 100644 index 0000000..b3216bf --- /dev/null +++ b/t/t4018/csharp-method-generics @@ -0,0 +1,11 @@ +class Example<T1, T2> +{ + Example<int, string> Method<TA, TB>(TA RIGHT, TB b) + { + // Filler + // Filler + + // ChangeMe + return null; + } +} diff --git a/t/t4018/csharp-method-generics-alternate-spaces b/t/t4018/csharp-method-generics-alternate-spaces new file mode 100644 index 0000000..9583621 --- /dev/null +++ b/t/t4018/csharp-method-generics-alternate-spaces @@ -0,0 +1,11 @@ +class Example<T1, T2> +{ + Example<int,string> Method<TA ,TB>(TA RIGHT, TB b) + { + // Filler + // Filler + + // ChangeMe + return null; + } +} diff --git a/t/t4018/csharp-method-modifiers b/t/t4018/csharp-method-modifiers new file mode 100644 index 0000000..caefa8e --- /dev/null +++ b/t/t4018/csharp-method-modifiers @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +class Example +{ + static internal async Task Method(int RIGHT) + { + // Filler + // Filler + + // ChangeMe + await Task.Delay(1); + } +} diff --git a/t/t4018/csharp-method-multiline b/t/t4018/csharp-method-multiline new file mode 100644 index 0000000..3983ff4 --- /dev/null +++ b/t/t4018/csharp-method-multiline @@ -0,0 +1,10 @@ +class Example +{ + string Method_RIGHT( + int a, + int b, + int c) + { + return "ChangeMe"; + } +} diff --git a/t/t4018/csharp-method-params b/t/t4018/csharp-method-params new file mode 100644 index 0000000..3f00410 --- /dev/null +++ b/t/t4018/csharp-method-params @@ -0,0 +1,10 @@ +class Example +{ + string Method(int RIGHT, int b, int c = 42) + { + // Filler + // Filler + + return "ChangeMe"; + } +} diff --git a/t/t4018/csharp-method-special-chars b/t/t4018/csharp-method-special-chars new file mode 100644 index 0000000..e6c7bc0 --- /dev/null +++ b/t/t4018/csharp-method-special-chars @@ -0,0 +1,11 @@ +class @Some_Type +{ + @Some_Type @Method_With_Underscore(int RIGHT) + { + // Filler + // Filler + + // ChangeMe + return new @Some_Type(); + } +} diff --git a/t/t4018/csharp-method-with-spacing b/t/t4018/csharp-method-with-spacing new file mode 100644 index 0000000..233bb97 --- /dev/null +++ b/t/t4018/csharp-method-with-spacing @@ -0,0 +1,10 @@ +class Example +{ + string Method ( int RIGHT ) + { + // Filler + // Filler + + return "ChangeMe"; + } +} diff --git a/t/t4018/csharp-property b/t/t4018/csharp-property new file mode 100644 index 0000000..e56dfce --- /dev/null +++ b/t/t4018/csharp-property @@ -0,0 +1,11 @@ +class Example +{ + public bool RIGHT + { + get { return true; } + set + { + // ChangeMe + } + } +} diff --git a/t/t4018/csharp-property-braces-same-line b/t/t4018/csharp-property-braces-same-line new file mode 100644 index 0000000..608131d --- /dev/null +++ b/t/t4018/csharp-property-braces-same-line @@ -0,0 +1,10 @@ +class Example +{ + public bool RIGHT { + get { return true; } + set + { + // ChangeMe + } + } +} diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index c1ac09e..fdd865f 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -232,7 +232,7 @@ keep_only_cr () { test_expect_success 'external diff with autocrlf = true' ' test_config core.autocrlf true && GIT_EXTERNAL_DIFF=./fake-diff.sh git diff && - test $(wc -l < crlfed.txt) = $(cat crlfed.txt | keep_only_cr | wc -c) + test $(wc -l <crlfed.txt) = $(keep_only_cr <crlfed.txt | wc -c) ' test_expect_success 'diff --cached' ' diff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh index bf33aed..8ebfa3c 100755 --- a/t/t4042-diff-textconv-caching.sh +++ b/t/t4042-diff-textconv-caching.sh @@ -118,4 +118,26 @@ test_expect_success 'log notes cache and still use cache for -p' ' git log --no-walk -p refs/notes/textconv/magic HEAD ' +test_expect_success 'caching is silently ignored outside repo' ' + mkdir -p non-repo && + echo one >non-repo/one && + echo two >non-repo/two && + echo "* diff=test" >attr && + test_expect_code 1 \ + nongit git -c core.attributesFile="$PWD/attr" \ + -c diff.test.textconv="tr a-z A-Z <" \ + -c diff.test.cachetextconv=true \ + diff --no-index one two >actual && + cat >expect <<-\EOF && + diff --git a/one b/two + index 5626abf..f719efd 100644 + --- a/one + +++ b/two + @@ -1 +1 @@ + -ONE + +TWO + EOF + test_cmp expect actual +' + test_done diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh index 5ce345d..651ec77 100755 --- a/t/t4053-diff-no-index.sh +++ b/t/t4053-diff-no-index.sh @@ -205,6 +205,18 @@ test_expect_success POSIXPERM,SYMLINKS 'diff --no-index normalizes: mode not lik test_cmp expected actual ' +test_expect_success POSIXPERM 'external diff with mode-only change' ' + echo content >not-executable && + echo content >executable && + chmod +x executable && + echo executable executable $(test_oid zero) 100755 \ + not-executable $(test_oid zero) 100644 not-executable \ + >expect && + test_expect_code 1 git -c diff.external=echo diff \ + --no-index executable not-executable >actual && + test_cmp expect actual +' + test_expect_success "diff --no-index treats '-' as stdin" ' cat >expect <<-EOF && diff --git a/- b/a/1 diff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh index d370ecf..144619a 100755 --- a/t/t4103-apply-binary.sh +++ b/t/t4103-apply-binary.sh @@ -9,6 +9,7 @@ test_description='git apply handling binary patches 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/t4104-apply-boundary.sh b/t/t4104-apply-boundary.sh index 71ef413..dc501aa 100755 --- a/t/t4104-apply-boundary.sh +++ b/t/t4104-apply-boundary.sh @@ -5,6 +5,7 @@ test_description='git apply boundary tests' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh L="c d e f g h i j k l m n o p q r s t u v w x" diff --git a/t/t4113-apply-ending.sh b/t/t4113-apply-ending.sh index 66fa515..2c65c6a 100755 --- a/t/t4113-apply-ending.sh +++ b/t/t4113-apply-ending.sh @@ -6,6 +6,7 @@ test_description='git apply trying to add an ending line. ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh # setup diff --git a/t/t4117-apply-reject.sh b/t/t4117-apply-reject.sh index c86d05a..4d15ccd 100755 --- a/t/t4117-apply-reject.sh +++ b/t/t4117-apply-reject.sh @@ -7,6 +7,7 @@ test_description='git apply with rejects ' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success setup ' diff --git a/t/t4123-apply-shrink.sh b/t/t4123-apply-shrink.sh index 3ef8461..3601c0c 100755 --- a/t/t4123-apply-shrink.sh +++ b/t/t4123-apply-shrink.sh @@ -2,6 +2,7 @@ test_description='apply a patch that is larger than the preimage' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh cat >F <<\EOF diff --git a/t/t4126-apply-empty.sh b/t/t4126-apply-empty.sh index ece9fae..56210b5 100755 --- a/t/t4126-apply-empty.sh +++ b/t/t4126-apply-empty.sh @@ -66,4 +66,28 @@ test_expect_success 'apply --index create' ' git diff --exit-code ' +test_expect_success !MINGW 'apply with no-contents and a funny pathname' ' + test_when_finished "rm -fr \"funny \"; git reset --hard" && + + mkdir "funny " && + >"funny /empty" && + git add "funny /empty" && + git diff HEAD -- "funny /" >sample.patch && + git diff -R HEAD -- "funny /" >elpmas.patch && + + git reset --hard && + + git apply --stat --check --apply sample.patch && + test_must_be_empty "funny /empty" && + + git apply --stat --check --apply elpmas.patch && + test_path_is_missing "funny /empty" && + + git apply -R --stat --check --apply elpmas.patch && + test_must_be_empty "funny /empty" && + + git apply -R --stat --check --apply sample.patch && + test_path_is_missing "funny /empty" +' + test_done diff --git a/t/t4129-apply-samemode.sh b/t/t4129-apply-samemode.sh index e7a7295..4eb8444 100755 --- a/t/t4129-apply-samemode.sh +++ b/t/t4129-apply-samemode.sh @@ -41,7 +41,8 @@ test_expect_success FILEMODE 'same mode (index only)' ' chmod +x file && git add file && git apply --cached patch-0.txt && - git ls-files -s file | grep "^100755" + git ls-files -s file >ls-files-output && + test_grep "^100755" ls-files-output ' test_expect_success FILEMODE 'mode update (no index)' ' @@ -60,7 +61,8 @@ test_expect_success FILEMODE 'mode update (with index)' ' test_expect_success FILEMODE 'mode update (index only)' ' git reset --hard && git apply --cached patch-1.txt && - git ls-files -s file | grep "^100755" + git ls-files -s file >ls-files-output && + test_grep "^100755" ls-files-output ' test_expect_success FILEMODE 'empty mode is rejected' ' @@ -101,4 +103,31 @@ test_expect_success POSIXPERM 'do not use core.sharedRepository for working tree ) ' +test_expect_success 'git apply respects core.fileMode' ' + test_config core.fileMode false && + echo true >script.sh && + git add --chmod=+x script.sh && + git ls-files -s script.sh >ls-files-output && + test_grep "^100755" ls-files-output && + test_tick && git commit -m "Add script" && + git ls-tree -r HEAD script.sh >ls-tree-output && + test_grep "^100755" ls-tree-output && + + echo true >>script.sh && + test_tick && git commit -m "Modify script" script.sh && + git format-patch -1 --stdout >patch && + test_grep "^index.*100755$" patch && + + git switch -c branch HEAD^ && + git apply --index patch 2>err && + test_grep ! "has type 100644, expected 100755" err && + git reset --hard && + + git apply patch 2>err && + test_grep ! "has type 100644, expected 100755" err && + + git apply --cached patch 2>err && + test_grep ! "has type 100644, expected 100755" err +' + test_done diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 3b12576..5e2b6c8 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -1224,8 +1224,8 @@ test_expect_success 'record as an empty commit when meeting e-mail message that test_expect_success 'skip an empty patch in the middle of an am session' ' git checkout empty-commit^ && - test_must_fail git am empty-commit.patch >err && - grep "Patch is empty." err && + test_must_fail git am empty-commit.patch >out 2>err && + grep "Patch is empty." out && grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err && git am --skip && test_path_is_missing .git/rebase-apply && @@ -1236,8 +1236,8 @@ test_expect_success 'skip an empty patch in the middle of an am session' ' test_expect_success 'record an empty patch as an empty commit in the middle of an am session' ' git checkout empty-commit^ && - test_must_fail git am empty-commit.patch >err && - grep "Patch is empty." err && + test_must_fail git am empty-commit.patch >out 2>err && + grep "Patch is empty." out && grep "To record the empty patch as an empty commit, run \"git am --allow-empty\"." err && git am --allow-empty >output && grep "No changes - recorded it as an empty commit." output && diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index fb53ddd..b0a3e84 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -671,4 +671,67 @@ test_expect_success 'test simple stage 1 handling' ' ) ' +test_expect_success 'rerere does not crash with missing preimage' ' + git config rerere.enabled true && + + echo bar >test && + git add test && + git commit -m "one" && + git branch rerere_no_crash && + + echo foo >>test && + git add test && + git commit -m "two" && + + git checkout rerere_no_crash && + echo "bar" >>test && + git add test && + git commit -m "three" && + + test_must_fail git rebase main && + rm .git/rr-cache/*/preimage && + git rebase --abort +' + +test_expect_success 'rerere does not crash with unmatched conflict marker' ' + git config rerere.enabled true && + + echo bar >test && + git add test && + git commit -m "one" && + git branch rerere_no_preimage && + + cat >test <<-EOF && + test + bar + foobar + EOF + git add test && + git commit -m "two" && + + git checkout rerere_no_preimage && + echo "bar" >>test && + git add test && + git commit -m "three" && + + cat >test <<-EOF && + foobar + bar + bar + EOF + git add test && + git commit -m "four" && + + test_must_fail git rebase main && + cat >test <<-EOF && + test + bar + <<<<<<< HEAD + foobar + bar + EOF + git add test && + test_must_fail git rebase --continue +' + test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index d738270..f698d0c 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -312,6 +312,38 @@ test_expect_success 'shortlog de-duplicates trailers in a single commit' ' test_cmp expect actual ' +# Trailers that have unfolded (single line) and folded (multiline) values which +# are otherwise identical are treated as the same trailer for de-duplication. +test_expect_success 'shortlog de-duplicates trailers in a single commit (folded/unfolded values)' ' + git commit --allow-empty -F - <<-\EOF && + subject one + + this message has two distinct values, plus a repeat (folded) + + Repeated-trailer: Foo foo foo + Repeated-trailer: Bar + Repeated-trailer: Foo + foo foo + EOF + + git commit --allow-empty -F - <<-\EOF && + subject two + + similar to the previous, but without the second distinct value + + Repeated-trailer: Foo foo foo + Repeated-trailer: Foo + foo foo + EOF + + cat >expect <<-\EOF && + 2 Foo foo foo + 1 Bar + EOF + git shortlog -ns --group=trailer:repeated-trailer -2 HEAD >actual && + test_cmp expect actual +' + test_expect_success 'shortlog can match multiple groups' ' git commit --allow-empty -F - <<-\EOF && subject one diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 7086366..60fe60d 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -2255,23 +2255,6 @@ test_expect_success 'log on empty repo fails' ' test_grep does.not.have.any.commits stderr ' -test_expect_success REFFILES 'log diagnoses bogus HEAD hash' ' - git init empty && - test_when_finished "rm -rf empty" && - echo 1234abcd >empty/.git/refs/heads/main && - test_must_fail git -C empty log 2>stderr && - test_grep broken stderr -' - -test_expect_success REFFILES 'log diagnoses bogus HEAD symref' ' - git init empty && - echo "ref: refs/heads/invalid.lock" > empty/.git/HEAD && - test_must_fail git -C empty log 2>stderr && - test_grep broken stderr && - test_must_fail git -C empty log --default totally-bogus 2>stderr && - test_grep broken stderr -' - test_expect_success 'log does not default to HEAD when rev input is given' ' git log --branches=does-not-exist >actual && test_must_be_empty actual diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index e3d655e..158b49d 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -30,40 +30,46 @@ test_expect_success 'set up basic repos' ' >bar && git add foo && test_tick && - git config i18n.commitEncoding $test_encoding && + test_config i18n.commitEncoding $test_encoding && commit_msg $test_encoding | git commit -F - && git add bar && test_tick && - git commit -m "add bar" && - git config --unset i18n.commitEncoding + git commit -m "add bar" ' test_expect_success 'alias builtin format' ' git log --pretty=oneline >expected && - git config pretty.test-alias oneline && + test_config pretty.test-alias oneline && git log --pretty=test-alias >actual && test_cmp expected actual ' test_expect_success 'alias masking builtin format' ' git log --pretty=oneline >expected && - git config pretty.oneline "%H" && + test_config pretty.oneline "%H" && git log --pretty=oneline >actual && test_cmp expected actual ' test_expect_success 'alias user-defined format' ' git log --pretty="format:%h" >expected && - git config pretty.test-alias "format:%h" && + test_config pretty.test-alias "format:%h" && git log --pretty=test-alias >actual && test_cmp expected actual ' +test_expect_success 'alias user-defined format is matched case-insensitively' ' + git log --pretty="format:%h" >expected && + test_config pretty.testone "format:%h" && + test_config pretty.testtwo testOne && + git log --pretty=testTwo >actual && + test_cmp expected actual +' + test_expect_success 'alias user-defined tformat with %s (ISO8859-1 encoding)' ' - git config i18n.logOutputEncoding $test_encoding && + test_config i18n.logOutputEncoding $test_encoding && git log --oneline >expected-s && git log --pretty="tformat:%h %s" >actual-s && - git config --unset i18n.logOutputEncoding && test_cmp expected-s actual-s ' @@ -75,34 +81,34 @@ test_expect_success 'alias user-defined tformat with %s (utf-8 encoding)' ' test_expect_success 'alias user-defined tformat' ' git log --pretty="tformat:%h" >expected && - git config pretty.test-alias "tformat:%h" && + test_config pretty.test-alias "tformat:%h" && git log --pretty=test-alias >actual && test_cmp expected actual ' test_expect_success 'alias non-existent format' ' - git config pretty.test-alias format-that-will-never-exist && + test_config pretty.test-alias format-that-will-never-exist && test_must_fail git log --pretty=test-alias ' test_expect_success 'alias of an alias' ' git log --pretty="tformat:%h" >expected && - git config pretty.test-foo "tformat:%h" && - git config pretty.test-bar test-foo && + test_config pretty.test-foo "tformat:%h" && + test_config pretty.test-bar test-foo && git log --pretty=test-bar >actual && test_cmp expected actual ' test_expect_success 'alias masking an alias' ' git log --pretty=format:"Two %H" >expected && - git config pretty.duplicate "format:One %H" && - git config --add pretty.duplicate "format:Two %H" && + test_config pretty.duplicate "format:One %H" && + test_config pretty.duplicate "format:Two %H" --add && git log --pretty=duplicate >actual && test_cmp expected actual ' test_expect_success 'alias loop' ' - git config pretty.test-foo test-bar && - git config pretty.test-bar test-foo && + test_config pretty.test-foo test-bar && + test_config pretty.test-bar test-foo && test_must_fail git log --pretty=test-foo ' @@ -156,7 +162,7 @@ test_expect_success 'NUL termination with --reflog --pretty=oneline' ' for r in $revs do git show -s --pretty=oneline "$r" >raw && - cat raw | lf_to_nul || return 1 + lf_to_nul <raw || return 1 done >expect && # the trailing NUL is already produced so we do not need to # output another one diff --git a/t/t4207-log-decoration-colors.sh b/t/t4207-log-decoration-colors.sh index 21986a8..73ea9e5 100755 --- a/t/t4207-log-decoration-colors.sh +++ b/t/t4207-log-decoration-colors.sh @@ -70,8 +70,14 @@ ${c_tag}tag: ${c_reset}${c_tag}A${c_reset}${c_commit})${c_reset} A cmp_filtered_decorations ' +remove_replace_refs () { + git for-each-ref 'refs/replace*/**' --format='delete %(refname)' >in && + git update-ref --stdin <in && + rm in +} + test_expect_success 'test coloring with replace-objects' ' - test_when_finished rm -rf .git/refs/replace* && + test_when_finished remove_replace_refs && test_commit C && test_commit D && @@ -99,7 +105,7 @@ EOF ' test_expect_success 'test coloring with grafted commit' ' - test_when_finished rm -rf .git/refs/replace* && + test_when_finished remove_replace_refs && git replace --graft HEAD HEAD~2 && diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh index d2dfcf1..75216f1 100755 --- a/t/t4210-log-i18n.sh +++ b/t/t4210-log-i18n.sh @@ -64,7 +64,7 @@ test_expect_success 'log --grep does not find non-reencoded values (latin1)' ' ' triggers_undefined_behaviour () { - local engine=$1 + local engine="$1" case $engine in fixed) @@ -85,7 +85,7 @@ triggers_undefined_behaviour () { } mismatched_git_log () { - local pattern=$1 + local pattern="$1" LC_ALL=$is_IS_locale git log --encoding=ISO-8859-1 --format=%s \ --grep=$pattern diff --git a/t/t4252-am-options.sh b/t/t4252-am-options.sh index e758e63..5b680dc 100755 --- a/t/t4252-am-options.sh +++ b/t/t4252-am-options.sh @@ -1,6 +1,8 @@ #!/bin/sh test_description='git am with options and not losing them' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh tm="$TEST_DIRECTORY/t4252" diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh index 45f1d4f..661feb6 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh @@ -59,7 +59,7 @@ test_expect_success setup ' # Also, it had the unwanted side-effect of deleting f. test_expect_success 'try to apply corrupted patch' ' test_when_finished "git am --abort" && - test_must_fail git -c advice.amWorkDir=false am bad-patch.diff 2>actual && + test_must_fail git -c advice.amWorkDir=false -c advice.mergeConflict=false am bad-patch.diff 2>actual && echo "error: git diff header lacks filename information (line 4)" >expected && test_path_is_file f && test_cmp expected actual diff --git a/t/t4258-am-quoted-cr.sh b/t/t4258-am-quoted-cr.sh index 201915b..3573c91 100755 --- a/t/t4258-am-quoted-cr.sh +++ b/t/t4258-am-quoted-cr.sh @@ -2,6 +2,7 @@ test_description='test am --quoted-cr=<action>' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh DATA="$TEST_DIRECTORY/t4258" diff --git a/t/t4301-merge-tree-write-tree.sh b/t/t4301-merge-tree-write-tree.sh index b2c8a43..eea1990 100755 --- a/t/t4301-merge-tree-write-tree.sh +++ b/t/t4301-merge-tree-write-tree.sh @@ -313,7 +313,7 @@ test_expect_success 'rename/add handling' ' # 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 " ") && + hash=$(tr "\0" "\n" <out | head -n 3 | grep 3.bar | cut -f 2 -d " ") && git rev-list --objects --all >all_blobs && ! grep $hash all_blobs && @@ -380,7 +380,7 @@ test_expect_success SYMLINKS 'rename/add, where add is a mode conflict' ' # 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 " ") && + hash=$(tr "\0" "\n" <out | head -n 3 | grep 3.bar | cut -f 2 -d " ") && git rev-list --objects --all >all_blobs && ! grep $hash all_blobs && @@ -630,8 +630,8 @@ test_expect_success 'mod6: chains of rename/rename(1to2) and add/add via collidi # 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 " ") && + hash1=$(tr "\0" "\n" <out | head | grep 2.four | cut -f 2 -d " ") && + hash2=$(tr "\0" "\n" <out | head | grep 3.two | cut -f 2 -d " ") && git rev-list --objects --all >all_blobs && ! grep $hash1 all_blobs && ! grep $hash2 all_blobs && @@ -887,7 +887,7 @@ test_expect_success '--stdin with both a successful and a conflicted merge' ' test_expect_success '--merge-base is incompatible with --stdin' ' test_must_fail git merge-tree --merge-base=side1 --stdin 2>expect && - grep "^fatal: --merge-base is incompatible with --stdin" expect + grep "^fatal: .*merge-base.*stdin.* cannot be used together" expect ' # specify merge-base as parent of branch2 @@ -945,4 +945,49 @@ test_expect_success 'check the input format when --stdin is passed' ' test_cmp expect actual ' +test_expect_success '--merge-base with tree OIDs' ' + git merge-tree --merge-base=side1^ side1 side3 >with-commits && + git merge-tree --merge-base=side1^^{tree} side1^{tree} side3^{tree} >with-trees && + test_cmp with-commits with-trees +' + +test_expect_success 'error out on missing tree objects' ' + git init --bare missing-tree.git && + git rev-list side3 >list && + git rev-parse side3^: >>list && + git pack-objects missing-tree.git/objects/pack/side3-tree-is-missing <list && + side3=$(git rev-parse side3) && + test_must_fail git --git-dir=missing-tree.git merge-tree $side3^ $side3 >actual 2>err && + test_grep "Could not read $(git rev-parse $side3:)" err && + test_must_be_empty actual +' + +test_expect_success 'error out on missing blob objects' ' + echo 1 | git hash-object -w --stdin >blob1 && + echo 2 | git hash-object -w --stdin >blob2 && + echo 3 | git hash-object -w --stdin >blob3 && + printf "100644 blob $(cat blob1)\tblob\n" | git mktree >tree1 && + printf "100644 blob $(cat blob2)\tblob\n" | git mktree >tree2 && + printf "100644 blob $(cat blob3)\tblob\n" | git mktree >tree3 && + git init --bare missing-blob.git && + cat blob1 blob3 tree1 tree2 tree3 | + git pack-objects missing-blob.git/objects/pack/side1-whatever-is-missing && + test_must_fail git --git-dir=missing-blob.git >actual 2>err \ + merge-tree --merge-base=$(cat tree1) $(cat tree2) $(cat tree3) && + test_grep "unable to read blob object $(cat blob2)" err && + test_must_be_empty actual +' + +test_expect_success 'error out on missing commits as well' ' + git init --bare missing-commit.git && + git rev-list --objects side1 side3 >list-including-initial && + grep -v ^$(git rev-parse side1^) <list-including-initial >list && + git pack-objects missing-commit.git/objects/pack/missing-initial <list && + side1=$(git rev-parse side1) && + side3=$(git rev-parse side3) && + test_must_fail git --git-dir=missing-commit.git \ + merge-tree --allow-unrelated-histories $side1 $side3 >actual && + test_must_be_empty actual +' + test_done diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index 4b4c331..72b8d0f 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -124,6 +124,16 @@ test_expect_success 'setup' ' EOF ' +test_expect_success '--list notices extra parameters' ' + test_must_fail git archive --list blah && + test_must_fail git archive --remote=. --list blah +' + +test_expect_success 'end-of-options is correctly eaten' ' + git archive --list --end-of-options && + git archive --remote=. --list --end-of-options +' + test_expect_success 'populate workdir' ' mkdir a && echo simple textfile >a/a && diff --git a/t/t5003-archive-zip.sh b/t/t5003-archive-zip.sh index fc499cd..961c6aa 100755 --- a/t/t5003-archive-zip.sh +++ b/t/t5003-archive-zip.sh @@ -239,4 +239,38 @@ check_zip with_untracked2 check_added with_untracked2 untracked one/untracked check_added with_untracked2 untracked two/untracked +# Test remote archive over HTTP protocol. +# +# Note: this should be the last part of this test suite, because +# by including lib-httpd.sh, the test may end early if httpd tests +# should not be run. +# +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success "setup for HTTP protocol" ' + cp -R bare.git "$HTTPD_DOCUMENT_ROOT_PATH/bare.git" && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/bare.git" \ + config http.uploadpack true && + set_askpass user@host pass@host +' + +setup_askpass_helper + +test_expect_success 'remote archive does not work with protocol v1' ' + test_must_fail git -c protocol.version=1 archive \ + --remote="$HTTPD_URL/auth/smart/bare.git" \ + --output=remote-http.zip HEAD >actual 2>&1 && + cat >expect <<-EOF && + fatal: can${SQ}t connect to subservice git-upload-archive + EOF + test_cmp expect actual +' + +test_expect_success 'archive remote http repository' ' + git archive --remote="$HTTPD_URL/auth/smart/bare.git" \ + --output=remote-http.zip HEAD && + test_cmp_bin d.zip remote-http.zip +' + test_done diff --git a/t/t5100-mailinfo.sh b/t/t5100-mailinfo.sh index db11cab..c8d0655 100755 --- a/t/t5100-mailinfo.sh +++ b/t/t5100-mailinfo.sh @@ -70,7 +70,7 @@ test_expect_success 'respect NULs' ' git mailsplit -d3 -o. "$DATA/nul-plain" && test_cmp "$DATA/nul-plain" 001 && - (cat 001 | git mailinfo msg patch) && + git mailinfo msg patch <001 && test_line_count = 4 patch ' @@ -268,4 +268,26 @@ test_expect_success 'mailinfo warn CR in base64 encoded email' ' test_must_be_empty quoted-cr/0002.err ' +test_expect_success 'from line with unterminated quoted string' ' + echo "From: bob \"unterminated string smith <bob@example.com>" >in && + git mailinfo /dev/null /dev/null <in >actual && + cat >expect <<-\EOF && + Author: bob unterminated string smith + Email: bob@example.com + + EOF + test_cmp expect actual +' + +test_expect_success 'from line with unterminated comment' ' + echo "From: bob (unterminated comment smith <bob@example.com>" >in && + git mailinfo /dev/null /dev/null <in >actual && + cat >expect <<-\EOF && + Author: bob (unterminated comment smith + Email: bob@example.com + + EOF + test_cmp expect actual +' + test_done diff --git a/t/t5100/comment.expect b/t/t5100/comment.expect index 7228177..bd71956 100644 --- a/t/t5100/comment.expect +++ b/t/t5100/comment.expect @@ -1,4 +1,4 @@ -Author: A U Thor (this is (really) a comment (honestly)) +Author: (this is (really) a "comment" (honestly)) A U Thor Email: somebody@example.com Subject: testing comments Date: Sun, 25 May 2008 00:38:18 -0700 diff --git a/t/t5100/comment.in b/t/t5100/comment.in index c53a192..0b7e903 100644 --- a/t/t5100/comment.in +++ b/t/t5100/comment.in @@ -1,5 +1,5 @@ From 1234567890123456789012345678901234567890 Mon Sep 17 00:00:00 2001 -From: "A U Thor" <somebody@example.com> (this is \(really\) a comment (honestly)) +From: (this is \(really\) a "comment" (honestly)) "A U Thor" <somebody@example.com> Date: Sun, 25 May 2008 00:38:18 -0700 Subject: [PATCH] testing comments diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index d402ec1..61e2be2 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -441,6 +441,47 @@ test_expect_success 'index-pack with --strict' ' ) ' +test_expect_success 'setup for --strict and --fsck-objects downgrading fsck msgs' ' + git init strict && + ( + cd strict && + test_commit first hello && + cat >commit <<-EOF && + tree $(git rev-parse HEAD^{tree}) + parent $(git rev-parse HEAD) + author A U Thor + committer A U Thor + + commit: this is a commit with bad emails + + EOF + git hash-object --literally -t commit -w --stdin <commit >commit_list && + git pack-objects test <commit_list >pack-name + ) +' + +test_with_bad_commit () { + must_fail_arg="$1" && + must_pass_arg="$2" && + ( + cd strict && + test_must_fail git index-pack "$must_fail_arg" "test-$(cat pack-name).pack" && + git index-pack "$must_pass_arg" "test-$(cat pack-name).pack" + ) +} + +test_expect_success 'index-pack with --strict downgrading fsck msgs' ' + test_with_bad_commit --strict --strict="missingEmail=ignore" +' + +test_expect_success 'index-pack with --fsck-objects downgrading fsck msgs' ' + test_with_bad_commit --fsck-objects --fsck-objects="missingEmail=ignore" +' + +test_expect_success 'cleanup for --strict and --fsck-objects downgrading fsck msgs' ' + rm -rf strict +' + test_expect_success 'honor pack.packSizeLimit' ' git config pack.packSizeLimit 3m && packname_10=$(git pack-objects test-10 <obj-list) && diff --git a/t/t5312-prune-corruption.sh b/t/t5312-prune-corruption.sh index 230cb38..d8d2e30 100755 --- a/t/t5312-prune-corruption.sh +++ b/t/t5312-prune-corruption.sh @@ -111,30 +111,4 @@ test_expect_success 'pack-refs does not silently delete broken loose ref' ' test_cmp expect actual ' -# we do not want to count on running pack-refs to -# actually pack it, as it is perfectly reasonable to -# skip processing a broken ref -test_expect_success REFFILES 'create packed-refs file with broken ref' ' - rm -f .git/refs/heads/main && - cat >.git/packed-refs <<-EOF && - $missing refs/heads/main - $recoverable refs/heads/other - EOF - echo $missing >expect && - git rev-parse refs/heads/main >actual && - test_cmp expect actual -' - -test_expect_success REFFILES 'pack-refs does not silently delete broken packed ref' ' - git pack-refs --all --prune && - git rev-parse refs/heads/main >actual && - test_cmp expect actual -' - -test_expect_success REFFILES 'pack-refs does not drop broken refs during deletion' ' - git update-ref -d refs/heads/other && - git rev-parse refs/heads/main >actual && - test_cmp expect actual -' - test_done diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh index 2ff3eef..79552d6 100755 --- a/t/t5317-pack-objects-filter-objects.sh +++ b/t/t5317-pack-objects-filter-objects.sh @@ -455,7 +455,7 @@ test_expect_success 'setup r1 - delete loose blobs' ' test_parse_ls_files_stage_oids <ls_files_result | sort >expected && - for id in `cat expected | sed "s|..|&/|"` + for id in `sed "s|..|&/|" expected` do rm r1/.git/objects/$id || return 1 done diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh index d4fc65e..a2b4442 100755 --- a/t/t5318-commit-graph.sh +++ b/t/t5318-commit-graph.sh @@ -540,17 +540,17 @@ test_expect_success 'detect low chunk count' ' test_expect_success 'detect missing OID fanout chunk' ' corrupt_graph_and_verify $GRAPH_BYTE_OID_FANOUT_ID "\0" \ - "missing the OID Fanout chunk" + "commit-graph required OID fanout chunk missing or corrupted" ' test_expect_success 'detect missing OID lookup chunk' ' corrupt_graph_and_verify $GRAPH_BYTE_OID_LOOKUP_ID "\0" \ - "missing the OID Lookup chunk" + "commit-graph required OID lookup chunk missing or corrupted" ' test_expect_success 'detect missing commit data chunk' ' corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_DATA_ID "\0" \ - "missing the Commit Data chunk" + "commit-graph required commit data chunk missing or corrupted" ' test_expect_success 'detect incorrect fanout' ' @@ -560,7 +560,7 @@ test_expect_success 'detect incorrect fanout' ' test_expect_success 'detect incorrect fanout final value' ' corrupt_graph_and_verify $GRAPH_BYTE_FANOUT2 "\01" \ - "oid table and fanout disagree on size" + "OID lookup chunk is the wrong size" ' test_expect_success 'detect incorrect OID order' ' @@ -842,7 +842,7 @@ test_expect_success 'reader notices too-small oid fanout chunk' ' check_corrupt_chunk OIDF clear $(printf "000000%02x" $(test_seq 250)) && cat >expect.err <<-\EOF && error: commit-graph oid fanout chunk is wrong size - error: commit-graph is missing the OID Fanout chunk + error: commit-graph required OID fanout chunk missing or corrupted EOF test_cmp expect.err err ' @@ -850,7 +850,8 @@ test_expect_success 'reader notices too-small oid fanout chunk' ' test_expect_success 'reader notices fanout/lookup table mismatch' ' check_corrupt_chunk OIDF 1020 "FFFFFFFF" && cat >expect.err <<-\EOF && - error: commit-graph oid table and fanout disagree on size + error: commit-graph OID lookup chunk is the wrong size + error: commit-graph required OID lookup chunk missing or corrupted EOF test_cmp expect.err err ' @@ -866,6 +867,7 @@ test_expect_success 'reader notices out-of-bounds fanout' ' check_corrupt_chunk OIDF 0 $(printf "%02x000000" $(test_seq 0 254)) && cat >expect.err <<-\EOF && error: commit-graph fanout values out of order + error: commit-graph required OID fanout chunk missing or corrupted EOF test_cmp expect.err err ' @@ -874,7 +876,7 @@ test_expect_success 'reader notices too-small commit data chunk' ' check_corrupt_chunk CDAT clear 00000000 && cat >expect.err <<-\EOF && error: commit-graph commit data chunk is wrong size - error: commit-graph is missing the Commit Data chunk + error: commit-graph required commit data chunk missing or corrupted EOF test_cmp expect.err err ' @@ -909,10 +911,10 @@ test_expect_success 'stale commit cannot be parsed when given directly' ' # Verify that it is possible to read the commit from the # commit graph when not being paranoid, ... - GIT_COMMIT_GRAPH_PARANOIA=false git rev-list B && + git rev-list B && # ... but parsing the commit when double checking that # it actually exists in the object database should fail. - test_must_fail git rev-list -1 B + test_must_fail env GIT_COMMIT_GRAPH_PARANOIA=true git rev-list -1 B ) ' @@ -936,9 +938,9 @@ test_expect_success 'stale commit cannot be parsed when traversing graph' ' # Again, we should be able to parse the commit when not # being paranoid about commit graph staleness... - GIT_COMMIT_GRAPH_PARANOIA=false git rev-parse HEAD~2 && + git rev-parse HEAD~2 && # ... but fail when we are paranoid. - test_must_fail git rev-parse HEAD~2 2>error && + test_must_fail env GIT_COMMIT_GRAPH_PARANOIA=true git rev-parse HEAD~2 2>error && grep "error: commit $oid exists in commit-graph but not in the object database" error ) ' diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh index c4c6060..dd09134 100755 --- a/t/t5319-multi-pack-index.sh +++ b/t/t5319-multi-pack-index.sh @@ -1157,4 +1157,53 @@ test_expect_success 'reader notices too-small revindex chunk' ' test_cmp expect.err err ' +test_expect_success 'reader notices out-of-bounds fanout' ' + # This is similar to the out-of-bounds fanout test in t5318. The values + # in adjacent entries should be large but not identical (they + # are used as hi/lo starts for a binary search, which would then abort + # immediately). + corrupt_chunk OIDF 0 $(printf "%02x000000" $(test_seq 0 254)) && + test_must_fail git log 2>err && + cat >expect <<-\EOF && + error: oid fanout out of order: fanout[254] = fe000000 > 5c = fanout[255] + fatal: multi-pack-index required OID fanout chunk missing or corrupted + EOF + test_cmp expect err +' + +test_expect_success 'bitmapped packs are stored via the BTMP chunk' ' + test_when_finished "rm -fr repo" && + git init repo && + ( + cd repo && + + for i in 1 2 3 4 5 + do + test_commit "$i" && + git repack -d || return 1 + done && + + find $objdir/pack -type f -name "*.idx" | xargs -n 1 basename | + sort >packs && + + git multi-pack-index write --stdin-packs <packs && + test_must_fail test-tool read-midx --bitmap $objdir 2>err && + cat >expect <<-\EOF && + error: MIDX does not contain the BTMP chunk + EOF + test_cmp expect err && + + git multi-pack-index write --stdin-packs --bitmap \ + --preferred-pack="$(head -n1 <packs)" <packs && + test-tool read-midx --bitmap $objdir >actual && + for i in $(test_seq $(wc -l <packs)) + do + sed -ne "${i}s/\.idx$/\.pack/p" packs && + echo " bitmap_pos: $((($i - 1) * 3))" && + echo " bitmap_nr: 3" || return 1 + done >expect && + test_cmp expect actual + ) +' + test_done diff --git a/t/t5326-multi-pack-bitmaps.sh b/t/t5326-multi-pack-bitmaps.sh index 70d1b58..5d7d321 100755 --- a/t/t5326-multi-pack-bitmaps.sh +++ b/t/t5326-multi-pack-bitmaps.sh @@ -513,4 +513,21 @@ test_expect_success 'corrupt MIDX with bitmap causes fallback' ' ) ' +for allow_pack_reuse in single multi +do + test_expect_success "reading MIDX without BTMP chunk does not complain with $allow_pack_reuse pack reuse" ' + test_when_finished "rm -rf midx-without-btmp" && + git init midx-without-btmp && + ( + cd midx-without-btmp && + test_commit initial && + + git repack -Adbl --write-bitmap-index --write-midx && + GIT_TEST_MIDX_READ_BTMP=false git -c pack.allowPackReuse=$allow_pack_reuse \ + pack-objects --all --use-bitmap-index --stdout </dev/null >/dev/null 2>err && + test_must_be_empty err + ) + ' +done + test_done diff --git a/t/t5332-multi-pack-reuse.sh b/t/t5332-multi-pack-reuse.sh new file mode 100755 index 0000000..3c20738 --- /dev/null +++ b/t/t5332-multi-pack-reuse.sh @@ -0,0 +1,207 @@ +#!/bin/sh + +test_description='pack-objects multi-pack reuse' + +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-bitmap.sh + +objdir=.git/objects +packdir=$objdir/pack + +test_pack_reused () { + test_trace2_data pack-objects pack-reused "$1" +} + +test_packs_reused () { + test_trace2_data pack-objects packs-reused "$1" +} + + +# pack_position <object> </path/to/pack.idx +pack_position () { + git show-index >objects && + grep "$1" objects | cut -d" " -f1 +} + +# test_pack_objects_reused_all <pack-reused> <packs-reused> +test_pack_objects_reused_all () { + : >trace2.txt && + GIT_TRACE2_EVENT="$PWD/trace2.txt" \ + git pack-objects --stdout --revs --all --delta-base-offset \ + >/dev/null && + + test_pack_reused "$1" <trace2.txt && + test_packs_reused "$2" <trace2.txt +} + +# test_pack_objects_reused <pack-reused> <packs-reused> +test_pack_objects_reused () { + : >trace2.txt && + GIT_TRACE2_EVENT="$PWD/trace2.txt" \ + git pack-objects --stdout --revs >/dev/null && + + test_pack_reused "$1" <trace2.txt && + test_packs_reused "$2" <trace2.txt +} + +test_expect_success 'preferred pack is reused for single-pack reuse' ' + test_config pack.allowPackReuse single && + + for i in A B + do + test_commit "$i" && + git repack -d || return 1 + done && + + git multi-pack-index write --bitmap && + + test_pack_objects_reused_all 3 1 +' + +test_expect_success 'multi-pack reuse is disabled by default' ' + test_pack_objects_reused_all 3 1 +' + +test_expect_success 'feature.experimental implies multi-pack reuse' ' + test_config feature.experimental true && + + test_pack_objects_reused_all 6 2 +' + +test_expect_success 'multi-pack reuse can be disabled with feature.experimental' ' + test_config feature.experimental true && + test_config pack.allowPackReuse single && + + test_pack_objects_reused_all 3 1 +' + +test_expect_success 'enable multi-pack reuse' ' + git config pack.allowPackReuse multi +' + +test_expect_success 'reuse all objects from subset of bitmapped packs' ' + test_commit C && + git repack -d && + + git multi-pack-index write --bitmap && + + cat >in <<-EOF && + $(git rev-parse C) + ^$(git rev-parse A) + EOF + + test_pack_objects_reused 6 2 <in +' + +test_expect_success 'reuse all objects from all packs' ' + test_pack_objects_reused_all 9 3 +' + +test_expect_success 'reuse objects from first pack with middle gap' ' + for i in D E F + do + test_commit "$i" || return 1 + done && + + # Set "pack.window" to zero to ensure that we do not create any + # deltas, which could alter the amount of pack reuse we perform + # (if, for e.g., we are not sending one or more bases). + D="$(git -c pack.window=0 pack-objects --all --unpacked $packdir/pack)" && + + d_pos="$(pack_position $(git rev-parse D) <$packdir/pack-$D.idx)" && + e_pos="$(pack_position $(git rev-parse E) <$packdir/pack-$D.idx)" && + f_pos="$(pack_position $(git rev-parse F) <$packdir/pack-$D.idx)" && + + # commits F, E, and D, should appear in that order at the + # beginning of the pack + test $f_pos -lt $e_pos && + test $e_pos -lt $d_pos && + + # Ensure that the pack we are constructing sorts ahead of any + # other packs in lexical/bitmap order by choosing it as the + # preferred pack. + git multi-pack-index write --bitmap --preferred-pack="pack-$D.idx" && + + cat >in <<-EOF && + $(git rev-parse E) + ^$(git rev-parse D) + EOF + + test_pack_objects_reused 3 1 <in +' + +test_expect_success 'reuse objects from middle pack with middle gap' ' + rm -fr $packdir/multi-pack-index* && + + # Ensure that the pack we are constructing sort into any + # position *but* the first one, by choosing a different pack as + # the preferred one. + git multi-pack-index write --bitmap --preferred-pack="pack-$A.idx" && + + cat >in <<-EOF && + $(git rev-parse E) + ^$(git rev-parse D) + EOF + + test_pack_objects_reused 3 1 <in +' + +test_expect_success 'omit delta with uninteresting base (same pack)' ' + git repack -adk && + + test_seq 32 >f && + git add f && + test_tick && + git commit -m "delta" && + delta="$(git rev-parse HEAD)" && + + test_seq 64 >f && + test_tick && + git commit -a -m "base" && + base="$(git rev-parse HEAD)" && + + test_commit other && + + git repack -d && + + have_delta "$(git rev-parse $delta:f)" "$(git rev-parse $base:f)" && + + git multi-pack-index write --bitmap && + + cat >in <<-EOF && + $(git rev-parse other) + ^$base + EOF + + # We can only reuse the 3 objects corresponding to "other" from + # the latest pack. + # + # This is because even though we want "delta", we do not want + # "base", meaning that we have to inflate the delta/base-pair + # corresponding to the blob in commit "delta", which bypasses + # the pack-reuse mechanism. + # + # The remaining objects from the other pack are similarly not + # reused because their objects are on the uninteresting side of + # the query. + test_pack_objects_reused 3 1 <in +' + +test_expect_success 'omit delta from uninteresting base (cross pack)' ' + cat >in <<-EOF && + $(git rev-parse $base) + ^$(git rev-parse $delta) + EOF + + P="$(git pack-objects --revs $packdir/pack <in)" && + + git multi-pack-index write --bitmap --preferred-pack="pack-$P.idx" && + + packs_nr="$(find $packdir -type f -name "pack-*.pack" | wc -l)" && + objects_nr="$(git rev-list --count --all --objects)" && + + test_pack_objects_reused_all $(($objects_nr - 1)) $packs_nr +' + +test_done diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 001b7a1..d8cadee 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -123,7 +123,7 @@ remote: STDOUT post-update remote: STDERR post-update EOF test_expect_success 'send-pack stderr contains hook messages' ' - grep ^remote: send.err | sed "s/ *\$//" >actual && + sed -n "/^remote:/s/ *\$//p" send.err >actual && test_cmp expect actual ' @@ -133,10 +133,8 @@ test_expect_success 'pre-receive hook that forgets to read its input' ' EOF rm -f victim.git/hooks/update victim.git/hooks/post-update && - for v in $(test_seq 100 999) - do - git branch branch_$v main || return - done && + printf "create refs/heads/branch_%d main\n" $(test_seq 100 999) >input && + git update-ref --stdin <input && git push ./victim.git "+refs/heads/*:refs/heads/*" ' diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index dcadd56..9441793 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -169,6 +169,7 @@ test_expect_success REFFILES 'fetch --prune fails to delete branches' ' git clone . prune-fail && cd prune-fail && git update-ref refs/remotes/origin/extrabranch main && + git pack-refs --all && : this will prevent --prune from locking packed-refs for deleting refs, but adding loose refs still succeeds && >.git/packed-refs.new && @@ -802,7 +803,8 @@ test_expect_success 'fetch.writeCommitGraph with submodules' ' cd super-clone && rm -rf .git/objects/info && git -c fetch.writeCommitGraph=true fetch origin && - test_path_is_file .git/objects/info/commit-graphs/commit-graph-chain + test_path_is_file .git/objects/info/commit-graphs/commit-graph-chain && + git -c fetch.writeCommitGraph=true fetch --recurse-submodules origin ) ' @@ -1250,6 +1252,30 @@ EOF test_cmp fatal-expect fatal-actual ' +test_expect_success SYMLINKS 'clone does not get confused by a D/F conflict' ' + git init df-conflict && + ( + cd df-conflict && + ln -s .git a && + git add a && + test_tick && + git commit -m symlink && + test_commit a- && + rm a && + mkdir -p a/hooks && + write_script a/hooks/post-checkout <<-EOF && + echo WHOOPSIE >&2 + echo whoopsie >"$TRASH_DIRECTORY"/whoops + EOF + git add a/hooks/post-checkout && + test_tick && + git commit -m post-checkout + ) && + git clone df-conflict clone 2>err && + test_grep ! WHOOPS err && + test_path_is_missing whoops +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index a95841d..25772c8 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -24,6 +24,15 @@ setup_repository () { ) } +setup_test_clone () { + test_dir="$1" && + git clone one "$test_dir" && + for r in one two three + do + git -C "$test_dir" remote add "$r" "../$r" || return 1 + done +} + test_expect_success setup ' setup_repository one && setup_repository two && @@ -209,4 +218,156 @@ test_expect_success 'git fetch --multiple --jobs=0 picks a default' ' git fetch --multiple --jobs=0) ' +create_fetch_all_expect () { + cat >expect <<-\EOF + one/main + one/side + origin/HEAD -> origin/main + origin/main + origin/side + three/another + three/main + three/side + two/another + two/main + two/side + EOF +} + +for fetch_all in true false +do + test_expect_success "git fetch --all (works with fetch.all = $fetch_all)" ' + test_dir="test_fetch_all_$fetch_all" && + setup_test_clone "$test_dir" && + ( + cd "$test_dir" && + git config fetch.all $fetch_all && + git fetch --all && + create_fetch_all_expect && + git branch -r >actual && + test_cmp expect actual + ) + ' +done + +test_expect_success 'git fetch (fetch all remotes with fetch.all = true)' ' + setup_test_clone test9 && + ( + cd test9 && + git config fetch.all true && + git fetch && + git branch -r >actual && + create_fetch_all_expect && + test_cmp expect actual + ) +' + +create_fetch_one_expect () { + cat >expect <<-\EOF + one/main + one/side + origin/HEAD -> origin/main + origin/main + origin/side + EOF +} + +test_expect_success 'git fetch one (explicit remote overrides fetch.all)' ' + setup_test_clone test10 && + ( + cd test10 && + git config fetch.all true && + git fetch one && + create_fetch_one_expect && + git branch -r >actual && + test_cmp expect actual + ) +' + +create_fetch_two_as_origin_expect () { + cat >expect <<-\EOF + origin/HEAD -> origin/main + origin/another + origin/main + origin/side + EOF +} + +test_expect_success 'git config fetch.all false (fetch only default remote)' ' + setup_test_clone test11 && + ( + cd test11 && + git config fetch.all false && + git remote set-url origin ../two && + git fetch && + create_fetch_two_as_origin_expect && + git branch -r >actual && + test_cmp expect actual + ) +' + +for fetch_all in true false +do + test_expect_success "git fetch --no-all (fetch only default remote with fetch.all = $fetch_all)" ' + test_dir="test_no_all_fetch_all_$fetch_all" && + setup_test_clone "$test_dir" && + ( + cd "$test_dir" && + git config fetch.all $fetch_all && + git remote set-url origin ../two && + git fetch --no-all && + create_fetch_two_as_origin_expect && + git branch -r >actual && + test_cmp expect actual + ) + ' +done + +test_expect_success 'git fetch --no-all (fetch only default remote without fetch.all)' ' + setup_test_clone test12 && + ( + cd test12 && + git config --unset-all fetch.all || true && + git remote set-url origin ../two && + git fetch --no-all && + create_fetch_two_as_origin_expect && + git branch -r >actual && + test_cmp expect actual + ) +' + +test_expect_success 'git fetch --all --no-all (fetch only default remote)' ' + setup_test_clone test13 && + ( + cd test13 && + git remote set-url origin ../two && + git fetch --all --no-all && + create_fetch_two_as_origin_expect && + git branch -r >actual && + test_cmp expect actual + ) +' + +test_expect_success 'git fetch --no-all one (fetch only explicit remote)' ' + setup_test_clone test14 && + ( + cd test14 && + git fetch --no-all one && + create_fetch_one_expect && + git branch -r >actual && + test_cmp expect actual + ) +' + +test_expect_success 'git fetch --no-all --all (fetch all remotes)' ' + setup_test_clone test15 && + ( + cd test15 && + git fetch --no-all --all && + create_fetch_all_expect && + git branch -r >actual && + test_cmp expect actual + ) +' + test_done diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 26e933f..5e56620 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -771,7 +771,7 @@ test_expect_success 'fetching submodule into a broken repository' ' git -C dst fetch --recurse-submodules && # Break the receiving submodule - rm -f dst/sub/.git/HEAD && + rm -r dst/sub/.git/objects && # NOTE: without the fix the following tests will recurse forever! # They should terminate with an error. diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh index b4bc246..c91a62b 100755 --- a/t/t5534-push-signed.sh +++ b/t/t5534-push-signed.sh @@ -303,7 +303,7 @@ test_expect_success GPGSM 'fail without key and heed user.signingkey x509' ' EOF sed -n -e "s/^nonce /NONCE=/p" -e "/^$/q" dst/push-cert ) >expect.in && - key=$(cat "${GNUPGHOME}/trustlist.txt" | cut -d" " -f1 | tr -d ":") && + key=$(cut -d" " -f1 <"${GNUPGHOME}/trustlist.txt" | tr -d ":") && sed -e "s/^KEY=/KEY=${key}/" expect.in >expect && noop=$(git rev-parse noop) && diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh index df758e1..71428f3 100755 --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@ -232,8 +232,9 @@ test_expect_success 'push --atomic fails on server-side errors' ' test_config -C "$d" http.receivepack true && up="$HTTPD_URL"/smart/atomic-branches.git && - # break ref updates for other on the remote site - mkdir "$d/refs/heads/other.lock" && + # Create d/f conflict to break ref updates for other on the remote site. + git -C "$d" update-ref -d refs/heads/other && + git -C "$d" update-ref refs/heads/other/conflict HEAD && # add the new commit to other git branch -f other collateral && @@ -241,18 +242,9 @@ test_expect_success 'push --atomic fails on server-side errors' ' # --atomic should cause entire push to be rejected test_must_fail git push --atomic "$up" atomic other 2>output && - # the new branch should not have been created upstream - test_must_fail git -C "$d" show-ref --verify refs/heads/atomic && - - # upstream should still reflect atomic2, the last thing we pushed - # successfully - git rev-parse atomic2 >expected && - # ...to other. - git -C "$d" rev-parse refs/heads/other >actual && - test_cmp expected actual && - - # the new branch should not have been created upstream + # The atomic and other branches should not be created upstream. test_must_fail git -C "$d" show-ref --verify refs/heads/atomic && + test_must_fail git -C "$d" show-ref --verify refs/heads/other && # the failed refs should be indicated to the user grep "^ ! .*rejected.* other -> other .*atomic transaction failed" output && diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index e444b30..4c3b327 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -66,11 +66,11 @@ test_expect_success 'create empty remote repository' ' setup_post_update_server_info_hook "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" ' -test_expect_success 'empty dumb HTTP repository has default hash algorithm' ' +test_expect_success 'empty dumb HTTP repository falls back to SHA1' ' test_when_finished "rm -fr clone-empty" && git clone $HTTPD_URL/dumb/empty.git clone-empty && git -C clone-empty rev-parse --show-object-format >empty-format && - test "$(cat empty-format)" = "$(test_oid algo)" + test "$(cat empty-format)" = sha1 ' setup_askpass_helper diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index 8a41adf..a623a10 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -359,7 +359,9 @@ create_tags () { # now assign tags to all the dangling commits we created above tag=$(perl -e "print \"bla\" x 30") && - sed -e "s|^:\([^ ]*\) \(.*\)$|\2 refs/tags/$tag-\1|" <marks >>packed-refs + sed -e "s|^:\([^ ]*\) \(.*\)$|create refs/tags/$tag-\1 \2|" <marks >input && + git update-ref --stdin <input && + rm input } test_expect_success 'create 2,000 tags in the repo' ' @@ -731,4 +733,22 @@ test_expect_success 'no empty path components' ' ! grep "//" log ' +test_expect_success 'tag following always works over v0 http' ' + upstream=$HTTPD_DOCUMENT_ROOT_PATH/tags && + git init "$upstream" && + ( + cd "$upstream" && + git commit --allow-empty -m base && + git tag not-annotated && + git tag -m foo annotated + ) && + git init tags && + git -C tags -c protocol.version=0 \ + fetch --depth 1 $HTTPD_URL/smart/tags \ + refs/tags/annotated:refs/tags/annotated && + git -C "$upstream" for-each-ref refs/tags >expect && + git -C tags for-each-ref >actual && + test_cmp expect actual +' + test_done diff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh index b1cfe8b..3dcb334 100755 --- a/t/t5555-http-smart-common.sh +++ b/t/t5555-http-smart-common.sh @@ -131,7 +131,6 @@ test_expect_success 'git upload-pack --advertise-refs: v2' ' fetch=shallow wait-for-done server-option object-format=$(test_oid algo) - object-info 0000 EOF diff --git a/t/t5558-clone-bundle-uri.sh b/t/t5558-clone-bundle-uri.sh index 996a08e..1ca5f74 100755 --- a/t/t5558-clone-bundle-uri.sh +++ b/t/t5558-clone-bundle-uri.sh @@ -33,6 +33,15 @@ test_expect_success 'clone with path bundle' ' test_cmp expect actual ' +test_expect_success 'clone with path bundle and non-default hash' ' + test_when_finished "rm -rf clone-path-non-default-hash" && + GIT_DEFAULT_HASH=sha256 git clone --bundle-uri="clone-from/B.bundle" \ + clone-from clone-path-non-default-hash && + git -C clone-path-non-default-hash 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 && @@ -284,6 +293,15 @@ test_expect_success 'clone HTTP bundle' ' test_config -C clone-http log.excludedecoration refs/bundle/ ' +test_expect_success 'clone HTTP bundle with non-default hash' ' + test_when_finished "rm -rf clone-http-non-default-hash" && + GIT_DEFAULT_HASH=sha256 git clone --bundle-uri="$HTTPD_URL/B.bundle" \ + "$HTTPD_URL/smart/fetch.git" clone-http-non-default-hash && + git -C clone-http-non-default-hash rev-parse refs/bundles/topic >actual && + git -C clone-from rev-parse topic >expect && + test_cmp expect actual +' + test_expect_success 'clone bundle list (HTTP, no heuristic)' ' test_when_finished rm -f trace*.txt && diff --git a/t/t5562/invoke-with-content-length.pl b/t/t5562/invoke-with-content-length.pl index 718dd9b..9babb9a 100644 --- a/t/t5562/invoke-with-content-length.pl +++ b/t/t5562/invoke-with-content-length.pl @@ -1,4 +1,4 @@ -use 5.008; +use 5.008001; use strict; use warnings; diff --git a/t/t5574-fetch-output.sh b/t/t5574-fetch-output.sh index a9b06b2..5883839 100755 --- a/t/t5574-fetch-output.sh +++ b/t/t5574-fetch-output.sh @@ -61,11 +61,10 @@ test_expect_success 'fetch compact output' ' test_cmp expect actual ' -test_expect_success 'fetch porcelain output' ' - test_when_finished "rm -rf porcelain" && - +test_expect_success 'setup for fetch porcelain output' ' # Set up a bunch of references that we can use to demonstrate different # kinds of flag symbols in the output format. + test_commit commit-for-porcelain-output && MAIN_OLD=$(git rev-parse HEAD) && git branch "fast-forward" && git branch "deleted-branch" && @@ -74,15 +73,10 @@ test_expect_success 'fetch porcelain output' ' FORCE_UPDATED_OLD=$(git rev-parse HEAD) && git checkout main && - # Clone and pre-seed the repositories. We fetch references into two - # namespaces so that we can test that rejected and force-updated - # references are reported properly. - refspecs="refs/heads/*:refs/unforced/* +refs/heads/*:refs/forced/*" && - git clone . porcelain && - git -C porcelain fetch origin $refspecs && + # Backup to preseed.git + git clone --mirror . preseed.git && - # Now that we have set up the client repositories we can change our - # local references. + # Continue changing our local references. git branch new-branch && git branch -d deleted-branch && git checkout fast-forward && @@ -91,36 +85,53 @@ test_expect_success 'fetch porcelain output' ' git checkout force-updated && git reset --hard HEAD~ && test_commit --no-tag force-update-new && - FORCE_UPDATED_NEW=$(git rev-parse HEAD) && - - cat >expect <<-EOF && - - $MAIN_OLD $ZERO_OID refs/forced/deleted-branch - - $MAIN_OLD $ZERO_OID refs/unforced/deleted-branch - $MAIN_OLD $FAST_FORWARD_NEW refs/unforced/fast-forward - ! $FORCE_UPDATED_OLD $FORCE_UPDATED_NEW refs/unforced/force-updated - * $ZERO_OID $MAIN_OLD refs/unforced/new-branch - $MAIN_OLD $FAST_FORWARD_NEW refs/forced/fast-forward - + $FORCE_UPDATED_OLD $FORCE_UPDATED_NEW refs/forced/force-updated - * $ZERO_OID $MAIN_OLD refs/forced/new-branch - $MAIN_OLD $FAST_FORWARD_NEW refs/remotes/origin/fast-forward - + $FORCE_UPDATED_OLD $FORCE_UPDATED_NEW refs/remotes/origin/force-updated - * $ZERO_OID $MAIN_OLD refs/remotes/origin/new-branch - EOF - - # Execute a dry-run fetch first. We do this to assert that the dry-run - # and non-dry-run fetches produces the same output. Execution of the - # fetch is expected to fail as we have a rejected reference update. - test_must_fail git -C porcelain fetch \ - --porcelain --dry-run --prune origin $refspecs >actual && - test_cmp expect actual && - - # And now we perform a non-dry-run fetch. - test_must_fail git -C porcelain fetch \ - --porcelain --prune origin $refspecs >actual 2>stderr && - test_cmp expect actual && - test_must_be_empty stderr + FORCE_UPDATED_NEW=$(git rev-parse HEAD) ' +for opt in "" "--atomic" +do + test_expect_success "fetch porcelain output ${opt:+(atomic)}" ' + test_when_finished "rm -rf porcelain" && + + # Clone and pre-seed the repositories. We fetch references into two + # namespaces so that we can test that rejected and force-updated + # references are reported properly. + refspecs="refs/heads/*:refs/unforced/* +refs/heads/*:refs/forced/*" && + git clone preseed.git porcelain && + git -C porcelain fetch origin $opt $refspecs && + + cat >expect <<-EOF && + - $MAIN_OLD $ZERO_OID refs/forced/deleted-branch + - $MAIN_OLD $ZERO_OID refs/unforced/deleted-branch + $MAIN_OLD $FAST_FORWARD_NEW refs/unforced/fast-forward + ! $FORCE_UPDATED_OLD $FORCE_UPDATED_NEW refs/unforced/force-updated + * $ZERO_OID $MAIN_OLD refs/unforced/new-branch + $MAIN_OLD $FAST_FORWARD_NEW refs/forced/fast-forward + + $FORCE_UPDATED_OLD $FORCE_UPDATED_NEW refs/forced/force-updated + * $ZERO_OID $MAIN_OLD refs/forced/new-branch + $MAIN_OLD $FAST_FORWARD_NEW refs/remotes/origin/fast-forward + + $FORCE_UPDATED_OLD $FORCE_UPDATED_NEW refs/remotes/origin/force-updated + * $ZERO_OID $MAIN_OLD refs/remotes/origin/new-branch + EOF + + # Change the URL of the repository to fetch different references. + git -C porcelain remote set-url origin .. && + + # Execute a dry-run fetch first. We do this to assert that the dry-run + # and non-dry-run fetches produces the same output. Execution of the + # fetch is expected to fail as we have a rejected reference update. + test_must_fail git -C porcelain fetch $opt \ + --porcelain --dry-run --prune origin $refspecs >actual && + test_cmp expect actual && + + # And now we perform a non-dry-run fetch. + test_must_fail git -C porcelain fetch $opt \ + --porcelain --prune origin $refspecs >actual 2>stderr && + test_cmp expect actual && + test_must_be_empty stderr + ' +done + test_expect_success 'fetch porcelain with multiple remotes' ' test_when_finished "rm -rf porcelain" && diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 47eae64..deb1c28 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -157,6 +157,23 @@ test_expect_success 'clone --mirror does not repeat tags' ' ' +test_expect_success 'clone with files ref format' ' + test_when_finished "rm -rf ref-storage" && + git clone --ref-format=files --mirror src ref-storage && + echo files >expect && + git -C ref-storage rev-parse --show-ref-format >actual && + test_cmp expect actual +' + +test_expect_success 'clone with garbage ref format' ' + cat >expect <<-EOF && + fatal: unknown ref storage format ${SQ}garbage${SQ} + EOF + test_must_fail git clone --ref-format=garbage --mirror src ref-storage 2>err && + test_cmp expect err && + test_path_is_missing ref-storage +' + test_expect_success 'clone to destination with trailing /' ' git clone src target-1/ && @@ -633,6 +650,21 @@ test_expect_success CASE_INSENSITIVE_FS 'colliding file detection' ' test_grep "the following paths have collided" icasefs/warning ' +test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \ + 'colliding symlink/directory keeps directory' ' + git init icasefs-colliding-symlink && + ( + cd icasefs-colliding-symlink && + a=$(printf a | git hash-object -w --stdin) && + printf "100644 %s 0\tA/dir/b\n120000 %s 0\ta\n" $a $a >idx && + git update-index --index-info <idx && + test_tick && + git commit -m initial + ) && + git clone icasefs-colliding-symlink icasefs-colliding-symlink-clone && + test_file_not_empty icasefs-colliding-symlink-clone/A/dir/b +' + test_expect_success 'clone with GIT_DEFAULT_HASH' ' ( sane_unset GIT_DEFAULT_HASH && @@ -756,9 +788,72 @@ test_expect_success 'batch missing blob request does not inadvertently try to fe git clone --filter=blob:limit=0 "file://$(pwd)/server" client ' +test_expect_success 'clone with init.templatedir runs hooks' ' + git init tmpl/hooks && + write_script tmpl/hooks/post-checkout <<-EOF && + echo HOOK-RUN >&2 + echo I was here >hook.run + EOF + git -C tmpl/hooks add . && + test_tick && + git -C tmpl/hooks commit -m post-checkout && + + test_when_finished "git config --global --unset init.templateDir || :" && + test_when_finished "git config --unset init.templateDir || :" && + ( + sane_unset GIT_TEMPLATE_DIR && + NO_SET_GIT_TEMPLATE_DIR=t && + export NO_SET_GIT_TEMPLATE_DIR && + + git -c core.hooksPath="$(pwd)/tmpl/hooks" \ + clone tmpl/hooks hook-run-hookspath 2>err && + test_grep ! "active .* hook found" err && + test_path_is_file hook-run-hookspath/hook.run && + + git -c init.templateDir="$(pwd)/tmpl" \ + clone tmpl/hooks hook-run-config 2>err && + test_grep ! "active .* hook found" err && + test_path_is_file hook-run-config/hook.run && + + git clone --template=tmpl tmpl/hooks hook-run-option 2>err && + test_grep ! "active .* hook found" err && + test_path_is_file hook-run-option/hook.run && + + git config --global init.templateDir "$(pwd)/tmpl" && + git clone tmpl/hooks hook-run-global-config 2>err && + git config --global --unset init.templateDir && + test_grep ! "active .* hook found" err && + test_path_is_file hook-run-global-config/hook.run && + + # clone ignores local `init.templateDir`; need to create + # a new repository because we deleted `.git/` in the + # `setup` test case above + git init local-clone && + cd local-clone && + + git config init.templateDir "$(pwd)/../tmpl" && + git clone ../tmpl/hooks hook-run-local-config 2>err && + git config --unset init.templateDir && + test_grep ! "active .* hook found" err && + test_path_is_missing hook-run-local-config/hook.run + ) +' + . "$TEST_DIRECTORY"/lib-httpd.sh start_httpd +test_expect_success 'clone with includeIf' ' + test_when_finished "rm -rf repo \"$HTTPD_DOCUMENT_ROOT_PATH/repo.git\"" && + git clone --bare --no-local src "$HTTPD_DOCUMENT_ROOT_PATH/repo.git" && + + test_when_finished "rm \"$HOME\"/.gitconfig" && + cat >"$HOME"/.gitconfig <<-EOF && + [includeIf "onbranch:something"] + path = /does/not/exist.inc + EOF + git clone $HTTPD_URL/smart/repo.git repo +' + test_expect_success 'partial clone using HTTP' ' partial_clone "$HTTPD_DOCUMENT_ROOT_PATH/server" "$HTTPD_URL/smart/server" ' diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh index 946c575..a305586 100755 --- a/t/t5605-clone-local.sh +++ b/t/t5605-clone-local.sh @@ -65,7 +65,7 @@ test_expect_success 'Even without -l, local will make a hardlink' ' ' test_expect_success 'local clone of repo with nonexistent ref in HEAD' ' - echo "ref: refs/heads/nonexistent" > a.git/HEAD && + git -C a.git symbolic-ref HEAD refs/heads/nonexistent && git clone a d && (cd d && git fetch && @@ -157,7 +157,7 @@ test_expect_success 'cloning locally respects "-u" for fetching refs' ' test_must_fail git clone --bare -u false a should_not_work.git ' -test_expect_success 'local clone from repo with corrupt refs fails gracefully' ' +test_expect_success REFFILES 'local clone from repo with corrupt refs fails gracefully' ' git init corrupt && test_commit -C corrupt one && echo a >corrupt/.git/refs/heads/topic && diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh index fc4bbd9..e93e0d0 100755 --- a/t/t5606-clone-options.sh +++ b/t/t5606-clone-options.sh @@ -64,7 +64,7 @@ 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 + grep "bundle-uri.* cannot be used together" err || return 1 done ' @@ -120,14 +120,14 @@ test_expect_success 'prefers -c config over --template config' ' ' -test_expect_failure 'prefers --template config even for core.bare' ' +test_expect_success 'ignore --template config for core.bare' ' template="$TRASH_DIRECTORY/template-with-bare-config" && mkdir "$template" && git config --file "$template/config" core.bare true && git clone "--template=$template" parent clone-bare-config && - test "$(git -C clone-bare-config config --local core.bare)" = "true" && - test_path_is_file clone-bare-config/HEAD + test "$(git -C clone-bare-config config --local core.bare)" = "false" && + test_path_is_missing clone-bare-config/HEAD ' test_expect_success 'prefers config "clone.defaultRemoteName" over default' ' diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 3591bc2..c48830d 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -20,7 +20,6 @@ test_expect_success 'test capability advertisement' ' fetch=shallow wait-for-done server-option object-format=$(test_oid algo) - object-info EOF cat >expect.trailer <<-EOF && 0000 @@ -323,6 +322,8 @@ test_expect_success 'unexpected lines are not allowed in fetch request' ' # Test the basics of object-info # test_expect_success 'basics of object-info' ' + test_config transfer.advertiseObjectInfo true && + test-tool pkt-line pack >in <<-EOF && command=object-info object-format=$(test_oid algo) @@ -380,4 +381,25 @@ test_expect_success 'basics of bundle-uri: dies if not enabled' ' test_must_be_empty out ' +test_expect_success 'object-info missing from capabilities when disabled' ' + test_config transfer.advertiseObjectInfo false && + + GIT_TEST_SIDEBAND_ALL=0 test-tool serve-v2 \ + --advertise-capabilities >out && + test-tool pkt-line unpack <out >actual && + + ! grep object.info actual +' + +test_expect_success 'object-info commands rejected when disabled' ' + test_config transfer.advertiseObjectInfo false && + + test-tool pkt-line pack >in <<-EOF && + command=object-info + EOF + + test_must_fail test-tool serve-v2 --stateless-rpc <in 2>err && + grep invalid.command err +' + test_done diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh index 3c0c604..1ef540f 100755 --- a/t/t5702-protocol-v2.sh +++ b/t/t5702-protocol-v2.sh @@ -221,7 +221,9 @@ test_expect_success 'clone of empty repo propagates name of default branch' ' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ git -c init.defaultBranch=main -c protocol.version=2 \ clone "file://$(pwd)/file_empty_parent" file_empty_child && - grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD + echo refs/heads/mydefaultbranch >expect && + git -C file_empty_child symbolic-ref HEAD >actual && + test_cmp expect actual ' test_expect_success '...but not if explicitly forbidden by config' ' @@ -234,7 +236,9 @@ test_expect_success '...but not if explicitly forbidden by config' ' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \ git -c init.defaultBranch=main -c protocol.version=2 \ clone "file://$(pwd)/file_empty_parent" file_empty_child && - ! grep "refs/heads/mydefaultbranch" file_empty_child/.git/HEAD + echo refs/heads/main >expect && + git -C file_empty_child symbolic-ref HEAD >actual && + test_cmp expect actual ' test_expect_success 'bare clone propagates empty default branch' ' @@ -247,7 +251,9 @@ test_expect_success 'bare clone propagates empty default branch' ' git -c init.defaultBranch=main -c protocol.version=2 \ clone --bare \ "file://$(pwd)/file_empty_parent" file_empty_child.git && - grep "refs/heads/mydefaultbranch" file_empty_child.git/HEAD + echo "refs/heads/mydefaultbranch" >expect && + git -C file_empty_child.git symbolic-ref HEAD >actual && + test_cmp expect actual ' test_expect_success 'clone propagates unborn HEAD from non-empty repo' ' @@ -265,7 +271,9 @@ test_expect_success 'clone propagates unborn HEAD from non-empty repo' ' git -c init.defaultBranch=main -c protocol.version=2 \ clone "file://$(pwd)/file_unborn_parent" \ file_unborn_child 2>stderr && - grep "refs/heads/mydefaultbranch" file_unborn_child/.git/HEAD && + echo "refs/heads/mydefaultbranch" >expect && + git -C file_unborn_child symbolic-ref HEAD >actual && + test_cmp expect actual && grep "warning: remote HEAD refers to nonexistent ref" stderr ' @@ -295,7 +303,9 @@ test_expect_success 'bare clone propagates unborn HEAD from non-empty repo' ' git -c init.defaultBranch=main -c protocol.version=2 \ clone --bare "file://$(pwd)/file_unborn_parent" \ file_unborn_child.git 2>stderr && - grep "refs/heads/mydefaultbranch" file_unborn_child.git/HEAD && + echo "refs/heads/mydefaultbranch" >expect && + git -C file_unborn_child.git symbolic-ref HEAD >actual && + test_cmp expect actual && ! grep "warning:" stderr ' @@ -315,7 +325,9 @@ test_expect_success 'defaulted HEAD uses remote branch if available' ' git -c init.defaultBranch=branchwithstuff -c protocol.version=2 \ clone "file://$(pwd)/file_unborn_parent" \ file_unborn_child 2>stderr && - grep "refs/heads/branchwithstuff" file_unborn_child/.git/HEAD && + echo "refs/heads/branchwithstuff" >expect && + git -C file_unborn_child symbolic-ref HEAD >actual && + test_cmp expect actual && test_path_is_file file_unborn_child/stuff.t && ! grep "warning:" stderr ' @@ -766,6 +778,25 @@ test_expect_success 'archive with custom path does not request v2' ' ! grep ^GIT_PROTOCOL env.trace ' +test_expect_success 'reject client packfile-uris if not advertised' ' + { + packetize command=fetch && + packetize object-format=$(test_oid algo) && + printf 0001 && + packetize packfile-uris https && + packetize done && + printf 0000 + } >input && + test_must_fail env GIT_PROTOCOL=version=2 \ + git upload-pack client <input && + test_must_fail env GIT_PROTOCOL=version=2 \ + git -c uploadpack.blobpackfileuri \ + upload-pack client <input && + GIT_PROTOCOL=version=2 \ + git -c uploadpack.blobpackfileuri=anything \ + upload-pack client <input +' + # Test protocol v2 with 'http://' transport # . "$TEST_DIRECTORY"/lib-httpd.sh diff --git a/t/t5801/git-remote-testgit b/t/t5801/git-remote-testgit index 1544d6d..c5b10f5 100755 --- a/t/t5801/git-remote-testgit +++ b/t/t5801/git-remote-testgit @@ -12,6 +12,11 @@ url=$2 dir="$GIT_DIR/testgit/$alias" +if ! git rev-parse --is-inside-git-dir +then + exit 1 +fi + h_refspec="refs/heads/*:refs/testgit/$alias/heads/*" t_refspec="refs/tags/*:refs/testgit/$alias/tags/*" @@ -25,6 +30,7 @@ GIT_DIR="$url/.git" export GIT_DIR force= +object_format= mkdir -p "$dir" @@ -56,7 +62,8 @@ do echo ;; list) - echo ":object-format $(git rev-parse --show-object-format=storage)" + test -n "$object_format" && + echo ":object-format $(git rev-parse --show-object-format=storage)" git for-each-ref --format='? %(refname)' 'refs/heads/' 'refs/tags/' head=$(git symbolic-ref HEAD) echo "@$head HEAD" diff --git a/t/t6005-rev-list-count.sh b/t/t6005-rev-list-count.sh index 0729f80..ee0306a 100755 --- a/t/t6005-rev-list-count.sh +++ b/t/t6005-rev-list-count.sh @@ -18,20 +18,34 @@ test_expect_success 'no options' ' ' test_expect_success '--max-count' ' + test_must_fail git rev-list --max-count=1q HEAD 2>error && + grep "not an integer" error && + test_stdout_line_count = 0 git rev-list HEAD --max-count=0 && test_stdout_line_count = 3 git rev-list HEAD --max-count=3 && test_stdout_line_count = 5 git rev-list HEAD --max-count=5 && - test_stdout_line_count = 5 git rev-list HEAD --max-count=10 + test_stdout_line_count = 5 git rev-list HEAD --max-count=10 && + test_stdout_line_count = 5 git rev-list HEAD --max-count=-1 ' test_expect_success '--max-count all forms' ' + test_must_fail git rev-list -1q HEAD 2>error && + grep "not an integer" error && + test_must_fail git rev-list --1 HEAD && + test_must_fail git rev-list -n 1q HEAD 2>error && + grep "not an integer" error && + test_stdout_line_count = 1 git rev-list HEAD --max-count=1 && test_stdout_line_count = 1 git rev-list HEAD -1 && test_stdout_line_count = 1 git rev-list HEAD -n1 && - test_stdout_line_count = 1 git rev-list HEAD -n 1 + test_stdout_line_count = 1 git rev-list HEAD -n 1 && + test_stdout_line_count = 5 git rev-list HEAD -n -1 ' test_expect_success '--skip' ' + test_must_fail git rev-list --skip 1q HEAD 2>error && + grep "not an integer" error && + test_stdout_line_count = 5 git rev-list HEAD --skip=0 && test_stdout_line_count = 2 git rev-list HEAD --skip=3 && test_stdout_line_count = 0 git rev-list HEAD --skip=5 && diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh index ced4015..91db8fa 100755 --- a/t/t6009-rev-list-parent.sh +++ b/t/t6009-rev-list-parent.sh @@ -63,6 +63,17 @@ test_expect_success 'setup roots, merges and octopuses' ' git checkout main ' +test_expect_success 'parse --max-parents & --min-parents' ' + test_must_fail git rev-list --max-parents=1q HEAD 2>error && + grep "not an integer" error && + + test_must_fail git rev-list --min-parents=1q HEAD 2>error && + grep "not an integer" error && + + git rev-list --max-parents=1 --min-parents=1 HEAD && + git rev-list --max-parents=-1 --min-parents=-1 HEAD +' + test_expect_success 'rev-list roots' ' check_revlist "--max-parents=0" one five diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index 67d523d..3b181f7 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -214,15 +214,13 @@ do for pseudoopt in branches tags remotes do test_expect_success "rev-parse --exclude-hidden=$section fails with --$pseudoopt" ' - echo "error: --exclude-hidden cannot be used together with --$pseudoopt" >expected && test_must_fail git rev-parse --exclude-hidden=$section --$pseudoopt 2>err && - test_cmp expected err + test_grep "error: options .--exclude-hidden. and .--$pseudoopt. cannot be used together" err ' test_expect_success "rev-parse --exclude-hidden=$section fails with --$pseudoopt=pattern" ' - echo "error: --exclude-hidden cannot be used together with --$pseudoopt" >expected && test_must_fail git rev-parse --exclude-hidden=$section --$pseudoopt=pattern 2>err && - test_cmp expected err + test_grep "error: options .--exclude-hidden. and .--$pseudoopt. cannot be used together" err ' done done diff --git a/t/t6021-rev-list-exclude-hidden.sh b/t/t6021-rev-list-exclude-hidden.sh index cdf7aa9..51df021 100755 --- a/t/t6021-rev-list-exclude-hidden.sh +++ b/t/t6021-rev-list-exclude-hidden.sh @@ -151,12 +151,12 @@ do do test_expect_success "$section: fails with --$pseudoopt" ' test_must_fail git rev-list --exclude-hidden=$section --$pseudoopt 2>err && - test_grep "error: --exclude-hidden cannot be used together with --$pseudoopt" err + test_grep "error: options .--exclude-hidden. and .--$pseudoopt. cannot be used together" err ' test_expect_success "$section: fails with --$pseudoopt=pattern" ' test_must_fail git rev-list --exclude-hidden=$section --$pseudoopt=pattern 2>err && - test_grep "error: --exclude-hidden cannot be used together with --$pseudoopt" err + test_grep "error: options .--exclude-hidden. and .--$pseudoopt. cannot be used together" err ' done done diff --git a/t/t6022-rev-list-missing.sh b/t/t6022-rev-list-missing.sh index 40265a4..127180e 100755 --- a/t/t6022-rev-list-missing.sh +++ b/t/t6022-rev-list-missing.sh @@ -10,9 +10,18 @@ TEST_PASSES_SANITIZE_LEAK=true test_expect_success 'create repository and alternate directory' ' test_commit 1 && test_commit 2 && - test_commit 3 + test_commit 3 && + git tag -m "tag message" annot_tag HEAD~1 && + git tag regul_tag HEAD~1 && + git branch a_branch HEAD~1 ' +# We manually corrupt the repository, which means that the commit-graph may +# contain references to already-deleted objects. We thus need to enable +# commit-graph paranoia to not returned these deleted commits from the graph. +GIT_COMMIT_GRAPH_PARANOIA=true +export GIT_COMMIT_GRAPH_PARANOIA + for obj in "HEAD~1" "HEAD~1^{tree}" "HEAD:1.t" do test_expect_success "rev-list --missing=error fails with missing object $obj" ' @@ -40,9 +49,10 @@ do git rev-list --objects --no-object-names \ HEAD ^$obj >expect.raw && - # Blobs are shared by all commits, so evethough a commit/tree + # Blobs are shared by all commits, so even though a commit/tree # might be skipped, its blob must be accounted for. - if [ $obj != "HEAD:1.t" ]; then + if test $obj != "HEAD:1.t" + then echo $(git rev-parse HEAD:1.t) >>expect.raw && echo $(git rev-parse HEAD:2.t) >>expect.raw fi && @@ -71,4 +81,69 @@ do done done +for missing_tip in "annot_tag" "regul_tag" "a_branch" "HEAD~1" "HEAD~1^{tree}" "HEAD:1.t" +do + # We want to check that things work when both + # - all the tips passed are missing (case existing_tip = ""), and + # - there is one missing tip and one existing tip (case existing_tip = "HEAD") + for existing_tip in "" "HEAD" + do + for action in "allow-any" "print" + do + test_expect_success "--missing=$action with tip '$missing_tip' missing and tip '$existing_tip'" ' + # Before the object is made missing, we use rev-list to + # get the expected oids. + if test "$existing_tip" = "HEAD" + then + git rev-list --objects --no-object-names \ + HEAD ^$missing_tip >expect.raw + else + >expect.raw + fi && + + # Blobs are shared by all commits, so even though a commit/tree + # might be skipped, its blob must be accounted for. + if test "$existing_tip" = "HEAD" && test $missing_tip != "HEAD:1.t" + then + echo $(git rev-parse HEAD:1.t) >>expect.raw && + echo $(git rev-parse HEAD:2.t) >>expect.raw + fi && + + missing_oid="$(git rev-parse $missing_tip)" && + + if test "$missing_tip" = "annot_tag" + then + oid="$(git rev-parse $missing_tip^{commit})" && + echo "$missing_oid" >>expect.raw + else + oid="$missing_oid" + fi && + + path=".git/objects/$(test_oid_to_path $oid)" && + + mv "$path" "$path.hidden" && + test_when_finished "mv $path.hidden $path" && + + git rev-list --missing=$action --objects --no-object-names \ + $missing_oid $existing_tip >actual.raw && + + # When the action is to print, we should also add the missing + # oid to the expect list. + case $action in + allow-any) + ;; + print) + grep ?$oid actual.raw && + echo ?$oid >>expect.raw + ;; + esac && + + sort actual.raw >actual && + sort expect.raw >expect && + test_cmp expect actual + ' + done + done +done + test_done diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh index 2a5b7d8..cdc0270 100755 --- a/t/t6030-bisect-porcelain.sh +++ b/t/t6030-bisect-porcelain.sh @@ -170,6 +170,12 @@ test_expect_success 'bisect reset when not bisecting' ' cmp branch.expect branch.output ' +test_expect_success 'bisect reset cleans up even when not bisecting' ' + echo garbage >.git/BISECT_LOG && + git bisect reset && + test_path_is_missing .git/BISECT_LOG +' + test_expect_success 'bisect reset removes packed refs' ' git bisect reset && git bisect start && @@ -872,7 +878,7 @@ test_expect_success 'broken branch creation' ' echo "" > expected.ok cat > expected.missing-tree.default <<EOF -fatal: unable to read tree $deleted +fatal: unable to read tree ($deleted) EOF test_expect_success 'bisect fails if tree is broken on start commit' ' @@ -1176,7 +1182,7 @@ test_expect_success 'git bisect reset cleans bisection state properly' ' git bisect bad $HASH4 && git bisect reset && test -z "$(git for-each-ref "refs/bisect/*")" && - test_path_is_missing ".git/BISECT_EXPECTED_REV" && + test_ref_missing BISECT_EXPECTED_REV && test_path_is_missing ".git/BISECT_ANCESTORS_OK" && test_path_is_missing ".git/BISECT_LOG" && test_path_is_missing ".git/BISECT_RUN" && diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh index 52822b9..43e1afd 100755 --- a/t/t6112-rev-list-filters-objects.sh +++ b/t/t6112-rev-list-filters-objects.sh @@ -670,7 +670,7 @@ test_expect_success 'rev-list W/ --missing=print' ' awk -f print_2.awk ls_files_result | sort >expected && - for id in `cat expected | sed "s|..|&/|"` + for id in `sed "s|..|&/|" expected` do rm r1/.git/objects/$id || return 1 done && diff --git a/t/t6113-rev-list-bitmap-filters.sh b/t/t6113-rev-list-bitmap-filters.sh index 86c7052..a9656a1 100755 --- a/t/t6113-rev-list-bitmap-filters.sh +++ b/t/t6113-rev-list-bitmap-filters.sh @@ -1,9 +1,12 @@ #!/bin/sh test_description='rev-list combining bitmaps and filters' + +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh . "$TEST_DIRECTORY"/lib-bitmap.sh + test_expect_success 'set up bitmapped repo' ' # one commit will have bitmaps, the other will not test_commit one && diff --git a/t/t6135-pathspec-with-attrs.sh b/t/t6135-pathspec-with-attrs.sh index a9c1e4e..120dcd7 100755 --- a/t/t6135-pathspec-with-attrs.sh +++ b/t/t6135-pathspec-with-attrs.sh @@ -64,12 +64,24 @@ test_expect_success 'setup .gitattributes' ' fileSetLabel label fileValue label=foo fileWrongLabel label☺ + newFileA* labelA + newFileB* labelB EOF echo fileSetLabel label1 >sub/.gitattributes && git add .gitattributes sub/.gitattributes && git commit -m "add attributes" ' +test_expect_success 'setup .gitignore' ' + cat <<-\EOF >.gitignore && + actual + expect + pathspec_file + EOF + git add .gitignore && + git commit -m "add gitignore" +' + test_expect_success 'check specific set attr' ' cat <<-\EOF >expect && fileSetLabel @@ -150,6 +162,7 @@ test_expect_success 'check specific value attr (2)' ' test_expect_success 'check unspecified attr' ' cat <<-\EOF >expect && .gitattributes + .gitignore fileA fileAB fileAC @@ -175,6 +188,7 @@ test_expect_success 'check unspecified attr' ' test_expect_success 'check unspecified attr (2)' ' cat <<-\EOF >expect && HEAD:.gitattributes + HEAD:.gitignore HEAD:fileA HEAD:fileAB HEAD:fileAC @@ -200,6 +214,7 @@ test_expect_success 'check unspecified attr (2)' ' test_expect_success 'check multiple unspecified attr' ' cat <<-\EOF >expect && .gitattributes + .gitignore fileC fileNoLabel fileWrongLabel @@ -239,16 +254,99 @@ test_expect_success 'fail on multiple attr specifiers in one pathspec item' ' test_grep "Only one" actual ' -test_expect_success 'fail if attr magic is used places not implemented' ' +test_expect_success 'fail if attr magic is used in places not implemented' ' # The main purpose of this test is to check that we actually fail # when you attempt to use attr magic in commands that do not implement - # attr magic. This test does not advocate git-add to stay that way, - # though, but git-add is convenient as it has its own internal pathspec - # parsing. - test_must_fail git add ":(attr:labelB)" 2>actual && + # attr magic. This test does not advocate check-ignore to stay that way. + # When you teach the command to grok the pathspec, you need to find + # another command to replace it for the test. + test_must_fail git check-ignore ":(attr:labelB)" 2>actual && test_grep "magic not supported" actual ' +test_expect_success 'check that attr magic works for git stash push' ' + cat <<-\EOF >expect && + A sub/newFileA-foo + EOF + >sub/newFileA-foo && + >sub/newFileB-foo && + git stash push --include-untracked -- ":(exclude,attr:labelB)" && + git stash show --include-untracked --name-status >actual && + test_cmp expect actual +' + +test_expect_success 'check that attr magic works for git add --all' ' + cat <<-\EOF >expect && + sub/newFileA-foo + EOF + >sub/newFileA-foo && + >sub/newFileB-foo && + git add --all ":(exclude,attr:labelB)" && + git diff --name-only --cached >actual && + git restore -W -S . && + test_cmp expect actual +' + +test_expect_success 'check that attr magic works for git add -u' ' + cat <<-\EOF >expect && + sub/fileA + EOF + >sub/newFileA-foo && + >sub/newFileB-foo && + >sub/fileA && + >sub/fileB && + git add -u ":(exclude,attr:labelB)" && + git diff --name-only --cached >actual && + git restore -S -W . && rm sub/new* && + test_cmp expect actual +' + +test_expect_success 'check that attr magic works for git add <path>' ' + cat <<-\EOF >expect && + fileA + fileB + sub/fileA + EOF + >fileA && + >fileB && + >sub/fileA && + >sub/fileB && + git add ":(exclude,attr:labelB)sub/*" && + git diff --name-only --cached >actual && + git restore -S -W . && + test_cmp expect actual +' + +test_expect_success 'check that attr magic works for git -add .' ' + cat <<-\EOF >expect && + sub/fileA + EOF + >fileA && + >fileB && + >sub/fileA && + >sub/fileB && + cd sub && + git add . ":(exclude,attr:labelB)" && + cd .. && + git diff --name-only --cached >actual && + git restore -S -W . && + test_cmp expect actual +' + +test_expect_success 'check that attr magic works for git add --pathspec-from-file' ' + cat <<-\EOF >pathspec_file && + :(exclude,attr:labelB) + EOF + cat <<-\EOF >expect && + sub/newFileA-foo + EOF + >sub/newFileA-foo && + >sub/newFileB-foo && + git add --all --pathspec-from-file=pathspec_file && + git diff --name-only --cached >actual && + test_cmp expect actual +' + test_expect_success 'abort on giving invalid label on the command line' ' test_must_fail git ls-files . ":(attr:☺)" ' @@ -295,4 +393,31 @@ test_expect_success 'reading from .gitattributes in a subdirectory (3)' ' test_cmp expect actual ' +test_expect_success POSIXPERM 'pathspec with builtin_objectmode attr can be used' ' + >mode_exec_file_1 && + + git status -s ":(attr:builtin_objectmode=100644)mode_exec_*" >actual && + echo ?? mode_exec_file_1 >expect && + test_cmp expect actual && + + git add mode_exec_file_1 && + chmod +x mode_exec_file_1 && + git status -s ":(attr:builtin_objectmode=100755)mode_exec_*" >actual && + echo AM mode_exec_file_1 >expect && + test_cmp expect actual +' + +test_expect_success POSIXPERM 'builtin_objectmode attr can be excluded' ' + >mode_1_regular && + >mode_1_exec && + chmod +x mode_1_exec && + git status -s ":(exclude,attr:builtin_objectmode=100644)" "mode_1_*" >actual && + echo ?? mode_1_exec >expect && + test_cmp expect actual && + + git status -s ":(exclude,attr:builtin_objectmode=100755)" "mode_1_*" >actual && + echo ?? mode_1_regular >expect && + test_cmp expect actual +' + test_done diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 00a060d..eb6c820 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -20,12 +20,13 @@ setdate_and_increment () { export GIT_COMMITTER_DATE GIT_AUTHOR_DATE } -test_expect_success setup ' - test_oid_cache <<-EOF && - disklen sha1:138 - disklen sha256:154 - EOF +test_object_file_size () { + oid=$(git rev-parse "$1") + path=".git/objects/$(test_oid_to_path $oid)" + test_file_size "$path" +} +test_expect_success setup ' # setup .mailmap cat >.mailmap <<-EOF && A Thor <athor@example.com> A U Thor <author@example.com> @@ -94,7 +95,6 @@ test_atom () { } hexlen=$(test_oid hexsz) -disklen=$(test_oid disklen) test_atom head refname refs/heads/main test_atom head refname: refs/heads/main @@ -129,7 +129,7 @@ test_atom head push:strip=1 remotes/myfork/main test_atom head push:strip=-1 main test_atom head objecttype commit test_atom head objectsize $((131 + hexlen)) -test_atom head objectsize:disk $disklen +test_atom head objectsize:disk $(test_object_file_size refs/heads/main) test_atom head deltabase $ZERO_OID test_atom head objectname $(git rev-parse refs/heads/main) test_atom head objectname:short $(git rev-parse --short refs/heads/main) @@ -203,8 +203,8 @@ test_atom tag upstream '' test_atom tag push '' test_atom tag objecttype tag test_atom tag objectsize $((114 + hexlen)) -test_atom tag objectsize:disk $disklen -test_atom tag '*objectsize:disk' $disklen +test_atom tag objectsize:disk $(test_object_file_size refs/tags/testtag) +test_atom tag '*objectsize:disk' $(test_object_file_size refs/heads/main) test_atom tag deltabase $ZERO_OID test_atom tag '*deltabase' $ZERO_OID test_atom tag objectname $(git rev-parse refs/tags/testtag) @@ -1335,6 +1335,73 @@ test_expect_success '--no-sort cancels the previous sort keys' ' test_cmp expected actual ' +test_expect_success '--no-sort without subsequent --sort prints expected refs' ' + cat >expected <<-\EOF && + refs/tags/multi-ref1-100000-user1 + refs/tags/multi-ref1-100000-user2 + refs/tags/multi-ref1-200000-user1 + refs/tags/multi-ref1-200000-user2 + refs/tags/multi-ref2-100000-user1 + refs/tags/multi-ref2-100000-user2 + refs/tags/multi-ref2-200000-user1 + refs/tags/multi-ref2-200000-user2 + EOF + + # Sort the results with `sort` for a consistent comparison against + # expected + git for-each-ref \ + --format="%(refname)" \ + --no-sort \ + "refs/tags/multi-*" | sort >actual && + test_cmp expected actual +' + +test_expect_success 'set up custom date sorting' ' + # Dates: + # - Wed Feb 07 2024 21:34:20 +0000 + # - Tue Dec 14 1999 00:05:22 +0000 + # - Fri Jun 04 2021 11:26:51 +0000 + # - Mon Jan 22 2007 16:44:01 GMT+0000 + i=1 && + for when in 1707341660 945129922 1622806011 1169484241 + do + GIT_COMMITTER_DATE="@$when +0000" \ + GIT_COMMITTER_EMAIL="user@example.com" \ + git tag -m "tag $when" custom-dates-$i && + i=$(($i+1)) || return 1 + done +' + +test_expect_success 'sort by date defaults to full timestamp' ' + cat >expected <<-\EOF && + 945129922 refs/tags/custom-dates-2 + 1169484241 refs/tags/custom-dates-4 + 1622806011 refs/tags/custom-dates-3 + 1707341660 refs/tags/custom-dates-1 + EOF + + git for-each-ref \ + --format="%(creatordate:unix) %(refname)" \ + --sort=creatordate \ + "refs/tags/custom-dates-*" >actual && + test_cmp expected actual +' + +test_expect_success 'sort by custom date format' ' + cat >expected <<-\EOF && + 00:05:22 refs/tags/custom-dates-2 + 11:26:51 refs/tags/custom-dates-3 + 16:44:01 refs/tags/custom-dates-4 + 21:34:20 refs/tags/custom-dates-1 + EOF + + git for-each-ref \ + --format="%(creatordate:format:%H:%M:%S) %(refname)" \ + --sort="creatordate:format:%H:%M:%S" \ + "refs/tags/custom-dates-*" >actual && + test_cmp expected actual +' + test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' ' test_when_finished "git checkout main" && git for-each-ref --format="%(HEAD) %(refname:short)" refs/heads/ >actual && @@ -1818,6 +1885,28 @@ test_expect_success 'git for-each-ref with non-existing refs' ' test_must_be_empty actual ' +test_expect_success 'git for-each-ref with nested tags' ' + git tag -am "Normal tag" nested/base HEAD && + git tag -am "Nested tag" nested/nest1 refs/tags/nested/base && + git tag -am "Double nested tag" nested/nest2 refs/tags/nested/nest1 && + + head_oid="$(git rev-parse HEAD)" && + base_tag_oid="$(git rev-parse refs/tags/nested/base)" && + nest1_tag_oid="$(git rev-parse refs/tags/nested/nest1)" && + nest2_tag_oid="$(git rev-parse refs/tags/nested/nest2)" && + + cat >expect <<-EOF && + refs/tags/nested/base $base_tag_oid tag $head_oid commit + refs/tags/nested/nest1 $nest1_tag_oid tag $head_oid commit + refs/tags/nested/nest2 $nest2_tag_oid tag $head_oid commit + EOF + + git for-each-ref \ + --format="%(refname) %(objectname) %(objecttype) %(*objectname) %(*objecttype)" \ + refs/tags/nested/ >actual && + test_cmp expect actual +' + GRADE_FORMAT="%(signature:grade)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" TRUSTLEVEL_FORMAT="%(signature:trustlevel)%0a%(signature:key)%0a%(signature:signer)%0a%(signature:fingerprint)%0a%(signature:primarykeyfingerprint)" diff --git a/t/t6301-for-each-ref-errors.sh b/t/t6301-for-each-ref-errors.sh index 2667dd1..83b8a19 100755 --- a/t/t6301-for-each-ref-errors.sh +++ b/t/t6301-for-each-ref-errors.sh @@ -15,7 +15,7 @@ test_expect_success setup ' git for-each-ref --format="%(objectname) %(refname)" >brief-list ' -test_expect_success 'Broken refs are reported correctly' ' +test_expect_success REFFILES 'Broken refs are reported correctly' ' r=refs/heads/bogus && : >.git/$r && test_when_finished "rm -f .git/$r" && @@ -25,7 +25,7 @@ test_expect_success 'Broken refs are reported correctly' ' test_cmp broken-err err ' -test_expect_success 'NULL_SHA1 refs are reported correctly' ' +test_expect_success REFFILES 'NULL_SHA1 refs are reported correctly' ' r=refs/heads/zeros && echo $ZEROS >.git/$r && test_when_finished "rm -f .git/$r" && @@ -39,15 +39,14 @@ test_expect_success 'NULL_SHA1 refs are reported correctly' ' ' test_expect_success 'Missing objects are reported correctly' ' - r=refs/heads/missing && - echo $MISSING >.git/$r && - test_when_finished "rm -f .git/$r" && - echo "fatal: missing object $MISSING for $r" >missing-err && + test_when_finished "git update-ref -d refs/heads/missing" && + test-tool ref-store main update-ref msg refs/heads/missing "$MISSING" "$ZERO_OID" REF_SKIP_OID_VERIFICATION && + echo "fatal: missing object $MISSING for refs/heads/missing" >missing-err && test_must_fail git for-each-ref 2>err && test_cmp missing-err err && ( cat brief-list && - echo "$MISSING $r" + echo "$MISSING refs/heads/missing" ) | sort -k 2 >missing-brief-expected && git for-each-ref --format="%(objectname) %(refname)" >brief-out 2>brief-err && test_cmp missing-brief-expected brief-out && diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh index af223e4..948f1bb 100755 --- a/t/t6302-for-each-ref-filter.sh +++ b/t/t6302-for-each-ref-filter.sh @@ -31,6 +31,37 @@ test_expect_success 'setup some history and refs' ' git update-ref refs/odd/spot main ' +test_expect_success '--include-root-refs pattern prints pseudorefs' ' + cat >expect <<-\EOF && + HEAD + ORIG_HEAD + refs/heads/main + refs/heads/side + refs/odd/spot + refs/tags/annotated-tag + refs/tags/doubly-annotated-tag + refs/tags/doubly-signed-tag + refs/tags/four + refs/tags/one + refs/tags/signed-tag + refs/tags/three + refs/tags/two + EOF + git update-ref ORIG_HEAD main && + git for-each-ref --format="%(refname)" --include-root-refs >actual && + test_cmp expect actual +' + +test_expect_success '--include-root-refs with other patterns' ' + cat >expect <<-\EOF && + HEAD + ORIG_HEAD + EOF + git update-ref ORIG_HEAD main && + git for-each-ref --format="%(refname)" --include-root-refs "*HEAD" >actual && + test_cmp expect actual +' + test_expect_success 'filtering with --points-at' ' cat >expect <<-\EOF && refs/heads/main @@ -45,8 +76,8 @@ test_expect_success 'check signed tags with --points-at' ' sed -e "s/Z$//" >expect <<-\EOF && refs/heads/side Z refs/tags/annotated-tag four - refs/tags/doubly-annotated-tag An annotated tag - refs/tags/doubly-signed-tag A signed tag + refs/tags/doubly-annotated-tag four + refs/tags/doubly-signed-tag four refs/tags/four Z refs/tags/signed-tag four EOF diff --git a/t/t6403-merge-file.sh b/t/t6403-merge-file.sh index 2c92209..fb872c5 100755 --- a/t/t6403-merge-file.sh +++ b/t/t6403-merge-file.sh @@ -56,7 +56,67 @@ test_expect_success 'setup' ' deduxit me super semitas jusitiae, EOF - printf "propter nomen suum." >>new4.txt + printf "propter nomen suum." >>new4.txt && + + cat >base.c <<-\EOF && + int f(int x, int y) + { + if (x == 0) + { + return y; + } + return x; + } + + int g(size_t u) + { + while (u < 30) + { + u++; + } + return u; + } + EOF + + cat >ours.c <<-\EOF && + int g(size_t u) + { + while (u < 30) + { + u++; + } + return u; + } + + int h(int x, int y, int z) + { + if (z == 0) + { + return x; + } + return y; + } + EOF + + cat >theirs.c <<-\EOF + int f(int x, int y) + { + if (x == 0) + { + return y; + } + return x; + } + + int g(size_t u) + { + while (u > 34) + { + u--; + } + return u; + } + EOF ' test_expect_success 'merge with no changes' ' @@ -447,4 +507,66 @@ test_expect_success '--object-id fails without repository' ' grep "not a git repository" err ' +test_expect_success 'merging C files with "myers" diff algorithm creates some spurious conflicts' ' + cat >expect.c <<-\EOF && + int g(size_t u) + { + while (u < 30) + { + u++; + } + return u; + } + + int h(int x, int y, int z) + { + <<<<<<< ours.c + if (z == 0) + ||||||| base.c + while (u < 30) + ======= + while (u > 34) + >>>>>>> theirs.c + { + <<<<<<< ours.c + return x; + ||||||| base.c + u++; + ======= + u--; + >>>>>>> theirs.c + } + return y; + } + EOF + + test_must_fail git merge-file -p --diff3 --diff-algorithm myers ours.c base.c theirs.c >myers_output.c && + test_cmp expect.c myers_output.c +' + +test_expect_success 'merging C files with "histogram" diff algorithm avoids some spurious conflicts' ' + cat >expect.c <<-\EOF && + int g(size_t u) + { + while (u > 34) + { + u--; + } + return u; + } + + int h(int x, int y, int z) + { + if (z == 0) + { + return x; + } + return y; + } + EOF + + git merge-file -p --diff3 --diff-algorithm histogram ours.c base.c theirs.c >histogram_output.c && + test_cmp expect.c histogram_output.c +' + test_done diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh index 72f8c17..156a1ef 100755 --- a/t/t6406-merge-attr.sh +++ b/t/t6406-merge-attr.sh @@ -42,11 +42,15 @@ test_expect_success setup ' #!/bin/sh orig="$1" ours="$2" theirs="$3" exit="$4" path=$5 + orig_name="$6" our_name="$7" their_name="$8" ( echo "orig is $orig" echo "ours is $ours" echo "theirs is $theirs" echo "path is $path" + echo "orig_name is $orig_name" + echo "our_name is $our_name" + echo "their_name is $their_name" echo "=== orig ===" cat "$orig" echo "=== ours ===" @@ -121,7 +125,7 @@ test_expect_success 'custom merge backend' ' git reset --hard anchor && git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 0 %P" && + merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" && git config --replace-all \ merge.custom.name "custom merge driver for testing" && @@ -132,7 +136,8 @@ test_expect_success 'custom merge backend' ' o=$(git unpack-file main^:text) && a=$(git unpack-file side^:text) && b=$(git unpack-file main:text) && - sh -c "./custom-merge $o $a $b 0 text" && + base_revid=$(git rev-parse --short main^) && + sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" && sed -e 1,3d $a >check-2 && cmp check-1 check-2 && rm -f $o $a $b @@ -142,7 +147,7 @@ test_expect_success 'custom merge backend' ' git reset --hard anchor && git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 1 %P" && + merge.custom.driver "./custom-merge %O %A %B 1 %P %S %X %Y" && git config --replace-all \ merge.custom.name "custom merge driver for testing" && @@ -159,7 +164,8 @@ test_expect_success 'custom merge backend' ' o=$(git unpack-file main^:text) && a=$(git unpack-file anchor:text) && b=$(git unpack-file main:text) && - sh -c "./custom-merge $o $a $b 0 text" && + base_revid=$(git rev-parse --short main^) && + sh -c "./custom-merge $o $a $b 0 text $base_revid HEAD main" && sed -e 1,3d $a >check-2 && cmp check-1 check-2 && sed -e 1,3d -e 4q $a >check-3 && @@ -173,7 +179,7 @@ test_expect_success !WINDOWS 'custom merge driver that is killed with a signal' git reset --hard anchor && git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 0 %P" && + merge.custom.driver "./custom-merge %O %A %B 0 %P %S %X %Y" && git config --replace-all \ merge.custom.name "custom merge driver for testing" && diff --git a/t/t6413-merge-crlf.sh b/t/t6413-merge-crlf.sh index b4f4a31..647ea1e 100755 --- a/t/t6413-merge-crlf.sh +++ b/t/t6413-merge-crlf.sh @@ -34,14 +34,14 @@ test_expect_success setup ' test_expect_success 'Check "ours" is CRLF' ' git reset --hard initial && git merge side -s ours && - cat file | remove_cr | append_cr >file.temp && + remove_cr <file | append_cr >file.temp && test_cmp file file.temp ' test_expect_success 'Check that conflict file is CRLF' ' git reset --hard a && test_must_fail git merge side && - cat file | remove_cr | append_cr >file.temp && + remove_cr <file | append_cr >file.temp && test_cmp file file.temp ' diff --git a/t/t6418-merge-text-auto.sh b/t/t6418-merge-text-auto.sh index 41288a6..48a62cb 100755 --- a/t/t6418-merge-text-auto.sh +++ b/t/t6418-merge-text-auto.sh @@ -15,6 +15,7 @@ test_description='CRLF merge conflict across text=auto change GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b diff --git a/t/t6429-merge-sequence-rename-caching.sh b/t/t6429-merge-sequence-rename-caching.sh index d02fa16..0f39ed0 100755 --- a/t/t6429-merge-sequence-rename-caching.sh +++ b/t/t6429-merge-sequence-rename-caching.sh @@ -71,8 +71,9 @@ test_expect_success 'caching renames does not preclude finding new ones' ' git switch upstream && - test-tool fast-rebase --onto HEAD upstream~1 topic && - #git cherry-pick upstream~1..topic + git replay --onto HEAD upstream~1..topic >out && + git update-ref --stdin <out && + git checkout topic && git ls-files >tracked-files && test_line_count = 2 tracked-files && @@ -140,8 +141,9 @@ test_expect_success 'cherry-pick both a commit and its immediate revert' ' GIT_TRACE2_PERF="$(pwd)/trace.output" && export GIT_TRACE2_PERF && - test-tool fast-rebase --onto HEAD upstream~1 topic && - #git cherry-pick upstream~1..topic && + git replay --onto HEAD upstream~1..topic >out && + git update-ref --stdin <out && + git checkout topic && grep region_enter.*diffcore_rename trace.output >calls && test_line_count = 1 calls @@ -199,8 +201,9 @@ test_expect_success 'rename same file identically, then reintroduce it' ' GIT_TRACE2_PERF="$(pwd)/trace.output" && export GIT_TRACE2_PERF && - test-tool fast-rebase --onto HEAD upstream~1 topic && - #git cherry-pick upstream~1..topic && + git replay --onto HEAD upstream~1..topic >out && + git update-ref --stdin <out && + git checkout topic && git ls-files >tracked && test_line_count = 2 tracked && @@ -276,8 +279,9 @@ test_expect_success 'rename same file identically, then add file to old dir' ' GIT_TRACE2_PERF="$(pwd)/trace.output" && export GIT_TRACE2_PERF && - test-tool fast-rebase --onto HEAD upstream~1 topic && - #git cherry-pick upstream~1..topic && + git replay --onto HEAD upstream~1..topic >out && + git update-ref --stdin <out && + git checkout topic && git ls-files >tracked && test_line_count = 4 tracked && @@ -353,10 +357,7 @@ test_expect_success 'cached dir rename does not prevent noticing later conflict' GIT_TRACE2_PERF="$(pwd)/trace.output" && export GIT_TRACE2_PERF && - test_must_fail test-tool fast-rebase --onto HEAD upstream~1 topic >output && - #git cherry-pick upstream..topic && - - grep CONFLICT..rename/rename output && + test_must_fail git replay --onto HEAD upstream~1..topic >output && grep region_enter.*diffcore_rename trace.output >calls && test_line_count = 2 calls @@ -455,8 +456,9 @@ test_expect_success 'dir rename unneeded, then add new file to old dir' ' GIT_TRACE2_PERF="$(pwd)/trace.output" && export GIT_TRACE2_PERF && - test-tool fast-rebase --onto HEAD upstream~1 topic && - #git cherry-pick upstream..topic && + git replay --onto HEAD upstream~1..topic >out && + git update-ref --stdin <out && + git checkout topic && grep region_enter.*diffcore_rename trace.output >calls && test_line_count = 2 calls && @@ -521,8 +523,9 @@ test_expect_success 'dir rename unneeded, then rename existing file into old dir GIT_TRACE2_PERF="$(pwd)/trace.output" && export GIT_TRACE2_PERF && - test-tool fast-rebase --onto HEAD upstream~1 topic && - #git cherry-pick upstream..topic && + git replay --onto HEAD upstream~1..topic >out && + git update-ref --stdin <out && + git checkout topic && grep region_enter.*diffcore_rename trace.output >calls && test_line_count = 3 calls && @@ -623,8 +626,9 @@ test_expect_success 'caching renames only on upstream side, part 1' ' GIT_TRACE2_PERF="$(pwd)/trace.output" && export GIT_TRACE2_PERF && - test-tool fast-rebase --onto HEAD upstream~1 topic && - #git cherry-pick upstream..topic && + git replay --onto HEAD upstream~1..topic >out && + git update-ref --stdin <out && + git checkout topic && grep region_enter.*diffcore_rename trace.output >calls && test_line_count = 1 calls && @@ -681,8 +685,9 @@ test_expect_success 'caching renames only on upstream side, part 2' ' GIT_TRACE2_PERF="$(pwd)/trace.output" && export GIT_TRACE2_PERF && - test-tool fast-rebase --onto HEAD upstream~1 topic && - #git cherry-pick upstream..topic && + git replay --onto HEAD upstream~1..topic >out && + git update-ref --stdin <out && + git checkout topic && grep region_enter.*diffcore_rename trace.output >calls && test_line_count = 2 calls && diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh index 7065052..7a3f1cb 100755 --- a/t/t6437-submodule-merge.sh +++ b/t/t6437-submodule-merge.sh @@ -113,7 +113,7 @@ test_expect_success 'merging should conflict for non fast-forward' ' git checkout -b test-nonforward-a b && if test "$GIT_TEST_MERGE_ALGORITHM" = ort then - test_must_fail git merge c >actual && + test_must_fail git merge c 2>actual && sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && grep "$sub_expect" actual else @@ -154,9 +154,9 @@ test_expect_success 'merging should conflict for non fast-forward (resolution ex 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 2>sub-actual && sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && - grep "$sub_expect" actual + grep "$sub_expect" sub-actual else test_must_fail git merge c 2> actual fi && @@ -181,9 +181,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 2>sub-actual && sub_expect="go to submodule (sub), and either merge commit $(git -C sub rev-parse --short sub-c)" && - grep "$sub_expect" actual + grep "$sub_expect" sub-actual else test_must_fail git merge c 2> actual fi && @@ -227,7 +227,7 @@ 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 >actual && + test_must_fail git merge f 2>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)" && @@ -535,7 +535,7 @@ test_expect_success 'merging should fail with no merge base' ' git checkout -b b init && git add sub && git commit -m "b" && - test_must_fail git merge a >actual && + test_must_fail git merge a 2>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)" && diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index 18fe1c2..43d4017 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -11,23 +11,7 @@ test_expect_success 'setup' ' # behavior, make sure we always pack everything to one pack by # default git config gc.bigPackThreshold 2g && - - # These are simply values which, when hashed as a blob with a newline, - # produce a hash where the first byte is 0x17 in their respective - # algorithms. - test_oid_cache <<-EOF - obj1 sha1:263 - obj1 sha256:34 - - obj2 sha1:410 - obj2 sha256:174 - - obj3 sha1:523 - obj3 sha256:313 - - obj4 sha1:790 - obj4 sha256:481 - EOF + test_oid_init ' test_expect_success 'gc empty repository' ' @@ -114,8 +98,8 @@ test_expect_success 'pre-auto-gc hook can stop auto gc' ' # We need to create two object whose sha1s start with 17 # since this is what git gc counts. As it happens, these # two blobs will do so. - test_commit "$(test_oid obj1)" && - test_commit "$(test_oid obj2)" && + test_commit "$(test_oid blob17_1)" && + test_commit "$(test_oid blob17_2)" && git gc --auto >../out.actual 2>../err.actual ) && @@ -146,13 +130,13 @@ test_expect_success 'auto gc with too many loose objects does not attempt to cre # We need to create two object whose sha1s start with 17 # since this is what git gc counts. As it happens, these # two blobs will do so. - test_commit "$(test_oid obj1)" && - test_commit "$(test_oid obj2)" && + test_commit "$(test_oid blob17_1)" && + test_commit "$(test_oid blob17_2)" && # Our first gc will create a pack; our second will create a second pack git gc --auto && ls .git/objects/pack/pack-*.pack | sort >existing_packs && - test_commit "$(test_oid obj3)" && - test_commit "$(test_oid obj4)" && + test_commit "$(test_oid blob17_3)" && + test_commit "$(test_oid blob17_4)" && git gc --auto 2>err && test_grep ! "^warning:" err && diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index f6aebe9..5ab4d41 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -396,10 +396,7 @@ test_expect_success '--prune-empty is able to prune entire branch' ' git branch prune-entire B && git filter-branch -f --prune-empty --index-filter "git update-index --remove A.t B.t" prune-entire && test_must_fail git rev-parse refs/heads/prune-entire && - if test_have_prereq REFFILES - then - test_must_fail git reflog exists refs/heads/prune-entire - fi + test_must_fail git reflog exists refs/heads/prune-entire ' test_expect_success '--remap-to-ancestor with filename filters' ' diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index e689db4..696866d 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -1777,10 +1777,10 @@ test_expect_success '--points-at finds annotated tags of tags' ' ' test_expect_success 'recursive tagging should give advice' ' - sed -e "s/|$//" <<-EOF >expect && + cat >expect <<-EOF && hint: You have created a nested tag. The object referred to by your new tag is hint: already a tag. If you meant to tag the object that it points to, use: - hint: | + hint: hint: git tag -f nested annotated-v4.0^{} hint: Disable this message with "git config advice.nestedTag false" EOF @@ -1862,6 +1862,51 @@ test_expect_success 'option override configured sort' ' test_cmp expect actual ' +test_expect_success '--no-sort cancels config sort keys' ' + test_config tag.sort "-refname" && + + # objecttype is identical for all of them, so sort falls back on + # default (ascending refname) + git tag -l \ + --no-sort \ + --sort="objecttype" \ + "foo*" >actual && + cat >expect <<-\EOF && + foo1.10 + foo1.3 + foo1.6 + EOF + test_cmp expect actual +' + +test_expect_success '--no-sort cancels command line sort keys' ' + # objecttype is identical for all of them, so sort falls back on + # default (ascending refname) + git tag -l \ + --sort="-refname" \ + --no-sort \ + --sort="objecttype" \ + "foo*" >actual && + cat >expect <<-\EOF && + foo1.10 + foo1.3 + foo1.6 + EOF + test_cmp expect actual +' + +test_expect_success '--no-sort without subsequent --sort prints expected tags' ' + # Sort the results with `sort` for a consistent comparison against + # expected + git tag -l --no-sort "foo*" | sort >actual && + cat >expect <<-\EOF && + foo1.10 + foo1.3 + foo1.6 + EOF + test_cmp expect actual +' + test_expect_success 'invalid sort parameter on command line' ' test_must_fail git tag -l --sort=notvalid "foo*" >actual ' diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index 4287863..62d9f84 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -616,4 +616,12 @@ test_expect_success 'reset --mixed sets up work tree' ' test_must_be_empty actual ' +test_expect_success 'reset handles --end-of-options' ' + git update-ref refs/heads/--foo HEAD^ && + git log -1 --format=%s refs/heads/--foo >expect && + git reset --hard --end-of-options --foo && + git log -1 --format=%s HEAD >actual && + test_cmp expect actual +' + test_done diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh index 05079c7..f4f3b7a 100755 --- a/t/t7105-reset-patch.sh +++ b/t/t7105-reset-patch.sh @@ -5,7 +5,7 @@ test_description='git reset --patch' TEST_PASSES_SANITIZE_LEAK=true . ./lib-patch-mode.sh -test_expect_success PERL 'setup' ' +test_expect_success 'setup' ' mkdir dir && echo parent > dir/foo && echo dummy > bar && @@ -19,42 +19,46 @@ test_expect_success PERL 'setup' ' # note: bar sorts before foo, so the first 'n' is always to skip 'bar' -test_expect_success PERL 'saying "n" does nothing' ' +test_expect_success 'saying "n" does nothing' ' set_and_save_state dir/foo work work && test_write_lines n n | git reset -p && verify_saved_state dir/foo && verify_saved_state bar ' -test_expect_success PERL 'git reset -p' ' - test_write_lines n y | git reset -p >output && - verify_state dir/foo work head && - verify_saved_state bar && - test_grep "Unstage" output -' - -test_expect_success PERL 'git reset -p HEAD^' ' +for opt in "HEAD" "@" "" +do + test_expect_success "git reset -p $opt" ' + set_and_save_state dir/foo work work && + test_write_lines n y | git reset -p $opt >output && + verify_state dir/foo work head && + verify_saved_state bar && + test_grep "Unstage" output + ' +done + +test_expect_success 'git reset -p HEAD^' ' test_write_lines n y | git reset -p HEAD^ >output && verify_state dir/foo work parent && verify_saved_state bar && test_grep "Apply" output ' -test_expect_success PERL 'git reset -p HEAD^^{tree}' ' +test_expect_success 'git reset -p HEAD^^{tree}' ' test_write_lines n y | git reset -p HEAD^^{tree} >output && verify_state dir/foo work parent && verify_saved_state bar && test_grep "Apply" output ' -test_expect_success PERL 'git reset -p HEAD^:dir/foo (blob fails)' ' +test_expect_success 'git reset -p HEAD^:dir/foo (blob fails)' ' set_and_save_state dir/foo work work && test_must_fail git reset -p HEAD^:dir/foo && verify_saved_state dir/foo && verify_saved_state bar ' -test_expect_success PERL 'git reset -p aaaaaaaa (unknown fails)' ' +test_expect_success 'git reset -p aaaaaaaa (unknown fails)' ' set_and_save_state dir/foo work work && test_must_fail git reset -p aaaaaaaa && verify_saved_state dir/foo && @@ -66,27 +70,27 @@ test_expect_success PERL 'git reset -p aaaaaaaa (unknown fails)' ' # dir/foo. There's always an extra 'n' to reject edits to dir/foo in # the failure case (and thus get out of the loop). -test_expect_success PERL 'git reset -p dir' ' +test_expect_success 'git reset -p dir' ' set_state dir/foo work work && test_write_lines y n | git reset -p dir && verify_state dir/foo work head && verify_saved_state bar ' -test_expect_success PERL 'git reset -p -- foo (inside dir)' ' +test_expect_success 'git reset -p -- foo (inside dir)' ' set_state dir/foo work work && test_write_lines y n | (cd dir && git reset -p -- foo) && verify_state dir/foo work head && verify_saved_state bar ' -test_expect_success PERL 'git reset -p HEAD^ -- dir' ' +test_expect_success 'git reset -p HEAD^ -- dir' ' test_write_lines y n | git reset -p HEAD^ -- dir && verify_state dir/foo work parent && verify_saved_state bar ' -test_expect_success PERL 'none of this moved HEAD' ' +test_expect_success 'none of this moved HEAD' ' verify_saved_head ' diff --git a/t/t7106-reset-unborn-branch.sh b/t/t7106-reset-unborn-branch.sh index d20e570..88d1c8a 100755 --- a/t/t7106-reset-unborn-branch.sh +++ b/t/t7106-reset-unborn-branch.sh @@ -34,7 +34,7 @@ test_expect_success 'reset $file' ' test_cmp expect actual ' -test_expect_success PERL 'reset -p' ' +test_expect_success 'reset -p' ' rm .git/index && git add a && echo y >yes && diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 10cc6c4..42352dc 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -631,6 +631,72 @@ test_expect_success 'checkout --conflict=diff3' ' test_cmp merged file ' +test_expect_success 'checkout --conflict=diff3 --no-conflict does not merge' ' + setup_conflicting_index && + echo "none of the above" >expect && + cat expect >fild && + cat expect >file && + test_must_fail git checkout --conflict=diff3 --no-conflict -- fild file 2>err && + test_cmp expect file && + test_cmp expect fild && + echo "error: path ${SQ}file${SQ} is unmerged" >expect && + test_cmp expect err +' + +test_expect_success 'checkout --conflict=diff3 --no-merge does not merge' ' + setup_conflicting_index && + echo "none of the above" >expect && + cat expect >fild && + cat expect >file && + test_must_fail git checkout --conflict=diff3 --no-merge -- fild file 2>err && + test_cmp expect file && + test_cmp expect fild && + echo "error: path ${SQ}file${SQ} is unmerged" >expect && + test_cmp expect err +' + +test_expect_success 'checkout --no-merge --conflict=diff3 does merge' ' + setup_conflicting_index && + echo "none of the above" >fild && + echo "none of the above" >file && + git checkout --no-merge --conflict=diff3 -- fild file && + echo "ourside" >expect && + test_cmp expect fild && + cat >expect <<-\EOF && + <<<<<<< ours + ourside + ||||||| base + original + ======= + theirside + >>>>>>> theirs + EOF + test_cmp expect file +' + +test_expect_success 'checkout --merge --conflict=diff3 --no-conflict does merge' ' + setup_conflicting_index && + echo "none of the above" >fild && + echo "none of the above" >file && + git checkout --merge --conflict=diff3 --no-conflict -- fild file && + echo "ourside" >expect && + test_cmp expect fild && + cat >expect <<-\EOF && + <<<<<<< ours + ourside + ======= + theirside + >>>>>>> theirs + EOF + test_cmp expect file +' + +test_expect_success 'checkout with invalid conflict style' ' + test_must_fail git checkout --conflict=bad 2>actual -- file && + echo "error: unknown conflict style ${SQ}bad${SQ}" >expect && + test_cmp expect actual +' + test_expect_success 'failing checkout -b should not break working tree' ' git clean -fd && # Remove untracked files in the way git reset --hard main && diff --git a/t/t7300-clean.sh b/t/t7300-clean.sh index 1a310a4..0aae0de 100755 --- a/t/t7300-clean.sh +++ b/t/t7300-clean.sh @@ -5,6 +5,7 @@ test_description='git clean basic tests' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh git config clean.requireForce no @@ -407,6 +408,12 @@ test_expect_success 'clean.requireForce and -f' ' ' +test_expect_success 'clean.requireForce and --interactive' ' + git clean --interactive </dev/null >output 2>error && + test_grep ! "requireForce is true and" error && + test_grep "\*\*\* Commands \*\*\*" output +' + test_expect_success 'core.excludesfile' ' echo excludes >excludes && @@ -517,8 +524,12 @@ test_expect_success 'nested (empty) git should be kept' ' git init empty_repo && mkdir to_clean && >to_clean/should_clean.this && + # Note that we put the expect file in the .git directory so that it + # does not get cleaned. + find empty_repo | sort >.git/expect && git clean -f -d && - test_path_is_file empty_repo/.git/HEAD && + find empty_repo | sort >actual && + test_cmp .git/expect actual && test_path_is_missing to_clean ' @@ -559,10 +570,10 @@ test_expect_success 'giving path in nested git work tree will NOT remove it' ' mkdir -p bar/baz && test_commit msg bar/baz/hello.world ) && + find repo | sort >expect && git clean -f -d repo/bar/baz && - test_path_is_file repo/.git/HEAD && - test_path_is_dir repo/bar/ && - test_path_is_file repo/bar/baz/hello.world + find repo | sort >actual && + test_cmp expect actual ' test_expect_success 'giving path to nested .git will not remove it' ' @@ -573,10 +584,10 @@ test_expect_success 'giving path to nested .git will not remove it' ' git init && test_commit msg hello.world ) && + find repo | sort >expect && git clean -f -d repo/.git && - test_path_is_file repo/.git/HEAD && - test_path_is_dir repo/.git/refs && - test_path_is_dir repo/.git/objects && + find repo | sort >actual && + test_cmp expect actual && test_path_is_dir untracked/ ' @@ -588,9 +599,10 @@ test_expect_success 'giving path to nested .git/ will NOT remove contents' ' git init && test_commit msg hello.world ) && + find repo | sort >expect && git clean -f -d repo/.git/ && - test_path_is_dir repo/.git && - test_path_is_file repo/.git/HEAD && + find repo | sort >actual && + test_cmp expect actual && test_path_is_dir untracked/ ' diff --git a/t/t7301-clean-interactive.sh b/t/t7301-clean-interactive.sh index d82a321..4afe53c 100755 --- a/t/t7301-clean-interactive.sh +++ b/t/t7301-clean-interactive.sh @@ -25,18 +25,18 @@ test_expect_success 'git clean -i (c: clean hotkey)' ' touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && echo c | git clean -i && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test -f docs/manual.txt && - test ! -f src/part3.c && - test ! -f src/part3.h && - test ! -f src/part4.c && - test ! -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_file docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_missing src/part3.h && + test_path_is_missing src/part4.c && + test_path_is_missing src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -46,18 +46,18 @@ test_expect_success 'git clean -i (cl: clean prefix)' ' touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && echo cl | git clean -i && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test -f docs/manual.txt && - test ! -f src/part3.c && - test ! -f src/part3.h && - test ! -f src/part4.c && - test ! -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_file docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_missing src/part3.h && + test_path_is_missing src/part4.c && + test_path_is_missing src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -67,18 +67,18 @@ test_expect_success 'git clean -i (quit)' ' touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && echo quit | git clean -i && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test -f a.out && - test -f docs/manual.txt && - test -f src/part3.c && - test -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_file a.out && + test_path_is_file docs/manual.txt && + test_path_is_file src/part3.c && + test_path_is_file src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -88,18 +88,18 @@ test_expect_success 'git clean -i (Ctrl+D)' ' touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && echo "\04" | git clean -i && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test -f a.out && - test -f docs/manual.txt && - test -f src/part3.c && - test -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_file a.out && + test_path_is_file docs/manual.txt && + test_path_is_file src/part3.c && + test_path_is_file src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -110,18 +110,18 @@ test_expect_success 'git clean -id (filter all)' ' docs/manual.txt obj.o build/lib.so && test_write_lines f "*" "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test -f a.out && - test -f docs/manual.txt && - test -f src/part3.c && - test -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_file a.out && + test_path_is_file docs/manual.txt && + test_path_is_file src/part3.c && + test_path_is_file src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -132,18 +132,18 @@ test_expect_success 'git clean -id (filter patterns)' ' docs/manual.txt obj.o build/lib.so && test_write_lines f "part3.* *.out" "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test -f a.out && - test ! -f docs/manual.txt && - test -f src/part3.c && - test -f src/part3.h && - test ! -f src/part4.c && - test ! -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_file a.out && + test_path_is_missing docs/manual.txt && + test_path_is_file src/part3.c && + test_path_is_file src/part3.h && + test_path_is_missing src/part4.c && + test_path_is_missing src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -154,18 +154,18 @@ test_expect_success 'git clean -id (filter patterns 2)' ' docs/manual.txt obj.o build/lib.so && test_write_lines f "* !*.out" "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test -f docs/manual.txt && - test -f src/part3.c && - test -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_file docs/manual.txt && + test_path_is_file src/part3.c && + test_path_is_file src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -176,18 +176,18 @@ test_expect_success 'git clean -id (select - all)' ' docs/manual.txt obj.o build/lib.so && test_write_lines s "*" "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test ! -f docs/manual.txt && - test ! -f src/part3.c && - test ! -f src/part3.h && - test ! -f src/part4.c && - test ! -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_missing docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_missing src/part3.h && + test_path_is_missing src/part4.c && + test_path_is_missing src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -198,18 +198,18 @@ test_expect_success 'git clean -id (select - none)' ' docs/manual.txt obj.o build/lib.so && test_write_lines s "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test -f a.out && - test -f docs/manual.txt && - test -f src/part3.c && - test -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_file a.out && + test_path_is_file docs/manual.txt && + test_path_is_file src/part3.c && + test_path_is_file src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -220,18 +220,18 @@ test_expect_success 'git clean -id (select - number)' ' docs/manual.txt obj.o build/lib.so && test_write_lines s 3 "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test -f a.out && - test -f docs/manual.txt && - test ! -f src/part3.c && - test -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_file a.out && + test_path_is_file docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_file src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -242,18 +242,18 @@ test_expect_success 'git clean -id (select - number 2)' ' docs/manual.txt obj.o build/lib.so && test_write_lines s "2 3" 5 "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test -f a.out && - test ! -f docs/manual.txt && - test ! -f src/part3.c && - test -f src/part3.h && - test ! -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_file a.out && + test_path_is_missing docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_file src/part3.h && + test_path_is_missing src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -264,18 +264,18 @@ test_expect_success 'git clean -id (select - number 3)' ' docs/manual.txt obj.o build/lib.so && test_write_lines s "3,4 5" "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test -f a.out && - test -f docs/manual.txt && - test ! -f src/part3.c && - test ! -f src/part3.h && - test ! -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_file a.out && + test_path_is_file docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_missing src/part3.h && + test_path_is_missing src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -285,11 +285,11 @@ test_expect_success 'git clean -id (select - filenames)' ' touch a.out foo.txt bar.txt baz.txt && test_write_lines s "a.out fo ba bar" "" c | git clean -id && - test -f Makefile && - test ! -f a.out && - test ! -f foo.txt && - test ! -f bar.txt && - test -f baz.txt && + test_path_is_file Makefile && + test_path_is_missing a.out && + test_path_is_missing foo.txt && + test_path_is_missing bar.txt && + test_path_is_file baz.txt && rm baz.txt ' @@ -301,18 +301,18 @@ test_expect_success 'git clean -id (select - range)' ' docs/manual.txt obj.o build/lib.so && test_write_lines s "1,3-4" 2 "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test ! -f src/part3.c && - test ! -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test ! -f docs/manual.txt && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_missing src/part3.c && + test_path_is_missing src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_missing docs/manual.txt && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -323,18 +323,18 @@ test_expect_success 'git clean -id (select - range 2)' ' docs/manual.txt obj.o build/lib.so && test_write_lines s "4- 1" "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test -f docs/manual.txt && - test -f src/part3.c && - test ! -f src/part3.h && - test ! -f src/part4.c && - test ! -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_file docs/manual.txt && + test_path_is_file src/part3.c && + test_path_is_missing src/part3.h && + test_path_is_missing src/part4.c && + test_path_is_missing src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -345,18 +345,18 @@ test_expect_success 'git clean -id (inverse select)' ' docs/manual.txt obj.o build/lib.so && test_write_lines s "*" "-5- 1 -2" "" c | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test -f docs/manual.txt && - test ! -f src/part3.c && - test ! -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_file docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_missing src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -367,18 +367,18 @@ test_expect_success 'git clean -id (ask)' ' docs/manual.txt obj.o build/lib.so && test_write_lines a Y y no yes bad "" | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test ! -f docs/manual.txt && - test -f src/part3.c && - test ! -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_missing docs/manual.txt && + test_path_is_file src/part3.c && + test_path_is_missing src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -389,18 +389,18 @@ test_expect_success 'git clean -id (ask - Ctrl+D)' ' docs/manual.txt obj.o build/lib.so && test_write_lines a Y no yes "\04" | git clean -id && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test -f docs/manual.txt && - test ! -f src/part3.c && - test -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_file docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_file src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -412,18 +412,18 @@ test_expect_success 'git clean -id with prefix and path (filter)' ' (cd build/ && test_write_lines f docs "*.h" "" c | git clean -id ..) && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test -f docs/manual.txt && - test ! -f src/part3.c && - test -f src/part3.h && - test ! -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_file docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_file src/part3.h && + test_path_is_missing src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -435,18 +435,18 @@ test_expect_success 'git clean -id with prefix and path (select by name)' ' (cd build/ && test_write_lines s ../docs/ ../src/part3.c ../src/part4.c "" c | git clean -id ..) && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test -f a.out && - test ! -f docs/manual.txt && - test ! -f src/part3.c && - test -f src/part3.h && - test ! -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_file a.out && + test_path_is_missing docs/manual.txt && + test_path_is_missing src/part3.c && + test_path_is_file src/part3.h && + test_path_is_missing src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' @@ -458,18 +458,18 @@ test_expect_success 'git clean -id with prefix and path (ask)' ' (cd build/ && test_write_lines a Y y no yes bad "" | git clean -id ..) && - test -f Makefile && - test -f README && - test -f src/part1.c && - test -f src/part2.c && - test ! -f a.out && - test ! -f docs/manual.txt && - test -f src/part3.c && - test ! -f src/part3.h && - test -f src/part4.c && - test -f src/part4.h && - test -f obj.o && - test -f build/lib.so + test_path_is_file Makefile && + test_path_is_file README && + test_path_is_file src/part1.c && + test_path_is_file src/part2.c && + test_path_is_missing a.out && + test_path_is_missing docs/manual.txt && + test_path_is_file src/part3.c && + test_path_is_missing src/part3.h && + test_path_is_file src/part4.c && + test_path_is_file src/part4.h && + test_path_is_file obj.o && + test_path_is_file build/lib.so ' diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 00c1f1a..9814888 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -212,8 +212,7 @@ test_expect_success 'submodule add to .gitignored path fails' ' The following paths are ignored by one of your .gitignore files: submod hint: Use -f if you really want to add them. - hint: Turn this message off by running - hint: "git config advice.addIgnoredFile false" + hint: Disable this message with "git config advice.addIgnoredFile false" EOF # Does not use test_commit due to the ignore echo "*" > .gitignore && @@ -1452,4 +1451,35 @@ test_expect_success 'recursive clone respects -q' ' test_must_be_empty actual ' +test_expect_success '`submodule init` and `init.templateDir`' ' + mkdir -p tmpl/hooks && + write_script tmpl/hooks/post-checkout <<-EOF && + echo HOOK-RUN >&2 + echo I was here >hook.run + exit 1 + EOF + + test_config init.templateDir "$(pwd)/tmpl" && + test_when_finished \ + "git config --global --unset init.templateDir || true" && + ( + sane_unset GIT_TEMPLATE_DIR && + NO_SET_GIT_TEMPLATE_DIR=t && + export NO_SET_GIT_TEMPLATE_DIR && + + git config --global init.templateDir "$(pwd)/tmpl" && + test_must_fail git submodule \ + add "$submodurl" sub-global 2>err && + git config --global --unset init.templateDir && + test_grep HOOK-RUN err && + test_path_is_file sub-global/hook.run && + + git config init.templateDir "$(pwd)/tmpl" && + git submodule add "$submodurl" sub-local 2>err && + git config --unset init.templateDir && + test_grep ! HOOK-RUN err && + test_path_is_missing sub-local/hook.run + ) +' + test_done diff --git a/t/t7402-submodule-rebase.sh b/t/t7402-submodule-rebase.sh index 2b3c363..aa2fdc3 100755 --- a/t/t7402-submodule-rebase.sh +++ b/t/t7402-submodule-rebase.sh @@ -116,7 +116,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 >actual_output && + test_must_fail git rebase --onto HEAD^^ HEAD^ HEAD^0 2>actual_output && git ls-files -s submodule >actual && ( cd submodule && diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 8491b8c..297c6c3 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -1202,4 +1202,52 @@ test_expect_success 'commit with staged submodule change with ignoreSubmodules a add_submodule_commit_and_validate ' +test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \ + 'submodule paths must not follow symlinks' ' + + # This is only needed because we want to run this in a self-contained + # test without having to spin up an HTTP server; However, it would not + # be needed in a real-world scenario where the submodule is simply + # hosted on a public site. + test_config_global protocol.file.allow always && + + # Make sure that Git tries to use symlinks on Windows + test_config_global core.symlinks true && + + tell_tale_path="$PWD/tell.tale" && + git init hook && + ( + cd hook && + mkdir -p y/hooks && + write_script y/hooks/post-checkout <<-EOF && + echo HOOK-RUN >&2 + echo hook-run >"$tell_tale_path" + EOF + git add y/hooks/post-checkout && + test_tick && + git commit -m post-checkout + ) && + + hook_repo_path="$(pwd)/hook" && + git init captain && + ( + cd captain && + git submodule add --name x/y "$hook_repo_path" A/modules/x && + test_tick && + git commit -m add-submodule && + + printf .git >dotgit.txt && + git hash-object -w --stdin <dotgit.txt >dot-git.hash && + printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" >index.info && + git update-index --index-info <index.info && + test_tick && + git commit -m add-symlink + ) && + + test_path_is_missing "$tell_tale_path" && + git clone --recursive captain hooked 2>err && + test_grep ! HOOK-RUN err && + test_path_is_missing "$tell_tale_path" +' + test_done diff --git a/t/t7423-submodule-symlinks.sh b/t/t7423-submodule-symlinks.sh new file mode 100755 index 0000000..3d3c7af --- /dev/null +++ b/t/t7423-submodule-symlinks.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +test_description='check that submodule operations do not follow symlinks' + +. ./test-lib.sh + +test_expect_success 'prepare' ' + git config --global protocol.file.allow always && + test_commit initial && + git init upstream && + test_commit -C upstream upstream submodule_file && + git submodule add ./upstream a/sm && + test_tick && + git commit -m submodule +' + +test_expect_success SYMLINKS 'git submodule update must not create submodule behind symlink' ' + rm -rf a b && + mkdir b && + ln -s b a && + test_path_is_missing b/sm && + test_must_fail git submodule update && + test_path_is_missing b/sm +' + +test_expect_success SYMLINKS,CASE_INSENSITIVE_FS 'git submodule update must not create submodule behind symlink on case insensitive fs' ' + rm -rf a b && + mkdir b && + ln -s b A && + test_must_fail git submodule update && + test_path_is_missing b/sm +' + +prepare_symlink_to_repo() { + rm -rf a && + mkdir a && + git init a/target && + git -C a/target fetch ../../upstream && + ln -s target a/sm +} + +test_expect_success SYMLINKS 'git restore --recurse-submodules must not be confused by a symlink' ' + prepare_symlink_to_repo && + test_must_fail git restore --recurse-submodules a/sm && + test_path_is_missing a/sm/submodule_file && + test_path_is_dir a/target/.git && + test_path_is_missing a/target/submodule_file +' + +test_expect_success SYMLINKS 'git restore --recurse-submodules must not migrate git dir of symlinked repo' ' + prepare_symlink_to_repo && + rm -rf .git/modules && + test_must_fail git restore --recurse-submodules a/sm && + test_path_is_dir a/target/.git && + test_path_is_missing .git/modules/a/sm && + test_path_is_missing a/target/submodule_file +' + +test_expect_success SYMLINKS 'git checkout -f --recurse-submodules must not migrate git dir of symlinked repo when removing submodule' ' + prepare_symlink_to_repo && + rm -rf .git/modules && + test_must_fail git checkout -f --recurse-submodules initial && + test_path_is_dir a/target/.git && + test_path_is_missing .git/modules/a/sm +' + +test_done diff --git a/t/t7450-bad-git-dotfiles.sh b/t/t7450-bad-git-dotfiles.sh index 35a31ac..4a9c22c 100755 --- a/t/t7450-bad-git-dotfiles.sh +++ b/t/t7450-bad-git-dotfiles.sh @@ -45,6 +45,32 @@ test_expect_success 'check names' ' test_cmp expect actual ' +test_expect_success 'check urls' ' + cat >expect <<-\EOF && + ./bar/baz/foo.git + https://example.com/foo.git + http://example.com:80/deeper/foo.git + EOF + + test-tool submodule check-url >actual <<-\EOF && + ./bar/baz/foo.git + https://example.com/foo.git + http://example.com:80/deeper/foo.git + -a./foo + ../../..//test/foo.git + ../../../../../:localhost:8080/foo.git + ..\../.\../:example.com/foo.git + ./%0ahost=example.com/foo.git + https://one.example.com/evil?%0ahost=two.example.com + https:///example.com/foo.git + http://example.com:test/foo.git + https::example.com/foo.git + http:::example.com/foo.git + EOF + + test_cmp expect actual +' + test_expect_success 'create innocent subrepo' ' git init innocent && git -C innocent commit --allow-empty -m foo @@ -294,7 +320,7 @@ test_expect_success WINDOWS 'prevent git~1 squatting on Windows' ' fi ' -test_expect_success 'git dirs of sibling submodules must not be nested' ' +test_expect_success 'setup submodules with nested git dirs' ' git init nested && test_commit -C nested nested && ( @@ -312,9 +338,39 @@ test_expect_success 'git dirs of sibling submodules must not be nested' ' git add .gitmodules thing1 thing2 && test_tick && git commit -m nested - ) && + ) +' + +test_expect_success 'git dirs of sibling submodules must not be nested' ' test_must_fail git clone --recurse-submodules nested clone 2>err && test_grep "is inside git dir" err ' +test_expect_success 'submodule git dir nesting detection must work with parallel cloning' ' + test_must_fail git clone --recurse-submodules --jobs=2 nested clone_parallel 2>err && + cat err && + grep -E "(already exists|is inside git dir|not a git repository)" err && + { + test_path_is_missing .git/modules/hippo/HEAD || + test_path_is_missing .git/modules/hippo/hooks/HEAD + } +' + +test_expect_success 'checkout -f --recurse-submodules must not use a nested gitdir' ' + git clone nested nested_checkout && + ( + cd nested_checkout && + git submodule init && + git submodule update thing1 && + mkdir -p .git/modules/hippo/hooks/refs && + mkdir -p .git/modules/hippo/hooks/objects/info && + echo "../../../../objects" >.git/modules/hippo/hooks/objects/info/alternates && + echo "ref: refs/heads/master" >.git/modules/hippo/hooks/HEAD + ) && + test_must_fail git -C nested_checkout checkout -f --recurse-submodules HEAD 2>err && + cat err && + grep "is inside git dir" err && + test_path_is_missing nested_checkout/thing2/.git +' + test_done diff --git a/t/t7501-commit-basic-functionality.sh b/t/t7501-commit-basic-functionality.sh index 3d8500a..cc12f99 100755 --- a/t/t7501-commit-basic-functionality.sh +++ b/t/t7501-commit-basic-functionality.sh @@ -3,8 +3,7 @@ # Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com> # -# FIXME: Test the various index usages, -i and -o, test reflog, -# signoff +# FIXME: Test the various index usages, test reflog test_description='git commit' GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main @@ -92,6 +91,20 @@ test_expect_success '--long fails with nothing to commit' ' test_must_fail git commit -m initial --long ' +test_expect_success 'fail to commit untracked file (even with --include/--only)' ' + echo content >baz && + error="error: pathspec .baz. did not match any file(s) known to git" && + + test_must_fail git commit -m "baz" baz 2>err && + test_grep -e "$error" err && + + test_must_fail git commit --only -m "baz" baz 2>err && + test_grep -e "$error" err && + + test_must_fail git commit --include -m "baz" baz 2>err && + test_grep -e "$error" err +' + test_expect_success 'setup: non-initial commit' ' echo bongo bongo bongo >file && git commit -m next -a @@ -117,6 +130,51 @@ test_expect_success '--long with stuff to commit returns ok' ' git commit -m next -a --long ' +for opt in "" "-o" "--only" +do + test_expect_success 'exclude additional staged changes when given pathspec' ' + echo content >>file && + echo content >>baz && + git add baz && + git commit $opt -m "file" file && + + git diff --name-only >actual && + test_must_be_empty actual && + + test_write_lines baz >expect && + git diff --name-only --cached >actual && + test_cmp expect actual && + + test_write_lines file >expect && + git diff --name-only HEAD^ HEAD >actual && + test_cmp expect actual + ' +done + +test_expect_success '-i/--include includes staged changes' ' + echo content >>file && + echo content >>baz && + git add file && + + # baz is in the index, therefore, it will be committed + git commit --include -m "file and baz" baz && + + git diff --name-only HEAD >remaining && + test_must_be_empty remaining && + + test_write_lines baz file >expect && + git diff --name-only HEAD^ HEAD >actual && + test_cmp expect actual +' + +test_expect_success '--include and --only do not mix' ' + test_when_finished "git reset --hard" && + echo content >>file && + echo content >>baz && + test_must_fail git commit --include --only -m "file baz" file baz 2>actual && + test_grep -e "fatal: options .-i/--include. and .-o/--only. cannot be used together" actual +' + test_expect_success 'commit message from non-existing file' ' echo more bongo: bongo bongo bongo bongo >file && test_must_fail git commit -F gah -a @@ -389,6 +447,28 @@ test_expect_success 'amend commit to fix date' ' ' +test_expect_success 'amend commit to add signoff' ' + + test_commit "msg" file content && + git commit --amend --signoff && + test_commit_message HEAD <<-EOF + msg + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF +' + +test_expect_success 'amend does not add signoff if it already exists' ' + + test_commit --signoff "tenor" file newcontent && + git commit --amend --signoff && + test_commit_message HEAD <<-EOF + tenor + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF +' + test_expect_success 'commit mentions forced date in output' ' git commit --amend --date=2010-01-02T03:04:05 >output && grep "Date: *Sat Jan 2 03:04:05 2010" output diff --git a/t/t7502-commit-porcelain.sh b/t/t7502-commit-porcelain.sh index 61c8e81..b37e201 100755 --- a/t/t7502-commit-porcelain.sh +++ b/t/t7502-commit-porcelain.sh @@ -485,6 +485,24 @@ test_expect_success 'commit --trailer not confused by --- separator' ' test_cmp expected actual ' +test_expect_success 'commit --trailer with --verbose' ' + cat >msg <<-\EOF && + subject + + body + EOF + GIT_EDITOR=: git commit --edit -F msg --allow-empty \ + --trailer="my-trailer: value" --verbose && + { + cat msg && + echo && + echo "my-trailer: value" + } >expected && + git cat-file commit HEAD >commit.msg && + sed -e "1,/^\$/d" commit.msg >actual && + test_cmp expected actual +' + test_expect_success 'multiple -m' ' >negative && @@ -718,6 +736,11 @@ test_expect_success 'message shows date when it is explicitly set' ' .git/COMMIT_EDITMSG ' +test_expect_success 'message does not have multiple scissors lines' ' + git commit --cleanup=scissors -v --allow-empty -e -m foo && + test $(grep -c -e "--- >8 ---" .git/COMMIT_EDITMSG) -eq 1 +' + test_expect_success AUTOIDENT 'message shows committer when it is automatic' ' echo >>negative && diff --git a/t/t7507-commit-verbose.sh b/t/t7507-commit-verbose.sh index c3281b1..4c7db19 100755 --- a/t/t7507-commit-verbose.sh +++ b/t/t7507-commit-verbose.sh @@ -101,6 +101,16 @@ test_expect_success 'verbose diff is stripped out with set core.commentChar' ' test_grep "Aborting commit due to empty commit message." err ' +test_expect_success 'verbose diff is stripped with multi-byte comment char' ' + ( + GIT_EDITOR=cat && + export GIT_EDITOR && + test_must_fail git -c core.commentchar="foo>" commit -a -v >out 2>err + ) && + grep "^foo> " out && + test_grep "Aborting commit due to empty commit message." err +' + test_expect_success 'status does not verbose without --verbose' ' git status >actual && ! grep "^diff --git" actual diff --git a/t/t7508-status.sh b/t/t7508-status.sh index a3c18a4..773383f 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -419,14 +419,19 @@ Changes not staged for commit: Untracked files not listed (use -u option to show untracked files) EOF git status -uno >output && + test_cmp expect output && + git status -ufalse >output && test_cmp expect output ' -test_expect_success 'status (status.showUntrackedFiles no)' ' - test_config status.showuntrackedfiles no && - git status >output && - test_cmp expect output -' +for no in no false 0 +do + test_expect_success "status (status.showUntrackedFiles $no)" ' + test_config status.showuntrackedfiles "$no" && + git status >output && + test_cmp expect output + ' +done test_expect_success 'status -uno (advice.statusHints false)' ' cat >expect <<EOF && @@ -488,14 +493,21 @@ Untracked files: EOF git status -unormal >output && + test_cmp expect output && + git status -utrue >output && + test_cmp expect output && + git status -uyes >output && test_cmp expect output ' -test_expect_success 'status (status.showUntrackedFiles normal)' ' - test_config status.showuntrackedfiles normal && - git status >output && - test_cmp expect output -' +for normal in normal true 1 +do + test_expect_success "status (status.showUntrackedFiles $normal)" ' + test_config status.showuntrackedfiles $normal && + git status >output && + test_cmp expect output + ' +done cat >expect <<EOF M dir1/modified @@ -1403,7 +1415,9 @@ test_expect_success "status (core.commentchar with submodule summary)" ' test_expect_success "status (core.commentchar with two chars with submodule summary)" ' test_config core.commentchar ";;" && - test_must_fail git -c status.displayCommentPrefix=true status + sed "s/^/;/" <expect >expect.double && + git -c status.displayCommentPrefix=true status >output && + test_cmp expect.double output ' test_expect_success "--ignore-submodules=all suppresses submodule summary" ' diff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh index c2ab8a4..802f8f7 100755 --- a/t/t7512-status-help.sh +++ b/t/t7512-status-help.sh @@ -692,6 +692,34 @@ EOF ' +test_expect_success 'status when bisecting while rebasing' ' + git reset --hard main && + test_when_finished "git rebase --abort" && + ONTO=$(git rev-parse --short HEAD^) && + FAKE_LINES="break" git rebase -i HEAD^ && + test_when_finished "git checkout -" && + git checkout -b bisect_while_rebasing && + test_when_finished "git bisect reset" && + git bisect start && + cat >expected <<EOF && +On branch bisect_while_rebasing +Last command done (1 command done): + break +No commands remaining. +You are currently editing a commit while rebasing branch '\''bisect'\'' on '\''$ONTO'\''. + (use "git commit --amend" to amend the current commit) + (use "git rebase --continue" once you are satisfied with your changes) + +You are currently bisecting, started from branch '\''bisect_while_rebasing'\''. + (use "git bisect reset" to get back to the original branch) + +nothing to commit (use -u to show untracked files) +EOF + git status --untracked-files=no >actual && + test_cmp expected actual +' + + test_expect_success 'status when rebase --apply conflicts with statushints disabled' ' git reset --hard main && git checkout -b statushints_disabled && diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index 832aff0..3d3e13c 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -1916,4 +1916,37 @@ test_expect_success 'suppress --- handling' ' test_cmp expected actual ' +test_expect_success 'suppressing --- does not disable cut-line handling' ' + echo "real-trailer: before the cut" >expected && + + git interpret-trailers --parse --no-divider >actual <<-\EOF && + subject + + This input has a cut-line in it; we should stop parsing when we see it + and consider only trailers before that line. + + real-trailer: before the cut + + # ------------------------ >8 ------------------------ + # Nothing below this line counts as part of the commit message. + not-a-trailer: too late + EOF + + test_cmp expected actual +' + +test_expect_success 'handling of --- lines in conjunction with cut-lines' ' + echo "my-trailer: here" >expected && + + git interpret-trailers --parse >actual <<-\EOF && + subject + + my-trailer: here + --- + # ------------------------ >8 ------------------------ + EOF + + test_cmp expected actual +' + test_done diff --git a/t/t7514-commit-patch.sh b/t/t7514-commit-patch.sh index 998a210..03ba0c0 100755 --- a/t/t7514-commit-patch.sh +++ b/t/t7514-commit-patch.sh @@ -1,13 +1,9 @@ #!/bin/sh test_description='hunk edit with "commit -p -m"' -. ./test-lib.sh -if ! test_have_prereq PERL -then - skip_all="skipping '$test_description' tests, perl not available" - test_done -fi +TEST_PASSES_SANITIZE_LEAK=true +. ./test-lib.sh test_expect_success 'setup (initial)' ' echo line1 >file && diff --git a/t/t7527-builtin-fsmonitor.sh b/t/t7527-builtin-fsmonitor.sh index 7850315..730f3c7 100755 --- a/t/t7527-builtin-fsmonitor.sh +++ b/t/t7527-builtin-fsmonitor.sh @@ -978,7 +978,7 @@ test_expect_success !UNICODE_COMPOSITION_SENSITIVE 'Unicode nfc/nfd' ' mkdir test_unicode/nfd && mkdir test_unicode/nfd/d_${utf8_nfd} && - git -C test_unicode fsmonitor--daemon stop && + test-tool -C test_unicode fsmonitor-client query --token 0 && if test_have_prereq UNICODE_NFC_PRESERVED then @@ -1037,4 +1037,227 @@ test_expect_success 'split-index and FSMonitor work well together' ' ) ' +# The FSMonitor daemon reports the OBSERVED pathname of modified files +# and thus contains the OBSERVED spelling on case-insensitive file +# systems. The daemon does not (and should not) load the .git/index +# file and therefore does not know the expected case-spelling. Since +# it is possible for the user to create files/subdirectories with the +# incorrect case, a modified file event for a tracked will not have +# the EXPECTED case. This can cause `index_name_pos()` to incorrectly +# report that the file is untracked. This causes the client to fail to +# mark the file as possibly dirty (keeping the CE_FSMONITOR_VALID bit +# set) so that `git status` will avoid inspecting it and thus not +# present in the status output. +# +# The setup is a little contrived. +# +test_expect_success CASE_INSENSITIVE_FS 'fsmonitor subdir case wrong on disk' ' + test_when_finished "stop_daemon_delete_repo subdir_case_wrong" && + + git init subdir_case_wrong && + ( + cd subdir_case_wrong && + echo x >AAA && + echo x >BBB && + + mkdir dir1 && + echo x >dir1/file1 && + mkdir dir1/dir2 && + echo x >dir1/dir2/file2 && + mkdir dir1/dir2/dir3 && + echo x >dir1/dir2/dir3/file3 && + + echo x >yyy && + echo x >zzz && + git add . && + git commit -m "data" && + + # This will cause "dir1/" and everything under it + # to be deleted. + git sparse-checkout set --cone --sparse-index && + + # Create dir2 with the wrong case and then let Git + # repopulate dir3 -- it will not correct the spelling + # of dir2. + mkdir dir1 && + mkdir dir1/DIR2 && + git sparse-checkout add dir1/dir2/dir3 + ) && + + start_daemon -C subdir_case_wrong --tf "$PWD/subdir_case_wrong.trace" && + + # Enable FSMonitor in the client. Run enough commands for + # the .git/index to sync up with the daemon with everything + # marked clean. + git -C subdir_case_wrong config core.fsmonitor true && + git -C subdir_case_wrong update-index --fsmonitor && + git -C subdir_case_wrong status && + + # Make some files dirty so that FSMonitor gets FSEvents for + # each of them. + echo xx >>subdir_case_wrong/AAA && + echo xx >>subdir_case_wrong/dir1/DIR2/dir3/file3 && + echo xx >>subdir_case_wrong/zzz && + + GIT_TRACE_FSMONITOR="$PWD/subdir_case_wrong.log" \ + git -C subdir_case_wrong --no-optional-locks status --short \ + >"$PWD/subdir_case_wrong.out" && + + # "git status" should have gotten file events for each of + # the 3 files. + # + # "dir2" should be in the observed case on disk. + grep "fsmonitor_refresh_callback" \ + <"$PWD/subdir_case_wrong.log" \ + >"$PWD/subdir_case_wrong.log1" && + + grep -q "AAA.*pos 0" "$PWD/subdir_case_wrong.log1" && + grep -q "zzz.*pos 6" "$PWD/subdir_case_wrong.log1" && + + grep -q "dir1/DIR2/dir3/file3.*pos -3" "$PWD/subdir_case_wrong.log1" && + + # Verify that we get a mapping event to correct the case. + grep -q "MAP:.*dir1/DIR2/dir3/file3.*dir1/dir2/dir3/file3" \ + "$PWD/subdir_case_wrong.log1" && + + # The refresh-callbacks should have caused "git status" to clear + # the CE_FSMONITOR_VALID bit on each of those files and caused + # the worktree scan to visit them and mark them as modified. + grep -q " M AAA" "$PWD/subdir_case_wrong.out" && + grep -q " M zzz" "$PWD/subdir_case_wrong.out" && + grep -q " M dir1/dir2/dir3/file3" "$PWD/subdir_case_wrong.out" +' + +test_expect_success CASE_INSENSITIVE_FS 'fsmonitor file case wrong on disk' ' + test_when_finished "stop_daemon_delete_repo file_case_wrong" && + + git init file_case_wrong && + ( + cd file_case_wrong && + echo x >AAA && + echo x >BBB && + + mkdir dir1 && + mkdir dir1/dir2 && + mkdir dir1/dir2/dir3 && + echo x >dir1/dir2/dir3/FILE-3-B && + echo x >dir1/dir2/dir3/XXXX-3-X && + echo x >dir1/dir2/dir3/file-3-a && + echo x >dir1/dir2/dir3/yyyy-3-y && + mkdir dir1/dir2/dir4 && + echo x >dir1/dir2/dir4/FILE-4-A && + echo x >dir1/dir2/dir4/XXXX-4-X && + echo x >dir1/dir2/dir4/file-4-b && + echo x >dir1/dir2/dir4/yyyy-4-y && + + echo x >yyy && + echo x >zzz && + git add . && + git commit -m "data" + ) && + + start_daemon -C file_case_wrong --tf "$PWD/file_case_wrong.trace" && + + # Enable FSMonitor in the client. Run enough commands for + # the .git/index to sync up with the daemon with everything + # marked clean. + git -C file_case_wrong config core.fsmonitor true && + git -C file_case_wrong update-index --fsmonitor && + git -C file_case_wrong status && + + # Make some files dirty so that FSMonitor gets FSEvents for + # each of them. + echo xx >>file_case_wrong/AAA && + echo xx >>file_case_wrong/zzz && + + # Rename some files so that FSMonitor sees a create and delete + # FSEvent for each. (A simple "mv foo FOO" is not portable + # between macOS and Windows. It works on both platforms, but makes + # the test messy, since (1) one platform updates "ctime" on the + # moved file and one does not and (2) it causes a directory event + # on one platform and not on the other which causes additional + # scanning during "git status" which causes a "H" vs "h" discrepancy + # in "git ls-files -f".) So old-school it and move it out of the + # way and copy it to the case-incorrect name so that we get fresh + # "ctime" and "mtime" values. + + mv file_case_wrong/dir1/dir2/dir3/file-3-a file_case_wrong/dir1/dir2/dir3/ORIG && + cp file_case_wrong/dir1/dir2/dir3/ORIG file_case_wrong/dir1/dir2/dir3/FILE-3-A && + rm file_case_wrong/dir1/dir2/dir3/ORIG && + mv file_case_wrong/dir1/dir2/dir4/FILE-4-A file_case_wrong/dir1/dir2/dir4/ORIG && + cp file_case_wrong/dir1/dir2/dir4/ORIG file_case_wrong/dir1/dir2/dir4/file-4-a && + rm file_case_wrong/dir1/dir2/dir4/ORIG && + + # Run status enough times to fully sync. + # + # The first instance should get the create and delete FSEvents + # for each pair. Status should update the index with a new FSM + # token (so the next invocation will not see data for these + # events). + + GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try1.log" \ + git -C file_case_wrong status --short \ + >"$PWD/file_case_wrong-try1.out" && + grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try1.log" && + grep -q "fsmonitor_refresh_callback.*file-3-a.*pos 4" "$PWD/file_case_wrong-try1.log" && + grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos 6" "$PWD/file_case_wrong-try1.log" && + grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try1.log" && + + # FSM refresh will have invalidated the FSM bit and cause a regular + # (real) scan of these tracked files, so they should have "H" status. + # (We will not see a "h" status until the next refresh (on the next + # command).) + + git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf1.out" && + grep -q "H dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf1.out" && + grep -q "H dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf1.out" && + + + # Try the status again. We assume that the above status command + # advanced the token so that the next one will not see those events. + + GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try2.log" \ + git -C file_case_wrong status --short \ + >"$PWD/file_case_wrong-try2.out" && + ! grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos" "$PWD/file_case_wrong-try2.log" && + ! grep -q "fsmonitor_refresh_callback.*file-3-a.*pos" "$PWD/file_case_wrong-try2.log" && + ! grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos" "$PWD/file_case_wrong-try2.log" && + ! grep -q "fsmonitor_refresh_callback.*file-4-a.*pos" "$PWD/file_case_wrong-try2.log" && + + # FSM refresh saw nothing, so it will mark all files as valid, + # so they should now have "h" status. + + git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf2.out" && + grep -q "h dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf2.out" && + grep -q "h dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf2.out" && + + + # We now have files with clean content, but with case-incorrect + # file names. Modify them to see if status properly reports + # them. + + echo xx >>file_case_wrong/dir1/dir2/dir3/FILE-3-A && + echo xx >>file_case_wrong/dir1/dir2/dir4/file-4-a && + + GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try3.log" \ + git -C file_case_wrong --no-optional-locks status --short \ + >"$PWD/file_case_wrong-try3.out" && + + # Verify that we get a mapping event to correct the case. + grep -q "fsmonitor_refresh_callback MAP:.*dir1/dir2/dir3/FILE-3-A.*dir1/dir2/dir3/file-3-a" \ + "$PWD/file_case_wrong-try3.log" && + grep -q "fsmonitor_refresh_callback MAP:.*dir1/dir2/dir4/file-4-a.*dir1/dir2/dir4/FILE-4-A" \ + "$PWD/file_case_wrong-try3.log" && + + # FSEvents are in observed case. + grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try3.log" && + grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try3.log" && + + # The refresh-callbacks should have caused "git status" to clear + # the CE_FSMONITOR_VALID bit on each of those files and caused + # the worktree scan to visit them and mark them as modified. + grep -q " M dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-try3.out" && + grep -q " M dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-try3.out" +' + test_done diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index d2975e6..127efe9 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -271,7 +271,7 @@ test_expect_success 'repacking fails when missing .pack actually means missing o ls .git/objects/pack/*.pack >before-pack-dir && test_must_fail git fsck && - test_must_fail git repack --cruft -d 2>err && + test_must_fail env GIT_COMMIT_GRAPH_PARANOIA=true git repack --cruft -d 2>err && grep "bad object" err && # Before failing, the repack did not modify the @@ -629,6 +629,7 @@ test_expect_success '--write-midx with preferred bitmap tips' ' git log --format="create refs/tags/%s/%s %H" HEAD >refs && git update-ref --stdin <refs && + GIT_TEST_MULTI_PACK_INDEX=0 \ git repack --write-midx --write-bitmap-index && test_path_is_file $midx && test_path_is_file $midx-$(midx_checksum $objdir).bitmap && @@ -749,6 +750,7 @@ test_expect_success '--write-midx with --pack-kept-objects' ' keep="$objdir/pack/pack-$one.keep" && touch "$keep" && + GIT_TEST_MULTI_PACK_INDEX=0 \ git repack --write-midx --write-bitmap-index --geometric=2 -d \ --pack-kept-objects && diff --git a/t/t7704-repack-cruft.sh b/t/t7704-repack-cruft.sh index be3735d..71e1ef3 100755 --- a/t/t7704-repack-cruft.sh +++ b/t/t7704-repack-cruft.sh @@ -48,7 +48,7 @@ test_expect_success '--expire-to stores pruned objects (now)' ' # ...in other words, the combined contents of this # repository and expired.git should be the same as the # set of objects we started with. - cat expired.objects remaining.objects | sort >actual && + sort expired.objects remaining.objects >actual && test_cmp expect actual && # The "moved" objects (i.e., those in expired.git) diff --git a/t/t7800-difftool.sh b/t/t7800-difftool.sh index 6a36be1..cc917b2 100755 --- a/t/t7800-difftool.sh +++ b/t/t7800-difftool.sh @@ -91,58 +91,67 @@ test_expect_success 'difftool forwards arguments to diff' ' rm for-diff ' -test_expect_success 'difftool ignores exit code' ' - test_config difftool.error.cmd false && - git difftool -y -t error branch -' - -test_expect_success 'difftool forwards exit code with --trust-exit-code' ' - test_config difftool.error.cmd false && - test_must_fail git difftool -y --trust-exit-code -t error branch -' - -test_expect_success 'difftool forwards exit code with --trust-exit-code for built-ins' ' - test_config difftool.vimdiff.path false && - test_must_fail git difftool -y --trust-exit-code -t vimdiff branch -' - -test_expect_success 'difftool honors difftool.trustExitCode = true' ' - test_config difftool.error.cmd false && - test_config difftool.trustExitCode true && - test_must_fail git difftool -y -t error branch -' - -test_expect_success 'difftool honors difftool.trustExitCode = false' ' - test_config difftool.error.cmd false && - test_config difftool.trustExitCode false && - git difftool -y -t error branch -' - -test_expect_success 'difftool ignores exit code with --no-trust-exit-code' ' - test_config difftool.error.cmd false && - test_config difftool.trustExitCode true && - git difftool -y --no-trust-exit-code -t error branch -' - -test_expect_success 'difftool stops on error with --trust-exit-code' ' - test_when_finished "rm -f for-diff .git/fail-right-file" && - test_when_finished "git reset -- for-diff" && - write_script .git/fail-right-file <<-\EOF && - echo failed - exit 1 - EOF - >for-diff && - git add for-diff && - test_must_fail git difftool -y --trust-exit-code \ - --extcmd .git/fail-right-file branch >actual && - test_line_count = 1 actual -' - -test_expect_success 'difftool honors exit status if command not found' ' - test_config difftool.nonexistent.cmd i-dont-exist && - test_config difftool.trustExitCode false && - test_must_fail git difftool -y -t nonexistent branch -' +for opt in '' '--dir-diff' +do + test_expect_success "difftool ${opt:-without options} ignores exit code" ' + test_config difftool.error.cmd false && + git difftool ${opt} -y -t error branch + ' + + test_expect_success "difftool ${opt:-without options} forwards exit code with --trust-exit-code" ' + test_config difftool.error.cmd false && + test_must_fail git difftool ${opt} -y --trust-exit-code -t error branch + ' + + test_expect_success "difftool ${opt:-without options} forwards exit code with --trust-exit-code for built-ins" ' + test_config difftool.vimdiff.path false && + test_must_fail git difftool ${opt} -y --trust-exit-code -t vimdiff branch + ' + + test_expect_success "difftool ${opt:-without options} honors difftool.trustExitCode = true" ' + test_config difftool.error.cmd false && + test_config difftool.trustExitCode true && + test_must_fail git difftool ${opt} -y -t error branch + ' + + test_expect_success "difftool ${opt:-without options} honors difftool.trustExitCode = false" ' + test_config difftool.error.cmd false && + test_config difftool.trustExitCode false && + git difftool ${opt} -y -t error branch + ' + + test_expect_success "difftool ${opt:-without options} ignores exit code with --no-trust-exit-code" ' + test_config difftool.error.cmd false && + test_config difftool.trustExitCode true && + git difftool ${opt} -y --no-trust-exit-code -t error branch + ' + + test_expect_success "difftool ${opt:-without options} stops on error with --trust-exit-code" ' + test_when_finished "rm -f for-diff .git/fail-right-file" && + test_when_finished "git reset -- for-diff" && + write_script .git/fail-right-file <<-\EOF && + echo failed + exit 1 + EOF + >for-diff && + git add for-diff && + test_must_fail git difftool ${opt} -y --trust-exit-code \ + --extcmd .git/fail-right-file branch >actual && + test_line_count = 1 actual + ' + + test_expect_success "difftool ${opt:-without options} honors exit status if command not found" ' + test_config difftool.nonexistent.cmd i-dont-exist && + test_config difftool.trustExitCode false && + if test "${opt}" = --dir-diff + then + expected_code=127 + else + expected_code=128 + fi && + test_expect_code ${expected_code} git difftool ${opt} -y -t nonexistent branch + ' +done test_expect_success 'difftool honors --gui' ' difftool_test_setup && diff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh index 935df6a..0943dfa 100755 --- a/t/t7900-maintenance.sh +++ b/t/t7900-maintenance.sh @@ -67,6 +67,51 @@ test_expect_success 'maintenance.auto config option' ' test_subcommand ! git maintenance run --auto --quiet <false ' +test_expect_success 'register uses XDG_CONFIG_HOME config if it exists' ' + test_when_finished rm -r .config/git/config && + ( + XDG_CONFIG_HOME=.config && + export XDG_CONFIG_HOME && + mkdir -p $XDG_CONFIG_HOME/git && + >$XDG_CONFIG_HOME/git/config && + git maintenance register && + git config --file=$XDG_CONFIG_HOME/git/config --get maintenance.repo >actual && + pwd >expect && + test_cmp expect actual + ) +' + +test_expect_success 'register does not need XDG_CONFIG_HOME config to exist' ' + test_when_finished git maintenance unregister && + test_path_is_missing $XDG_CONFIG_HOME/git/config && + git maintenance register && + git config --global --get maintenance.repo >actual && + pwd >expect && + test_cmp expect actual +' + +test_expect_success 'unregister uses XDG_CONFIG_HOME config if it exists' ' + test_when_finished rm -r .config/git/config && + ( + XDG_CONFIG_HOME=.config && + export XDG_CONFIG_HOME && + mkdir -p $XDG_CONFIG_HOME/git && + >$XDG_CONFIG_HOME/git/config && + git maintenance register && + git maintenance unregister && + test_must_fail git config --file=$XDG_CONFIG_HOME/git/config --get maintenance.repo >actual && + test_must_be_empty actual + ) +' + +test_expect_success 'unregister does not need XDG_CONFIG_HOME config to exist' ' + test_path_is_missing $XDG_CONFIG_HOME/git/config && + git maintenance register && + git maintenance unregister && + test_must_fail git config --global --get maintenance.repo >actual && + test_must_be_empty actual +' + test_expect_success 'maintenance.<task>.enabled' ' git config maintenance.gc.enabled false && git config maintenance.commit-graph.enabled true && @@ -157,7 +202,8 @@ test_expect_success 'prefetch multiple remotes' ' fetchargs="--prefetch --prune --no-tags --no-write-fetch-head --recurse-submodules=no --quiet" && test_subcommand git fetch remote1 $fetchargs <run-prefetch.txt && test_subcommand git fetch remote2 $fetchargs <run-prefetch.txt && - test_path_is_missing .git/refs/remotes && + git for-each-ref refs/remotes >actual && + test_must_be_empty actual && git log prefetch/remotes/remote1/one && git log prefetch/remotes/remote2/two && git fetch --all && diff --git a/t/t8010-cat-file-filters.sh b/t/t8010-cat-file-filters.sh index ca04242..eb64b76 100755 --- a/t/t8010-cat-file-filters.sh +++ b/t/t8010-cat-file-filters.sh @@ -43,7 +43,7 @@ test_expect_success 'cat-file --textconv --path=<path> works' ' sha1=$(git rev-parse -q --verify HEAD:world.txt) && test_config diff.txt.textconv "tr A-Za-z N-ZA-Mn-za-m <" && git cat-file --textconv --path=hello.txt $sha1 >rot13 && - test uryyb = "$(cat rot13 | remove_cr)" + test uryyb = "$(remove_cr <rot13)" ' test_expect_success '--path=<path> complains without --textconv/--filters' ' diff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh index 9a03b0f..dbfbd86 100755 --- a/t/t8013-blame-ignore-revs.sh +++ b/t/t8013-blame-ignore-revs.sh @@ -25,11 +25,11 @@ test_expect_success setup ' git blame --line-porcelain file >blame_raw && - grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 1/s/ .*//p" blame_raw >actual && git rev-parse X >expect && test_cmp expect actual && - grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 2/s/ .*//p" blame_raw >actual && git rev-parse X >expect && test_cmp expect actual ' @@ -53,11 +53,11 @@ do test_expect_success "ignore_rev_changing_lines ($I)" ' git blame --line-porcelain --ignore-rev $I file >blame_raw && - grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 1/s/ .*//p" blame_raw >actual && git rev-parse A >expect && test_cmp expect actual && - grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 2/s/ .*//p" blame_raw >actual && git rev-parse B >expect && test_cmp expect actual ' @@ -79,10 +79,10 @@ test_expect_success ignore_rev_adding_unblamable_lines ' git rev-parse Y >expect && git blame --line-porcelain file --ignore-rev Y >blame_raw && - grep -E "^[0-9a-f]+ [0-9]+ 3" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 3/s/ .*//p" blame_raw >actual && test_cmp expect actual && - grep -E "^[0-9a-f]+ [0-9]+ 4" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 4/s/ .*//p" blame_raw >actual && test_cmp expect actual ' @@ -92,11 +92,11 @@ test_expect_success ignore_revs_from_files ' git rev-parse Y >ignore_y && git blame --line-porcelain file --ignore-revs-file ignore_x --ignore-revs-file ignore_y >blame_raw && - grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 1/s/ .*//p" blame_raw >actual && git rev-parse A >expect && test_cmp expect actual && - grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 2/s/ .*//p" blame_raw >actual && git rev-parse B >expect && test_cmp expect actual ' @@ -106,11 +106,11 @@ test_expect_success ignore_revs_from_configs_and_files ' git config --add blame.ignoreRevsFile ignore_x && git blame --line-porcelain file --ignore-revs-file ignore_y >blame_raw && - grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 1/s/ .*//p" blame_raw >actual && git rev-parse A >expect && test_cmp expect actual && - grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 2/s/ .*//p" blame_raw >actual && git rev-parse B >expect && test_cmp expect actual ' @@ -121,10 +121,10 @@ test_expect_success override_ignore_revs_file ' git blame --line-porcelain file --ignore-revs-file "" --ignore-revs-file ignore_y >blame_raw && git rev-parse X >expect && - grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 1/s/ .*//p" blame_raw >actual && test_cmp expect actual && - grep -E "^[0-9a-f]+ [0-9]+ 2" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 2/s/ .*//p" blame_raw >actual && test_cmp expect actual ' test_expect_success bad_files_and_revs ' @@ -279,11 +279,11 @@ test_expect_success ignore_merge ' test_merge M B && git blame --line-porcelain file --ignore-rev M >blame_raw && - grep -E "^[0-9a-f]+ [0-9]+ 1" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 1/s/ .*//p" blame_raw >actual && git rev-parse B >expect && test_cmp expect actual && - grep -E "^[0-9a-f]+ [0-9]+ 9" blame_raw | sed -e "s/ .*//" >actual && + sed -ne "/^[0-9a-f][0-9a-f]* [0-9][0-9]* 9/s/ .*//p" blame_raw >actual && git rev-parse C >expect && test_cmp expect actual ' diff --git a/t/t9002-column.sh b/t/t9002-column.sh index 6d3dbde..d5b98e6 100755 --- a/t/t9002-column.sh +++ b/t/t9002-column.sh @@ -1,6 +1,7 @@ #!/bin/sh test_description='git column' +TEST_PASSES_SANITIZE_LEAK=true . ./test-lib.sh test_expect_success 'setup' ' @@ -195,4 +196,15 @@ EOF test_cmp expected actual ' +test_expect_success 'padding must be non-negative' ' + cat >input <<\EOF && +1 2 3 4 5 6 +EOF + cat >expected <<\EOF && +fatal: --padding must be non-negative +EOF + test_must_fail git column --mode=column --padding=-1 <input >actual 2>&1 && + test_cmp expected actual +' + test_done diff --git a/t/t9114-git-svn-dcommit-merge.sh b/t/t9114-git-svn-dcommit-merge.sh index 32317d6..e06538b 100755 --- a/t/t9114-git-svn-dcommit-merge.sh +++ b/t/t9114-git-svn-dcommit-merge.sh @@ -27,7 +27,7 @@ cat << EOF # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program; if not, see <http://www.gnu.org/licenses/>. +# along with this program; if not, see <https://www.gnu.org/licenses/>. # EOF } diff --git a/t/t9117-git-svn-init-clone.sh b/t/t9117-git-svn-init-clone.sh index 62de819..3b038c3 100755 --- a/t/t9117-git-svn-init-clone.sh +++ b/t/t9117-git-svn-init-clone.sh @@ -17,32 +17,32 @@ test_expect_success 'setup svnrepo' ' test_expect_success 'basic clone' ' test ! -d trunk && git svn clone "$svnrepo"/project/trunk && - test -d trunk/.git/svn && - test -e trunk/foo && + test_path_is_dir trunk/.git/svn && + test_path_exists trunk/foo && rm -rf trunk ' test_expect_success 'clone to target directory' ' test ! -d target && git svn clone "$svnrepo"/project/trunk target && - test -d target/.git/svn && - test -e target/foo && + test_path_is_dir target/.git/svn && + test_path_exists target/foo && rm -rf target ' test_expect_success 'clone with --stdlayout' ' test ! -d project && git svn clone -s "$svnrepo"/project && - test -d project/.git/svn && - test -e project/foo && + test_path_is_dir project/.git/svn && + test_path_exists project/foo && rm -rf project ' test_expect_success 'clone to target directory with --stdlayout' ' test ! -d target && git svn clone -s "$svnrepo"/project target && - test -d target/.git/svn && - test -e target/foo && + test_path_is_dir target/.git/svn && + test_path_exists target/foo && rm -rf target ' diff --git a/t/t9118-git-svn-funky-branch-names.sh b/t/t9118-git-svn-funky-branch-names.sh index a159ff9..d3261e3 100755 --- a/t/t9118-git-svn-funky-branch-names.sh +++ b/t/t9118-git-svn-funky-branch-names.sh @@ -38,7 +38,7 @@ test_expect_success 'setup svnrepo' ' # SVN 1.7 will truncate "not-a%40{0]" to just "not-a". # Look at what SVN wound up naming the branch and use that. # Be sure to escape the @ if it shows up. -non_reflog=$(svn_cmd ls "$svnrepo/pr ject/branches" | grep not-a | sed 's/\///' | sed 's/@/%40/') +non_reflog=$(svn_cmd ls "$svnrepo/pr ject/branches" | sed -ne '/not-a/ { s/\///; s/@/%40/; p }') test_expect_success 'test clone with funky branch names' ' git svn clone -s "$svnrepo/pr ject" project && diff --git a/t/t9133-git-svn-nested-git-repo.sh b/t/t9133-git-svn-nested-git-repo.sh index d8d5362..8ca2467 100755 --- a/t/t9133-git-svn-nested-git-repo.sh +++ b/t/t9133-git-svn-nested-git-repo.sh @@ -11,7 +11,7 @@ test_expect_success 'setup repo with a git repo inside it' ' ( cd s && git init && - test -f .git/HEAD && + git symbolic-ref HEAD && > .git/a && echo a > a && svn_cmd add .git a && diff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh index 09606f1..926ac81 100755 --- a/t/t9146-git-svn-empty-dirs.sh +++ b/t/t9146-git-svn-empty-dirs.sh @@ -20,11 +20,7 @@ test_expect_success 'empty directories exist' ' cd cloned && for i in a b c d d/e d/e/f "weird file name" do - if ! test -d "$i" - then - echo >&2 "$i does not exist" && - exit 1 - fi + test_path_is_dir "$i" || exit 1 done ) ' @@ -37,11 +33,7 @@ test_expect_success 'option automkdirs set to false' ' git svn fetch && for i in a b c d d/e d/e/f "weird file name" do - if test -d "$i" - then - echo >&2 "$i exists" && - exit 1 - fi + test_path_is_missing "$i" || exit 1 done ) ' @@ -52,7 +44,7 @@ test_expect_success 'more emptiness' ' test_expect_success 'git svn rebase creates empty directory' ' ( cd cloned && git svn rebase ) && - test -d cloned/"! !" + test_path_is_dir cloned/"! !" ' test_expect_success 'git svn mkdirs recreates empty directories' ' @@ -62,11 +54,7 @@ test_expect_success 'git svn mkdirs recreates empty directories' ' git svn mkdirs && for i in a b c d d/e d/e/f "weird file name" "! !" do - if ! test -d "$i" - then - echo >&2 "$i does not exist" && - exit 1 - fi + test_path_is_dir "$i" || exit 1 done ) ' @@ -78,25 +66,13 @@ test_expect_success 'git svn mkdirs -r works' ' git svn mkdirs -r7 && for i in a b c d d/e d/e/f "weird file name" do - if ! test -d "$i" - then - echo >&2 "$i does not exist" && - exit 1 - fi + test_path_is_dir "$i" || exit 1 done && - if test -d "! !" - then - echo >&2 "$i should not exist" && - exit 1 - fi && + test_path_is_missing "! !" || exit 1 && git svn mkdirs -r8 && - if ! test -d "! !" - then - echo >&2 "$i not exist" && - exit 1 - fi + test_path_is_dir "! !" || exit 1 ) ' @@ -114,11 +90,7 @@ test_expect_success 'empty directories in trunk exist' ' cd trunk && for i in a "weird file name" do - if ! test -d "$i" - then - echo >&2 "$i does not exist" && - exit 1 - fi + test_path_is_dir "$i" || exit 1 done ) ' @@ -129,7 +101,7 @@ test_expect_success 'remove a top-level directory from svn' ' test_expect_success 'removed top-level directory does not exist' ' git svn clone "$svnrepo" removed && - test ! -e removed/d + test_path_is_missing removed/d ' unhandled=.git/svn/refs/remotes/git-svn/unhandled.log @@ -143,15 +115,11 @@ test_expect_success 'git svn gc-ed files work' ' svn_cmd mkdir -m gz "$svnrepo"/gz && git reset --hard $(git rev-list HEAD | tail -1) && git svn rebase && - test -f "$unhandled".gz && - test -f "$unhandled" && + test_path_is_file "$unhandled".gz && + test_path_is_file "$unhandled" && for i in a b c "weird file name" gz "! !" do - if ! test -d "$i" - then - echo >&2 "$i does not exist" && - exit 1 - fi + test_path_is_dir "$i" || exit 1 done fi ) diff --git a/t/t9164-git-svn-dcommit-concurrent.sh b/t/t9164-git-svn-dcommit-concurrent.sh index c8e6c07..d1dec89 100755 --- a/t/t9164-git-svn-dcommit-concurrent.sh +++ b/t/t9164-git-svn-dcommit-concurrent.sh @@ -46,6 +46,14 @@ setup_hook() "passed to setup_hook" >&2 ; return 1; } echo "cnt=$skip_revs" > "$hook_type-counter" rm -f "$rawsvnrepo/hooks/"*-commit # drop previous hooks + + # Subversion hooks run with an empty environment by default. We thus + # need to propagate PATH so that we can find executables. + cat >"$rawsvnrepo/conf/hooks-env" <<-EOF + [default] + PATH = ${PATH} + EOF + hook="$rawsvnrepo/hooks/$hook_type" cat > "$hook" <<- 'EOF1' #!/bin/sh @@ -63,7 +71,6 @@ EOF1 if [ "$hook_type" = "pre-commit" ]; then echo "echo 'commit disallowed' >&2; exit 1" >>"$hook" else - echo "PATH=\"$PATH\"; export PATH" >>"$hook" echo "svnconf=\"$svnconf\"" >>"$hook" cat >>"$hook" <<- 'EOF2' cd work-auto-commits.svn diff --git a/t/t9210-scalar.sh b/t/t9210-scalar.sh index 4432a30..428339e 100755 --- a/t/t9210-scalar.sh +++ b/t/t9210-scalar.sh @@ -154,7 +154,14 @@ test_expect_success 'scalar clone' ' test_cmp expect actual && test_path_is_missing 1/2 && - test_must_fail git rev-list --missing=print $second && + + # This relies on the fact that the presence of "--missing" + # on the command line forces lazy fetching off before + # "$second^{blob}" gets parsed. Without "^{blob}", a + # bare object name "$second" is taken into the queue and + # the command may not fail with a fixed "rev-list --missing". + test_must_fail git rev-list --missing=print "$second^{blob}" -- && + git rev-list $second && git cat-file blob $second >actual && echo "second" >expect && diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index dbb5042..1e68426 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -986,7 +986,7 @@ test_expect_success 'L: nested tree copy does not corrupt deltas' ' test_when_finished "git update-ref -d refs/heads/L2" && git fast-import <input && git ls-tree L2 g/b/ >tmp && - cat tmp | cut -f 2 >actual && + cut -f 2 <tmp >actual && test_cmp expect actual && git fsck $(git rev-parse L2) ' @@ -1059,30 +1059,33 @@ test_expect_success 'M: rename subdirectory to new subdirectory' ' compare_diff_raw expect actual ' -test_expect_success 'M: rename root to subdirectory' ' - cat >input <<-INPUT_END && - commit refs/heads/M4 - committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - data <<COMMIT - rename root - COMMIT +for root in '""' '' +do + test_expect_success "M: rename root ($root) to subdirectory" ' + cat >input <<-INPUT_END && + commit refs/heads/M4 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + rename root + COMMIT - from refs/heads/M2^0 - R "" sub + from refs/heads/M2^0 + R $root sub - INPUT_END + INPUT_END - cat >expect <<-EOF && - :100644 100644 $oldf $oldf R100 file2/oldf sub/file2/oldf - :100755 100755 $f4id $f4id R100 file4 sub/file4 - :100755 100755 $newf $newf R100 i/am/new/to/you sub/i/am/new/to/you - :100755 100755 $f6id $f6id R100 newdir/exec.sh sub/newdir/exec.sh - :100644 100644 $f5id $f5id R100 newdir/interesting sub/newdir/interesting - EOF - git fast-import <input && - git diff-tree -M -r M4^ M4 >actual && - compare_diff_raw expect actual -' + cat >expect <<-EOF && + :100644 100644 $oldf $oldf R100 file2/oldf sub/file2/oldf + :100755 100755 $f4id $f4id R100 file4 sub/file4 + :100755 100755 $newf $newf R100 i/am/new/to/you sub/i/am/new/to/you + :100755 100755 $f6id $f6id R100 newdir/exec.sh sub/newdir/exec.sh + :100644 100644 $f5id $f5id R100 newdir/interesting sub/newdir/interesting + EOF + git fast-import <input && + git diff-tree -M -r M4^ M4 >actual && + compare_diff_raw expect actual + ' +done ### ### series N @@ -1259,49 +1262,52 @@ test_expect_success PIPE 'N: empty directory reads as missing' ' test_cmp expect actual ' -test_expect_success 'N: copy root directory by tree hash' ' - cat >expect <<-EOF && - :100755 000000 $newf $zero D file3/newf - :100644 000000 $oldf $zero D file3/oldf - EOF - root=$(git rev-parse refs/heads/branch^0^{tree}) && - cat >input <<-INPUT_END && - commit refs/heads/N6 - committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - data <<COMMIT - copy root directory by tree hash - COMMIT - - from refs/heads/branch^0 - M 040000 $root "" - INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r N4 N6 >actual && - compare_diff_raw expect actual -' +for root in '""' '' +do + test_expect_success "N: copy root ($root) by tree hash" ' + cat >expect <<-EOF && + :100755 000000 $newf $zero D file3/newf + :100644 000000 $oldf $zero D file3/oldf + EOF + root_tree=$(git rev-parse refs/heads/branch^0^{tree}) && + cat >input <<-INPUT_END && + commit refs/heads/N6 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + copy root directory by tree hash + COMMIT -test_expect_success 'N: copy root by path' ' - cat >expect <<-EOF && - :100755 100755 $newf $newf C100 file2/newf oldroot/file2/newf - :100644 100644 $oldf $oldf C100 file2/oldf oldroot/file2/oldf - :100755 100755 $f4id $f4id C100 file4 oldroot/file4 - :100755 100755 $f6id $f6id C100 newdir/exec.sh oldroot/newdir/exec.sh - :100644 100644 $f5id $f5id C100 newdir/interesting oldroot/newdir/interesting - EOF - cat >input <<-INPUT_END && - commit refs/heads/N-copy-root-path - committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - data <<COMMIT - copy root directory by (empty) path - COMMIT + from refs/heads/branch^0 + M 040000 $root_tree $root + INPUT_END + git fast-import <input && + git diff-tree -C --find-copies-harder -r N4 N6 >actual && + compare_diff_raw expect actual + ' + + test_expect_success "N: copy root ($root) by path" ' + cat >expect <<-EOF && + :100755 100755 $newf $newf C100 file2/newf oldroot/file2/newf + :100644 100644 $oldf $oldf C100 file2/oldf oldroot/file2/oldf + :100755 100755 $f4id $f4id C100 file4 oldroot/file4 + :100755 100755 $f6id $f6id C100 newdir/exec.sh oldroot/newdir/exec.sh + :100644 100644 $f5id $f5id C100 newdir/interesting oldroot/newdir/interesting + EOF + cat >input <<-INPUT_END && + commit refs/heads/N-copy-root-path + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + copy root directory by (empty) path + COMMIT - from refs/heads/branch^0 - C "" oldroot - INPUT_END - git fast-import <input && - git diff-tree -C --find-copies-harder -r branch N-copy-root-path >actual && - compare_diff_raw expect actual -' + from refs/heads/branch^0 + C $root oldroot + INPUT_END + git fast-import <input && + git diff-tree -C --find-copies-harder -r branch N-copy-root-path >actual && + compare_diff_raw expect actual + ' +done test_expect_success 'N: delete directory by copying' ' cat >expect <<-\EOF && @@ -1431,98 +1437,102 @@ test_expect_success 'N: reject foo/ syntax in ls argument' ' INPUT_END ' -test_expect_success 'N: copy to root by id and modify' ' - echo "hello, world" >expect.foo && - echo hello >expect.bar && - git fast-import <<-SETUP_END && - commit refs/heads/N7 - committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - data <<COMMIT - hello, tree - COMMIT - - deleteall - M 644 inline foo/bar - data <<EOF - hello - EOF - SETUP_END - - tree=$(git rev-parse --verify N7:) && - git fast-import <<-INPUT_END && - commit refs/heads/N8 - committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - data <<COMMIT - copy to root by id and modify - COMMIT +for root in '""' '' +do + test_expect_success "N: copy to root ($root) by id and modify" ' + echo "hello, world" >expect.foo && + echo hello >expect.bar && + git fast-import <<-SETUP_END && + commit refs/heads/N7 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + hello, tree + COMMIT - M 040000 $tree "" - M 644 inline foo/foo - data <<EOF - hello, world - EOF - INPUT_END - git show N8:foo/foo >actual.foo && - git show N8:foo/bar >actual.bar && - test_cmp expect.foo actual.foo && - test_cmp expect.bar actual.bar -' + deleteall + M 644 inline foo/bar + data <<EOF + hello + EOF + SETUP_END -test_expect_success 'N: extract subtree' ' - branch=$(git rev-parse --verify refs/heads/branch^{tree}) && - cat >input <<-INPUT_END && - commit refs/heads/N9 - committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - data <<COMMIT - extract subtree branch:newdir - COMMIT + tree=$(git rev-parse --verify N7:) && + git fast-import <<-INPUT_END && + commit refs/heads/N8 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + copy to root by id and modify + COMMIT - M 040000 $branch "" - C "newdir" "" - INPUT_END - git fast-import <input && - git diff --exit-code branch:newdir N9 -' + M 040000 $tree $root + M 644 inline foo/foo + data <<EOF + hello, world + EOF + INPUT_END + git show N8:foo/foo >actual.foo && + git show N8:foo/bar >actual.bar && + test_cmp expect.foo actual.foo && + test_cmp expect.bar actual.bar + ' + + test_expect_success "N: extract subtree to the root ($root)" ' + branch=$(git rev-parse --verify refs/heads/branch^{tree}) && + cat >input <<-INPUT_END && + commit refs/heads/N9 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + extract subtree branch:newdir + COMMIT -test_expect_success 'N: modify subtree, extract it, and modify again' ' - echo hello >expect.baz && - echo hello, world >expect.qux && - git fast-import <<-SETUP_END && - commit refs/heads/N10 - committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - data <<COMMIT - hello, tree - COMMIT + M 040000 $branch $root + C "newdir" $root + INPUT_END + git fast-import <input && + git diff --exit-code branch:newdir N9 + ' + + test_expect_success "N: modify subtree, extract it to the root ($root), and modify again" ' + echo hello >expect.baz && + echo hello, world >expect.qux && + git fast-import <<-SETUP_END && + commit refs/heads/N10 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + hello, tree + COMMIT - deleteall - M 644 inline foo/bar/baz - data <<EOF - hello - EOF - SETUP_END + deleteall + M 644 inline foo/bar/baz + data <<EOF + hello + EOF + SETUP_END - tree=$(git rev-parse --verify N10:) && - git fast-import <<-INPUT_END && - commit refs/heads/N11 - committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - data <<COMMIT - copy to root by id and modify - COMMIT + tree=$(git rev-parse --verify N10:) && + git fast-import <<-INPUT_END && + commit refs/heads/N11 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + copy to root by id and modify + COMMIT - M 040000 $tree "" - M 100644 inline foo/bar/qux - data <<EOF - hello, world - EOF - R "foo" "" - C "bar/qux" "bar/quux" - INPUT_END - git show N11:bar/baz >actual.baz && - git show N11:bar/qux >actual.qux && - git show N11:bar/quux >actual.quux && - test_cmp expect.baz actual.baz && - test_cmp expect.qux actual.qux && - test_cmp expect.qux actual.quux' + M 040000 $tree $root + M 100644 inline foo/bar/qux + data <<EOF + hello, world + EOF + R "foo" $root + C "bar/qux" "bar/quux" + INPUT_END + git show N11:bar/baz >actual.baz && + git show N11:bar/qux >actual.qux && + git show N11:bar/quux >actual.quux && + test_cmp expect.baz actual.baz && + test_cmp expect.qux actual.qux && + test_cmp expect.qux actual.quux + ' +done ### ### series O @@ -2007,12 +2017,11 @@ test_expect_success 'Q: verify first notes commit' ' ' test_expect_success 'Q: verify first notes tree' ' - cat >expect.unsorted <<-EOF && + sort >expect <<-EOF && 100644 blob $commit1 100644 blob $commit2 100644 blob $commit3 EOF - cat expect.unsorted | sort >expect && git cat-file -p refs/notes/foobar~2^{tree} | sed "s/ [0-9a-f]* / /" >actual && test_cmp expect actual ' @@ -2048,12 +2057,11 @@ test_expect_success 'Q: verify second notes commit' ' ' test_expect_success 'Q: verify second notes tree' ' - cat >expect.unsorted <<-EOF && + sort >expect <<-EOF && 100644 blob $commit1 100644 blob $commit2 100644 blob $commit3 EOF - cat expect.unsorted | sort >expect && git cat-file -p refs/notes/foobar^^{tree} | sed "s/ [0-9a-f]* / /" >actual && test_cmp expect actual ' @@ -2088,10 +2096,9 @@ test_expect_success 'Q: verify third notes commit' ' ' test_expect_success 'Q: verify third notes tree' ' - cat >expect.unsorted <<-EOF && + sort >expect <<-EOF && 100644 blob $commit1 EOF - cat expect.unsorted | sort >expect && git cat-file -p refs/notes/foobar2^{tree} | sed "s/ [0-9a-f]* / /" >actual && test_cmp expect actual ' @@ -2115,10 +2122,9 @@ test_expect_success 'Q: verify fourth notes commit' ' ' test_expect_success 'Q: verify fourth notes tree' ' - cat >expect.unsorted <<-EOF && + sort >expect <<-EOF && 100644 blob $commit2 EOF - cat expect.unsorted | sort >expect && git cat-file -p refs/notes/foobar^{tree} | sed "s/ [0-9a-f]* / /" >actual && test_cmp expect actual ' @@ -2146,6 +2152,7 @@ test_expect_success 'Q: deny note on empty branch' ' EOF test_must_fail git fast-import <input ' + ### ### series R (feature and option) ### @@ -2794,7 +2801,7 @@ test_expect_success 'R: blob appears only once' ' ' ### -### series S +### series S (mark and path parsing) ### # # Make sure missing spaces and EOLs after mark references @@ -3064,21 +3071,283 @@ test_expect_success 'S: ls with garbage after sha1 must fail' ' test_grep "space after tree-ish" err ' +# +# Path parsing +# +# There are two sorts of ways a path can be parsed, depending on whether it is +# the last field on the line. Additionally, ls without a <dataref> has a special +# case. Test every occurrence of <path> in the grammar against every error case. +# Paths for the root (empty strings) are tested elsewhere. +# + +# +# Valid paths at the end of a line: filemodify, filedelete, filecopy (dest), +# filerename (dest), and ls. +# +# commit :301 from root -- modify hello.c (for setup) +# commit :302 from :301 -- modify $path +# commit :303 from :302 -- delete $path +# commit :304 from :301 -- copy hello.c $path +# commit :305 from :301 -- rename hello.c $path +# ls :305 $path +# +test_path_eol_success () { + local test="$1" path="$2" unquoted_path="$3" + test_expect_success "S: paths at EOL with $test must work" ' + test_when_finished "git branch -D S-path-eol" && + + git fast-import --export-marks=marks.out <<-EOF >out 2>err && + blob + mark :401 + data <<BLOB + hello world + BLOB + + blob + mark :402 + data <<BLOB + hallo welt + BLOB + + commit refs/heads/S-path-eol + mark :301 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + initial commit + COMMIT + M 100644 :401 hello.c + + commit refs/heads/S-path-eol + mark :302 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit filemodify + COMMIT + from :301 + M 100644 :402 $path + + commit refs/heads/S-path-eol + mark :303 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit filedelete + COMMIT + from :302 + D $path + + commit refs/heads/S-path-eol + mark :304 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit filecopy dest + COMMIT + from :301 + C hello.c $path + + commit refs/heads/S-path-eol + mark :305 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit filerename dest + COMMIT + from :301 + R hello.c $path + + ls :305 $path + EOF + + commit_m=$(grep :302 marks.out | cut -d\ -f2) && + commit_d=$(grep :303 marks.out | cut -d\ -f2) && + commit_c=$(grep :304 marks.out | cut -d\ -f2) && + commit_r=$(grep :305 marks.out | cut -d\ -f2) && + blob1=$(grep :401 marks.out | cut -d\ -f2) && + blob2=$(grep :402 marks.out | cut -d\ -f2) && + + ( + printf "100644 blob $blob2\t$unquoted_path\n" && + printf "100644 blob $blob1\thello.c\n" + ) | sort >tree_m.exp && + git ls-tree $commit_m | sort >tree_m.out && + test_cmp tree_m.exp tree_m.out && + + printf "100644 blob $blob1\thello.c\n" >tree_d.exp && + git ls-tree $commit_d >tree_d.out && + test_cmp tree_d.exp tree_d.out && + + ( + printf "100644 blob $blob1\t$unquoted_path\n" && + printf "100644 blob $blob1\thello.c\n" + ) | sort >tree_c.exp && + git ls-tree $commit_c | sort >tree_c.out && + test_cmp tree_c.exp tree_c.out && + + printf "100644 blob $blob1\t$unquoted_path\n" >tree_r.exp && + git ls-tree $commit_r >tree_r.out && + test_cmp tree_r.exp tree_r.out && + + test_cmp out tree_r.exp + ' +} + +test_path_eol_success 'quoted spaces' '" hello world.c "' ' hello world.c ' +test_path_eol_success 'unquoted spaces' ' hello world.c ' ' hello world.c ' +test_path_eol_success 'octal escapes' '"\150\151\056\143"' 'hi.c' + +# +# Valid paths before a space: filecopy (source) and filerename (source). +# +# commit :301 from root -- modify $path (for setup) +# commit :302 from :301 -- copy $path hello2.c +# commit :303 from :301 -- rename $path hello2.c +# +test_path_space_success () { + local test="$1" path="$2" unquoted_path="$3" + test_expect_success "S: paths before space with $test must work" ' + test_when_finished "git branch -D S-path-space" && + + git fast-import --export-marks=marks.out <<-EOF 2>err && + blob + mark :401 + data <<BLOB + hello world + BLOB + + commit refs/heads/S-path-space + mark :301 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + initial commit + COMMIT + M 100644 :401 $path + + commit refs/heads/S-path-space + mark :302 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit filecopy source + COMMIT + from :301 + C $path hello2.c + + commit refs/heads/S-path-space + mark :303 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit filerename source + COMMIT + from :301 + R $path hello2.c + + EOF + + commit_c=$(grep :302 marks.out | cut -d\ -f2) && + commit_r=$(grep :303 marks.out | cut -d\ -f2) && + blob=$(grep :401 marks.out | cut -d\ -f2) && + + ( + printf "100644 blob $blob\t$unquoted_path\n" && + printf "100644 blob $blob\thello2.c\n" + ) | sort >tree_c.exp && + git ls-tree $commit_c | sort >tree_c.out && + test_cmp tree_c.exp tree_c.out && + + printf "100644 blob $blob\thello2.c\n" >tree_r.exp && + git ls-tree $commit_r >tree_r.out && + test_cmp tree_r.exp tree_r.out + ' +} + +test_path_space_success 'quoted spaces' '" hello world.c "' ' hello world.c ' +test_path_space_success 'no unquoted spaces' 'hello_world.c' 'hello_world.c' +test_path_space_success 'octal escapes' '"\150\151\056\143"' 'hi.c' + +# +# Test a single commit change with an invalid path. Run it with all occurrences +# of <path> in the grammar against all error kinds. +# +test_path_fail () { + local change="$1" what="$2" prefix="$3" path="$4" suffix="$5" err_grep="$6" + test_expect_success "S: $change with $what must fail" ' + test_must_fail git fast-import <<-EOF 2>err && + blob + mark :1 + data <<BLOB + hello world + BLOB + + commit refs/heads/S-path-fail + mark :2 + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit setup + COMMIT + M 100644 :1 hello.c + + commit refs/heads/S-path-fail + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + commit with bad path + COMMIT + from :2 + $prefix$path$suffix + EOF + + test_grep "$err_grep" err + ' +} + +test_path_base_fail () { + local change="$1" prefix="$2" field="$3" suffix="$4" + test_path_fail "$change" 'unclosed " in '"$field" "$prefix" '"hello.c' "$suffix" "Invalid $field" + test_path_fail "$change" "invalid escape in quoted $field" "$prefix" '"hello\xff"' "$suffix" "Invalid $field" + test_path_fail "$change" "escaped NUL in quoted $field" "$prefix" '"hello\000"' "$suffix" "NUL in $field" +} +test_path_eol_quoted_fail () { + local change="$1" prefix="$2" field="$3" + test_path_base_fail "$change" "$prefix" "$field" '' + test_path_fail "$change" "garbage after quoted $field" "$prefix" '"hello.c"' 'x' "Garbage after $field" + test_path_fail "$change" "space after quoted $field" "$prefix" '"hello.c"' ' ' "Garbage after $field" +} +test_path_eol_fail () { + local change="$1" prefix="$2" field="$3" + test_path_eol_quoted_fail "$change" "$prefix" "$field" +} +test_path_space_fail () { + local change="$1" prefix="$2" field="$3" + test_path_base_fail "$change" "$prefix" "$field" ' world.c' + test_path_fail "$change" "missing space after quoted $field" "$prefix" '"hello.c"' 'x world.c' "Missing space after $field" + test_path_fail "$change" "missing space after unquoted $field" "$prefix" 'hello.c' '' "Missing space after $field" +} + +test_path_eol_fail filemodify 'M 100644 :1 ' path +test_path_eol_fail filedelete 'D ' path +test_path_space_fail filecopy 'C ' source +test_path_eol_fail filecopy 'C hello.c ' dest +test_path_space_fail filerename 'R ' source +test_path_eol_fail filerename 'R hello.c ' dest +test_path_eol_fail 'ls (in commit)' 'ls :2 ' path + +# When 'ls' has no <dataref>, the <path> must be quoted. +test_path_eol_quoted_fail 'ls (without dataref in commit)' 'ls ' path + ### ### series T (ls) ### # Setup is carried over from series S. -test_expect_success 'T: ls root tree' ' - sed -e "s/Z\$//" >expect <<-EOF && - 040000 tree $(git rev-parse S^{tree}) Z - EOF - sha1=$(git rev-parse --verify S) && - git fast-import --import-marks=marks <<-EOF >actual && - ls $sha1 "" - EOF - test_cmp expect actual -' +for root in '""' '' +do + test_expect_success "T: ls root ($root) tree" ' + sed -e "s/Z\$//" >expect <<-EOF && + 040000 tree $(git rev-parse S^{tree}) Z + EOF + sha1=$(git rev-parse --verify S) && + git fast-import --import-marks=marks <<-EOF >actual && + ls $sha1 $root + EOF + test_cmp expect actual + ' +done test_expect_success 'T: delete branch' ' git branch to-delete && @@ -3180,30 +3449,33 @@ test_expect_success 'U: validate directory delete result' ' compare_diff_raw expect actual ' -test_expect_success 'U: filedelete root succeeds' ' - cat >input <<-INPUT_END && - commit refs/heads/U - committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE - data <<COMMIT - must succeed - COMMIT - from refs/heads/U^0 - D "" +for root in '""' '' +do + test_expect_success "U: filedelete root ($root) succeeds" ' + cat >input <<-INPUT_END && + commit refs/heads/U-delete-root + committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE + data <<COMMIT + must succeed + COMMIT + from refs/heads/U^0 + D $root - INPUT_END + INPUT_END - git fast-import <input -' + git fast-import <input + ' -test_expect_success 'U: validate root delete result' ' - cat >expect <<-EOF && - :100644 000000 $f7id $ZERO_OID D hello.c - EOF + test_expect_success "U: validate root ($root) delete result" ' + cat >expect <<-EOF && + :100644 000000 $f7id $ZERO_OID D hello.c + EOF - git diff-tree -M -r U^1 U >actual && + git diff-tree -M -r U U-delete-root >actual && - compare_diff_raw expect actual -' + compare_diff_raw expect actual + ' +done ### ### series V (checkpoint) diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index 26c25c0..1eb035e 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -236,7 +236,7 @@ EOF test_expect_success 'set up faked signed tag' ' - cat signed-tag-import | git fast-import + git fast-import <signed-tag-import ' @@ -537,7 +537,7 @@ test_expect_success 'full-tree re-shows unmodified files' ' test_expect_success 'set-up a few more tags for tag export tests' ' git checkout -f main && - HEAD_TREE=$(git show -s --pretty=raw HEAD | grep tree | sed "s/tree //") && + HEAD_TREE=$(git show -s --pretty=raw HEAD | sed -n "/tree/s/tree //p") && git tag tree_tag -m "tagging a tree" $HEAD_TREE && git tag -a tree_tag-obj -m "tagging a tree" $HEAD_TREE && git tag tag-obj_tag -m "tagging a tag" tree_tag-obj && @@ -791,4 +791,14 @@ test_expect_success 'fast-export --first-parent outputs all revisions output by ) ' +test_expect_success 'fast-export handles --end-of-options' ' + git update-ref refs/heads/nodash HEAD && + git update-ref refs/heads/--dashes HEAD && + git fast-export --end-of-options nodash >expect && + git fast-export --end-of-options --dashes >actual.raw && + # fix up lines which mention the ref for comparison + sed s/--dashes/nodash/ <actual.raw >actual && + test_cmp expect actual +' + test_done diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 003c0b6..e499c7f 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -117,12 +117,12 @@ END VERIFICATION REQUEST EOF test_expect_success 'pserver authentication' ' - cat request-anonymous | git-cvsserver pserver >log 2>&1 && + git-cvsserver pserver <request-anonymous >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$" ' test_expect_success 'pserver authentication failure (non-anonymous user)' ' - if cat request-git | git-cvsserver pserver >log 2>&1 + if git-cvsserver pserver <request-git >log 2>&1 then false else @@ -132,17 +132,17 @@ test_expect_success 'pserver authentication failure (non-anonymous user)' ' ' test_expect_success 'pserver authentication success (non-anonymous user with password)' ' - cat login-git-ok | git-cvsserver pserver >log 2>&1 && + git-cvsserver pserver <login-git-ok >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$" ' test_expect_success 'pserver authentication (login)' ' - cat login-anonymous | git-cvsserver pserver >log 2>&1 && + git-cvsserver pserver <login-anonymous >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$" ' test_expect_success 'pserver authentication failure (login/non-anonymous user)' ' - if cat login-git | git-cvsserver pserver >log 2>&1 + if git-cvsserver pserver <login-git >log 2>&1 then false else @@ -172,7 +172,7 @@ Root $WORKDIR EOF test_expect_success 'req_Root failure (relative pathname)' ' - if cat request-relative | git-cvsserver pserver >log 2>&1 + if git-cvsserver pserver <request-relative >log 2>&1 then echo unexpected success false @@ -183,28 +183,26 @@ test_expect_success 'req_Root failure (relative pathname)' ' ' test_expect_success 'req_Root failure (conflicting roots)' ' - cat request-conflict | git-cvsserver pserver >log 2>&1 && + git-cvsserver pserver <request-conflict >log 2>&1 && tail log | grep "^error 1 Conflicting roots specified$" ' test_expect_success 'req_Root (strict paths)' ' - cat request-anonymous | git-cvsserver --strict-paths pserver "$SERVERDIR" >log 2>&1 && + git-cvsserver --strict-paths pserver "$SERVERDIR" <request-anonymous >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$" ' test_expect_success 'req_Root failure (strict-paths)' ' - ! cat request-anonymous | - git-cvsserver --strict-paths pserver "$WORKDIR" >log 2>&1 + ! git-cvsserver --strict-paths pserver "$WORKDIR" <request-anonymous >log 2>&1 ' test_expect_success 'req_Root (w/o strict-paths)' ' - cat request-anonymous | git-cvsserver pserver "$WORKDIR/" >log 2>&1 && + git-cvsserver pserver "$WORKDIR/" <request-anonymous >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$" ' test_expect_success 'req_Root failure (w/o strict-paths)' ' - ! cat request-anonymous | - git-cvsserver pserver "$WORKDIR/gitcvs" >log 2>&1 + ! git-cvsserver pserver "$WORKDIR/gitcvs" <request-anonymous >log 2>&1 ' cat >request-base <<EOF @@ -217,27 +215,26 @@ Root /gitcvs.git EOF test_expect_success 'req_Root (base-path)' ' - cat request-base | git-cvsserver --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 && + git-cvsserver --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" <request-base >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$" ' test_expect_success 'req_Root failure (base-path)' ' - ! cat request-anonymous | - git-cvsserver --strict-paths --base-path "$WORKDIR" pserver "$SERVERDIR" >log 2>&1 + ! git-cvsserver --strict-paths --base-path "$WORKDIR" pserver "$SERVERDIR" <request-anonymous >log 2>&1 ' GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled false || exit 1 test_expect_success 'req_Root (export-all)' ' - cat request-anonymous | git-cvsserver --export-all pserver "$WORKDIR" >log 2>&1 && + git-cvsserver --export-all pserver "$WORKDIR" <request-anonymous >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$" ' test_expect_success 'req_Root failure (export-all w/o directory list)' ' - ! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)' + ! (git-cvsserver --export-all pserver <request-anonymous >log 2>&1 || false)' test_expect_success 'req_Root (everything together)' ' - cat request-base | git-cvsserver --export-all --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 && + git-cvsserver --export-all --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" <request-base >log 2>&1 && sed -ne \$p log | grep "^I LOVE YOU\$" ' diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh index 0333065..7679780 100755 --- a/t/t9500-gitweb-standalone-no-errors.sh +++ b/t/t9500-gitweb-standalone-no-errors.sh @@ -627,6 +627,7 @@ test_expect_success \ test_expect_success 'setup' ' version=$(git config core.repositoryformatversion) && algo=$(test_might_fail git config extensions.objectformat) && + refstorage=$(test_might_fail git config extensions.refstorage) && cat >.git/config <<-\EOF && # testing noval and alternate separator [gitweb] @@ -637,6 +638,10 @@ test_expect_success 'setup' ' if test -n "$algo" then git config extensions.objectformat "$algo" + fi && + if test -n "$refstorage" + then + git config extensions.refstorage "$refstorage" fi ' diff --git a/t/t9604-cvsimport-timestamps.sh b/t/t9604-cvsimport-timestamps.sh index 2ff4aa9..2d03259 100755 --- a/t/t9604-cvsimport-timestamps.sh +++ b/t/t9604-cvsimport-timestamps.sh @@ -3,11 +3,28 @@ test_description='git cvsimport timestamps' . ./lib-cvs.sh +test_lazy_prereq POSIX_TIMEZONE ' + local tz=XST-1XDT,M3.5.0,M11.1.0 + echo "1711846799 -> 2024-03-31 01:59:59 +0100" >expected && + TZ="$tz" test-tool date show:iso-local 1711846799 >actual && + test_cmp expected actual && + echo "1711846800 -> 2024-03-31 03:00:00 +0200" >expected && + TZ="$tz" test-tool date show:iso-local 1711846800 >actual && + test_cmp expected actual && + echo "1730591999 -> 2024-11-03 01:59:59 +0200" >expected && + TZ="$tz" test-tool date show:iso-local 1730591999 >actual && + test_cmp expected actual && + echo "1730592000 -> 2024-11-03 01:00:00 +0100" >expected && + TZ="$tz" test-tool date show:iso-local 1730592000 >actual && + test_cmp expected actual +' + setup_cvs_test_repository t9604 -test_expect_success PERL 'check timestamps are UTC (TZ=CST6CDT)' ' +test_expect_success PERL,POSIX_TIMEZONE 'check timestamps are UTC' ' - TZ=CST6CDT git cvsimport -p"-x" -C module-1 module && + TZ=CST6CDT,M4.1.0,M10.5.0 \ + git cvsimport -p"-x" -C module-1 module && git cvsimport -p"-x" -C module-1 module && ( cd module-1 && @@ -34,13 +51,13 @@ test_expect_success PERL 'check timestamps are UTC (TZ=CST6CDT)' ' test_cmp expect-1 actual-1 ' -test_expect_success PERL 'check timestamps with author-specific timezones' ' +test_expect_success PERL,POSIX_TIMEZONE 'check timestamps with author-specific timezones' ' cat >cvs-authors <<-EOF && user1=User One <user1@domain.org> - user2=User Two <user2@domain.org> CST6CDT - user3=User Three <user3@domain.org> EST5EDT - user4=User Four <user4@domain.org> MST7MDT + user2=User Two <user2@domain.org> CST6CDT,M4.1.0,M10.5.0 + user3=User Three <user3@domain.org> EST5EDT,M4.1.0,M10.5.0 + user4=User Four <user4@domain.org> MST7MDT,M4.1.0,M10.5.0 EOF git cvsimport -p"-x" -A cvs-authors -C module-2 module && ( diff --git a/t/t9700/test.pl b/t/t9700/test.pl index 6d75370..d8e8548 100755 --- a/t/t9700/test.pl +++ b/t/t9700/test.pl @@ -1,7 +1,7 @@ #!/usr/bin/perl use lib (split(/:/, $ENV{GITPERLLIB})); -use 5.008; +use 5.008001; use warnings; use strict; diff --git a/t/t9801-git-p4-branch.sh b/t/t9801-git-p4-branch.sh index 73cca0d..c598011 100755 --- a/t/t9801-git-p4-branch.sh +++ b/t/t9801-git-p4-branch.sh @@ -466,7 +466,7 @@ test_expect_success 'git p4 clone complex branches with excluded files' ' ) ' -# From a report in http://stackoverflow.com/questions/11893688 +# From a report in https://stackoverflow.com/questions/11893688 # where --use-client-spec caused branch prefixes not to be removed; # every file in git appeared into a subdirectory of the branch name. test_expect_success 'use-client-spec detect-branches setup' ' diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh index 2a6ee2a..bb236cd 100755 --- a/t/t9802-git-p4-filetype.sh +++ b/t/t9802-git-p4-filetype.sh @@ -175,7 +175,7 @@ test_expect_success 'keyword file create' ' cp k-text-k k-text-ko && p4 add -t text+ko k-text-ko && - cat k-text-k | iconv -f ascii -t utf-16 >k-utf16-k && + iconv -f ascii -t utf-16 <k-text-k >k-utf16-k && p4 add -t utf16+k k-utf16-k && cp k-utf16-k k-utf16-ko && diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index af4b286..6ae7ced 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -418,7 +418,7 @@ test_expect_success 'description with Jobs and values on separate lines' ' marshal_dump job0 <change && marshal_dump job1 <change ) | sort >jobs && - cat jobname1 jobname2 | sort >expected && + sort jobname1 jobname2 >expected && test_cmp expected jobs ) ' diff --git a/t/t9816-git-p4-locked.sh b/t/t9816-git-p4-locked.sh index 9328410..5e904ac 100755 --- a/t/t9816-git-p4-locked.sh +++ b/t/t9816-git-p4-locked.sh @@ -9,7 +9,7 @@ test_expect_success 'start p4d' ' ' # See -# http://www.perforce.com/perforce/doc.current/manuals/p4sag/03_superuser.html#1088563 +# https://web.archive.org/web/20150602090517/http://www.perforce.com/perforce/doc.current/manuals/p4sag/chapter.superuser.html#superuser.basic.typemap_locking # for suggestions on how to configure "sitewide pessimistic locking" # where only one person can have a file open for edit at a time. test_expect_success 'init depot' ' diff --git a/t/t9824-git-p4-git-lfs.sh b/t/t9824-git-p4-git-lfs.sh index a28dbbd..80c8c31 100755 --- a/t/t9824-git-p4-git-lfs.sh +++ b/t/t9824-git-p4-git-lfs.sh @@ -17,8 +17,8 @@ test_file_in_lfs () { sed -n '2,2 p' "$FILE" | grep "^oid " && sed -n '3,3 p' "$FILE" | grep "^size " && test_line_count = 3 "$FILE" && - cat "$FILE" | grep "size $SIZE" && - HASH=$(cat "$FILE" | grep "oid sha256:" | sed -e "s/oid sha256://g") && + grep "size $SIZE" "$FILE" && + HASH=$(sed -ne "/oid sha256:/s/oid sha256://gp" "$FILE") && LFS_FILE=".git/lfs/objects/$(echo "$HASH" | cut -c1-2)/$(echo "$HASH" | cut -c3-4)/$HASH" && echo $EXPECTED_CONTENT >expect && test_path_is_file "$FILE" && diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index a7c3b4e..569cf23 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -5,6 +5,17 @@ test_description='test bash completion' +# The Bash completion scripts must not print anything to either stdout or +# stderr, which we try to verify. When tracing is enabled without support for +# BASH_XTRACEFD this assertion will fail, so we have to mark the test as +# untraceable with such ancient Bash versions. +test_untraceable=UnfortunatelyYes + +# Override environment and always use master for the default initial branch +# name for these tests, so that rev completion candidates are as expected. +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + . ./lib-bash.sh complete () @@ -87,9 +98,11 @@ test_completion () else sed -e 's/Z$//' |sort >expected fi && - run_completion "$1" && + run_completion "$1" >"$TRASH_DIRECTORY"/bash-completion-output 2>&1 && sort out >out_sorted && - test_cmp expected out_sorted + test_cmp expected out_sorted && + test_must_be_empty "$TRASH_DIRECTORY"/bash-completion-output && + rm "$TRASH_DIRECTORY"/bash-completion-output } # Test __gitcomp. @@ -1250,6 +1263,29 @@ test_expect_success '__git_complete_fetch_refspecs - fully qualified & prefix' ' test_cmp expected out ' +test_expect_success '__git_complete_worktree_paths' ' + test_when_finished "git worktree remove other_wt" && + git worktree add --orphan other_wt && + run_completion "git worktree remove " && + grep other_wt out +' + +test_expect_success '__git_complete_worktree_paths - not a git repository' ' + ( + cd non-repo && + GIT_CEILING_DIRECTORIES="$ROOT" && + export GIT_CEILING_DIRECTORIES && + test_completion "git worktree remove " "" + ) +' + +test_expect_success '__git_complete_worktree_paths with -C' ' + test_when_finished "git -C otherrepo worktree remove otherrepo_wt" && + git -C otherrepo worktree add --orphan otherrepo_wt && + run_completion "git -C otherrepo worktree remove " && + grep otherrepo_wt out +' + test_expect_success 'git switch - with no options, complete local branches and unique remote branch names for DWIM logic' ' test_completion "git switch " <<-\EOF branch-in-other Z @@ -1259,6 +1295,142 @@ test_expect_success 'git switch - with no options, complete local branches and u EOF ' +test_expect_success 'git bisect - when not bisecting, complete only replay and start subcommands' ' + test_completion "git bisect " <<-\EOF + replay Z + start Z + EOF +' + +test_expect_success 'git bisect - complete options to start subcommand' ' + test_completion "git bisect start --" <<-\EOF + --term-new Z + --term-bad Z + --term-old Z + --term-good Z + --no-checkout Z + --first-parent Z + EOF +' + +test_expect_success 'setup for git-bisect tests requiring a repo' ' + git init git-bisect && + ( + cd git-bisect && + echo "initial contents" >file && + git add file && + git commit -am "Initial commit" && + git tag initial && + echo "new line" >>file && + git commit -am "First change" && + echo "another new line" >>file && + git commit -am "Second change" && + git tag final + ) +' + +test_expect_success 'git bisect - start subcommand arguments before double-dash are completed as revs' ' + ( + cd git-bisect && + test_completion "git bisect start " <<-\EOF + HEAD Z + final Z + initial Z + master Z + EOF + ) +' + +# Note that these arguments are <pathspec>s, which in practice the fallback +# completion (not the git completion) later ends up completing as paths. +test_expect_success 'git bisect - start subcommand arguments after double-dash are not completed' ' + ( + cd git-bisect && + test_completion "git bisect start final initial -- " "" + ) +' + +test_expect_success 'setup for git-bisect tests requiring ongoing bisection' ' + ( + cd git-bisect && + git bisect start --term-new=custom_new --term-old=custom_old final initial + ) +' + +test_expect_success 'git-bisect - when bisecting all subcommands are candidates' ' + ( + cd git-bisect && + test_completion "git bisect " <<-\EOF + start Z + bad Z + custom_new Z + custom_old Z + new Z + good Z + old Z + terms Z + skip Z + reset Z + visualize Z + replay Z + log Z + run Z + help Z + EOF + ) +' + +test_expect_success 'git-bisect - options to terms subcommand are candidates' ' + ( + cd git-bisect && + test_completion "git bisect terms --" <<-\EOF + --term-bad Z + --term-good Z + --term-new Z + --term-old Z + EOF + ) +' + +test_expect_success 'git-bisect - git-log options to visualize subcommand are candidates' ' + ( + cd git-bisect && + # The completion used for git-log and here does not complete + # every git-log option, so rather than hope to stay in sync + # with exactly what it does we will just spot-test here. + test_completion "git bisect visualize --sta" <<-\EOF && + --stat Z + EOF + test_completion "git bisect visualize --summar" <<-\EOF + --summary Z + EOF + ) +' + +test_expect_success 'git-bisect - view subcommand is not a candidate' ' + ( + cd git-bisect && + test_completion "git bisect vi" <<-\EOF + visualize Z + EOF + ) +' + +test_expect_success 'git-bisect - existing view subcommand is recognized and enables completion of git-log options' ' + ( + cd git-bisect && + # The completion used for git-log and here does not complete + # every git-log option, so rather than hope to stay in sync + # with exactly what it does we will just spot-test here. + test_completion "git bisect view --sta" <<-\EOF && + --stat Z + EOF + test_completion "git bisect view --summar" <<-\EOF + --summary Z + EOF + ) +' + test_expect_success 'git checkout - completes refs and unique remote branches for DWIM' ' test_completion "git checkout " <<-\EOF HEAD Z @@ -1571,7 +1743,7 @@ test_expect_success FUNNYNAMES,!CYGWIN 'cone mode sparse-checkout completes dire ) ' -test_expect_success 'non-cone mode sparse-checkout uses bash completion' ' +test_expect_success 'non-cone mode sparse-checkout gives rooted paths' ' # reset sparse-checkout repo to non-cone mode git -C sparse-checkout sparse-checkout disable && git -C sparse-checkout sparse-checkout set --no-cone && @@ -1581,7 +1753,12 @@ test_expect_success 'non-cone mode sparse-checkout uses bash completion' ' # expected to be empty since we have not configured # custom completion for non-cone mode test_completion "git sparse-checkout set f" <<-\EOF - + /folder1/0/1/t.txt Z + /folder1/expected Z + /folder1/out Z + /folder1/out_sorted Z + /folder2/0/t.txt Z + /folder3/t.txt Z EOF ) ' @@ -1920,6 +2097,14 @@ test_expect_success 'git checkout - --orphan with branch already provided comple EOF ' +test_expect_success 'git restore completes modified files' ' + test_commit A a.file && + echo B >a.file && + test_completion "git restore a." <<-\EOF + a.file + EOF +' + test_expect_success 'teardown after ref completion' ' git branch -d matching-branch && git tag -d matching-tag && @@ -2562,6 +2747,35 @@ test_expect_success 'git config - variable name include' ' EOF ' +test_expect_success 'setup for git config submodule tests' ' + test_create_repo sub && + test_commit -C sub initial && + git submodule add ./sub +' + +test_expect_success 'git config - variable name - submodule and __git_compute_first_level_config_vars_for_section' ' + test_completion "git config submodule." <<-\EOF + submodule.active Z + submodule.alternateErrorStrategy Z + submodule.alternateLocation Z + submodule.fetchJobs Z + submodule.propagateBranches Z + submodule.recurse Z + submodule.sub.Z + EOF +' + +test_expect_success 'git config - variable name - __git_compute_second_level_config_vars_for_section' ' + test_completion "git config submodule.sub." <<-\EOF + submodule.sub.url Z + submodule.sub.update Z + submodule.sub.branch Z + submodule.sub.fetchRecurseSubmodules Z + submodule.sub.ignore Z + submodule.sub.active Z + EOF +' + test_expect_success 'git config - value' ' test_completion "git config color.pager " <<-\EOF false Z @@ -2613,6 +2827,20 @@ test_expect_success 'git clone --config= - value' ' EOF ' +test_expect_success 'git reflog show' ' + test_when_finished "git checkout - && git branch -d shown" && + git checkout -b shown && + test_completion "git reflog sho" <<-\EOF && + show Z + shown Z + EOF + test_completion "git reflog show sho" "shown " && + test_completion "git reflog shown sho" "shown " && + test_completion "git reflog --unt" "--until=" && + test_completion "git reflog show --unt" "--until=" && + test_completion "git reflog shown --unt" "--until=" +' + test_expect_success 'options with value' ' test_completion "git merge -X diff-algorithm=" <<-\EOF @@ -2715,4 +2943,31 @@ test_expect_success '__git_complete' ' test_must_fail __git_complete ga missing ' +test_expect_success '__git_pseudoref_exists' ' + test_when_finished "rm -rf repo" && + git init repo && + ( + cd repo && + sane_unset __git_repo_path && + + # HEAD should exist, even if it points to an unborn branch. + __git_pseudoref_exists HEAD >output 2>&1 && + test_must_be_empty output && + + # HEAD points to an existing branch, so it should exist. + test_commit A && + __git_pseudoref_exists HEAD >output 2>&1 && + test_must_be_empty output && + + # CHERRY_PICK_HEAD does not exist, so the existence check should fail. + ! __git_pseudoref_exists CHERRY_PICK_HEAD >output 2>&1 && + test_must_be_empty output && + + # CHERRY_PICK_HEAD points to a commit, so it should exist. + git update-ref CHERRY_PICK_HEAD A && + __git_pseudoref_exists CHERRY_PICK_HEAD >output 2>&1 && + test_must_be_empty output + ) +' + test_done diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 9c3cf12..862d80c 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -14,7 +14,7 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ . +# along with this program. If not, see https://www.gnu.org/licenses/ . # The semantics of the editor variables are that of invoking # sh -c "$EDITOR \"$@\"" files ... @@ -385,7 +385,7 @@ test_commit () { shift done && indir=${indir:+"$indir"/} && - local file=${2:-"$1.t"} && + local file="${2:-"$1.t"}" && if test -n "$append" then $echo "${3-$1}" >>"$indir$file" @@ -1263,9 +1263,8 @@ test_cmp_bin () { cmp "$@" } -# Deprecated - do not use this in new code test_i18ngrep () { - test_grep "$@" + BUG "do not use test_i18ngrep---use test_grep instead" } test_grep () { @@ -1277,7 +1276,7 @@ test_grep () { if test $# -lt 2 || { test "x!" = "x$1" && test $# -lt 3 ; } then - BUG "too few parameters to test_i18ngrep" + BUG "too few parameters to test_grep" fi if test "x!" = "x$1" @@ -1656,7 +1655,21 @@ test_set_hash () { # Detect the hash algorithm in use. test_detect_hash () { - test_hash_algo="${GIT_TEST_DEFAULT_HASH:-sha1}" + case "$GIT_TEST_DEFAULT_HASH" in + "sha256") + test_hash_algo=sha256 + test_compat_hash_algo=sha1 + ;; + *) + test_hash_algo=sha1 + test_compat_hash_algo=sha256 + ;; + esac +} + +# Detect the hash algorithm in use. +test_detect_ref_format () { + echo "${GIT_TEST_DEFAULT_REF_FORMAT:-files}" } # Load common hash metadata and common placeholder object IDs for use with @@ -1708,6 +1721,12 @@ test_oid () { local algo="${test_hash_algo}" && case "$1" in + --hash=storage) + algo="$test_hash_algo" && + shift;; + --hash=compat) + algo="$test_compat_hash_algo" && + shift;; --hash=*) algo="${1#--hash=}" && shift;; @@ -1729,7 +1748,7 @@ test_oid () { # Insert a slash into an object ID so it can be used to reference a location # under ".git/objects". For example, "deadbeef..." becomes "de/adbeef..". test_oid_to_path () { - local basename=${1#??} + local basename="${1#??}" echo "${1%$basename}/$basename" } @@ -1746,7 +1765,7 @@ test_parse_ls_tree_oids () { # Choose a port number based on the test script's number and store it in # the given variable name, unless that variable already contains a number. test_set_port () { - local var=$1 port + local var="$1" port if test $# -ne 1 || test -z "$var" then @@ -1821,7 +1840,7 @@ test_subcommand () { shift fi - local expr=$(printf '"%s",' "$@") + local expr="$(printf '"%s",' "$@")" expr="${expr%,}" if test -n "$negate" @@ -1874,6 +1893,20 @@ test_region () { return 0 } +# Check that the given data fragment was included as part of the +# trace2-format trace on stdin. +# +# test_trace2_data <category> <key> <value> +# +# For example, to look for trace2_data_intmax("pack-objects", repo, +# "reused", N) in an invocation of "git pack-objects", run: +# +# GIT_TRACE2_EVENT="$(pwd)/trace.txt" git pack-objects ... && +# test_trace2_data pack-objects reused N <trace2.txt +test_trace2_data () { + grep -e '"category":"'"$1"'","key":"'"$2"'","value":"'"$3"'"' +} + # Given a GIT_TRACE2_EVENT log over stdin, writes to stdout a list of URLs # sent to git-remote-https child processes. test_remote_https_urls() { @@ -1897,7 +1930,7 @@ test_readlink () { # An optional increment to the magic timestamp may be specified as second # argument. test_set_magic_mtime () { - local inc=${2:-0} && + local inc="${2:-0}" && local mtime=$((1234567890 + $inc)) && test-tool chmtime =$mtime "$1" && test_is_magic_mtime "$1" $inc @@ -1910,7 +1943,7 @@ test_set_magic_mtime () { # argument. Usually, this should be the same increment which was used for # the associated test_set_magic_mtime. test_is_magic_mtime () { - local inc=${2:-0} && + local inc="${2:-0}" && local mtime=$((1234567890 + $inc)) && echo $mtime >.git/test-mtime-expect && test-tool chmtime --get "$1" >.git/test-mtime-actual && diff --git a/t/test-lib-github-workflow-markup.sh b/t/test-lib-github-workflow-markup.sh index 9c5339c..33405c9 100644 --- a/t/test-lib-github-workflow-markup.sh +++ b/t/test-lib-github-workflow-markup.sh @@ -14,7 +14,7 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ . +# along with this program. If not, see https://www.gnu.org/licenses/ . # # The idea is for `test-lib.sh` to source this file when run in GitHub # workflows; these functions will then override (empty) functions @@ -42,8 +42,8 @@ finalize_test_case_output () { fixed) echo >>$github_markup_output "::notice::fixed: $this_test.$test_count $1" ;; - ok) - # Exit without printing the "ok" tests + ok|broken) + # Exit without printing the "ok" or ""broken" tests return ;; esac diff --git a/t/test-lib-junit.sh b/t/test-lib-junit.sh index 79c31c7..76cbbd3 100644 --- a/t/test-lib-junit.sh +++ b/t/test-lib-junit.sh @@ -15,7 +15,7 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ . +# along with this program. If not, see https://www.gnu.org/licenses/ . # # The idea is for `test-lib.sh` to source this file when the user asks # for JUnit XML; these functions will then override (empty) functions diff --git a/t/test-lib.sh b/t/test-lib.sh index 1656c9e..79d3e0e 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -13,7 +13,7 @@ # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see http://www.gnu.org/licenses/ . +# along with this program. If not, see https://www.gnu.org/licenses/ . # Test the binaries we have just built. The tests are kept in # t/ subdirectory and are run in 'trash directory' subdirectory. @@ -542,6 +542,8 @@ export EDITOR GIT_DEFAULT_HASH="${GIT_TEST_DEFAULT_HASH:-sha1}" export GIT_DEFAULT_HASH +GIT_DEFAULT_REF_FORMAT="${GIT_TEST_DEFAULT_REF_FORMAT:-files}" +export GIT_DEFAULT_REF_FORMAT GIT_TEST_MERGE_ALGORITHM="${GIT_TEST_MERGE_ALGORITHM:-ort}" export GIT_TEST_MERGE_ALGORITHM @@ -1295,6 +1297,11 @@ test_done () { EOF fi + if test -z "$passes_sanitize_leak" && test_bool_env TEST_PASSES_SANITIZE_LEAK false + then + BAIL_OUT "Please, set TEST_PASSES_SANITIZE_LEAK before sourcing test-lib.sh" + fi + if test "$test_fixed" != 0 then say_color error "# $test_fixed known breakage(s) vanished; please update test(s)" @@ -1745,7 +1752,16 @@ parisc* | hppa*) ;; esac -test_set_prereq REFFILES +case "$GIT_DEFAULT_REF_FORMAT" in +files) + test_set_prereq REFFILES;; +reftable) + test_set_prereq REFTABLE;; +*) + echo 2>&1 "error: unknown ref format $GIT_DEFAULT_REF_FORMAT" + exit 1 + ;; +esac ( COLUMNS=1 && test $COLUMNS = 1 ) && test_set_prereq COLUMNS_CAN_BE_1 test -z "$NO_CURL" && test_set_prereq LIBCURL @@ -1936,12 +1952,17 @@ test_lazy_prereq SHA1 ' esac ' +test_lazy_prereq DEFAULT_REPO_FORMAT ' + test_have_prereq SHA1,REFFILES +' + # Ensure that no test accidentally triggers a Git command # that runs the actual maintenance scheduler, affecting a user's # system permanently. # Tests that verify the scheduler integration must set this locally # to avoid errors. GIT_TEST_MAINT_SCHEDULER="none:exit 1" +export GIT_TEST_MAINT_SCHEDULER # Does this platform support `git fsmonitor--daemon` # diff --git a/t/test-terminal.perl b/t/test-terminal.perl index 1bcf01a..3810e9b 100755 --- a/t/test-terminal.perl +++ b/t/test-terminal.perl @@ -1,5 +1,5 @@ #!/usr/bin/perl -use 5.008; +use 5.008001; use strict; use warnings; use IO::Pty; diff --git a/t/unit-tests/.gitignore b/t/unit-tests/.gitignore new file mode 100644 index 0000000..5e56e04 --- /dev/null +++ b/t/unit-tests/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/t/unit-tests/t-basic.c b/t/unit-tests/t-basic.c new file mode 100644 index 0000000..fda1ae5 --- /dev/null +++ b/t/unit-tests/t-basic.c @@ -0,0 +1,95 @@ +#include "test-lib.h" + +/* + * The purpose of this "unit test" is to verify a few invariants of the unit + * test framework itself, as well as to provide examples of output from actually + * failing tests. As such, it is intended that this test fails, and thus it + * should not be run as part of `make unit-tests`. Instead, we verify it behaves + * as expected in the integration test t0080-unit-test-output.sh + */ + +/* Used to store the return value of check_int(). */ +static int check_res; + +/* Used to store the return value of TEST(). */ +static int test_res; + +static void t_res(int expect) +{ + check_int(check_res, ==, expect); + check_int(test_res, ==, expect); +} + +static void t_todo(int x) +{ + check_res = TEST_TODO(check(x)); +} + +static void t_skip(void) +{ + check(0); + test_skip("missing prerequisite"); + check(1); +} + +static int do_skip(void) +{ + test_skip("missing prerequisite"); + return 1; +} + +static void t_skip_todo(void) +{ + check_res = TEST_TODO(do_skip()); +} + +static void t_todo_after_fail(void) +{ + check(0); + TEST_TODO(check(0)); +} + +static void t_fail_after_todo(void) +{ + check(1); + TEST_TODO(check(0)); + check(0); +} + +static void t_messages(void) +{ + check_str("\thello\\", "there\"\n"); + check_str("NULL", NULL); + check_char('a', ==, '\n'); + check_char('\\', ==, '\''); +} + +static void t_empty(void) +{ + ; /* empty */ +} + +int cmd_main(int argc, const char **argv) +{ + test_res = TEST(check_res = check_int(1, ==, 1), "passing test"); + TEST(t_res(1), "passing test and assertion return 1"); + test_res = TEST(check_res = check_int(1, ==, 2), "failing test"); + TEST(t_res(0), "failing test and assertion return 0"); + test_res = TEST(t_todo(0), "passing TEST_TODO()"); + TEST(t_res(1), "passing TEST_TODO() returns 1"); + test_res = TEST(t_todo(1), "failing TEST_TODO()"); + TEST(t_res(0), "failing TEST_TODO() returns 0"); + test_res = TEST(t_skip(), "test_skip()"); + TEST(check_int(test_res, ==, 1), "skipped test returns 1"); + test_res = TEST(t_skip_todo(), "test_skip() inside TEST_TODO()"); + TEST(t_res(1), "test_skip() inside TEST_TODO() returns 1"); + test_res = TEST(t_todo_after_fail(), "TEST_TODO() after failing check"); + TEST(check_int(test_res, ==, 0), "TEST_TODO() after failing check returns 0"); + test_res = TEST(t_fail_after_todo(), "failing check after TEST_TODO()"); + TEST(check_int(test_res, ==, 0), "failing check after TEST_TODO() returns 0"); + TEST(t_messages(), "messages from failing string and char comparison"); + test_res = TEST(t_empty(), "test with no checks"); + TEST(check_int(test_res, ==, 0), "test with no checks returns 0"); + + return test_done(); +} diff --git a/t/unit-tests/t-ctype.c b/t/unit-tests/t-ctype.c new file mode 100644 index 0000000..d6ac1fe --- /dev/null +++ b/t/unit-tests/t-ctype.c @@ -0,0 +1,53 @@ +#include "test-lib.h" + +#define TEST_CHAR_CLASS(class, string) do { \ + size_t len = ARRAY_SIZE(string) - 1 + \ + BUILD_ASSERT_OR_ZERO(ARRAY_SIZE(string) > 0) + \ + BUILD_ASSERT_OR_ZERO(sizeof(string[0]) == sizeof(char)); \ + int skip = test__run_begin(); \ + if (!skip) { \ + for (int i = 0; i < 256; i++) { \ + if (!check_int(class(i), ==, !!memchr(string, i, len)))\ + test_msg(" i: 0x%02x", i); \ + } \ + check(!class(EOF)); \ + } \ + test__run_end(!skip, TEST_LOCATION(), #class " works"); \ +} while (0) + +#define DIGIT "0123456789" +#define LOWER "abcdefghijklmnopqrstuvwxyz" +#define UPPER "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define PUNCT "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~" +#define ASCII \ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \ + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \ + "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f" \ + "\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" \ + "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f" \ + "\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" \ + "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f" \ + "\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f" +#define CNTRL \ + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" \ + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" \ + "\x7f" + +int cmd_main(int argc, const char **argv) { + TEST_CHAR_CLASS(isspace, " \n\r\t"); + TEST_CHAR_CLASS(isdigit, DIGIT); + TEST_CHAR_CLASS(isalpha, LOWER UPPER); + TEST_CHAR_CLASS(isalnum, LOWER UPPER DIGIT); + TEST_CHAR_CLASS(is_glob_special, "*?[\\"); + TEST_CHAR_CLASS(is_regex_special, "$()*+.?[\\^{|"); + TEST_CHAR_CLASS(is_pathspec_magic, "!\"#%&',-/:;<=>@_`~"); + TEST_CHAR_CLASS(isascii, ASCII); + TEST_CHAR_CLASS(islower, LOWER); + TEST_CHAR_CLASS(isupper, UPPER); + TEST_CHAR_CLASS(iscntrl, CNTRL); + TEST_CHAR_CLASS(ispunct, PUNCT); + TEST_CHAR_CLASS(isxdigit, DIGIT "abcdefABCDEF"); + TEST_CHAR_CLASS(isprint, LOWER UPPER DIGIT PUNCT " "); + + return test_done(); +} diff --git a/t/unit-tests/t-mem-pool.c b/t/unit-tests/t-mem-pool.c new file mode 100644 index 0000000..a0d57df --- /dev/null +++ b/t/unit-tests/t-mem-pool.c @@ -0,0 +1,31 @@ +#include "test-lib.h" +#include "mem-pool.h" + +static void setup_static(void (*f)(struct mem_pool *), size_t block_alloc) +{ + struct mem_pool pool = { .block_alloc = block_alloc }; + f(&pool); + mem_pool_discard(&pool, 0); +} + +static void t_calloc_100(struct mem_pool *pool) +{ + size_t size = 100; + char *buffer = mem_pool_calloc(pool, 1, size); + for (size_t i = 0; i < size; i++) + check_int(buffer[i], ==, 0); + if (!check(pool->mp_block != NULL)) + return; + check(pool->mp_block->next_free != NULL); + check(pool->mp_block->end != NULL); +} + +int cmd_main(int argc, const char **argv) +{ + TEST(setup_static(t_calloc_100, 1024 * 1024), + "mem_pool_calloc returns 100 zeroed bytes with big block"); + TEST(setup_static(t_calloc_100, 1), + "mem_pool_calloc returns 100 zeroed bytes with tiny block"); + + return test_done(); +} diff --git a/t/unit-tests/t-prio-queue.c b/t/unit-tests/t-prio-queue.c new file mode 100644 index 0000000..7a4e578 --- /dev/null +++ b/t/unit-tests/t-prio-queue.c @@ -0,0 +1,91 @@ +#include "test-lib.h" +#include "prio-queue.h" + +static int intcmp(const void *va, const void *vb, void *data UNUSED) +{ + const int *a = va, *b = vb; + return *a - *b; +} + + +#define MISSING -1 +#define DUMP -2 +#define STACK -3 +#define GET -4 +#define REVERSE -5 + +static int show(int *v) +{ + return v ? *v : MISSING; +} + +static void test_prio_queue(int *input, size_t input_size, + int *result, size_t result_size) +{ + struct prio_queue pq = { intcmp }; + int j = 0; + + for (int i = 0; i < input_size; i++) { + void *peek, *get; + switch(input[i]) { + case GET: + peek = prio_queue_peek(&pq); + get = prio_queue_get(&pq); + if (!check(peek == get)) + return; + if (!check_uint(j, <, result_size)) + break; + if (!check_int(result[j], ==, show(get))) + test_msg(" j: %d", j); + j++; + break; + case DUMP: + while ((peek = prio_queue_peek(&pq))) { + get = prio_queue_get(&pq); + if (!check(peek == get)) + return; + if (!check_uint(j, <, result_size)) + break; + if (!check_int(result[j], ==, show(get))) + test_msg(" j: %d", j); + j++; + } + break; + case STACK: + pq.compare = NULL; + break; + case REVERSE: + prio_queue_reverse(&pq); + break; + default: + prio_queue_put(&pq, &input[i]); + break; + } + } + check_uint(j, ==, result_size); + clear_prio_queue(&pq); +} + +#define TEST_INPUT(input, result) \ + test_prio_queue(input, ARRAY_SIZE(input), result, ARRAY_SIZE(result)) + +int cmd_main(int argc, const char **argv) +{ + TEST(TEST_INPUT(((int []){ 2, 6, 3, 10, 9, 5, 7, 4, 5, 8, 1, DUMP }), + ((int []){ 1, 2, 3, 4, 5, 5, 6, 7, 8, 9, 10 })), + "prio-queue works for basic input"); + TEST(TEST_INPUT(((int []){ 6, 2, 4, GET, 5, 3, GET, GET, 1, DUMP }), + ((int []){ 2, 3, 4, 1, 5, 6 })), + "prio-queue works for mixed put & get commands"); + TEST(TEST_INPUT(((int []){ 1, 2, GET, GET, GET, 1, 2, GET, GET, GET }), + ((int []){ 1, 2, MISSING, 1, 2, MISSING })), + "prio-queue works when queue is empty"); + TEST(TEST_INPUT(((int []){ STACK, 8, 1, 5, 4, 6, 2, 3, DUMP }), + ((int []){ 3, 2, 6, 4, 5, 1, 8 })), + "prio-queue works when used as a LIFO stack"); + TEST(TEST_INPUT(((int []){ STACK, 1, 2, 3, 4, 5, 6, REVERSE, DUMP }), + ((int []){ 1, 2, 3, 4, 5, 6 })), + "prio-queue works when LIFO stack is reversed"); + + return test_done(); +} diff --git a/t/unit-tests/t-strbuf.c b/t/unit-tests/t-strbuf.c new file mode 100644 index 0000000..de434a4 --- /dev/null +++ b/t/unit-tests/t-strbuf.c @@ -0,0 +1,120 @@ +#include "test-lib.h" +#include "strbuf.h" + +/* wrapper that supplies tests with an empty, initialized strbuf */ +static void setup(void (*f)(struct strbuf*, void*), void *data) +{ + struct strbuf buf = STRBUF_INIT; + + f(&buf, data); + strbuf_release(&buf); + check_uint(buf.len, ==, 0); + check_uint(buf.alloc, ==, 0); +} + +/* wrapper that supplies tests with a populated, initialized strbuf */ +static void setup_populated(void (*f)(struct strbuf*, void*), char *init_str, void *data) +{ + struct strbuf buf = STRBUF_INIT; + + strbuf_addstr(&buf, init_str); + check_uint(buf.len, ==, strlen(init_str)); + f(&buf, data); + strbuf_release(&buf); + check_uint(buf.len, ==, 0); + check_uint(buf.alloc, ==, 0); +} + +static int assert_sane_strbuf(struct strbuf *buf) +{ + /* Initialized strbufs should always have a non-NULL buffer */ + if (!check(!!buf->buf)) + return 0; + /* Buffers should always be NUL-terminated */ + if (!check_char(buf->buf[buf->len], ==, '\0')) + return 0; + /* + * Freshly-initialized strbufs may not have a dynamically allocated + * buffer + */ + if (buf->len == 0 && buf->alloc == 0) + return 1; + /* alloc must be at least one byte larger than len */ + return check_uint(buf->len, <, buf->alloc); +} + +static void t_static_init(void) +{ + struct strbuf buf = STRBUF_INIT; + + check_uint(buf.len, ==, 0); + check_uint(buf.alloc, ==, 0); + check_char(buf.buf[0], ==, '\0'); +} + +static void t_dynamic_init(void) +{ + struct strbuf buf; + + strbuf_init(&buf, 1024); + check(assert_sane_strbuf(&buf)); + check_uint(buf.len, ==, 0); + check_uint(buf.alloc, >=, 1024); + check_char(buf.buf[0], ==, '\0'); + strbuf_release(&buf); +} + +static void t_addch(struct strbuf *buf, void *data) +{ + const char *p_ch = data; + const char ch = *p_ch; + size_t orig_alloc = buf->alloc; + size_t orig_len = buf->len; + + if (!check(assert_sane_strbuf(buf))) + return; + strbuf_addch(buf, ch); + if (!check(assert_sane_strbuf(buf))) + return; + if (!(check_uint(buf->len, ==, orig_len + 1) && + check_uint(buf->alloc, >=, orig_alloc))) + return; /* avoid de-referencing buf->buf */ + check_char(buf->buf[buf->len - 1], ==, ch); + check_char(buf->buf[buf->len], ==, '\0'); +} + +static void t_addstr(struct strbuf *buf, void *data) +{ + const char *text = data; + size_t len = strlen(text); + size_t orig_alloc = buf->alloc; + size_t orig_len = buf->len; + + if (!check(assert_sane_strbuf(buf))) + return; + strbuf_addstr(buf, text); + if (!check(assert_sane_strbuf(buf))) + return; + if (!(check_uint(buf->len, ==, orig_len + len) && + check_uint(buf->alloc, >=, orig_alloc) && + check_uint(buf->alloc, >, orig_len + len) && + check_char(buf->buf[orig_len + len], ==, '\0'))) + return; + check_str(buf->buf + orig_len, text); +} + +int cmd_main(int argc, const char **argv) +{ + if (!TEST(t_static_init(), "static initialization works")) + test_skip_all("STRBUF_INIT is broken"); + TEST(t_dynamic_init(), "dynamic initialization works"); + TEST(setup(t_addch, "a"), "strbuf_addch adds char"); + TEST(setup(t_addch, ""), "strbuf_addch adds NUL char"); + TEST(setup_populated(t_addch, "initial value", "a"), + "strbuf_addch appends to initial value"); + TEST(setup(t_addstr, "hello there"), "strbuf_addstr adds string"); + TEST(setup_populated(t_addstr, "initial value", "hello there"), + "strbuf_addstr appends string to initial value"); + + return test_done(); +} diff --git a/t/unit-tests/test-lib.c b/t/unit-tests/test-lib.c new file mode 100644 index 0000000..66d6980 --- /dev/null +++ b/t/unit-tests/test-lib.c @@ -0,0 +1,407 @@ +#include "test-lib.h" + +enum result { + RESULT_NONE, + RESULT_FAILURE, + RESULT_SKIP, + RESULT_SUCCESS, + RESULT_TODO +}; + +static struct { + enum result result; + int count; + unsigned failed :1; + unsigned lazy_plan :1; + unsigned running :1; + unsigned skip_all :1; + unsigned todo :1; +} ctx = { + .lazy_plan = 1, + .result = RESULT_NONE, +}; + +/* + * Visual C interpolates the absolute Windows path for `__FILE__`, + * but we want to see relative paths, as verified by t0080. + * There are other compilers that do the same, and are not for + * Windows. + */ +#include "dir.h" + +static const char *make_relative(const char *location) +{ + static char prefix[] = __FILE__, buf[PATH_MAX], *p; + static size_t prefix_len; + static int need_bs_to_fs = -1; + + /* one-time preparation */ + if (need_bs_to_fs < 0) { + size_t len = strlen(prefix); + char needle[] = "t\\unit-tests\\test-lib.c"; + size_t needle_len = strlen(needle); + + if (len < needle_len) + die("unexpected prefix '%s'", prefix); + + /* + * The path could be relative (t/unit-tests/test-lib.c) + * or full (/home/user/git/t/unit-tests/test-lib.c). + * Check the slash between "t" and "unit-tests". + */ + prefix_len = len - needle_len; + if (prefix[prefix_len + 1] == '/') { + /* Oh, we're not Windows */ + for (size_t i = 0; i < needle_len; i++) + if (needle[i] == '\\') + needle[i] = '/'; + need_bs_to_fs = 0; + } else { + need_bs_to_fs = 1; + } + + /* + * prefix_len == 0 if the compiler gives paths relative + * to the root of the working tree. Otherwise, we want + * to see that we did find the needle[] at a directory + * boundary. Again we rely on that needle[] begins with + * "t" followed by the directory separator. + */ + if (fspathcmp(needle, prefix + prefix_len) || + (prefix_len && prefix[prefix_len - 1] != needle[1])) + die("unexpected suffix of '%s'", prefix); + } + + /* + * Does it not start with the expected prefix? + * Return it as-is without making it worse. + */ + if (prefix_len && fspathncmp(location, prefix, prefix_len)) + return location; + + /* + * If we do not need to munge directory separator, we can return + * the substring at the tail of the location. + */ + if (!need_bs_to_fs) + return location + prefix_len; + + /* convert backslashes to forward slashes */ + strlcpy(buf, location + prefix_len, sizeof(buf)); + for (p = buf; *p; p++) + if (*p == '\\') + *p = '/'; + return buf; +} + +static void msg_with_prefix(const char *prefix, const char *format, va_list ap) +{ + fflush(stderr); + if (prefix) + fprintf(stdout, "%s", prefix); + vprintf(format, ap); /* TODO: handle newlines */ + putc('\n', stdout); + fflush(stdout); +} + +void test_msg(const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + msg_with_prefix("# ", format, ap); + va_end(ap); +} + +void test_plan(int count) +{ + assert(!ctx.running); + + fflush(stderr); + printf("1..%d\n", count); + fflush(stdout); + ctx.lazy_plan = 0; +} + +int test_done(void) +{ + assert(!ctx.running); + + if (ctx.lazy_plan) + test_plan(ctx.count); + + return ctx.failed; +} + +void test_skip(const char *format, ...) +{ + va_list ap; + + assert(ctx.running); + + ctx.result = RESULT_SKIP; + va_start(ap, format); + if (format) + msg_with_prefix("# skipping test - ", format, ap); + va_end(ap); +} + +void test_skip_all(const char *format, ...) +{ + va_list ap; + const char *prefix; + + if (!ctx.count && ctx.lazy_plan) { + /* We have not printed a test plan yet */ + prefix = "1..0 # SKIP "; + ctx.lazy_plan = 0; + } else { + /* We have already printed a test plan */ + prefix = "Bail out! # "; + ctx.failed = 1; + } + ctx.skip_all = 1; + ctx.result = RESULT_SKIP; + va_start(ap, format); + msg_with_prefix(prefix, format, ap); + va_end(ap); +} + +int test__run_begin(void) +{ + assert(!ctx.running); + + ctx.count++; + ctx.result = RESULT_NONE; + ctx.running = 1; + + return ctx.skip_all; +} + +static void print_description(const char *format, va_list ap) +{ + if (format) { + fputs(" - ", stdout); + vprintf(format, ap); + } +} + +int test__run_end(int was_run UNUSED, const char *location, const char *format, ...) +{ + va_list ap; + + assert(ctx.running); + assert(!ctx.todo); + + fflush(stderr); + va_start(ap, format); + if (!ctx.skip_all) { + switch (ctx.result) { + case RESULT_SUCCESS: + printf("ok %d", ctx.count); + print_description(format, ap); + break; + + case RESULT_FAILURE: + printf("not ok %d", ctx.count); + print_description(format, ap); + break; + + case RESULT_TODO: + printf("not ok %d", ctx.count); + print_description(format, ap); + printf(" # TODO"); + break; + + case RESULT_SKIP: + printf("ok %d", ctx.count); + print_description(format, ap); + printf(" # SKIP"); + break; + + case RESULT_NONE: + test_msg("BUG: test has no checks at %s", + make_relative(location)); + printf("not ok %d", ctx.count); + print_description(format, ap); + ctx.result = RESULT_FAILURE; + break; + } + } + va_end(ap); + ctx.running = 0; + if (ctx.skip_all) + return 1; + putc('\n', stdout); + fflush(stdout); + ctx.failed |= ctx.result == RESULT_FAILURE; + + return ctx.result != RESULT_FAILURE; +} + +static void test_fail(void) +{ + assert(ctx.result != RESULT_SKIP); + + ctx.result = RESULT_FAILURE; +} + +static void test_pass(void) +{ + assert(ctx.result != RESULT_SKIP); + + if (ctx.result == RESULT_NONE) + ctx.result = RESULT_SUCCESS; +} + +static void test_todo(void) +{ + assert(ctx.result != RESULT_SKIP); + + if (ctx.result != RESULT_FAILURE) + ctx.result = RESULT_TODO; +} + +int test_assert(const char *location, const char *check, int ok) +{ + assert(ctx.running); + + if (ctx.result == RESULT_SKIP) { + test_msg("skipping check '%s' at %s", check, + make_relative(location)); + return 1; + } + if (!ctx.todo) { + if (ok) { + test_pass(); + } else { + test_msg("check \"%s\" failed at %s", check, + make_relative(location)); + test_fail(); + } + } + + return !!ok; +} + +void test__todo_begin(void) +{ + assert(ctx.running); + assert(!ctx.todo); + + ctx.todo = 1; +} + +int test__todo_end(const char *location, const char *check, int res) +{ + assert(ctx.running); + assert(ctx.todo); + + ctx.todo = 0; + if (ctx.result == RESULT_SKIP) + return 1; + if (res) { + test_msg("todo check '%s' succeeded at %s", check, + make_relative(location)); + test_fail(); + } else { + test_todo(); + } + + return !res; +} + +int check_bool_loc(const char *loc, const char *check, int ok) +{ + return test_assert(loc, check, ok); +} + +union test__tmp test__tmp[2]; + +int check_int_loc(const char *loc, const char *check, int ok, + intmax_t a, intmax_t b) +{ + int ret = test_assert(loc, check, ok); + + if (!ret) { + test_msg(" left: %"PRIdMAX, a); + test_msg(" right: %"PRIdMAX, b); + } + + return ret; +} + +int check_uint_loc(const char *loc, const char *check, int ok, + uintmax_t a, uintmax_t b) +{ + int ret = test_assert(loc, check, ok); + + if (!ret) { + test_msg(" left: %"PRIuMAX, a); + test_msg(" right: %"PRIuMAX, b); + } + + return ret; +} + +static void print_one_char(char ch, char quote) +{ + if ((unsigned char)ch < 0x20u || ch == 0x7f) { + /* TODO: improve handling of \a, \b, \f ... */ + printf("\\%03o", (unsigned char)ch); + } else { + if (ch == '\\' || ch == quote) + putc('\\', stdout); + putc(ch, stdout); + } +} + +static void print_char(const char *prefix, char ch) +{ + printf("# %s: '", prefix); + print_one_char(ch, '\''); + fputs("'\n", stdout); +} + +int check_char_loc(const char *loc, const char *check, int ok, char a, char b) +{ + int ret = test_assert(loc, check, ok); + + if (!ret) { + fflush(stderr); + print_char(" left", a); + print_char(" right", b); + fflush(stdout); + } + + return ret; +} + +static void print_str(const char *prefix, const char *str) +{ + printf("# %s: ", prefix); + if (!str) { + fputs("NULL\n", stdout); + } else { + putc('"', stdout); + while (*str) + print_one_char(*str++, '"'); + fputs("\"\n", stdout); + } +} + +int check_str_loc(const char *loc, const char *check, + const char *a, const char *b) +{ + int ok = (!a && !b) || (a && b && !strcmp(a, b)); + int ret = test_assert(loc, check, ok); + + if (!ret) { + fflush(stderr); + print_str(" left", a); + print_str(" right", b); + fflush(stdout); + } + + return ret; +} diff --git a/t/unit-tests/test-lib.h b/t/unit-tests/test-lib.h new file mode 100644 index 0000000..a8f07ae --- /dev/null +++ b/t/unit-tests/test-lib.h @@ -0,0 +1,149 @@ +#ifndef TEST_LIB_H +#define TEST_LIB_H + +#include "git-compat-util.h" + +/* + * Run a test function, returns 1 if the test succeeds, 0 if it + * fails. If test_skip_all() has been called then the test will not be + * run. The description for each test should be unique. For example: + * + * TEST(test_something(arg1, arg2), "something %d %d", arg1, arg2) + */ +#define TEST(t, ...) \ + test__run_end(test__run_begin() ? 0 : (t, 1), \ + TEST_LOCATION(), __VA_ARGS__) + +/* + * Print a test plan, should be called before any tests. If the number + * of tests is not known in advance test_done() will automatically + * print a plan at the end of the test program. + */ +void test_plan(int count); + +/* + * test_done() must be called at the end of main(). It will print the + * plan if plan() was not called at the beginning of the test program + * and returns the exit code for the test program. + */ +int test_done(void); + +/* Skip the current test. */ +__attribute__((format (printf, 1, 2))) +void test_skip(const char *format, ...); + +/* Skip all remaining tests. */ +__attribute__((format (printf, 1, 2))) +void test_skip_all(const char *format, ...); + +/* Print a diagnostic message to stdout. */ +__attribute__((format (printf, 1, 2))) +void test_msg(const char *format, ...); + +/* + * Test checks are built around test_assert(). checks return 1 on + * success, 0 on failure. If any check fails then the test will fail. To + * create a custom check define a function that wraps test_assert() and + * a macro to wrap that function to provide a source location and + * stringified arguments. Custom checks that take pointer arguments + * should be careful to check that they are non-NULL before + * dereferencing them. For example: + * + * static int check_oid_loc(const char *loc, const char *check, + * struct object_id *a, struct object_id *b) + * { + * int res = test_assert(loc, check, a && b && oideq(a, b)); + * + * if (!res) { + * test_msg(" left: %s", a ? oid_to_hex(a) : "NULL"; + * test_msg(" right: %s", b ? oid_to_hex(a) : "NULL"; + * + * } + * return res; + * } + * + * #define check_oid(a, b) \ + * check_oid_loc(TEST_LOCATION(), "oideq("#a", "#b")", a, b) + */ +int test_assert(const char *location, const char *check, int ok); + +/* Helper macro to pass the location to checks */ +#define TEST_LOCATION() TEST__MAKE_LOCATION(__LINE__) + +/* Check a boolean condition. */ +#define check(x) \ + check_bool_loc(TEST_LOCATION(), #x, x) +int check_bool_loc(const char *loc, const char *check, int ok); + +/* + * Compare two integers. Prints a message with the two values if the + * comparison fails. NB this is not thread safe. + */ +#define check_int(a, op, b) \ + (test__tmp[0].i = (a), test__tmp[1].i = (b), \ + check_int_loc(TEST_LOCATION(), #a" "#op" "#b, \ + test__tmp[0].i op test__tmp[1].i, \ + test__tmp[0].i, test__tmp[1].i)) +int check_int_loc(const char *loc, const char *check, int ok, + intmax_t a, intmax_t b); + +/* + * Compare two unsigned integers. Prints a message with the two values + * if the comparison fails. NB this is not thread safe. + */ +#define check_uint(a, op, b) \ + (test__tmp[0].u = (a), test__tmp[1].u = (b), \ + check_uint_loc(TEST_LOCATION(), #a" "#op" "#b, \ + test__tmp[0].u op test__tmp[1].u, \ + test__tmp[0].u, test__tmp[1].u)) +int check_uint_loc(const char *loc, const char *check, int ok, + uintmax_t a, uintmax_t b); + +/* + * Compare two chars. Prints a message with the two values if the + * comparison fails. NB this is not thread safe. + */ +#define check_char(a, op, b) \ + (test__tmp[0].c = (a), test__tmp[1].c = (b), \ + check_char_loc(TEST_LOCATION(), #a" "#op" "#b, \ + test__tmp[0].c op test__tmp[1].c, \ + test__tmp[0].c, test__tmp[1].c)) +int check_char_loc(const char *loc, const char *check, int ok, + char a, char b); + +/* Check whether two strings are equal. */ +#define check_str(a, b) \ + check_str_loc(TEST_LOCATION(), "!strcmp("#a", "#b")", a, b) +int check_str_loc(const char *loc, const char *check, + const char *a, const char *b); + +/* + * Wrap a check that is known to fail. If the check succeeds then the + * test will fail. Returns 1 if the check fails, 0 if it + * succeeds. For example: + * + * TEST_TODO(check(0)); + */ +#define TEST_TODO(check) \ + (test__todo_begin(), test__todo_end(TEST_LOCATION(), #check, check)) + +/* Private helpers */ + +#define TEST__STR(x) #x +#define TEST__MAKE_LOCATION(line) __FILE__ ":" TEST__STR(line) + +union test__tmp { + intmax_t i; + uintmax_t u; + char c; +}; + +extern union test__tmp test__tmp[2]; + +int test__run_begin(void); +__attribute__((format (printf, 3, 4))) +int test__run_end(int, const char *, const char *, ...); +void test__todo_begin(void); +int test__todo_end(const char *, const char *, int); + +#endif /* TEST_LIB_H */ diff --git a/t/valgrind/valgrind.sh b/t/valgrind/valgrind.sh index 669ebaf..3c8ee19 100755 --- a/t/valgrind/valgrind.sh +++ b/t/valgrind/valgrind.sh @@ -23,7 +23,7 @@ memcheck) VALGRIND_MAJOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*\([0-9]*\)') VALGRIND_MINOR=$(expr "$VALGRIND_VERSION" : '[^0-9]*[0-9]*\.\([0-9]*\)') test 3 -gt "$VALGRIND_MAJOR" || - test 3 -eq "$VALGRIND_MAJOR" -a 4 -gt "$VALGRIND_MINOR" || + { test 3 -eq "$VALGRIND_MAJOR" && test 4 -gt "$VALGRIND_MINOR"; } || TOOL_OPTIONS="$TOOL_OPTIONS --track-origins=yes" ;; *) @@ -50,15 +50,17 @@ static VOLATILE_LIST_HEAD(tempfile_list); -static void remove_template_directory(struct tempfile *tempfile, +static int remove_template_directory(struct tempfile *tempfile, int in_signal_handler) { if (tempfile->directory) { if (in_signal_handler) - rmdir(tempfile->directory); + return rmdir(tempfile->directory); else - rmdir_or_warn(tempfile->directory); + return rmdir_or_warn(tempfile->directory); } + + return 0; } static void remove_tempfiles(int in_signal_handler) @@ -353,16 +355,19 @@ int rename_tempfile(struct tempfile **tempfile_p, const char *path) return 0; } -void delete_tempfile(struct tempfile **tempfile_p) +int delete_tempfile(struct tempfile **tempfile_p) { struct tempfile *tempfile = *tempfile_p; + int err = 0; if (!is_tempfile_active(tempfile)) - return; + return 0; - close_tempfile_gently(tempfile); - unlink_or_warn(tempfile->filename.buf); - remove_template_directory(tempfile, 0); + err |= close_tempfile_gently(tempfile); + err |= unlink_or_warn(tempfile->filename.buf); + err |= remove_template_directory(tempfile, 0); deactivate_tempfile(tempfile); *tempfile_p = NULL; + + return err ? -1 : 0; } @@ -269,7 +269,7 @@ int reopen_tempfile(struct tempfile *tempfile); * `delete_tempfile()` for a `tempfile` object that has already been * deleted or renamed. */ -void delete_tempfile(struct tempfile **tempfile_p); +int delete_tempfile(struct tempfile **tempfile_p); /* * Close the file descriptor and/or file pointer if they are still diff --git a/templates/hooks--pre-commit.sample b/templates/hooks--pre-commit.sample index e144712..29ed5ee 100755 --- a/templates/hooks--pre-commit.sample +++ b/templates/hooks--pre-commit.sample @@ -28,7 +28,7 @@ if [ "$allownonascii" != "true" ] && # Note that the use of brackets around a tr range is ok here, (it's # even required, for portability to Solaris 10's /usr/bin/tr), since # the square bracket bytes happen to fall in the designated range. - test $(git diff --cached --name-only --diff-filter=A -z $against | + test $(git diff-index --cached --name-only --diff-filter=A -z $against | LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 then cat <<\EOF diff --git a/tmp-objdir.c b/tmp-objdir.c index 5f9074a..3509258 100644 --- a/tmp-objdir.c +++ b/tmp-objdir.c @@ -6,7 +6,6 @@ #include "environment.h" #include "object-file.h" #include "path.h" -#include "sigchain.h" #include "string-list.h" #include "strbuf.h" #include "strvec.h" @@ -18,7 +18,7 @@ * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License - * along with this program; if not, see <http://www.gnu.org/licenses/>. + * along with this program; if not, see <https://www.gnu.org/licenses/>. */ #include "git-compat-util.h" @@ -1,12 +1,9 @@ #include "git-compat-util.h" #include "config.h" -#include "json-writer.h" -#include "quote.h" #include "repository.h" #include "run-command.h" #include "sigchain.h" #include "thread-utils.h" -#include "version.h" #include "trace.h" #include "trace2.h" #include "trace2/tr2_cfg.h" @@ -20,6 +17,7 @@ #include "trace2/tr2_tmr.h" static int trace2_enabled; +static int trace2_redact = 1; static int tr2_next_child_id; /* modify under lock */ static int tr2_next_exec_id; /* modify under lock */ @@ -227,6 +225,8 @@ void trace2_initialize_fl(const char *file, int line) if (!tr2_tgt_want_builtins()) return; trace2_enabled = 1; + if (!git_env_bool("GIT_TRACE2_REDACT", 1)) + trace2_redact = 0; tr2_sid_get(); @@ -247,12 +247,93 @@ int trace2_is_enabled(void) return trace2_enabled; } +/* + * Redacts an argument, i.e. ensures that no password in + * https://user:password@host/-style URLs is logged. + * + * Returns the original if nothing needed to be redacted. + * Returns a pointer that needs to be `free()`d otherwise. + */ +static const char *redact_arg(const char *arg) +{ + const char *p, *colon; + size_t at; + + if (!trace2_redact || + (!skip_prefix(arg, "https://", &p) && + !skip_prefix(arg, "http://", &p))) + return arg; + + at = strcspn(p, "@/"); + if (p[at] != '@') + return arg; + + colon = memchr(p, ':', at); + if (!colon) + return arg; + + return xstrfmt("%.*s:<REDACTED>%s", (int)(colon - arg), arg, p + at); +} + +/* + * Redacts arguments in an argument list. + * + * Returns the original if nothing needed to be redacted. + * Otherwise, returns a new array that needs to be released + * via `free_redacted_argv()`. + */ +static const char **redact_argv(const char **argv) +{ + int i, j; + const char *redacted = NULL; + const char **ret; + + if (!trace2_redact) + return argv; + + for (i = 0; argv[i]; i++) + if ((redacted = redact_arg(argv[i])) != argv[i]) + break; + + if (!argv[i]) + return argv; + + for (j = 0; argv[j]; j++) + ; /* keep counting */ + + ALLOC_ARRAY(ret, j + 1); + ret[j] = NULL; + + for (j = 0; j < i; j++) + ret[j] = argv[j]; + ret[i] = redacted; + for (++i; argv[i]; i++) { + redacted = redact_arg(argv[i]); + ret[i] = redacted ? redacted : argv[i]; + } + + return ret; +} + +static void free_redacted_argv(const char **redacted, const char **argv) +{ + int i; + + if (redacted != argv) { + for (i = 0; argv[i]; i++) + if (redacted[i] != argv[i]) + free((void *)redacted[i]); + free((void *)redacted); + } +} + void trace2_cmd_start_fl(const char *file, int line, const char **argv) { struct tr2_tgt *tgt_j; int j; uint64_t us_now; uint64_t us_elapsed_absolute; + const char **redacted; if (!trace2_enabled) return; @@ -260,10 +341,14 @@ void trace2_cmd_start_fl(const char *file, int line, const char **argv) us_now = getnanotime() / 1000; us_elapsed_absolute = tr2tls_absolute_elapsed(us_now); + redacted = redact_argv(argv); + for_each_wanted_builtin (j, tgt_j) if (tgt_j->pfn_start_fl) tgt_j->pfn_start_fl(file, line, us_elapsed_absolute, - argv); + redacted); + + free_redacted_argv(redacted, argv); } void trace2_cmd_exit_fl(const char *file, int line, int code) @@ -348,6 +433,9 @@ void trace2_cmd_name_fl(const char *file, int line, const char *name) for_each_wanted_builtin (j, tgt_j) if (tgt_j->pfn_command_name_fl) tgt_j->pfn_command_name_fl(file, line, name, hierarchy); + + trace2_cmd_list_config(); + trace2_cmd_list_env_vars(); } void trace2_cmd_mode_fl(const char *file, int line, const char *mode) @@ -379,17 +467,29 @@ void trace2_cmd_alias_fl(const char *file, int line, const char *alias, void trace2_cmd_list_config_fl(const char *file, int line) { + static int emitted = 0; + if (!trace2_enabled) return; + if (emitted) + return; + emitted = 1; + tr2_cfg_list_config_fl(file, line); } void trace2_cmd_list_env_vars_fl(const char *file, int line) { + static int emitted = 0; + if (!trace2_enabled) return; + if (emitted) + return; + emitted = 1; + tr2_list_env_vars_fl(file, line); } @@ -409,6 +509,7 @@ void trace2_child_start_fl(const char *file, int line, int j; uint64_t us_now; uint64_t us_elapsed_absolute; + const char **orig_argv = cmd->args.v; if (!trace2_enabled) return; @@ -419,10 +520,24 @@ void trace2_child_start_fl(const char *file, int line, cmd->trace2_child_id = tr2tls_locked_increment(&tr2_next_child_id); cmd->trace2_child_us_start = us_now; + /* + * The `pfn_child_start_fl` API takes a `struct child_process` + * rather than a simple `argv` for the child because some + * targets make use of the additional context bits/values. So + * temporarily replace the original argv (inside the `strvec`) + * with a possibly redacted version. + */ + cmd->args.v = redact_argv(orig_argv); + for_each_wanted_builtin (j, tgt_j) if (tgt_j->pfn_child_start_fl) tgt_j->pfn_child_start_fl(file, line, us_elapsed_absolute, cmd); + + if (cmd->args.v != orig_argv) { + free_redacted_argv(cmd->args.v, orig_argv); + cmd->args.v = orig_argv; + } } void trace2_child_exit_fl(const char *file, int line, struct child_process *cmd, @@ -493,6 +608,7 @@ int trace2_exec_fl(const char *file, int line, const char *exe, int exec_id; uint64_t us_now; uint64_t us_elapsed_absolute; + const char **redacted; if (!trace2_enabled) return -1; @@ -502,10 +618,14 @@ int trace2_exec_fl(const char *file, int line, const char *exe, exec_id = tr2tls_locked_increment(&tr2_next_exec_id); + redacted = redact_argv(argv); + for_each_wanted_builtin (j, tgt_j) if (tgt_j->pfn_exec_fl) tgt_j->pfn_exec_fl(file, line, us_elapsed_absolute, - exec_id, exe, argv); + exec_id, exe, redacted); + + free_redacted_argv(redacted, argv); return exec_id; } @@ -637,13 +757,19 @@ void trace2_def_param_fl(const char *file, int line, const char *param, { struct tr2_tgt *tgt_j; int j; + const char *redacted; if (!trace2_enabled) return; + redacted = redact_arg(value); + for_each_wanted_builtin (j, tgt_j) if (tgt_j->pfn_param_fl) - tgt_j->pfn_param_fl(file, line, param, value, kvi); + tgt_j->pfn_param_fl(file, line, param, redacted, kvi); + + if (redacted != value) + free((void *)redacted); } void trace2_def_repo_fl(const char *file, int line, struct repository *repo) @@ -337,8 +337,8 @@ struct key_value_info; void trace2_def_param_fl(const char *file, int line, const char *param, const char *value, const struct key_value_info *kvi); -#define trace2_def_param(param, value) \ - trace2_def_param_fl(__FILE__, __LINE__, (param), (value)) +#define trace2_def_param(param, value, kvi) \ + trace2_def_param_fl(__FILE__, __LINE__, (param), (value), (kvi)) /* * Tell trace2 about a newly instantiated repo object and assign diff --git a/trace2/tr2_ctr.c b/trace2/tr2_ctr.c index 87cf903..d3a3371 100644 --- a/trace2/tr2_ctr.c +++ b/trace2/tr2_ctr.c @@ -1,5 +1,4 @@ #include "git-compat-util.h" -#include "thread-utils.h" #include "trace2/tr2_tgt.h" #include "trace2/tr2_tls.h" #include "trace2/tr2_ctr.h" diff --git a/trace2/tr2_sysenv.c b/trace2/tr2_sysenv.c index d3ecac2..048cdd5 100644 --- a/trace2/tr2_sysenv.c +++ b/trace2/tr2_sysenv.c @@ -68,6 +68,8 @@ static int tr2_sysenv_cb(const char *key, const char *value, for (k = 0; k < ARRAY_SIZE(tr2_sysenv_settings); k++) { if (!strcmp(key, tr2_sysenv_settings[k].git_config_name)) { + if (!value) + return config_error_nonbool(key); free(tr2_sysenv_settings[k].value); tr2_sysenv_settings[k].value = xstrdup(value); return 0; diff --git a/trace2/tr2_tgt_normal.c b/trace2/tr2_tgt_normal.c index 38d5ebd..baef48a 100644 --- a/trace2/tr2_tgt_normal.c +++ b/trace2/tr2_tgt_normal.c @@ -2,6 +2,7 @@ #include "config.h" #include "repository.h" #include "run-command.h" +#include "strbuf.h" #include "quote.h" #include "version.h" #include "trace2/tr2_dst.h" diff --git a/trace2/tr2_tls.c b/trace2/tr2_tls.c index 601c9e5..4f75392 100644 --- a/trace2/tr2_tls.c +++ b/trace2/tr2_tls.c @@ -1,4 +1,5 @@ #include "git-compat-util.h" +#include "strbuf.h" #include "thread-utils.h" #include "trace.h" #include "trace2/tr2_tls.h" diff --git a/trace2/tr2_tls.h b/trace2/tr2_tls.h index f904980..3dfe655 100644 --- a/trace2/tr2_tls.h +++ b/trace2/tr2_tls.h @@ -1,7 +1,6 @@ #ifndef TR2_TLS_H #define TR2_TLS_H -#include "strbuf.h" #include "trace2/tr2_ctr.h" #include "trace2/tr2_tmr.h" diff --git a/trace2/tr2_tmr.c b/trace2/tr2_tmr.c index 31d0e4d..51f564b 100644 --- a/trace2/tr2_tmr.c +++ b/trace2/tr2_tmr.c @@ -1,5 +1,4 @@ #include "git-compat-util.h" -#include "thread-utils.h" #include "trace2/tr2_tgt.h" #include "trace2/tr2_tls.h" #include "trace2/tr2_tmr.h" @@ -5,7 +5,6 @@ #include "string-list.h" #include "run-command.h" #include "commit.h" -#include "tempfile.h" #include "trailer.h" #include "list.h" /* @@ -145,37 +144,6 @@ static char last_non_space_char(const char *s) return '\0'; } -static void print_tok_val(FILE *outfile, const char *tok, const char *val) -{ - char c; - - if (!tok) { - fprintf(outfile, "%s\n", val); - return; - } - - c = last_non_space_char(tok); - if (!c) - return; - if (strchr(separators, c)) - fprintf(outfile, "%s%s\n", tok, val); - else - fprintf(outfile, "%s%c %s\n", tok, separators[0], val); -} - -static void print_all(FILE *outfile, struct list_head *head, - const struct process_trailer_options *opts) -{ - struct list_head *pos; - struct trailer_item *item; - list_for_each(pos, head) { - item = list_entry(pos, struct trailer_item, list); - if ((!opts->trim_empty || strlen(item->value) > 0) && - (!opts->only_trailers || item->token)) - print_tok_val(outfile, item->token, item->value); - } -} - static struct trailer_item *trailer_from_arg(struct arg_item *arg_tok) { struct trailer_item *new_item = xcalloc(1, sizeof(*new_item)); @@ -366,8 +334,8 @@ static int find_same_and_apply_arg(struct list_head *head, return 0; } -static void process_trailers_lists(struct list_head *head, - struct list_head *arg_head) +void process_trailers_lists(struct list_head *head, + struct list_head *arg_head) { struct list_head *pos, *p; struct arg_item *arg_tok; @@ -507,6 +475,8 @@ static int git_trailer_default_config(const char *conf_key, const char *value, warning(_("unknown value '%s' for key '%s'"), value, conf_key); } else if (!strcmp(trailer_item, "separators")) { + if (!value) + return config_error_nonbool(conf_key); separators = xstrdup(value); } } @@ -551,16 +521,22 @@ static int git_trailer_config(const char *conf_key, const char *value, case TRAILER_KEY: if (conf->key) warning(_("more than one %s"), conf_key); + if (!value) + return config_error_nonbool(conf_key); conf->key = xstrdup(value); break; case TRAILER_COMMAND: if (conf->command) warning(_("more than one %s"), conf_key); + if (!value) + return config_error_nonbool(conf_key); conf->command = xstrdup(value); break; case TRAILER_CMD: if (conf->cmd) warning(_("more than one %s"), conf_key); + if (!value) + return config_error_nonbool(conf_key); conf->cmd = xstrdup(value); break; case TRAILER_WHERE: @@ -581,7 +557,7 @@ static int git_trailer_config(const char *conf_key, const char *value, return 0; } -static void ensure_configured(void) +void trailer_config_init(void) { if (configured) return; @@ -711,7 +687,7 @@ static void add_arg_item(struct list_head *arg_head, char *tok, char *val, list_add_tail(&new_item->list, arg_head); } -static void parse_trailers_from_config(struct list_head *config_head) +void parse_trailers_from_config(struct list_head *config_head) { struct arg_item *item; struct list_head *pos; @@ -727,8 +703,8 @@ static void parse_trailers_from_config(struct list_head *config_head) } } -static void parse_trailers_from_command_line_args(struct list_head *arg_head, - struct list_head *new_trailer_head) +void parse_trailers_from_command_line_args(struct list_head *arg_head, + struct list_head *new_trailer_head) { struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; @@ -767,17 +743,6 @@ static void parse_trailers_from_command_line_args(struct list_head *arg_head, free(cl_separators); } -static void read_input_file(struct strbuf *sb, const char *file) -{ - if (file) { - if (strbuf_read_file(sb, file, 0) < 0) - die_errno(_("could not read input file '%s'"), file); - } else { - if (strbuf_read(sb, fileno(stdin), 0) < 0) - die_errno(_("could not read from stdin")); - } -} - static const char *next_line(const char *str) { const char *nl = strchrnul(str, '\n'); @@ -809,28 +774,55 @@ static ssize_t last_line(const char *buf, size_t len) } /* - * Return the position of the start of the patch or the length of str if there - * is no patch in the message. + * Find the end of the log message as an offset from the start of the input + * (where callers of this function are interested in looking for a trailers + * block in the same input). We have to consider two categories of content that + * can come at the end of the input which we want to ignore (because they don't + * belong in the log message): + * + * (1) the "patch part" which begins with a "---" divider and has patch + * information (like the output of git-format-patch), and + * + * (2) any trailing comment lines, blank lines like in the output of "git + * commit -v", or stuff below the "cut" (scissor) line. + * + * As a formula, the situation looks like this: + * + * INPUT = LOG MESSAGE + IGNORED + * + * where IGNORED can be either of the two categories described above. It may be + * that there is nothing to ignore. Now it may be the case that the LOG MESSAGE + * contains a trailer block, but that's not the concern of this function. */ -static size_t find_patch_start(const char *str) +static size_t find_end_of_log_message(const char *input, int no_divider) { + size_t end; const char *s; - for (s = str; *s; s = next_line(s)) { - const char *v; + /* Assume the naive end of the input is already what we want. */ + end = strlen(input); + + /* Optionally skip over any patch part ("---" line and below). */ + if (!no_divider) { + for (s = input; *s; s = next_line(s)) { + const char *v; - if (skip_prefix(s, "---", &v) && isspace(*v)) - return s - str; + if (skip_prefix(s, "---", &v) && isspace(*v)) { + end = s - input; + break; + } + } } - return s - str; + /* Skip over other ignorable bits. */ + return end - ignored_log_message_bytes(input, end); } /* * Return the position of the first trailer line or len if there are no * trailers. */ -static size_t find_trailer_start(const char *buf, size_t len) +static size_t find_trailer_block_start(const char *buf, size_t len) { const char *s; ssize_t end_of_title, l; @@ -847,7 +839,7 @@ static size_t find_trailer_start(const char *buf, size_t len) /* The first paragraph is the title and cannot be trailers */ for (s = buf; s < buf + len; s = next_line(s)) { - if (s[0] == comment_line_char) + if (starts_with_mem(s, buf + len - s, comment_line_str)) continue; if (is_blank_line(s)) break; @@ -867,7 +859,7 @@ static size_t find_trailer_start(const char *buf, size_t len) const char **p; ssize_t separator_pos; - if (bol[0] == comment_line_char) { + if (starts_with_mem(bol, buf + len - bol, comment_line_str)) { non_trailer_lines += possible_continuation_lines; possible_continuation_lines = 0; continue; @@ -925,12 +917,6 @@ continue_outer_loop: return len; } -/* Return the position of the end of the trailers. */ -static size_t find_trailer_end(const char *buf, size_t len) -{ - return len - ignore_non_trailer(buf, len); -} - static int ends_with_blank_line(const char *buf, size_t len) { ssize_t ll = last_line(buf, len); @@ -970,21 +956,21 @@ static void unfold_value(struct strbuf *val) * Parse trailers in "str", populating the trailer info and "head" * linked list structure. */ -static void parse_trailers(struct trailer_info *info, - const char *str, - struct list_head *head, - const struct process_trailer_options *opts) +void parse_trailers(const struct process_trailer_options *opts, + struct trailer_info *info, + const char *str, + struct list_head *head) { struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; size_t i; - trailer_info_get(info, str, opts); + trailer_info_get(opts, str, info); for (i = 0; i < info->trailer_nr; i++) { int separator_pos; char *trailer = info->trailers[i]; - if (trailer[0] == comment_line_char) + if (starts_with(trailer, comment_line_str)) continue; separator_pos = find_separator(trailer, separators); if (separator_pos >= 1) { @@ -1005,120 +991,32 @@ static void parse_trailers(struct trailer_info *info, } } -static void free_all(struct list_head *head) +void free_trailers(struct list_head *trailers) { struct list_head *pos, *p; - list_for_each_safe(pos, p, head) { + list_for_each_safe(pos, p, trailers) { list_del(pos); free_trailer_item(list_entry(pos, struct trailer_item, list)); } } -static struct tempfile *trailers_tempfile; - -static FILE *create_in_place_tempfile(const char *file) +void trailer_info_get(const struct process_trailer_options *opts, + const char *str, + struct trailer_info *info) { - struct stat st; - struct strbuf filename_template = STRBUF_INIT; - const char *tail; - FILE *outfile; - - if (stat(file, &st)) - die_errno(_("could not stat %s"), file); - if (!S_ISREG(st.st_mode)) - die(_("file %s is not a regular file"), file); - if (!(st.st_mode & S_IWUSR)) - die(_("file %s is not writable by user"), file); - - /* Create temporary file in the same directory as the original */ - tail = strrchr(file, '/'); - if (tail) - strbuf_add(&filename_template, file, tail - file + 1); - strbuf_addstr(&filename_template, "git-interpret-trailers-XXXXXX"); - - trailers_tempfile = xmks_tempfile_m(filename_template.buf, st.st_mode); - strbuf_release(&filename_template); - outfile = fdopen_tempfile(trailers_tempfile, "w"); - if (!outfile) - die_errno(_("could not open temporary file")); - - return outfile; -} - -void process_trailers(const char *file, - const struct process_trailer_options *opts, - struct list_head *new_trailer_head) -{ - LIST_HEAD(head); - struct strbuf sb = STRBUF_INIT; - struct trailer_info info; - size_t trailer_end; - FILE *outfile = stdout; - - ensure_configured(); - - read_input_file(&sb, file); - - if (opts->in_place) - outfile = create_in_place_tempfile(file); - - parse_trailers(&info, sb.buf, &head, opts); - trailer_end = info.trailer_end - sb.buf; - - /* Print the lines before the trailers */ - if (!opts->only_trailers) - fwrite(sb.buf, 1, info.trailer_start - sb.buf, outfile); - - if (!opts->only_trailers && !info.blank_line_before_trailer) - fprintf(outfile, "\n"); - - - if (!opts->only_input) { - LIST_HEAD(config_head); - LIST_HEAD(arg_head); - parse_trailers_from_config(&config_head); - parse_trailers_from_command_line_args(&arg_head, new_trailer_head); - list_splice(&config_head, &arg_head); - process_trailers_lists(&head, &arg_head); - } - - print_all(outfile, &head, opts); - - free_all(&head); - trailer_info_release(&info); - - /* Print the lines after the trailers as is */ - if (!opts->only_trailers) - fwrite(sb.buf + trailer_end, 1, sb.len - trailer_end, outfile); - - if (opts->in_place) - if (rename_tempfile(&trailers_tempfile, file)) - die_errno(_("could not rename temporary file to %s"), file); - - strbuf_release(&sb); -} - -void trailer_info_get(struct trailer_info *info, const char *str, - const struct process_trailer_options *opts) -{ - int patch_start, trailer_end, trailer_start; + size_t end_of_log_message = 0, trailer_block_start = 0; struct strbuf **trailer_lines, **ptr; char **trailer_strings = NULL; size_t nr = 0, alloc = 0; char **last = NULL; - ensure_configured(); + trailer_config_init(); - if (opts->no_divider) - patch_start = strlen(str); - else - patch_start = find_patch_start(str); + end_of_log_message = find_end_of_log_message(str, opts->no_divider); + trailer_block_start = find_trailer_block_start(str, end_of_log_message); - trailer_end = find_trailer_end(str, patch_start); - trailer_start = find_trailer_start(str, trailer_end); - - trailer_lines = strbuf_split_buf(str + trailer_start, - trailer_end - trailer_start, + trailer_lines = strbuf_split_buf(str + trailer_block_start, + end_of_log_message - trailer_block_start, '\n', 0); for (ptr = trailer_lines; *ptr; ptr++) { @@ -1139,9 +1037,9 @@ void trailer_info_get(struct trailer_info *info, const char *str, strbuf_list_free(trailer_lines); info->blank_line_before_trailer = ends_with_blank_line(str, - trailer_start); - info->trailer_start = str + trailer_start; - info->trailer_end = str + trailer_end; + trailer_block_start); + info->trailer_block_start = trailer_block_start; + info->trailer_block_end = end_of_log_message; info->trailers = trailer_strings; info->trailer_nr = nr; } @@ -1154,35 +1052,32 @@ void trailer_info_release(struct trailer_info *info) free(info->trailers); } -static void format_trailer_info(struct strbuf *out, - const struct trailer_info *info, - const struct process_trailer_options *opts) +void format_trailers(const struct process_trailer_options *opts, + struct list_head *trailers, + struct strbuf *out) { size_t origlen = out->len; - size_t i; - - /* If we want the whole block untouched, we can take the fast path. */ - if (!opts->only_trailers && !opts->unfold && !opts->filter && - !opts->separator && !opts->key_only && !opts->value_only && - !opts->key_value_separator) { - strbuf_add(out, info->trailer_start, - info->trailer_end - info->trailer_start); - return; - } - - for (i = 0; i < info->trailer_nr; i++) { - char *trailer = info->trailers[i]; - ssize_t separator_pos = find_separator(trailer, separators); + struct list_head *pos; + struct trailer_item *item; - if (separator_pos >= 1) { + list_for_each(pos, trailers) { + item = list_entry(pos, struct trailer_item, list); + if (item->token) { struct strbuf tok = STRBUF_INIT; struct strbuf val = STRBUF_INIT; + strbuf_addstr(&tok, item->token); + strbuf_addstr(&val, item->value); + + /* + * Skip key/value pairs where the value was empty. This + * can happen from trailers specified without a + * separator, like `--trailer "Reviewed-by"` (no + * corresponding value). + */ + if (opts->trim_empty && !strlen(item->value)) + continue; - parse_trailer(&tok, &val, NULL, trailer, separator_pos); if (!opts->filter || opts->filter(&tok, opts->filter_data)) { - if (opts->unfold) - unfold_value(&val); - if (opts->separator && out->len != origlen) strbuf_addbuf(out, opts->separator); if (!opts->value_only) @@ -1190,8 +1085,11 @@ static void format_trailer_info(struct strbuf *out, if (!opts->key_only && !opts->value_only) { if (opts->key_value_separator) strbuf_addbuf(out, opts->key_value_separator); - else - strbuf_addstr(out, ": "); + else { + char c = last_non_space_char(tok.buf); + if (c && !strchr(separators, c)) + strbuf_addf(out, "%c ", separators[0]); + } } if (!opts->key_only) strbuf_addbuf(out, &val); @@ -1205,22 +1103,34 @@ static void format_trailer_info(struct strbuf *out, if (opts->separator && out->len != origlen) { strbuf_addbuf(out, opts->separator); } - strbuf_addstr(out, trailer); - if (opts->separator) { + strbuf_addstr(out, item->value); + if (opts->separator) strbuf_rtrim(out); - } + else + strbuf_addch(out, '\n'); } } - } -void format_trailers_from_commit(struct strbuf *out, const char *msg, - const struct process_trailer_options *opts) +void format_trailers_from_commit(const struct process_trailer_options *opts, + const char *msg, + struct strbuf *out) { + LIST_HEAD(trailer_objects); struct trailer_info info; - trailer_info_get(&info, msg, opts); - format_trailer_info(out, &info, opts); + parse_trailers(opts, &info, msg, &trailer_objects); + + /* If we want the whole block untouched, we can take the fast path. */ + if (!opts->only_trailers && !opts->unfold && !opts->filter && + !opts->separator && !opts->key_only && !opts->value_only && + !opts->key_value_separator) { + strbuf_add(out, msg + info.trailer_block_start, + info.trailer_block_end - info.trailer_block_start); + } else + format_trailers(opts, &trailer_objects, out); + + free_trailers(&trailer_objects); trailer_info_release(&info); } @@ -1230,7 +1140,7 @@ void trailer_iterator_init(struct trailer_iterator *iter, const char *msg) strbuf_init(&iter->key, 0); strbuf_init(&iter->val, 0); opts.no_divider = 1; - trailer_info_get(&iter->internal.info, msg, &opts); + trailer_info_get(&opts, msg, &iter->internal.info); iter->internal.cur = 0; } @@ -1247,6 +1157,7 @@ int trailer_iterator_advance(struct trailer_iterator *iter) strbuf_reset(&iter->val); parse_trailer(&iter->key, &iter->val, NULL, trailer, separator_pos); + /* Always unfold values during iteration. */ unfold_value(&iter->val); return 1; } @@ -32,16 +32,16 @@ int trailer_set_if_missing(enum trailer_if_missing *item, const char *value); struct trailer_info { /* * True if there is a blank line before the location pointed to by - * trailer_start. + * trailer_block_start. */ int blank_line_before_trailer; /* - * Pointers to the start and end of the trailer block found. If there - * is no trailer block found, these 2 pointers point to the end of the - * input string. + * Offsets to the trailer block start and end positions in the input + * string. If no trailer block is found, these are both set to the + * "true" end of the input (find_end_of_log_message()). */ - const char *trailer_start, *trailer_end; + size_t trailer_block_start, trailer_block_end; /* * Array of trailers found. @@ -81,28 +81,38 @@ struct process_trailer_options { #define PROCESS_TRAILER_OPTIONS_INIT {0} -void process_trailers(const char *file, - const struct process_trailer_options *opts, - struct list_head *new_trailer_head); +void parse_trailers_from_config(struct list_head *config_head); -void trailer_info_get(struct trailer_info *info, const char *str, - const struct process_trailer_options *opts); +void parse_trailers_from_command_line_args(struct list_head *arg_head, + struct list_head *new_trailer_head); + +void process_trailers_lists(struct list_head *head, + struct list_head *arg_head); + +void parse_trailers(const struct process_trailer_options *, + struct trailer_info *, + const char *str, + struct list_head *head); + +void trailer_info_get(const struct process_trailer_options *, + const char *str, + struct trailer_info *); void trailer_info_release(struct trailer_info *info); +void trailer_config_init(void); +void format_trailers(const struct process_trailer_options *, + struct list_head *trailers, + struct strbuf *out); +void free_trailers(struct list_head *); + /* - * Format the trailers from the commit msg "msg" into the strbuf "out". - * Note two caveats about "opts": - * - * - this is primarily a helper for pretty.c, and not - * all of the flags are supported. - * - * - this differs from process_trailers slightly in that we always format - * only the trailer block itself, even if the "only_trailers" option is not - * set. + * Convenience function to format the trailers from the commit msg "msg" into + * the strbuf "out". Reuses format_trailers() internally. */ -void format_trailers_from_commit(struct strbuf *out, const char *msg, - const struct process_trailer_options *opts); +void format_trailers_from_commit(const struct process_trailer_options *, + const char *msg, + struct strbuf *out); /* * An interface for iterating over the trailers found in a particular commit diff --git a/transport-helper.c b/transport-helper.c index 49811ef..8d284b2 100644 --- a/transport-helper.c +++ b/transport-helper.c @@ -3,13 +3,11 @@ #include "quote.h" #include "run-command.h" #include "commit.h" -#include "diff.h" #include "environment.h" #include "gettext.h" #include "hex.h" #include "object-name.h" #include "repository.h" -#include "revision.h" #include "remote.h" #include "string-list.h" #include "thread-utils.h" @@ -19,6 +17,7 @@ #include "refspec.h" #include "transport-internal.h" #include "protocol.h" +#include "packfile.h" static int debug; @@ -434,6 +433,8 @@ static int fetch_with_fetch(struct transport *transport, warning(_("%s unexpectedly said: '%s'"), data->name, buf.buf); } strbuf_release(&buf); + + reprepare_packed_git(the_repository); return 0; } @@ -628,7 +629,8 @@ static int process_connect_service(struct transport *transport, ret = run_connect(transport, &cmdbuf); } else if (data->stateless_connect && (get_protocol_version_config() == protocol_v2) && - !strcmp("git-upload-pack", name)) { + (!strcmp("git-upload-pack", name) || + !strcmp("git-upload-archive", name))) { strbuf_addf(&cmdbuf, "stateless-connect %s\n", name); ret = run_connect(transport, &cmdbuf); if (ret) @@ -645,6 +647,7 @@ static int process_connect(struct transport *transport, struct helper_data *data = transport->data; const char *name; const char *exec; + int ret; name = for_push ? "git-receive-pack" : "git-upload-pack"; if (for_push) @@ -652,7 +655,10 @@ static int process_connect(struct transport *transport, else exec = data->transport_options.uploadpack; - return process_connect_service(transport, name, exec); + ret = process_connect_service(transport, name, exec); + if (ret) + do_take_over(transport); + return ret; } static int connect_helper(struct transport *transport, const char *name, @@ -662,14 +668,14 @@ static int connect_helper(struct transport *transport, const char *name, /* Get_helper so connect is inited. */ get_helper(transport); - if (!data->connect) - die(_("operation not supported by protocol")); if (!process_connect_service(transport, name, exec)) die(_("can't connect to subservice %s"), name); fd[0] = data->helper->out; fd[1] = data->helper->in; + + do_take_over(transport); return 0; } @@ -684,10 +690,8 @@ static int fetch_refs(struct transport *transport, get_helper(transport); - if (process_connect(transport, 0)) { - do_take_over(transport); + if (process_connect(transport, 0)) return transport->vtable->fetch_refs(transport, nr_heads, to_fetch); - } /* * If we reach here, then the server, the client, and/or the transport @@ -1074,7 +1078,7 @@ static int push_refs_with_export(struct transport *transport, set_common_push_options(transport, data->name, flags); if (flags & TRANSPORT_PUSH_FORCE) { if (set_helper_option(transport, "force", "true") != 0) - warning(_("helper %s does not support 'force'"), data->name); + warning(_("helper %s does not support '--force'"), data->name); } helper = get_helper(transport); @@ -1144,10 +1148,8 @@ static int push_refs(struct transport *transport, { struct helper_data *data = transport->data; - if (process_connect(transport, 1)) { - do_take_over(transport); + if (process_connect(transport, 1)) return transport->vtable->push_refs(transport, remote_refs, flags); - } if (!remote_refs) { fprintf(stderr, @@ -1188,11 +1190,9 @@ static struct ref *get_refs_list(struct transport *transport, int for_push, { get_helper(transport); - if (process_connect(transport, for_push)) { - do_take_over(transport); + if (process_connect(transport, for_push)) return transport->vtable->get_refs_list(transport, for_push, transport_options); - } return get_refs_list_using_list(transport, for_push); } @@ -1210,16 +1210,13 @@ static struct ref *get_refs_list_using_list(struct transport *transport, data->get_refs_list_called = 1; helper = get_helper(transport); - if (data->object_format) { - write_str_in_full(helper->in, "option object-format\n"); - if (recvline(data, &buf) || strcmp(buf.buf, "ok")) - exit(128); - } + if (data->object_format) + set_helper_option(transport, "object-format", "true"); if (data->push && for_push) - write_str_in_full(helper->in, "list for-push\n"); + write_constant(helper->in, "list for-push\n"); else - write_str_in_full(helper->in, "list\n"); + write_constant(helper->in, "list\n"); while (1) { char *eov, *eon; @@ -1276,10 +1273,8 @@ static int get_bundle_uri(struct transport *transport) { get_helper(transport); - if (process_connect(transport, 0)) { - do_take_over(transport); + if (process_connect(transport, 0)) return transport->vtable->get_bundle_uri(transport); - } return -1; } diff --git a/transport.c b/transport.c index 219af8f..df518ea 100644 --- a/transport.c +++ b/transport.c @@ -10,9 +10,7 @@ #include "remote.h" #include "connect.h" #include "send-pack.h" -#include "walker.h" #include "bundle.h" -#include "dir.h" #include "gettext.h" #include "refs.h" #include "refspec.h" @@ -26,7 +24,6 @@ #include "transport-internal.h" #include "protocol.h" #include "object-name.h" -#include "object-store-ll.h" #include "color.h" #include "bundle-uri.h" @@ -1470,6 +1467,7 @@ int transport_push(struct repository *r, if (porcelain && !push_ret) puts("Done"); else if (!quiet && !ret && !transport_refs_pushed(remote_refs)) + /* stable plumbing output; do not modify or localize */ fprintf(stderr, "Everything up-to-date\n"); done: diff --git a/tree-walk.c b/tree-walk.c index b517792..6565d9a 100644 --- a/tree-walk.c +++ b/tree-walk.c @@ -11,35 +11,19 @@ #include "json-writer.h" #include "environment.h" -static const char *get_mode(const char *str, unsigned int *modep) -{ - unsigned char c; - unsigned int mode = 0; - - if (*str == ' ') - return NULL; - - while ((c = *str++) != ' ') { - if (c < '0' || c > '7') - return NULL; - mode = (mode << 3) + (c - '0'); - } - *modep = mode; - return str; -} - static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned long size, struct strbuf *err) { const char *path; - unsigned int mode, len; - const unsigned hashsz = the_hash_algo->rawsz; + unsigned int len; + uint16_t mode; + const unsigned hashsz = desc->algo->rawsz; if (size < hashsz + 3 || buf[size - (hashsz + 1)]) { strbuf_addstr(err, _("too-short tree object")); return -1; } - path = get_mode(buf, &mode); + path = parse_mode(buf, &mode); if (!path) { strbuf_addstr(err, _("malformed mode in tree entry")); return -1; @@ -54,15 +38,19 @@ static int decode_tree_entry(struct tree_desc *desc, const char *buf, unsigned l desc->entry.path = path; desc->entry.mode = (desc->flags & TREE_DESC_RAW_MODES) ? mode : canon_mode(mode); desc->entry.pathlen = len - 1; - oidread(&desc->entry.oid, (const unsigned char *)path + len); + oidread_algop(&desc->entry.oid, (const unsigned char *)path + len, + desc->algo); return 0; } -static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer, - unsigned long size, struct strbuf *err, +static int init_tree_desc_internal(struct tree_desc *desc, + const struct object_id *oid, + const void *buffer, unsigned long size, + struct strbuf *err, enum tree_desc_flags flags) { + desc->algo = (oid && oid->algo) ? &hash_algos[oid->algo] : the_hash_algo; desc->buffer = buffer; desc->size = size; desc->flags = flags; @@ -71,19 +59,21 @@ static int init_tree_desc_internal(struct tree_desc *desc, const void *buffer, return 0; } -void init_tree_desc(struct tree_desc *desc, const void *buffer, unsigned long size) +void init_tree_desc(struct tree_desc *desc, const struct object_id *tree_oid, + const void *buffer, unsigned long size) { struct strbuf err = STRBUF_INIT; - if (init_tree_desc_internal(desc, buffer, size, &err, 0)) + if (init_tree_desc_internal(desc, tree_oid, buffer, size, &err, 0)) die("%s", err.buf); strbuf_release(&err); } -int init_tree_desc_gently(struct tree_desc *desc, const void *buffer, unsigned long size, +int init_tree_desc_gently(struct tree_desc *desc, const struct object_id *oid, + const void *buffer, unsigned long size, enum tree_desc_flags flags) { struct strbuf err = STRBUF_INIT; - int result = init_tree_desc_internal(desc, buffer, size, &err, flags); + int result = init_tree_desc_internal(desc, oid, buffer, size, &err, flags); if (result) error("%s", err.buf); strbuf_release(&err); @@ -100,9 +90,9 @@ void *fill_tree_descriptor(struct repository *r, if (oid) { buf = read_object_with_reference(r, oid, OBJ_TREE, &size, NULL); if (!buf) - die("unable to read tree %s", oid_to_hex(oid)); + die(_("unable to read tree (%s)"), oid_to_hex(oid)); } - init_tree_desc(desc, buf, size); + init_tree_desc(desc, oid, buf, size); return buf; } @@ -119,7 +109,7 @@ static void entry_extract(struct tree_desc *t, struct name_entry *a) static int update_tree_entry_internal(struct tree_desc *desc, struct strbuf *err) { const void *buf = desc->buffer; - const unsigned char *end = (const unsigned char *)desc->entry.path + desc->entry.pathlen + 1 + the_hash_algo->rawsz; + const unsigned char *end = (const unsigned char *)desc->entry.path + desc->entry.pathlen + 1 + desc->algo->rawsz; unsigned long size = desc->size; unsigned long len = end - (const unsigned char *)buf; @@ -633,7 +623,7 @@ int get_tree_entry(struct repository *r, retval = -1; } else { struct tree_desc t; - init_tree_desc(&t, tree, size); + init_tree_desc(&t, tree_oid, tree, size); retval = find_tree_entry(r, &t, name, oid, mode); } free(tree); @@ -676,7 +666,7 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, struct tree_desc t; int follows_remaining = GET_TREE_ENTRY_FOLLOW_SYMLINKS_MAX_LINKS; - init_tree_desc(&t, NULL, 0UL); + init_tree_desc(&t, NULL, NULL, 0UL); strbuf_addstr(&namebuf, name); oidcpy(¤t_tree_oid, tree_oid); @@ -712,7 +702,7 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, goto done; /* descend */ - init_tree_desc(&t, tree, size); + init_tree_desc(&t, ¤t_tree_oid, tree, size); } /* Handle symlinks to e.g. a//b by removing leading slashes */ @@ -746,7 +736,7 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, free(parent->tree); parents_nr--; parent = &parents[parents_nr - 1]; - init_tree_desc(&t, parent->tree, parent->size); + init_tree_desc(&t, &parent->oid, parent->tree, parent->size); strbuf_remove(&namebuf, 0, remainder ? 3 : 2); continue; } @@ -826,7 +816,7 @@ enum get_oid_result get_tree_entry_follow_symlinks(struct repository *r, contents_start = contents; parent = &parents[parents_nr - 1]; - init_tree_desc(&t, parent->tree, parent->size); + init_tree_desc(&t, &parent->oid, parent->tree, parent->size); strbuf_splice(&namebuf, 0, len, contents_start, link_len); if (remainder) diff --git a/tree-walk.h b/tree-walk.h index a6bfa3d..0b1067f 100644 --- a/tree-walk.h +++ b/tree-walk.h @@ -24,6 +24,7 @@ struct name_entry { * A semi-opaque data structure used to maintain the current state of the walk. */ struct tree_desc { + const struct git_hash_algo *algo; /* * pointer into the memory representation of the tree. It always * points at the current entry being visited. @@ -83,9 +84,11 @@ int update_tree_entry_gently(struct tree_desc *); * size parameters are assumed to be the same as the buffer and size * members of `struct tree`. */ -void init_tree_desc(struct tree_desc *desc, const void *buf, unsigned long size); +void init_tree_desc(struct tree_desc *desc, const struct object_id *tree_oid, + const void *buf, unsigned long size); -int init_tree_desc_gently(struct tree_desc *desc, const void *buf, unsigned long size, +int init_tree_desc_gently(struct tree_desc *desc, const struct object_id *oid, + const void *buf, unsigned long size, enum tree_desc_flags flags); /* @@ -1,12 +1,9 @@ #include "git-compat-util.h" -#include "cache-tree.h" #include "hex.h" #include "tree.h" #include "object-name.h" #include "object-store-ll.h" -#include "blob.h" #include "commit.h" -#include "tag.h" #include "alloc.h" #include "tree-walk.h" #include "repository.h" @@ -32,7 +29,7 @@ int read_tree_at(struct repository *r, if (parse_tree(tree)) return -1; - init_tree_desc(&desc, tree->buffer, tree->size); + init_tree_desc(&desc, &tree->object.oid, tree->buffer, tree->size); while (tree_entry(&desc, &entry)) { if (retval != all_entries_interesting) { |