diff options
Diffstat (limited to 't')
483 files changed, 23386 insertions, 3042 deletions
diff --git a/t/.gitattributes b/t/.gitattributes index 3bd959ae52..e7acedabe1 100644 --- a/t/.gitattributes +++ b/t/.gitattributes @@ -1,6 +1,8 @@ t[0-9][0-9][0-9][0-9]/* -whitespace +/chainlint/*.expect eol=lf /diff-lib/* eol=lf /t0110/url-* binary +/t3206/* eol=lf /t3900/*.txt eol=lf /t3901/*.txt eol=lf /t4034/*/* eol=lf diff --git a/t/.gitignore b/t/.gitignore index 4e731dc1e3..348715f0e4 100644 --- a/t/.gitignore +++ b/t/.gitignore @@ -1,3 +1,4 @@ /trash directory* /test-results /.prove +/chainlinttmp diff --git a/t/Makefile b/t/Makefile index 96317a35f4..c83fd18861 100644 --- a/t/Makefile +++ b/t/Makefile @@ -18,8 +18,10 @@ TEST_LINT ?= test-lint ifdef TEST_OUTPUT_DIRECTORY TEST_RESULTS_DIRECTORY = $(TEST_OUTPUT_DIRECTORY)/test-results +CHAINLINTTMP = $(TEST_OUTPUT_DIRECTORY)/chainlinttmp else TEST_RESULTS_DIRECTORY = test-results +CHAINLINTTMP = chainlinttmp endif # Shell quote; @@ -27,14 +29,17 @@ SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) TEST_SHELL_PATH_SQ = $(subst ','\'',$(TEST_SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) TEST_RESULTS_DIRECTORY_SQ = $(subst ','\'',$(TEST_RESULTS_DIRECTORY)) +CHAINLINTTMP_SQ = $(subst ','\'',$(CHAINLINTTMP)) T = $(sort $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)) TGITWEB = $(sort $(wildcard t95[0-9][0-9]-*.sh)) THELPERS = $(sort $(filter-out $(T),$(wildcard *.sh))) +CHAINLINTTESTS = $(sort $(patsubst chainlint/%.test,%,$(wildcard chainlint/*.test))) +CHAINLINT = sed -f chainlint.sed all: $(DEFAULT_TEST_TARGET) -test: pre-clean $(TEST_LINT) +test: pre-clean check-chainlint $(TEST_LINT) $(MAKE) aggregate-results-and-cleanup failed: @@ -43,7 +48,7 @@ failed: sed -n 's/\.counts$$/.sh/p') && \ test -z "$$failed" || $(MAKE) $$failed -prove: pre-clean $(TEST_LINT) +prove: pre-clean check-chainlint $(TEST_LINT) @echo "*** prove ***"; $(PROVE) --exec '$(TEST_SHELL_PATH_SQ)' $(GIT_PROVE_OPTS) $(T) :: $(GIT_TEST_OPTS) $(MAKE) clean-except-prove-cache @@ -53,13 +58,25 @@ $(T): pre-clean: $(RM) -r '$(TEST_RESULTS_DIRECTORY_SQ)' -clean-except-prove-cache: +clean-except-prove-cache: clean-chainlint $(RM) -r 'trash directory'.* '$(TEST_RESULTS_DIRECTORY_SQ)' $(RM) -r valgrind/bin clean: clean-except-prove-cache $(RM) .prove +clean-chainlint: + $(RM) -r '$(CHAINLINTTMP_SQ)' + +check-chainlint: + @mkdir -p '$(CHAINLINTTMP_SQ)' && \ + err=0 && \ + for i in $(CHAINLINTTESTS); do \ + $(CHAINLINT) <chainlint/$$i.test | \ + sed -e '/^# LINT: /d' >'$(CHAINLINTTMP_SQ)'/$$i.actual && \ + diff -u chainlint/$$i.expect '$(CHAINLINTTMP_SQ)'/$$i.actual || err=1; \ + done && exit $$err + test-lint: test-lint-duplicates test-lint-executable test-lint-shell-syntax \ test-lint-filenames @@ -102,4 +119,4 @@ valgrind: perf: $(MAKE) -C perf/ all -.PHONY: pre-clean $(T) aggregate-results clean valgrind perf +.PHONY: pre-clean $(T) aggregate-results clean valgrind perf check-chainlint clean-chainlint @@ -293,6 +293,40 @@ and know what setup is needed for it. Or when you want to run everything up to a certain test. +Running tests with special setups +--------------------------------- + +The whole test suite could be run to test some special features +that cannot be easily covered by a few specific test cases. These +could be enabled by running the test suite with correct GIT_TEST_ +environment set. + +GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole +test suite. Accept any boolean values that are accepted by git-config. + +GIT_TEST_FULL_IN_PACK_ARRAY=<boolean> exercises the uncommon +pack-objects code path where there are more than 1024 packs even if +the actual number of packs in repository is below this limit. Accept +any boolean values that are accepted by git-config. + +GIT_TEST_OE_SIZE=<n> exercises the uncommon pack-objects code path +where we do not cache object size in memory and read it from existing +packs on demand. This normally only happens when the object size is +over 2GB. This variable forces the code path on any object larger than +<n> bytes. + +GIT_TEST_OE_DELTA_SIZE=<n> exercises the uncomon pack-objects code +path where deltas larger than this limit require extra memory +allocation for bookkeeping. + +GIT_TEST_VALIDATE_INDEX_CACHE_ENTRIES=<boolean> checks that cache-tree +records are valid when the index is written out or after a merge. This +is mostly to catch missing invalidation. Default is true. + +GIT_TEST_COMMIT_GRAPH=<boolean>, when true, forces the commit-graph to +be written after every 'git commit' command, and overrides the +'core.commitGraph' setting to true. + Naming Tests ------------ diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index 093832fef1..6da48a2e0a 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -320,11 +320,11 @@ test_expect_success 'blame -L ,Y (Y == nlines)' ' test_expect_success 'blame -L ,Y (Y == nlines + 1)' ' n=$(expr $(wc -l <file) + 2) && - test_must_fail $PROG -L,$n file + check_count -L,$n A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 E 1 ' test_expect_success 'blame -L ,Y (Y > nlines)' ' - test_must_fail $PROG -L,12345 file + check_count -L,12345 A 1 B 1 B1 1 B2 1 "A U Thor" 1 C 1 D 1 E 1 ' test_expect_success 'blame -L multiple (disjoint)' ' diff --git a/t/chainlint.sed b/t/chainlint.sed new file mode 100644 index 0000000000..70df40e34b --- /dev/null +++ b/t/chainlint.sed @@ -0,0 +1,369 @@ +#------------------------------------------------------------------------------ +# Detect broken &&-chains in tests. +# +# At present, only &&-chains in subshells are examined by this linter; +# top-level &&-chains are instead checked directly by the test framework. Like +# the top-level &&-chain linter, the subshell linter (intentionally) does not +# check &&-chains within {...} blocks. +# +# Checking for &&-chain breakage is done line-by-line by pure textual +# inspection. +# +# Incomplete lines (those ending with "\") are stitched together with following +# lines to simplify processing, particularly of "one-liner" statements. +# Top-level here-docs are swallowed to avoid false positives within the +# here-doc body, although the statement to which the here-doc is attached is +# retained. +# +# Heuristics are used to detect end-of-subshell when the closing ")" is cuddled +# with the final subshell statement on the same line: +# +# (cd foo && +# bar) +# +# in order to avoid misinterpreting the ")" in constructs such as "x=$(...)" +# and "case $x in *)" as ending the subshell. +# +# Lines missing a final "&&" are flagged with "?!AMP?!", and lines which chain +# commands with ";" internally rather than "&&" are flagged "?!SEMI?!". A line +# may be flagged for both violations. +# +# Detection of a missing &&-link in a multi-line subshell is complicated by the +# fact that the last statement before the closing ")" must not end with "&&". +# Since processing is line-by-line, it is not known whether a missing "&&" is +# legitimate or not until the _next_ line is seen. To accommodate this, within +# multi-line subshells, each line is stored in sed's "hold" area until after +# the next line is seen and processed. If the next line is a stand-alone ")", +# then a missing "&&" on the previous line is legitimate; otherwise a missing +# "&&" is a break in the &&-chain. +# +# ( +# cd foo && +# bar +# ) +# +# In practical terms, when "bar" is encountered, it is flagged with "?!AMP?!", +# but when the stand-alone ")" line is seen which closes the subshell, the +# "?!AMP?!" violation is removed from the "bar" line (retrieved from the "hold" +# area) since the final statement of a subshell must not end with "&&". The +# final line of a subshell may still break the &&-chain by using ";" internally +# to chain commands together rather than "&&", so "?!SEMI?!" is never removed +# from a line (even though "?!AMP?!" might be). +# +# Care is taken to recognize the last _statement_ of a multi-line subshell, not +# necessarily the last textual _line_ within the subshell, since &&-chaining +# applies to statements, not to lines. Consequently, blank lines, comment +# lines, and here-docs are swallowed (but not the command to which the here-doc +# is attached), leaving the last statement in the "hold" area, not the last +# line, thus simplifying &&-link checking. +# +# The final statement before "done" in for- and while-loops, and before "elif", +# "else", and "fi" in if-then-else likewise must not end with "&&", thus +# receives similar treatment. +# +# Swallowing here-docs with arbitrary tags requires a bit of finesse. When a +# line such as "cat <<EOF >out" is seen, the here-doc tag is moved to the front +# of the line enclosed in angle brackets as a sentinel, giving "<EOF>cat >out". +# As each subsequent line is read, it is appended to the target line and a +# (whitespace-loose) back-reference match /^<(.*)>\n\1$/ is attempted to see if +# the content inside "<...>" matches the entirety of the newly-read line. For +# instance, if the next line read is "some data", when concatenated with the +# target line, it becomes "<EOF>cat >out\nsome data", and a match is attempted +# to see if "EOF" matches "some data". Since it doesn't, the next line is +# attempted. When a line consisting of only "EOF" (and possible whitespace) is +# encountered, it is appended to the target line giving "<EOF>cat >out\nEOF", +# in which case the "EOF" inside "<...>" does match the text following the +# newline, thus the closing here-doc tag has been found. The closing tag line +# and the "<...>" prefix on the target line are then discarded, leaving just +# the target line "cat >out". +# +# To facilitate regression testing (and manual debugging), a ">" annotation is +# applied to the line containing ")" which closes a subshell, ">>" to a line +# closing a nested subshell, and ">>>" to a line closing both at once. This +# makes it easy to detect whether the heuristics correctly identify +# end-of-subshell. +#------------------------------------------------------------------------------ + +# incomplete line -- slurp up next line +:squash +/\\$/ { + N + s/\\\n// + bsquash +} + +# here-doc -- swallow it to avoid false hits within its body (but keep the +# command to which it was attached) +/<<[ ]*[-\\'"]*[A-Za-z0-9_]/ { + s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</ + s/[ ]*<<// + :hered + N + /^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{ + s/\n.*$// + bhered + } + s/^<[^>]*>// + s/\n.*$// +} + +# one-liner "(...) &&" +/^[ ]*!*[ ]*(..*)[ ]*&&[ ]*$/boneline + +# same as above but without trailing "&&" +/^[ ]*!*[ ]*(..*)[ ]*$/boneline + +# one-liner "(...) >x" (or "2>x" or "<x" or "|x" or "&" +/^[ ]*!*[ ]*(..*)[ ]*[0-9]*[<>|&]/boneline + +# multi-line "(...\n...)" +/^[ ]*(/bsubshell + +# innocuous line -- print it and advance to next line +b + +# found one-liner "(...)" -- mark suspect if it uses ";" internally rather than +# "&&" (but not ";" in a string) +:oneline +/;/{ + /"[^"]*;[^"]*"/!s/^/?!SEMI?!/ +} +b + +:subshell +# bare "(" line? -- stash for later printing +/^[ ]*([ ]*$/ { + h + bnextline +} +# "(..." line -- split off and stash "(", then process "..." as its own line +x +s/.*/(/ +x +s/(// +bslurp + +:nextline +N +s/.*\n// + +:slurp +# incomplete line "...\" +/\\$/bicmplte +# multi-line quoted string "...\n..."? +/"/bdqstring +# multi-line quoted string '...\n...'? (but not contraction in string "it's") +/'/{ + /"[^'"]*'[^'"]*"/!bsqstring +} +:folded +# here-doc -- swallow it +/<<[ ]*[-\\'"]*[A-Za-z0-9_]/bheredoc +# comment or empty line -- discard since final non-comment, non-empty line +# before closing ")", "done", "elsif", "else", or "fi" will need to be +# re-visited to drop "suspect" marking since final line of those constructs +# legitimately lacks "&&", so "suspect" mark must be removed +/^[ ]*#/bnextline +/^[ ]*$/bnextline +# in-line comment -- strip it (but not "#" in a string, Bash ${#...} array +# length, or Perforce "//depot/path#42" revision in filespec) +/[ ]#/{ + /"[^"]*#[^"]*"/!s/[ ]#.*$// +} +# one-liner "case ... esac" +/^[ ]*case[ ]*..*esac/bchkchn +# multi-line "case ... esac" +/^[ ]*case[ ]..*[ ]in/bcase +# multi-line "for ... done" or "while ... done" +/^[ ]*for[ ]..*[ ]in/bcontinue +/^[ ]*while[ ]/bcontinue +/^[ ]*do[ ]/bcontinue +/^[ ]*do[ ]*$/bcontinue +/;[ ]*do/bcontinue +/^[ ]*done[ ]*&&[ ]*$/bdone +/^[ ]*done[ ]*$/bdone +/^[ ]*done[ ]*[<>|]/bdone +/^[ ]*done[ ]*)/bdone +/||[ ]*exit[ ]/bcontinue +/||[ ]*exit[ ]*$/bcontinue +# multi-line "if...elsif...else...fi" +/^[ ]*if[ ]/bcontinue +/^[ ]*then[ ]/bcontinue +/^[ ]*then[ ]*$/bcontinue +/;[ ]*then/bcontinue +/^[ ]*elif[ ]/belse +/^[ ]*elif[ ]*$/belse +/^[ ]*else[ ]/belse +/^[ ]*else[ ]*$/belse +/^[ ]*fi[ ]*&&[ ]*$/bdone +/^[ ]*fi[ ]*$/bdone +/^[ ]*fi[ ]*[<>|]/bdone +/^[ ]*fi[ ]*)/bdone +# nested one-liner "(...) &&" +/^[ ]*(.*)[ ]*&&[ ]*$/bchkchn +# nested one-liner "(...)" +/^[ ]*(.*)[ ]*$/bchkchn +# nested one-liner "(...) >x" (or "2>x" or "<x" or "|x") +/^[ ]*(.*)[ ]*[0-9]*[<>|]/bchkchn +# nested multi-line "(...\n...)" +/^[ ]*(/bnest +# multi-line "{...\n...}" +/^[ ]*{/bblock +# closing ")" on own line -- exit subshell +/^[ ]*)/bclssolo +# "$((...))" -- arithmetic expansion; not closing ")" +/\$(([^)][^)]*))[^)]*$/bchkchn +# "$(...)" -- command substitution; not closing ")" +/\$([^)][^)]*)[^)]*$/bchkchn +# multi-line "$(...\n...)" -- command substitution; treat as nested subshell +/\$([^)]*$/bnest +# "=(...)" -- Bash array assignment; not closing ")" +/=(/bchkchn +# closing "...) &&" +/)[ ]*&&[ ]*$/bclose +# closing "...)" +/)[ ]*$/bclose +# closing "...) >x" (or "2>x" or "<x" or "|x") +/)[ ]*[<>|]/bclose +:chkchn +# mark suspect if line uses ";" internally rather than "&&" (but not ";" in a +# string and not ";;" in one-liner "case...esac") +/;/{ + /;;/!{ + /"[^"]*;[^"]*"/!s/^/?!SEMI?!/ + } +} +# line ends with pipe "...|" -- valid; not missing "&&" +/|[ ]*$/bcontinue +# missing end-of-line "&&" -- mark suspect +/&&[ ]*$/!s/^/?!AMP?!/ +:continue +# retrieve and print previous line +x +n +bslurp + +# found incomplete line "...\" -- slurp up next line +:icmplte +N +s/\\\n// +bslurp + +# check for multi-line double-quoted string "...\n..." -- fold to one line +:dqstring +# remove all quote pairs +s/"\([^"]*\)"/@!\1@!/g +# done if no dangling quote +/"/!bdqdone +# otherwise, slurp next line and try again +N +s/\n// +bdqstring +:dqdone +s/@!/"/g +bfolded + +# check for multi-line single-quoted string '...\n...' -- fold to one line +:sqstring +# remove all quote pairs +s/'\([^']*\)'/@!\1@!/g +# done if no dangling quote +/'/!bsqdone +# otherwise, slurp next line and try again +N +s/\n// +bsqstring +:sqdone +s/@!/'/g +bfolded + +# found here-doc -- swallow it to avoid false hits within its body (but keep +# the command to which it was attached) +:heredoc +s/^\(.*\)<<[ ]*[-\\'"]*\([A-Za-z0-9_][A-Za-z0-9_]*\)['"]*/<\2>\1<</ +s/[ ]*<<// +:heredsub +N +/^<\([^>]*\)>.*\n[ ]*\1[ ]*$/!{ + s/\n.*$// + bheredsub +} +s/^<[^>]*>// +s/\n.*$// +bfolded + +# found "case ... in" -- pass through untouched +:case +x +n +/^[ ]*esac/bslurp +bcase + +# found "else" or "elif" -- drop "suspect" from final line before "else" since +# that line legitimately lacks "&&" +:else +x +s/?!AMP?!// +x +bcontinue + +# found "done" closing for-loop or while-loop, or "fi" closing if-then -- drop +# "suspect" from final contained line since that line legitimately lacks "&&" +:done +x +s/?!AMP?!// +x +# is 'done' or 'fi' cuddled with ")" to close subshell? +/done.*)/bclose +/fi.*)/bclose +bchkchn + +# found nested multi-line "(...\n...)" -- pass through untouched +:nest +x +:nstslurp +n +# closing ")" on own line -- stop nested slurp +/^[ ]*)/bnstclose +# comment -- not closing ")" if in comment +/^[ ]*#/bnstcnt +# "$((...))" -- arithmetic expansion; not closing ")" +/\$(([^)][^)]*))[^)]*$/bnstcnt +# "$(...)" -- command substitution; not closing ")" +/\$([^)][^)]*)[^)]*$/bnstcnt +# closing "...)" -- stop nested slurp +/)/bnstclose +:nstcnt +x +bnstslurp +:nstclose +s/^/>>/ +# is it "))" which closes nested and parent subshells? +/)[ ]*)/bslurp +bchkchn + +# found multi-line "{...\n...}" block -- pass through untouched +:block +x +n +# closing "}" -- stop block slurp +/}/bchkchn +bblock + +# found closing ")" on own line -- drop "suspect" from final line of subshell +# since that line legitimately lacks "&&" and exit subshell loop +:clssolo +x +s/?!AMP?!// +p +x +s/^/>/ +b + +# found closing "...)" -- exit subshell loop +:close +x +p +x +s/^/>/ +b diff --git a/t/chainlint/arithmetic-expansion.expect b/t/chainlint/arithmetic-expansion.expect new file mode 100644 index 0000000000..09457d3196 --- /dev/null +++ b/t/chainlint/arithmetic-expansion.expect @@ -0,0 +1,9 @@ +( + foo && + bar=$((42 + 1)) && + baz +>) && +( +?!AMP?! bar=$((42 + 1)) + baz +>) diff --git a/t/chainlint/arithmetic-expansion.test b/t/chainlint/arithmetic-expansion.test new file mode 100644 index 0000000000..16206960d8 --- /dev/null +++ b/t/chainlint/arithmetic-expansion.test @@ -0,0 +1,11 @@ +( + foo && +# LINT: closing ")" of $((...)) not misinterpreted as subshell-closing ")" + bar=$((42 + 1)) && + baz +) && +( +# LINT: missing "&&" on $((...)) + bar=$((42 + 1)) + baz +) diff --git a/t/chainlint/bash-array.expect b/t/chainlint/bash-array.expect new file mode 100644 index 0000000000..c4a830d1c1 --- /dev/null +++ b/t/chainlint/bash-array.expect @@ -0,0 +1,10 @@ +( + foo && + bar=(gumbo stumbo wumbo) && + baz +>) && +( + foo && + bar=${#bar[@]} && + baz +>) diff --git a/t/chainlint/bash-array.test b/t/chainlint/bash-array.test new file mode 100644 index 0000000000..92bbb777b8 --- /dev/null +++ b/t/chainlint/bash-array.test @@ -0,0 +1,12 @@ +( + foo && +# LINT: ")" in Bash array assignment not misinterpreted as subshell-closing ")" + bar=(gumbo stumbo wumbo) && + baz +) && +( + foo && +# LINT: Bash array length operator not misinterpreted as comment + bar=${#bar[@]} && + baz +) diff --git a/t/chainlint/blank-line.expect b/t/chainlint/blank-line.expect new file mode 100644 index 0000000000..3be939ed38 --- /dev/null +++ b/t/chainlint/blank-line.expect @@ -0,0 +1,4 @@ +( + nothing && + something +>) diff --git a/t/chainlint/blank-line.test b/t/chainlint/blank-line.test new file mode 100644 index 0000000000..f6dd14302b --- /dev/null +++ b/t/chainlint/blank-line.test @@ -0,0 +1,10 @@ +( + + nothing && + + something +# LINT: swallow blank lines since final _statement_ before subshell end is +# LINT: significant to "&&"-check, not final _line_ (which might be blank) + + +) diff --git a/t/chainlint/block.expect b/t/chainlint/block.expect new file mode 100644 index 0000000000..fed7e89ae8 --- /dev/null +++ b/t/chainlint/block.expect @@ -0,0 +1,12 @@ +( + foo && + { + echo a + echo b + } && + bar && + { + echo c +?!AMP?! } + baz +>) diff --git a/t/chainlint/block.test b/t/chainlint/block.test new file mode 100644 index 0000000000..d859151af1 --- /dev/null +++ b/t/chainlint/block.test @@ -0,0 +1,15 @@ +( +# LINT: missing "&&" in block not currently detected (for consistency with +# LINT: --chain-lint at top level and to provide escape hatch if needed) + foo && + { + echo a + echo b + } && + bar && +# LINT: missing "&&" at closing "}" + { + echo c + } + baz +) diff --git a/t/chainlint/broken-chain.expect b/t/chainlint/broken-chain.expect new file mode 100644 index 0000000000..55b0f42a53 --- /dev/null +++ b/t/chainlint/broken-chain.expect @@ -0,0 +1,6 @@ +( + foo && +?!AMP?! bar + baz && + wop +>) diff --git a/t/chainlint/broken-chain.test b/t/chainlint/broken-chain.test new file mode 100644 index 0000000000..3cc67b65d0 --- /dev/null +++ b/t/chainlint/broken-chain.test @@ -0,0 +1,8 @@ +( + foo && +# LINT: missing "&&" from 'bar' + bar + baz && +# LINT: final statement before closing ")" legitimately lacks "&&" + wop +) diff --git a/t/chainlint/case.expect b/t/chainlint/case.expect new file mode 100644 index 0000000000..41f121fbbf --- /dev/null +++ b/t/chainlint/case.expect @@ -0,0 +1,19 @@ +( + case "$x" in + x) foo ;; + *) bar ;; + esac && + foobar +>) && +( + case "$x" in + x) foo ;; + *) bar ;; +?!AMP?! esac + foobar +>) && +( + case "$x" in 1) true;; esac && +?!AMP?! case "$y" in 2) false;; esac + foobar +>) diff --git a/t/chainlint/case.test b/t/chainlint/case.test new file mode 100644 index 0000000000..5ef6ff7db5 --- /dev/null +++ b/t/chainlint/case.test @@ -0,0 +1,23 @@ +( +# LINT: "...)" arms in 'case' not misinterpreted as subshell-closing ")" + case "$x" in + x) foo ;; + *) bar ;; + esac && + foobar +) && +( +# LINT: missing "&&" on 'esac' + case "$x" in + x) foo ;; + *) bar ;; + esac + foobar +) && +( +# LINT: "...)" arm in one-liner 'case' not misinterpreted as closing ")" + case "$x" in 1) true;; esac && +# LINT: same but missing "&&" + case "$y" in 2) false;; esac + foobar +) diff --git a/t/chainlint/close-nested-and-parent-together.expect b/t/chainlint/close-nested-and-parent-together.expect new file mode 100644 index 0000000000..2a910f9d66 --- /dev/null +++ b/t/chainlint/close-nested-and-parent-together.expect @@ -0,0 +1,4 @@ +( +cd foo && + (bar && +>>> baz)) diff --git a/t/chainlint/close-nested-and-parent-together.test b/t/chainlint/close-nested-and-parent-together.test new file mode 100644 index 0000000000..72d482f76d --- /dev/null +++ b/t/chainlint/close-nested-and-parent-together.test @@ -0,0 +1,3 @@ +(cd foo && + (bar && + baz)) diff --git a/t/chainlint/close-subshell.expect b/t/chainlint/close-subshell.expect new file mode 100644 index 0000000000..184688718a --- /dev/null +++ b/t/chainlint/close-subshell.expect @@ -0,0 +1,25 @@ +( + foo +>) && +( + bar +>) >out && +( + baz +>) 2>err && +( + boo +>) <input && +( + bip +>) | wuzzle && +( + bop +>) | fazz fozz && +( + bup +>) | +fuzzle && +( + yop +>) diff --git a/t/chainlint/close-subshell.test b/t/chainlint/close-subshell.test new file mode 100644 index 0000000000..508ca447fd --- /dev/null +++ b/t/chainlint/close-subshell.test @@ -0,0 +1,27 @@ +# LINT: closing ")" with various decorations ("&&", ">", "|", etc.) +( + foo +) && +( + bar +) >out && +( + baz +) 2>err && +( + boo +) <input && +( + bip +) | wuzzle && +( + bop +) | fazz \ + fozz && +( + bup +) | +fuzzle && +( + yop +) diff --git a/t/chainlint/command-substitution.expect b/t/chainlint/command-substitution.expect new file mode 100644 index 0000000000..ad4118e537 --- /dev/null +++ b/t/chainlint/command-substitution.expect @@ -0,0 +1,9 @@ +( + foo && + bar=$(gobble) && + baz +>) && +( +?!AMP?! bar=$(gobble blocks) + baz +>) diff --git a/t/chainlint/command-substitution.test b/t/chainlint/command-substitution.test new file mode 100644 index 0000000000..3bbb002a4c --- /dev/null +++ b/t/chainlint/command-substitution.test @@ -0,0 +1,11 @@ +( + foo && +# LINT: closing ")" of $(...) not misinterpreted as subshell-closing ")" + bar=$(gobble) && + baz +) && +( +# LINT: missing "&&" on $(...) + bar=$(gobble blocks) + baz +) diff --git a/t/chainlint/comment.expect b/t/chainlint/comment.expect new file mode 100644 index 0000000000..3be939ed38 --- /dev/null +++ b/t/chainlint/comment.expect @@ -0,0 +1,4 @@ +( + nothing && + something +>) diff --git a/t/chainlint/comment.test b/t/chainlint/comment.test new file mode 100644 index 0000000000..113c0c466f --- /dev/null +++ b/t/chainlint/comment.test @@ -0,0 +1,11 @@ +( +# LINT: swallow comment lines + # comment 1 + nothing && + # comment 2 + something +# LINT: swallow comment lines since final _statement_ before subshell end is +# LINT: significant to "&&"-check, not final _line_ (which might be comment) + # comment 3 + # comment 4 +) diff --git a/t/chainlint/complex-if-in-cuddled-loop.expect b/t/chainlint/complex-if-in-cuddled-loop.expect new file mode 100644 index 0000000000..9674b88cf2 --- /dev/null +++ b/t/chainlint/complex-if-in-cuddled-loop.expect @@ -0,0 +1,10 @@ +( +for i in a b c; do + if test "$(echo $(waffle bat))" = "eleventeen" && + test "$x" = "$y"; then + : + else + echo >file + fi +> done) && +test ! -f file diff --git a/t/chainlint/complex-if-in-cuddled-loop.test b/t/chainlint/complex-if-in-cuddled-loop.test new file mode 100644 index 0000000000..571bbd85cd --- /dev/null +++ b/t/chainlint/complex-if-in-cuddled-loop.test @@ -0,0 +1,11 @@ +# LINT: 'for' loop cuddled with "(" and ")" and nested 'if' with complex +# LINT: multi-line condition; indented with spaces, not tabs +(for i in a b c; do + if test "$(echo $(waffle bat))" = "eleventeen" && + test "$x" = "$y"; then + : + else + echo >file + fi + done) && +test ! -f file diff --git a/t/chainlint/cuddled-if-then-else.expect b/t/chainlint/cuddled-if-then-else.expect new file mode 100644 index 0000000000..ab2a026fbc --- /dev/null +++ b/t/chainlint/cuddled-if-then-else.expect @@ -0,0 +1,7 @@ +( +if test -z ""; then + echo empty + else + echo bizzy +> fi) && +echo foobar diff --git a/t/chainlint/cuddled-if-then-else.test b/t/chainlint/cuddled-if-then-else.test new file mode 100644 index 0000000000..eed774a9d6 --- /dev/null +++ b/t/chainlint/cuddled-if-then-else.test @@ -0,0 +1,7 @@ +# LINT: 'if' cuddled with "(" and ")"; indented with spaces, not tabs +(if test -z ""; then + echo empty + else + echo bizzy + fi) && +echo foobar diff --git a/t/chainlint/cuddled-loop.expect b/t/chainlint/cuddled-loop.expect new file mode 100644 index 0000000000..8c0260d7f1 --- /dev/null +++ b/t/chainlint/cuddled-loop.expect @@ -0,0 +1,5 @@ +( + while read x + do foobar bop || exit 1 +> done <file ) && +outside subshell diff --git a/t/chainlint/cuddled-loop.test b/t/chainlint/cuddled-loop.test new file mode 100644 index 0000000000..a841d781f0 --- /dev/null +++ b/t/chainlint/cuddled-loop.test @@ -0,0 +1,7 @@ +# LINT: 'while' loop cuddled with "(" and ")", with embedded (allowed) +# LINT: "|| exit {n}" to exit loop early, and using redirection "<" to feed +# LINT: loop; indented with spaces, not tabs +( while read x + do foobar bop || exit 1 + done <file ) && +outside subshell diff --git a/t/chainlint/cuddled.expect b/t/chainlint/cuddled.expect new file mode 100644 index 0000000000..b506d46221 --- /dev/null +++ b/t/chainlint/cuddled.expect @@ -0,0 +1,21 @@ +( +cd foo && + bar +>) && + +( +?!AMP?!cd foo + bar +>) && + +( + cd foo && +> bar) && + +( +cd foo && +> bar) && + +( +?!AMP?!cd foo +> bar) diff --git a/t/chainlint/cuddled.test b/t/chainlint/cuddled.test new file mode 100644 index 0000000000..0499fa4180 --- /dev/null +++ b/t/chainlint/cuddled.test @@ -0,0 +1,23 @@ +# LINT: first subshell statement cuddled with opening "("; for implementation +# LINT: simplicity, "(..." is split into two lines, "(" and "..." +(cd foo && + bar +) && + +# LINT: same with missing "&&" +(cd foo + bar +) && + +# LINT: closing ")" cuddled with final subshell statement +( + cd foo && + bar) && + +# LINT: "(" and ")" cuddled with first and final subshell statements +(cd foo && + bar) && + +# LINT: same with missing "&&" +(cd foo + bar) diff --git a/t/chainlint/exit-loop.expect b/t/chainlint/exit-loop.expect new file mode 100644 index 0000000000..84d8bdebc0 --- /dev/null +++ b/t/chainlint/exit-loop.expect @@ -0,0 +1,24 @@ +( + for i in a b c + do + foo || exit 1 + bar && + baz + done +>) && +( + while true + do + foo || exit 1 + bar && + baz + done +>) && +( + i=0 && + while test $i -lt 10 + do + echo $i || exit + i=$(($i + 1)) + done +>) diff --git a/t/chainlint/exit-loop.test b/t/chainlint/exit-loop.test new file mode 100644 index 0000000000..2f038207e1 --- /dev/null +++ b/t/chainlint/exit-loop.test @@ -0,0 +1,27 @@ +( + for i in a b c + do +# LINT: "|| exit {n}" valid for-loop escape in subshell; no "&&" needed + foo || exit 1 + bar && + baz + done +) && +( + while true + do +# LINT: "|| exit {n}" valid while-loop escape in subshell; no "&&" needed + foo || exit 1 + bar && + baz + done +) && +( + i=0 && + while test $i -lt 10 + do +# LINT: "|| exit" (sans exit code) valid escape in subshell; no "&&" needed + echo $i || exit + i=$(($i + 1)) + done +) diff --git a/t/chainlint/exit-subshell.expect b/t/chainlint/exit-subshell.expect new file mode 100644 index 0000000000..bf78454f74 --- /dev/null +++ b/t/chainlint/exit-subshell.expect @@ -0,0 +1,5 @@ +( + foo || exit 1 + bar && + baz +>) diff --git a/t/chainlint/exit-subshell.test b/t/chainlint/exit-subshell.test new file mode 100644 index 0000000000..4e6ab69b88 --- /dev/null +++ b/t/chainlint/exit-subshell.test @@ -0,0 +1,6 @@ +( +# LINT: "|| exit {n}" valid subshell escape without hurting &&-chain + foo || exit 1 + bar && + baz +) diff --git a/t/chainlint/for-loop.expect b/t/chainlint/for-loop.expect new file mode 100644 index 0000000000..c33cf56ee7 --- /dev/null +++ b/t/chainlint/for-loop.expect @@ -0,0 +1,11 @@ +( + for i in a b c + do +?!AMP?! echo $i + cat +?!AMP?! done + for i in a b c; do + echo $i && + cat $i + done +>) diff --git a/t/chainlint/for-loop.test b/t/chainlint/for-loop.test new file mode 100644 index 0000000000..7db76262bc --- /dev/null +++ b/t/chainlint/for-loop.test @@ -0,0 +1,19 @@ +( +# LINT: 'for', 'do', 'done' do not need "&&" + for i in a b c + do +# LINT: missing "&&" on 'echo' + echo $i +# LINT: last statement of while does not need "&&" + cat <<-\EOF + bar + EOF +# LINT: missing "&&" on 'done' + done + +# LINT: 'do' on same line as 'for' + for i in a b c; do + echo $i && + cat $i + done +) diff --git a/t/chainlint/here-doc-close-subshell.expect b/t/chainlint/here-doc-close-subshell.expect new file mode 100644 index 0000000000..f011e335e5 --- /dev/null +++ b/t/chainlint/here-doc-close-subshell.expect @@ -0,0 +1,2 @@ +( +> cat) diff --git a/t/chainlint/here-doc-close-subshell.test b/t/chainlint/here-doc-close-subshell.test new file mode 100644 index 0000000000..b857ff5467 --- /dev/null +++ b/t/chainlint/here-doc-close-subshell.test @@ -0,0 +1,5 @@ +( +# LINT: line contains here-doc and closes nested subshell + cat <<-\INPUT) + fizz + INPUT diff --git a/t/chainlint/here-doc-multi-line-command-subst.expect b/t/chainlint/here-doc-multi-line-command-subst.expect new file mode 100644 index 0000000000..e5fb752d2f --- /dev/null +++ b/t/chainlint/here-doc-multi-line-command-subst.expect @@ -0,0 +1,5 @@ +( + x=$(bobble && +?!AMP?!>> wiffle) + echo $x +>) diff --git a/t/chainlint/here-doc-multi-line-command-subst.test b/t/chainlint/here-doc-multi-line-command-subst.test new file mode 100644 index 0000000000..899bc5de8b --- /dev/null +++ b/t/chainlint/here-doc-multi-line-command-subst.test @@ -0,0 +1,9 @@ +( +# LINT: line contains here-doc and opens multi-line $(...) + x=$(bobble <<-\END && + fossil + vegetable + END + wiffle) + echo $x +) diff --git a/t/chainlint/here-doc-multi-line-string.expect b/t/chainlint/here-doc-multi-line-string.expect new file mode 100644 index 0000000000..32038a070c --- /dev/null +++ b/t/chainlint/here-doc-multi-line-string.expect @@ -0,0 +1,4 @@ +( +?!AMP?! cat && echo "multi-line string" + bap +>) diff --git a/t/chainlint/here-doc-multi-line-string.test b/t/chainlint/here-doc-multi-line-string.test new file mode 100644 index 0000000000..a53edbcc8d --- /dev/null +++ b/t/chainlint/here-doc-multi-line-string.test @@ -0,0 +1,8 @@ +( +# LINT: line contains here-doc and opens multi-line string + cat <<-\TXT && echo "multi-line + string" + fizzle + TXT + bap +) diff --git a/t/chainlint/here-doc.expect b/t/chainlint/here-doc.expect new file mode 100644 index 0000000000..534b065e38 --- /dev/null +++ b/t/chainlint/here-doc.expect @@ -0,0 +1,9 @@ +boodle wobba gorgo snoot wafta snurb && + +cat >foo && + +cat >bar && + +cat >boo && + +horticulture diff --git a/t/chainlint/here-doc.test b/t/chainlint/here-doc.test new file mode 100644 index 0000000000..ad4ce8afd9 --- /dev/null +++ b/t/chainlint/here-doc.test @@ -0,0 +1,37 @@ +# LINT: stitch together incomplete \-ending lines +# LINT: swallow here-doc to avoid false positives in content +boodle wobba \ + gorgo snoot \ + wafta snurb <<EOF && +quoth the raven, +nevermore... +EOF + +# LINT: swallow here-doc with arbitrary tag +cat <<-Arbitrary_Tag_42 >foo && +snoz +boz +woz +Arbitrary_Tag_42 + +# LINT: swallow 'quoted' here-doc +cat <<'FUMP' >bar && +snoz +boz +woz +FUMP + +# LINT: swallow "quoted" here-doc +cat <<"zump" >boo && +snoz +boz +woz +zump + +# LINT: swallow here-doc (EOF is last line of test) +horticulture <<\EOF +gomez +morticia +wednesday +pugsly +EOF diff --git a/t/chainlint/if-in-loop.expect b/t/chainlint/if-in-loop.expect new file mode 100644 index 0000000000..03d3ceb22d --- /dev/null +++ b/t/chainlint/if-in-loop.expect @@ -0,0 +1,12 @@ +( + for i in a b c + do + if false + then +?!AMP?! echo "err" + exit 1 +?!AMP?! fi + foo +?!AMP?! done + bar +>) diff --git a/t/chainlint/if-in-loop.test b/t/chainlint/if-in-loop.test new file mode 100644 index 0000000000..daf22da164 --- /dev/null +++ b/t/chainlint/if-in-loop.test @@ -0,0 +1,15 @@ +( + for i in a b c + do + if false + then +# LINT: missing "&&" on 'echo' + echo "err" + exit 1 +# LINT: missing "&&" on 'fi' + fi + foo +# LINT: missing "&&" on 'done' + done + bar +) diff --git a/t/chainlint/if-then-else.expect b/t/chainlint/if-then-else.expect new file mode 100644 index 0000000000..5953c7bfbc --- /dev/null +++ b/t/chainlint/if-then-else.expect @@ -0,0 +1,19 @@ +( + if test -n "" + then +?!AMP?! echo very + echo empty + elif test -z "" + echo foo + else + echo foo && + cat +?!AMP?! fi + echo poodle +>) && +( + if test -n ""; then + echo very && +?!AMP?! echo empty + if +>) diff --git a/t/chainlint/if-then-else.test b/t/chainlint/if-then-else.test new file mode 100644 index 0000000000..9bd8e9a4c6 --- /dev/null +++ b/t/chainlint/if-then-else.test @@ -0,0 +1,28 @@ +( +# LINT: 'if', 'then', 'elif', 'else', 'fi' do not need "&&" + if test -n "" + then +# LINT: missing "&&" on 'echo' + echo very +# LINT: last statement before 'elif' does not need "&&" + echo empty + elif test -z "" +# LINT: last statement before 'else' does not need "&&" + echo foo + else + echo foo && +# LINT: last statement before 'fi' does not need "&&" + cat <<-\EOF + bar + EOF +# LINT: missing "&&" on 'fi' + fi + echo poodle +) && +( +# LINT: 'then' on same line as 'if' + if test -n ""; then + echo very && + echo empty + if +) diff --git a/t/chainlint/incomplete-line.expect b/t/chainlint/incomplete-line.expect new file mode 100644 index 0000000000..2f3ebabdc2 --- /dev/null +++ b/t/chainlint/incomplete-line.expect @@ -0,0 +1,4 @@ +line 1 line 2 line 3 line 4 && +( + line 5 line 6 line 7 line 8 +>) diff --git a/t/chainlint/incomplete-line.test b/t/chainlint/incomplete-line.test new file mode 100644 index 0000000000..d856658083 --- /dev/null +++ b/t/chainlint/incomplete-line.test @@ -0,0 +1,12 @@ +# LINT: stitch together all incomplete \-ending lines +line 1 \ +line 2 \ +line 3 \ +line 4 && +( +# LINT: stitch together all incomplete \-ending lines (subshell) + line 5 \ + line 6 \ + line 7 \ + line 8 +) diff --git a/t/chainlint/inline-comment.expect b/t/chainlint/inline-comment.expect new file mode 100644 index 0000000000..fc9f250ac4 --- /dev/null +++ b/t/chainlint/inline-comment.expect @@ -0,0 +1,9 @@ +( + foobar && +?!AMP?! barfoo + flibble "not a # comment" +>) && + +( +cd foo && +> flibble "not a # comment") diff --git a/t/chainlint/inline-comment.test b/t/chainlint/inline-comment.test new file mode 100644 index 0000000000..8f26856e77 --- /dev/null +++ b/t/chainlint/inline-comment.test @@ -0,0 +1,12 @@ +( +# LINT: swallow inline comment (leaving command intact) + foobar && # comment 1 +# LINT: mispositioned "&&" (correctly) swallowed with comment + barfoo # wrong position for && +# LINT: "#" in string not misinterpreted as comment + flibble "not a # comment" +) && + +# LINT: "#" in string in cuddled subshell not misinterpreted as comment +(cd foo && + flibble "not a # comment") diff --git a/t/chainlint/loop-in-if.expect b/t/chainlint/loop-in-if.expect new file mode 100644 index 0000000000..088e622c31 --- /dev/null +++ b/t/chainlint/loop-in-if.expect @@ -0,0 +1,12 @@ +( + if true + then + while true + do +?!AMP?! echo "pop" + echo "glup" +?!AMP?! done + foo +?!AMP?! fi + bar +>) diff --git a/t/chainlint/loop-in-if.test b/t/chainlint/loop-in-if.test new file mode 100644 index 0000000000..93e8ba8e4d --- /dev/null +++ b/t/chainlint/loop-in-if.test @@ -0,0 +1,15 @@ +( + if true + then + while true + do +# LINT: missing "&&" on 'echo' + echo "pop" + echo "glup" +# LINT: missing "&&" on 'done' + done + foo +# LINT: missing "&&" on 'fi' + fi + bar +) diff --git a/t/chainlint/multi-line-nested-command-substitution.expect b/t/chainlint/multi-line-nested-command-substitution.expect new file mode 100644 index 0000000000..59b6c8b850 --- /dev/null +++ b/t/chainlint/multi-line-nested-command-substitution.expect @@ -0,0 +1,18 @@ +( + foo && + x=$( + echo bar | + cat +>> ) && + echo ok +>) | +sort && +( + bar && + x=$(echo bar | + cat +>> ) && + y=$(echo baz | +>> fip) && + echo fail +>) diff --git a/t/chainlint/multi-line-nested-command-substitution.test b/t/chainlint/multi-line-nested-command-substitution.test new file mode 100644 index 0000000000..300058341b --- /dev/null +++ b/t/chainlint/multi-line-nested-command-substitution.test @@ -0,0 +1,18 @@ +( + foo && + x=$( + echo bar | + cat + ) && + echo ok +) | +sort && +( + bar && + x=$(echo bar | + cat + ) && + y=$(echo baz | + fip) && + echo fail +) diff --git a/t/chainlint/multi-line-string.expect b/t/chainlint/multi-line-string.expect new file mode 100644 index 0000000000..170cb59993 --- /dev/null +++ b/t/chainlint/multi-line-string.expect @@ -0,0 +1,15 @@ +( + x="line 1 line 2 line 3" && +?!AMP?! y='line 1 line2' + foobar +>) && +( + echo "there's nothing to see here" && + exit +>) && +( + echo "xyz" "abc def ghi" && + echo 'xyz' 'abc def ghi' && + echo 'xyz' "abc def ghi" && + barfoo +>) diff --git a/t/chainlint/multi-line-string.test b/t/chainlint/multi-line-string.test new file mode 100644 index 0000000000..287ab89705 --- /dev/null +++ b/t/chainlint/multi-line-string.test @@ -0,0 +1,27 @@ +( + x="line 1 + line 2 + line 3" && +# LINT: missing "&&" on assignment + y='line 1 + line2' + foobar +) && +( +# LINT: apostrophe (in a contraction) within string not misinterpreted as +# LINT: starting multi-line single-quoted string + echo "there's nothing to see here" && + exit +) && +( + echo "xyz" "abc + def + ghi" && + echo 'xyz' 'abc + def + ghi' && + echo 'xyz' "abc + def + ghi" && + barfoo +) diff --git a/t/chainlint/negated-one-liner.expect b/t/chainlint/negated-one-liner.expect new file mode 100644 index 0000000000..cf18429d03 --- /dev/null +++ b/t/chainlint/negated-one-liner.expect @@ -0,0 +1,5 @@ +! (foo && bar) && +! (foo && bar) >baz && + +?!SEMI?!! (foo; bar) && +?!SEMI?!! (foo; bar) >baz diff --git a/t/chainlint/negated-one-liner.test b/t/chainlint/negated-one-liner.test new file mode 100644 index 0000000000..c9598e9153 --- /dev/null +++ b/t/chainlint/negated-one-liner.test @@ -0,0 +1,7 @@ +# LINT: top-level one-liner subshell +! (foo && bar) && +! (foo && bar) >baz && + +# LINT: top-level one-liner subshell missing internal "&&" +! (foo; bar) && +! (foo; bar) >baz diff --git a/t/chainlint/nested-cuddled-subshell.expect b/t/chainlint/nested-cuddled-subshell.expect new file mode 100644 index 0000000000..c2a59ffc33 --- /dev/null +++ b/t/chainlint/nested-cuddled-subshell.expect @@ -0,0 +1,19 @@ +( + (cd foo && + bar +>> ) && + (cd foo && + bar +?!AMP?!>> ) + ( + cd foo && +>> bar) && + ( + cd foo && +?!AMP?!>> bar) + (cd foo && +>> bar) && + (cd foo && +?!AMP?!>> bar) + foobar +>) diff --git a/t/chainlint/nested-cuddled-subshell.test b/t/chainlint/nested-cuddled-subshell.test new file mode 100644 index 0000000000..8fd656c7b5 --- /dev/null +++ b/t/chainlint/nested-cuddled-subshell.test @@ -0,0 +1,31 @@ +( +# LINT: opening "(" cuddled with first nested subshell statement + (cd foo && + bar + ) && + +# LINT: same but "&&" missing + (cd foo && + bar + ) + +# LINT: closing ")" cuddled with final nested subshell statement + ( + cd foo && + bar) && + +# LINT: same but "&&" missing + ( + cd foo && + bar) + +# LINT: "(" and ")" cuddled with first and final subshell statements + (cd foo && + bar) && + +# LINT: same but "&&" missing + (cd foo && + bar) + + foobar +) diff --git a/t/chainlint/nested-here-doc.expect b/t/chainlint/nested-here-doc.expect new file mode 100644 index 0000000000..0c9ef1cfc6 --- /dev/null +++ b/t/chainlint/nested-here-doc.expect @@ -0,0 +1,7 @@ +cat >foop && + +( + cat && +?!AMP?! cat + foobar +>) diff --git a/t/chainlint/nested-here-doc.test b/t/chainlint/nested-here-doc.test new file mode 100644 index 0000000000..f35404bf0f --- /dev/null +++ b/t/chainlint/nested-here-doc.test @@ -0,0 +1,33 @@ +# LINT: inner "EOF" not misintrepreted as closing ARBITRARY here-doc +cat <<ARBITRARY >foop && +naddle +fub <<EOF + nozzle + noodle +EOF +formp +ARBITRARY + +( +# LINT: inner "EOF" not misintrepreted as closing INPUT_END here-doc + cat <<-\INPUT_END && + fish are mice + but geese go slow + data <<EOF + perl is lerp + and nothing else + EOF + toink + INPUT_END + +# LINT: same but missing "&&" + cat <<-\EOT + text goes here + data <<EOF + data goes here + EOF + more test here + EOT + + foobar +) diff --git a/t/chainlint/nested-subshell-comment.expect b/t/chainlint/nested-subshell-comment.expect new file mode 100644 index 0000000000..15b68d4373 --- /dev/null +++ b/t/chainlint/nested-subshell-comment.expect @@ -0,0 +1,11 @@ +( + foo && + ( + bar && + # bottles wobble while fiddles gobble + # minor numbers of cows (or do they?) + baz && + snaff +?!AMP?!>> ) + fuzzy +>) diff --git a/t/chainlint/nested-subshell-comment.test b/t/chainlint/nested-subshell-comment.test new file mode 100644 index 0000000000..0ff136ab3c --- /dev/null +++ b/t/chainlint/nested-subshell-comment.test @@ -0,0 +1,13 @@ +( + foo && + ( + bar && +# LINT: ")" in comment in nested subshell not misinterpreted as closing ")" + # bottles wobble while fiddles gobble + # minor numbers of cows (or do they?) + baz && + snaff +# LINT: missing "&&" on ')' + ) + fuzzy +) diff --git a/t/chainlint/nested-subshell.expect b/t/chainlint/nested-subshell.expect new file mode 100644 index 0000000000..c8165ad19e --- /dev/null +++ b/t/chainlint/nested-subshell.expect @@ -0,0 +1,12 @@ +( + cd foo && + ( + echo a && + echo b +>> ) >file && + cd foo && + ( + echo a + echo b +>> ) >file +>) diff --git a/t/chainlint/nested-subshell.test b/t/chainlint/nested-subshell.test new file mode 100644 index 0000000000..998b05a47d --- /dev/null +++ b/t/chainlint/nested-subshell.test @@ -0,0 +1,14 @@ +( + cd foo && + ( + echo a && + echo b + ) >file && + + cd foo && + ( +# LINT: nested multi-line subshell not presently checked for missing "&&" + echo a + echo b + ) >file +) diff --git a/t/chainlint/one-liner.expect b/t/chainlint/one-liner.expect new file mode 100644 index 0000000000..237f227349 --- /dev/null +++ b/t/chainlint/one-liner.expect @@ -0,0 +1,9 @@ +(foo && bar) && +(foo && bar) | +(foo && bar) >baz && + +?!SEMI?!(foo; bar) && +?!SEMI?!(foo; bar) | +?!SEMI?!(foo; bar) >baz + +(foo "bar; baz") diff --git a/t/chainlint/one-liner.test b/t/chainlint/one-liner.test new file mode 100644 index 0000000000..ec9acb9825 --- /dev/null +++ b/t/chainlint/one-liner.test @@ -0,0 +1,12 @@ +# LINT: top-level one-liner subshell +(foo && bar) && +(foo && bar) | +(foo && bar) >baz && + +# LINT: top-level one-liner subshell missing internal "&&" +(foo; bar) && +(foo; bar) | +(foo; bar) >baz + +# LINT: ";" in string not misinterpreted as broken &&-chain +(foo "bar; baz") diff --git a/t/chainlint/p4-filespec.expect b/t/chainlint/p4-filespec.expect new file mode 100644 index 0000000000..98b3d881fd --- /dev/null +++ b/t/chainlint/p4-filespec.expect @@ -0,0 +1,4 @@ +( + p4 print -1 //depot/fiddle#42 >file && + foobar +>) diff --git a/t/chainlint/p4-filespec.test b/t/chainlint/p4-filespec.test new file mode 100644 index 0000000000..4fd2d6e2b8 --- /dev/null +++ b/t/chainlint/p4-filespec.test @@ -0,0 +1,5 @@ +( +# LINT: Perforce revspec in filespec not misinterpreted as in-line comment + p4 print -1 //depot/fiddle#42 >file && + foobar +) diff --git a/t/chainlint/pipe.expect b/t/chainlint/pipe.expect new file mode 100644 index 0000000000..211b901dbc --- /dev/null +++ b/t/chainlint/pipe.expect @@ -0,0 +1,8 @@ +( + foo | + bar | + baz && + fish | +?!AMP?! cow + sunder +>) diff --git a/t/chainlint/pipe.test b/t/chainlint/pipe.test new file mode 100644 index 0000000000..e6af4de916 --- /dev/null +++ b/t/chainlint/pipe.test @@ -0,0 +1,12 @@ +( +# LINT: no "&&" needed on line ending with "|" + foo | + bar | + baz && + +# LINT: final line of pipe sequence ('cow') lacking "&&" + fish | + cow + + sunder +) diff --git a/t/chainlint/semicolon.expect b/t/chainlint/semicolon.expect new file mode 100644 index 0000000000..1d79384606 --- /dev/null +++ b/t/chainlint/semicolon.expect @@ -0,0 +1,20 @@ +( +?!AMP?!?!SEMI?! cat foo ; echo bar +?!SEMI?! cat foo ; echo bar +>) && +( +?!SEMI?! cat foo ; echo bar && +?!SEMI?! cat foo ; echo bar +>) && +( + echo "foo; bar" && +?!SEMI?! cat foo; echo bar +>) && +( +?!SEMI?! foo; +>) && +( +cd foo && + for i in a b c; do +?!SEMI?! echo; +> done) diff --git a/t/chainlint/semicolon.test b/t/chainlint/semicolon.test new file mode 100644 index 0000000000..d82c8ebbc0 --- /dev/null +++ b/t/chainlint/semicolon.test @@ -0,0 +1,25 @@ +( +# LINT: missing internal "&&" and ending "&&" + cat foo ; echo bar +# LINT: final statement before ")" only missing internal "&&" + cat foo ; echo bar +) && +( +# LINT: missing internal "&&" + cat foo ; echo bar && + cat foo ; echo bar +) && +( +# LINT: not fooled by semicolon in string + echo "foo; bar" && + cat foo; echo bar +) && +( +# LINT: unnecessary terminating semicolon + foo; +) && +(cd foo && + for i in a b c; do +# LINT: unnecessary terminating semicolon + echo; + done) diff --git a/t/chainlint/subshell-here-doc.expect b/t/chainlint/subshell-here-doc.expect new file mode 100644 index 0000000000..74723e7340 --- /dev/null +++ b/t/chainlint/subshell-here-doc.expect @@ -0,0 +1,11 @@ +( + echo wobba gorgo snoot wafta snurb && +?!AMP?! cat >bip + echo >bop +>) && +( + cat >bup && + cat >bup2 && + cat >bup3 && + meep +>) diff --git a/t/chainlint/subshell-here-doc.test b/t/chainlint/subshell-here-doc.test new file mode 100644 index 0000000000..f6b3ba4214 --- /dev/null +++ b/t/chainlint/subshell-here-doc.test @@ -0,0 +1,39 @@ +( +# LINT: stitch together incomplete \-ending lines +# LINT: swallow here-doc to avoid false positives in content + echo wobba \ + gorgo snoot \ + wafta snurb <<-EOF && + quoth the raven, + nevermore... + EOF + +# LINT: missing "&&" on 'cat' + cat <<EOF >bip + fish fly high + EOF + +# LINT: swallow here-doc (EOF is last line of subshell) + echo <<-\EOF >bop + gomez + morticia + wednesday + pugsly + EOF +) && +( +# LINT: swallow here-doc with arbitrary tag + cat <<-\ARBITRARY >bup && + glink + FIZZ + ARBITRARY + cat <<-'ARBITRARY2' >bup2 && + glink + FIZZ + ARBITRARY2 + cat <<-"ARBITRARY3" >bup3 && + glink + FIZZ + ARBITRARY3 + meep +) diff --git a/t/chainlint/subshell-one-liner.expect b/t/chainlint/subshell-one-liner.expect new file mode 100644 index 0000000000..51162821d7 --- /dev/null +++ b/t/chainlint/subshell-one-liner.expect @@ -0,0 +1,14 @@ +( + (foo && bar) && + (foo && bar) | + (foo && bar) >baz && +?!SEMI?! (foo; bar) && +?!SEMI?! (foo; bar) | +?!SEMI?! (foo; bar) >baz && + (foo || exit 1) && + (foo || exit 1) | + (foo || exit 1) >baz && +?!AMP?! (foo && bar) +?!AMP?!?!SEMI?! (foo && bar; baz) + foobar +>) diff --git a/t/chainlint/subshell-one-liner.test b/t/chainlint/subshell-one-liner.test new file mode 100644 index 0000000000..37fa643c20 --- /dev/null +++ b/t/chainlint/subshell-one-liner.test @@ -0,0 +1,24 @@ +( +# LINT: nested one-liner subshell + (foo && bar) && + (foo && bar) | + (foo && bar) >baz && + +# LINT: nested one-liner subshell missing internal "&&" + (foo; bar) && + (foo; bar) | + (foo; bar) >baz && + +# LINT: nested one-liner subshell with "|| exit" + (foo || exit 1) && + (foo || exit 1) | + (foo || exit 1) >baz && + +# LINT: nested one-liner subshell lacking ending "&&" + (foo && bar) + +# LINT: nested one-liner subshell missing internal "&&" and lacking ending "&&" + (foo && bar; baz) + + foobar +) diff --git a/t/chainlint/t7900-subtree.expect b/t/chainlint/t7900-subtree.expect new file mode 100644 index 0000000000..c9913429e6 --- /dev/null +++ b/t/chainlint/t7900-subtree.expect @@ -0,0 +1,10 @@ +( + chks="sub1sub2sub3sub4" && + chks_sub=$(cat | sed 's,^,sub dir/,' +>>) && + chkms="main-sub1main-sub2main-sub3main-sub4" && + chkms_sub=$(cat | sed 's,^,sub dir/,' +>>) && + subfiles=$(git ls-files) && + check_equal "$subfiles" "$chkms$chks" +>) diff --git a/t/chainlint/t7900-subtree.test b/t/chainlint/t7900-subtree.test new file mode 100644 index 0000000000..277d8358df --- /dev/null +++ b/t/chainlint/t7900-subtree.test @@ -0,0 +1,22 @@ +( + chks="sub1 +sub2 +sub3 +sub4" && + chks_sub=$(cat <<TXT | sed 's,^,sub dir/,' +$chks +TXT +) && + chkms="main-sub1 +main-sub2 +main-sub3 +main-sub4" && + chkms_sub=$(cat <<TXT | sed 's,^,sub dir/,' +$chkms +TXT +) && + + subfiles=$(git ls-files) && + check_equal "$subfiles" "$chkms +$chks" +) diff --git a/t/chainlint/while-loop.expect b/t/chainlint/while-loop.expect new file mode 100644 index 0000000000..13cff2c0a5 --- /dev/null +++ b/t/chainlint/while-loop.expect @@ -0,0 +1,11 @@ +( + while true + do +?!AMP?! echo foo + cat +?!AMP?! done + while true; do + echo foo && + cat bar + done +>) diff --git a/t/chainlint/while-loop.test b/t/chainlint/while-loop.test new file mode 100644 index 0000000000..f1df085bf0 --- /dev/null +++ b/t/chainlint/while-loop.test @@ -0,0 +1,19 @@ +( +# LINT: 'while, 'do', 'done' do not need "&&" + while true + do +# LINT: missing "&&" on 'echo' + echo foo +# LINT: last statement of while does not need "&&" + cat <<-\EOF + bar + EOF +# LINT: missing "&&" on 'done' + done + +# LINT: 'do' on same line as 'while' + while true; do + echo foo && + cat bar + done +) diff --git a/t/check-non-portable-shell.pl b/t/check-non-portable-shell.pl index e07f028437..b45bdac688 100755 --- a/t/check-non-portable-shell.pl +++ b/t/check-non-portable-shell.pl @@ -7,22 +7,46 @@ use strict; use warnings; my $exit_code=0; +my %func; sub err { my $msg = shift; + s/^\s+//; + s/\s+$//; + s/\s+/ /g; print "$ARGV:$.: error: $msg: $_\n"; $exit_code = 1; } +# glean names of shell functions +for my $i (@ARGV) { + open(my $f, '<', $i) or die "$0: $i: $!\n"; + while (<$f>) { + $func{$1} = 1 if /^\s*(\w+)\s*\(\)\s*{\s*$/; + } + close $f; +} + while (<>) { chomp; + # stitch together incomplete lines (those ending with "\") + while (s/\\$//) { + $_ .= readline; + chomp; + } + /\bsed\s+-i/ and err 'sed -i is not portable'; - /\becho\s+-[neE]/ and err 'echo with option is not portable (please use printf)'; + /\becho\s+-[neE]/ and err 'echo with option is not portable (use printf)'; /^\s*declare\s+/ and err 'arrays/declare not portable'; - /^\s*[^#]\s*which\s/ and err 'which is not portable (please use type)'; - /\btest\s+[^=]*==/ and err '"test a == b" is not portable (please use =)'; - /\bwc -l.*"\s*=/ and err '`"$(wc -l)"` is not portable (please use test_line_count)'; - /\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (please use FOO=bar && export FOO)'; + /^\s*[^#]\s*which\s/ and err 'which is not portable (use type)'; + /\btest\s+[^=]*==/ and err '"test a == b" is not portable (use =)'; + /\bwc -l.*"\s*=/ and err '`"$(wc -l)"` is not portable (use test_line_count)'; + /\bhead\s+-c\b/ and err 'head -c is not portable (use test_copy_bytes BYTES <file >out)'; + /(?:\$\(seq|^\s*seq\b)/ and err 'seq is not portable (use test_seq)'; + /\bgrep\b.*--file\b/ and err 'grep --file FILE is not portable (use grep -f FILE)'; + /\bexport\s+[A-Za-z0-9_]*=/ and err '"export FOO=bar" is not portable (use FOO=bar && export FOO)'; + /^\s*([A-Z0-9_]+=(\w+|(["']).*?\3)\s+)+(\w+)/ and exists($func{$4}) and + err '"FOO=bar shell_func" assignment extends beyond "shell_func"'; # this resets our $. for each file close ARGV if eof; } diff --git a/t/diff-lib.sh b/t/diff-lib.sh index c211dc40ee..2de880f7a5 100644 --- a/t/diff-lib.sh +++ b/t/diff-lib.sh @@ -1,6 +1,6 @@ : -sanitize_diff_raw='/^:/s/ '"\($_x40\)"' '"\($_x40\)"' \([A-Z]\)[0-9]* / \1 \2 \3# /' +sanitize_diff_raw='/^:/s/ '"\($OID_REGEX\)"' '"\($OID_REGEX\)"' \([A-Z]\)[0-9]* / \1 \2 \3# /' compare_diff_raw () { # When heuristics are improved, the score numbers would change. # Ignore them while comparing. @@ -12,7 +12,7 @@ compare_diff_raw () { test_cmp .tmp-1 .tmp-2 && rm -f .tmp-1 .tmp-2 } -sanitize_diff_raw_z='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]*$/ X X \1#/' +sanitize_diff_raw_z='/^:/s/ '"$OID_REGEX"' '"$OID_REGEX"' \([A-Z]\)[0-9]*$/ X X \1#/' compare_diff_raw_z () { # When heuristics are improved, the score numbers would change. # Ignore them while comparing. diff --git a/t/helper/test-chmtime.c b/t/helper/test-chmtime.c index 1790ceab51..aa22af48c2 100644 --- a/t/helper/test-chmtime.c +++ b/t/helper/test-chmtime.c @@ -18,20 +18,30 @@ * * Examples: * - * To just print the mtime use --verbose and set the file mtime offset to 0: + * To print the mtime and the file name use --verbose and set + * the file mtime offset to 0: * * test-tool chmtime -v +0 file * + * To print only the mtime use --get: + * + * test-tool chmtime --get file + * * To set the mtime to current time: * * test-tool chmtime =+0 file * + * To set the file mtime offset to +1 and print the new value: + * + * test-tool chmtime --get +1 file + * */ #include "test-tool.h" #include "git-compat-util.h" #include <utime.h> -static const char usage_str[] = "-v|--verbose (+|=|=+|=-|-)<seconds> <file>..."; +static const char usage_str[] = + "(-v|--verbose|-g|--get) (+|=|=+|=-|-)<seconds> <file>..."; static int timespec_arg(const char *arg, long int *set_time, int *set_eq) { @@ -47,7 +57,6 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq) } *set_time = strtol(timespec, &test, 10); if (*test) { - fprintf(stderr, "Not a base-10 integer: %s\n", arg + 1); return 0; } if ((*set_eq && *set_time < 0) || *set_eq == 2) { @@ -60,6 +69,7 @@ static int timespec_arg(const char *arg, long int *set_time, int *set_eq) int cmd__chmtime(int argc, const char **argv) { static int verbose; + static int get; int i = 1; /* no mtime change by default */ @@ -69,18 +79,34 @@ int cmd__chmtime(int argc, const char **argv) if (argc < 3) goto usage; - if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) { + if (strcmp(argv[i], "--get") == 0 || strcmp(argv[i], "-g") == 0) { + get = 1; + ++i; + } else if (strcmp(argv[i], "--verbose") == 0 || strcmp(argv[i], "-v") == 0) { verbose = 1; ++i; } - if (timespec_arg(argv[i], &set_time, &set_eq)) + + if (i == argc) { + goto usage; + } + + if (timespec_arg(argv[i], &set_time, &set_eq)) { ++i; - else + } else { + if (get == 0) { + fprintf(stderr, "Not a base-10 integer: %s\n", argv[i] + 1); + goto usage; + } + } + + if (i == argc) goto usage; for (; i < argc; i++) { struct stat sb; struct utimbuf utb; + uintmax_t mtime; if (stat(argv[i], &sb) < 0) { fprintf(stderr, "Failed to stat %s: %s\n", @@ -100,8 +126,10 @@ int cmd__chmtime(int argc, const char **argv) utb.actime = sb.st_atime; utb.modtime = set_eq ? set_time : sb.st_mtime + set_time; - if (verbose) { - uintmax_t mtime = utb.modtime < 0 ? 0: utb.modtime; + mtime = utb.modtime < 0 ? 0: utb.modtime; + if (get) { + printf("%"PRIuMAX"\n", mtime); + } else if (verbose) { printf("%"PRIuMAX"\t%s\n", mtime, argv[i]); } diff --git a/t/helper/test-delta.c b/t/helper/test-delta.c index 34c7259248..e749a49c88 100644 --- a/t/helper/test-delta.c +++ b/t/helper/test-delta.c @@ -34,8 +34,8 @@ int cmd__delta(int argc, const char **argv) return 1; } from_size = st.st_size; - from_buf = mmap(NULL, from_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (from_buf == MAP_FAILED) { + from_buf = xmalloc(from_size); + if (read_in_full(fd, from_buf, from_size) < 0) { perror(argv[2]); close(fd); return 1; @@ -48,8 +48,8 @@ int cmd__delta(int argc, const char **argv) return 1; } data_size = st.st_size; - data_buf = mmap(NULL, data_size, PROT_READ, MAP_PRIVATE, fd, 0); - if (data_buf == MAP_FAILED) { + data_buf = xmalloc(data_size); + if (read_in_full(fd, data_buf, data_size) < 0) { perror(argv[3]); close(fd); return 1; diff --git a/t/helper/test-drop-caches.c b/t/helper/test-drop-caches.c index 838760898b..f65e301f9d 100644 --- a/t/helper/test-drop-caches.c +++ b/t/helper/test-drop-caches.c @@ -2,6 +2,7 @@ #include "git-compat-util.h" #if defined(GIT_WINDOWS_NATIVE) +#include "lazyload.h" static int cmd_sync(void) { @@ -15,8 +16,8 @@ static int cmd_sync(void) if ((0 == dwRet) || (dwRet > MAX_PATH)) return error("Error getting current directory"); - if ((Buffer[0] < 'A') || (Buffer[0] > 'Z')) - return error("Invalid drive letter '%c'", Buffer[0]); + if (!has_dos_drive_prefix(Buffer)) + return error("'%s': invalid drive letter", Buffer); szVolumeAccessPath[4] = Buffer[0]; hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE, @@ -82,8 +83,7 @@ static int cmd_dropcaches(void) { HANDLE hProcess = GetCurrentProcess(); HANDLE hToken; - HMODULE ntdll; - DWORD(WINAPI *NtSetSystemInformation)(INT, PVOID, ULONG); + DECLARE_PROC_ADDR(ntdll.dll, DWORD, NtSetSystemInformation, INT, PVOID, ULONG); SYSTEM_MEMORY_LIST_COMMAND command; int status; @@ -95,14 +95,8 @@ static int cmd_dropcaches(void) CloseHandle(hToken); - ntdll = LoadLibrary("ntdll.dll"); - if (!ntdll) - return error("Can't load ntdll.dll, wrong Windows version?"); - - NtSetSystemInformation = - (DWORD(WINAPI *)(INT, PVOID, ULONG))GetProcAddress(ntdll, "NtSetSystemInformation"); - if (!NtSetSystemInformation) - return error("Can't get function addresses, wrong Windows version?"); + if (!INIT_PROC_ADDR(NtSetSystemInformation)) + return error("Could not find NtSetSystemInformation() function"); command = MemoryPurgeStandbyList; status = NtSetSystemInformation( @@ -115,8 +109,6 @@ static int cmd_dropcaches(void) else if (status != STATUS_SUCCESS) error("Unable to execute the memory list command %d", status); - FreeLibrary(ntdll); - return status; } diff --git a/t/helper/test-dump-cache-tree.c b/t/helper/test-dump-cache-tree.c index 98a4891f1d..6a3f88f5f5 100644 --- a/t/helper/test-dump-cache-tree.c +++ b/t/helper/test-dump-cache-tree.c @@ -33,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it, } else { dump_one(it, pfx, ""); - if (oidcmp(&it->oid, &ref->oid) || + if (!oideq(&it->oid, &ref->oid) || ref->entry_count != it->entry_count || ref->subtree_nr != it->subtree_nr) { /* claims to be valid but is lying */ diff --git a/t/helper/test-dump-split-index.c b/t/helper/test-dump-split-index.c index 4e2fdb5e30..63c689d6ee 100644 --- a/t/helper/test-dump-split-index.c +++ b/t/helper/test-dump-split-index.c @@ -14,13 +14,13 @@ int cmd__dump_split_index(int ac, const char **av) int i; do_read_index(&the_index, av[1], 1); - printf("own %s\n", sha1_to_hex(the_index.sha1)); + printf("own %s\n", oid_to_hex(&the_index.oid)); si = the_index.split_index; if (!si) { printf("not a split index\n"); return 0; } - printf("base %s\n", sha1_to_hex(si->base_sha1)); + printf("base %s\n", oid_to_hex(&si->base_oid)); for (i = 0; i < the_index.cache_nr; i++) { struct cache_entry *ce = the_index.cache[i]; printf("%06o %s %d\t%s\n", ce->ce_mode, diff --git a/t/helper/test-dump-untracked-cache.c b/t/helper/test-dump-untracked-cache.c index d7c55c2355..bd92fb305a 100644 --- a/t/helper/test-dump-untracked-cache.c +++ b/t/helper/test-dump-untracked-cache.c @@ -23,7 +23,7 @@ static void dump(struct untracked_cache_dir *ucd, struct strbuf *base) len = base->len; strbuf_addf(base, "%s/", ucd->name); printf("%s %s", base->buf, - sha1_to_hex(ucd->exclude_sha1)); + oid_to_hex(&ucd->exclude_oid)); if (ucd->recurse) fputs(" recurse", stdout); if (ucd->check_only) diff --git a/t/helper/test-example-decorate.c b/t/helper/test-example-decorate.c index 081115bf8e..a20a6161e4 100644 --- a/t/helper/test-example-decorate.c +++ b/t/helper/test-example-decorate.c @@ -30,10 +30,10 @@ int cmd__example_decorate(int argc, const char **argv) two = lookup_unknown_object(two_oid.hash); ret = add_decoration(&n, one, &decoration_a); if (ret) - die("BUG: when adding a brand-new object, NULL should be returned"); + BUG("when adding a brand-new object, NULL should be returned"); ret = add_decoration(&n, two, NULL); if (ret) - die("BUG: when adding a brand-new object, NULL should be returned"); + BUG("when adding a brand-new object, NULL should be returned"); /* * When re-adding an already existing object, the old decoration is @@ -41,10 +41,10 @@ int cmd__example_decorate(int argc, const char **argv) */ ret = add_decoration(&n, one, NULL); if (ret != &decoration_a) - die("BUG: when readding an already existing object, existing decoration should be returned"); + BUG("when readding an already existing object, existing decoration should be returned"); ret = add_decoration(&n, two, &decoration_b); if (ret) - die("BUG: when readding an already existing object, existing decoration should be returned"); + BUG("when readding an already existing object, existing decoration should be returned"); /* * Lookup returns the added declarations, or NULL if the object was @@ -52,14 +52,14 @@ int cmd__example_decorate(int argc, const char **argv) */ ret = lookup_decoration(&n, one); if (ret) - die("BUG: lookup should return added declaration"); + BUG("lookup should return added declaration"); ret = lookup_decoration(&n, two); if (ret != &decoration_b) - die("BUG: lookup should return added declaration"); + BUG("lookup should return added declaration"); three = lookup_unknown_object(three_oid.hash); ret = lookup_decoration(&n, three); if (ret) - die("BUG: lookup for unknown object should return NULL"); + BUG("lookup for unknown object should return NULL"); /* * The user can also loop through all entries. @@ -69,7 +69,7 @@ int cmd__example_decorate(int argc, const char **argv) objects_noticed++; } if (objects_noticed != 2) - die("BUG: should have 2 objects"); + BUG("should have 2 objects"); return 0; } diff --git a/t/helper/test-json-writer.c b/t/helper/test-json-writer.c new file mode 100644 index 0000000000..37c452535f --- /dev/null +++ b/t/helper/test-json-writer.c @@ -0,0 +1,565 @@ +#include "test-tool.h" +#include "cache.h" +#include "json-writer.h" + +static const char *expect_obj1 = "{\"a\":\"abc\",\"b\":42,\"c\":true}"; +static const char *expect_obj2 = "{\"a\":-1,\"b\":2147483647,\"c\":0}"; +static const char *expect_obj3 = "{\"a\":0,\"b\":4294967295,\"c\":9223372036854775807}"; +static const char *expect_obj4 = "{\"t\":true,\"f\":false,\"n\":null}"; +static const char *expect_obj5 = "{\"abc\\tdef\":\"abc\\\\def\"}"; +static const char *expect_obj6 = "{\"a\":3.14}"; + +static const char *pretty_obj1 = ("{\n" + " \"a\": \"abc\",\n" + " \"b\": 42,\n" + " \"c\": true\n" + "}"); +static const char *pretty_obj2 = ("{\n" + " \"a\": -1,\n" + " \"b\": 2147483647,\n" + " \"c\": 0\n" + "}"); +static const char *pretty_obj3 = ("{\n" + " \"a\": 0,\n" + " \"b\": 4294967295,\n" + " \"c\": 9223372036854775807\n" + "}"); +static const char *pretty_obj4 = ("{\n" + " \"t\": true,\n" + " \"f\": false,\n" + " \"n\": null\n" + "}"); + +static struct json_writer obj1 = JSON_WRITER_INIT; +static struct json_writer obj2 = JSON_WRITER_INIT; +static struct json_writer obj3 = JSON_WRITER_INIT; +static struct json_writer obj4 = JSON_WRITER_INIT; +static struct json_writer obj5 = JSON_WRITER_INIT; +static struct json_writer obj6 = JSON_WRITER_INIT; + +static void make_obj1(int pretty) +{ + jw_object_begin(&obj1, pretty); + { + jw_object_string(&obj1, "a", "abc"); + jw_object_intmax(&obj1, "b", 42); + jw_object_true(&obj1, "c"); + } + jw_end(&obj1); +} + +static void make_obj2(int pretty) +{ + jw_object_begin(&obj2, pretty); + { + jw_object_intmax(&obj2, "a", -1); + jw_object_intmax(&obj2, "b", 0x7fffffff); + jw_object_intmax(&obj2, "c", 0); + } + jw_end(&obj2); +} + +static void make_obj3(int pretty) +{ + jw_object_begin(&obj3, pretty); + { + jw_object_intmax(&obj3, "a", 0); + jw_object_intmax(&obj3, "b", 0xffffffff); + jw_object_intmax(&obj3, "c", 0x7fffffffffffffffULL); + } + jw_end(&obj3); +} + +static void make_obj4(int pretty) +{ + jw_object_begin(&obj4, pretty); + { + jw_object_true(&obj4, "t"); + jw_object_false(&obj4, "f"); + jw_object_null(&obj4, "n"); + } + jw_end(&obj4); +} + +static void make_obj5(int pretty) +{ + jw_object_begin(&obj5, pretty); + { + jw_object_string(&obj5, "abc" "\x09" "def", "abc" "\\" "def"); + } + jw_end(&obj5); +} + +static void make_obj6(int pretty) +{ + jw_object_begin(&obj6, pretty); + { + jw_object_double(&obj6, "a", 2, 3.14159); + } + jw_end(&obj6); +} + +static const char *expect_arr1 = "[\"abc\",42,true]"; +static const char *expect_arr2 = "[-1,2147483647,0]"; +static const char *expect_arr3 = "[0,4294967295,9223372036854775807]"; +static const char *expect_arr4 = "[true,false,null]"; + +static const char *pretty_arr1 = ("[\n" + " \"abc\",\n" + " 42,\n" + " true\n" + "]"); +static const char *pretty_arr2 = ("[\n" + " -1,\n" + " 2147483647,\n" + " 0\n" + "]"); +static const char *pretty_arr3 = ("[\n" + " 0,\n" + " 4294967295,\n" + " 9223372036854775807\n" + "]"); +static const char *pretty_arr4 = ("[\n" + " true,\n" + " false,\n" + " null\n" + "]"); + +static struct json_writer arr1 = JSON_WRITER_INIT; +static struct json_writer arr2 = JSON_WRITER_INIT; +static struct json_writer arr3 = JSON_WRITER_INIT; +static struct json_writer arr4 = JSON_WRITER_INIT; + +static void make_arr1(int pretty) +{ + jw_array_begin(&arr1, pretty); + { + jw_array_string(&arr1, "abc"); + jw_array_intmax(&arr1, 42); + jw_array_true(&arr1); + } + jw_end(&arr1); +} + +static void make_arr2(int pretty) +{ + jw_array_begin(&arr2, pretty); + { + jw_array_intmax(&arr2, -1); + jw_array_intmax(&arr2, 0x7fffffff); + jw_array_intmax(&arr2, 0); + } + jw_end(&arr2); +} + +static void make_arr3(int pretty) +{ + jw_array_begin(&arr3, pretty); + { + jw_array_intmax(&arr3, 0); + jw_array_intmax(&arr3, 0xffffffff); + jw_array_intmax(&arr3, 0x7fffffffffffffffULL); + } + jw_end(&arr3); +} + +static void make_arr4(int pretty) +{ + jw_array_begin(&arr4, pretty); + { + jw_array_true(&arr4); + jw_array_false(&arr4); + jw_array_null(&arr4); + } + jw_end(&arr4); +} + +static char *expect_nest1 = + "{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; + +static struct json_writer nest1 = JSON_WRITER_INIT; + +static void make_nest1(int pretty) +{ + jw_object_begin(&nest1, pretty); + { + jw_object_sub_jw(&nest1, "obj1", &obj1); + jw_object_sub_jw(&nest1, "arr1", &arr1); + } + jw_end(&nest1); +} + +static char *expect_inline1 = + "{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true},\"arr1\":[\"abc\",42,true]}"; + +static char *pretty_inline1 = + ("{\n" + " \"obj1\": {\n" + " \"a\": \"abc\",\n" + " \"b\": 42,\n" + " \"c\": true\n" + " },\n" + " \"arr1\": [\n" + " \"abc\",\n" + " 42,\n" + " true\n" + " ]\n" + "}"); + +static struct json_writer inline1 = JSON_WRITER_INIT; + +static void make_inline1(int pretty) +{ + jw_object_begin(&inline1, pretty); + { + jw_object_inline_begin_object(&inline1, "obj1"); + { + jw_object_string(&inline1, "a", "abc"); + jw_object_intmax(&inline1, "b", 42); + jw_object_true(&inline1, "c"); + } + jw_end(&inline1); + jw_object_inline_begin_array(&inline1, "arr1"); + { + jw_array_string(&inline1, "abc"); + jw_array_intmax(&inline1, 42); + jw_array_true(&inline1); + } + jw_end(&inline1); + } + jw_end(&inline1); +} + +static char *expect_inline2 = + "[[1,2],[3,4],{\"a\":\"abc\"}]"; + +static char *pretty_inline2 = + ("[\n" + " [\n" + " 1,\n" + " 2\n" + " ],\n" + " [\n" + " 3,\n" + " 4\n" + " ],\n" + " {\n" + " \"a\": \"abc\"\n" + " }\n" + "]"); + +static struct json_writer inline2 = JSON_WRITER_INIT; + +static void make_inline2(int pretty) +{ + jw_array_begin(&inline2, pretty); + { + jw_array_inline_begin_array(&inline2); + { + jw_array_intmax(&inline2, 1); + jw_array_intmax(&inline2, 2); + } + jw_end(&inline2); + jw_array_inline_begin_array(&inline2); + { + jw_array_intmax(&inline2, 3); + jw_array_intmax(&inline2, 4); + } + jw_end(&inline2); + jw_array_inline_begin_object(&inline2); + { + jw_object_string(&inline2, "a", "abc"); + } + jw_end(&inline2); + } + jw_end(&inline2); +} + +/* + * When super is compact, we expect subs to be compacted (even if originally + * pretty). + */ +static const char *expect_mixed1 = + ("{\"obj1\":{\"a\":\"abc\",\"b\":42,\"c\":true}," + "\"arr1\":[\"abc\",42,true]}"); + +/* + * When super is pretty, a compact sub (obj1) is kept compact and a pretty + * sub (arr1) is re-indented. + */ +static const char *pretty_mixed1 = + ("{\n" + " \"obj1\": {\"a\":\"abc\",\"b\":42,\"c\":true},\n" + " \"arr1\": [\n" + " \"abc\",\n" + " 42,\n" + " true\n" + " ]\n" + "}"); + +static struct json_writer mixed1 = JSON_WRITER_INIT; + +static void make_mixed1(int pretty) +{ + jw_init(&obj1); + jw_init(&arr1); + + make_obj1(0); /* obj1 is compact */ + make_arr1(1); /* arr1 is pretty */ + + jw_object_begin(&mixed1, pretty); + { + jw_object_sub_jw(&mixed1, "obj1", &obj1); + jw_object_sub_jw(&mixed1, "arr1", &arr1); + } + jw_end(&mixed1); +} + +static void cmp(const char *test, const struct json_writer *jw, const char *exp) +{ + if (!strcmp(jw->json.buf, exp)) + return; + + printf("error[%s]: observed '%s' expected '%s'\n", + test, jw->json.buf, exp); + exit(1); +} + +#define t(v) do { make_##v(0); cmp(#v, &v, expect_##v); } while (0) +#define p(v) do { make_##v(1); cmp(#v, &v, pretty_##v); } while (0) + +/* + * Run some basic regression tests with some known patterns. + * These tests also demonstrate how to use the jw_ API. + */ +static int unit_tests(void) +{ + /* comptact (canonical) forms */ + t(obj1); + t(obj2); + t(obj3); + t(obj4); + t(obj5); + t(obj6); + + t(arr1); + t(arr2); + t(arr3); + t(arr4); + + t(nest1); + + t(inline1); + t(inline2); + + jw_init(&obj1); + jw_init(&obj2); + jw_init(&obj3); + jw_init(&obj4); + + jw_init(&arr1); + jw_init(&arr2); + jw_init(&arr3); + jw_init(&arr4); + + jw_init(&inline1); + jw_init(&inline2); + + /* pretty forms */ + p(obj1); + p(obj2); + p(obj3); + p(obj4); + + p(arr1); + p(arr2); + p(arr3); + p(arr4); + + p(inline1); + p(inline2); + + /* mixed forms */ + t(mixed1); + jw_init(&mixed1); + p(mixed1); + + return 0; +} + +static void get_s(int line_nr, char **s_in) +{ + *s_in = strtok(NULL, " "); + if (!*s_in) + die("line[%d]: expected: <s>", line_nr); +} + +static void get_i(int line_nr, intmax_t *s_in) +{ + char *s; + char *endptr; + + get_s(line_nr, &s); + + *s_in = strtol(s, &endptr, 10); + if (*endptr || errno == ERANGE) + die("line[%d]: invalid integer value", line_nr); +} + +static void get_d(int line_nr, double *s_in) +{ + char *s; + char *endptr; + + get_s(line_nr, &s); + + *s_in = strtod(s, &endptr); + if (*endptr || errno == ERANGE) + die("line[%d]: invalid float value", line_nr); +} + +static int pretty; + +#define MAX_LINE_LENGTH (64 * 1024) + +static char *get_trimmed_line(char *buf, int buf_size) +{ + int len; + + if (!fgets(buf, buf_size, stdin)) + return NULL; + + len = strlen(buf); + while (len > 0) { + char c = buf[len - 1]; + if (c == '\n' || c == '\r' || c == ' ' || c == '\t') + buf[--len] = 0; + else + break; + } + + while (*buf == ' ' || *buf == '\t') + buf++; + + return buf; +} + +static int scripted(void) +{ + struct json_writer jw = JSON_WRITER_INIT; + char buf[MAX_LINE_LENGTH]; + char *line; + int line_nr = 0; + + line = get_trimmed_line(buf, MAX_LINE_LENGTH); + if (!line) + return 0; + + if (!strcmp(line, "object")) + jw_object_begin(&jw, pretty); + else if (!strcmp(line, "array")) + jw_array_begin(&jw, pretty); + else + die("expected first line to be 'object' or 'array'"); + + while ((line = get_trimmed_line(buf, MAX_LINE_LENGTH)) != NULL) { + char *verb; + char *key; + char *s_value; + intmax_t i_value; + double d_value; + + line_nr++; + + verb = strtok(line, " "); + + if (!strcmp(verb, "end")) { + jw_end(&jw); + } + else if (!strcmp(verb, "object-string")) { + get_s(line_nr, &key); + get_s(line_nr, &s_value); + jw_object_string(&jw, key, s_value); + } + else if (!strcmp(verb, "object-int")) { + get_s(line_nr, &key); + get_i(line_nr, &i_value); + jw_object_intmax(&jw, key, i_value); + } + else if (!strcmp(verb, "object-double")) { + get_s(line_nr, &key); + get_i(line_nr, &i_value); + get_d(line_nr, &d_value); + jw_object_double(&jw, key, i_value, d_value); + } + else if (!strcmp(verb, "object-true")) { + get_s(line_nr, &key); + jw_object_true(&jw, key); + } + else if (!strcmp(verb, "object-false")) { + get_s(line_nr, &key); + jw_object_false(&jw, key); + } + else if (!strcmp(verb, "object-null")) { + get_s(line_nr, &key); + jw_object_null(&jw, key); + } + else if (!strcmp(verb, "object-object")) { + get_s(line_nr, &key); + jw_object_inline_begin_object(&jw, key); + } + else if (!strcmp(verb, "object-array")) { + get_s(line_nr, &key); + jw_object_inline_begin_array(&jw, key); + } + else if (!strcmp(verb, "array-string")) { + get_s(line_nr, &s_value); + jw_array_string(&jw, s_value); + } + else if (!strcmp(verb, "array-int")) { + get_i(line_nr, &i_value); + jw_array_intmax(&jw, i_value); + } + else if (!strcmp(verb, "array-double")) { + get_i(line_nr, &i_value); + get_d(line_nr, &d_value); + jw_array_double(&jw, i_value, d_value); + } + else if (!strcmp(verb, "array-true")) + jw_array_true(&jw); + else if (!strcmp(verb, "array-false")) + jw_array_false(&jw); + else if (!strcmp(verb, "array-null")) + jw_array_null(&jw); + else if (!strcmp(verb, "array-object")) + jw_array_inline_begin_object(&jw); + else if (!strcmp(verb, "array-array")) + jw_array_inline_begin_array(&jw); + else + die("unrecognized token: '%s'", verb); + } + + if (!jw_is_terminated(&jw)) + die("json not terminated: '%s'", jw.json.buf); + + printf("%s\n", jw.json.buf); + + strbuf_release(&jw.json); + return 0; +} + +int cmd__json_writer(int argc, const char **argv) +{ + argc--; /* skip over "json-writer" arg */ + argv++; + + if (argc > 0 && argv[0][0] == '-') { + if (!strcmp(argv[0], "-u") || !strcmp(argv[0], "--unit")) + return unit_tests(); + + if (!strcmp(argv[0], "-p") || !strcmp(argv[0], "--pretty")) + pretty = 1; + } + + return scripted(); +} diff --git a/t/helper/test-path-utils.c b/t/helper/test-path-utils.c index e115d44ac2..ae091d9b3e 100644 --- a/t/helper/test-path-utils.c +++ b/t/helper/test-path-utils.c @@ -1,6 +1,7 @@ #include "test-tool.h" #include "cache.h" #include "string-list.h" +#include "utf8.h" /* * A "string_list_each_func_t" function that normalizes an entry from @@ -171,6 +172,11 @@ static struct test_data dirname_data[] = { { NULL, NULL } }; +static int is_dotgitmodules(const char *path) +{ + return is_hfs_dotgitmodules(path) || is_ntfs_dotgitmodules(path); +} + int cmd__path_utils(int argc, const char **argv) { if (argc == 3 && !strcmp(argv[1], "normalize_path_copy")) { @@ -271,6 +277,20 @@ int cmd__path_utils(int argc, const char **argv) if (argc == 2 && !strcmp(argv[1], "dirname")) return test_function(dirname_data, posix_dirname, argv[1]); + if (argc > 2 && !strcmp(argv[1], "is_dotgitmodules")) { + int res = 0, expect = 1, i; + for (i = 2; i < argc; i++) + if (!strcmp("--not", argv[i])) + expect = !expect; + else if (expect != is_dotgitmodules(argv[i])) + res = error("'%s' is %s.gitmodules", argv[i], + expect ? "not " : ""); + else + fprintf(stderr, "ok: '%s' is %s.gitmodules\n", + argv[i], expect ? "" : "not "); + return !!res; + } + 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 new file mode 100644 index 0000000000..30775f986f --- /dev/null +++ b/t/helper/test-pkt-line.c @@ -0,0 +1,97 @@ +#include "cache.h" +#include "pkt-line.h" + +static void pack_line(const char *line) +{ + if (!strcmp(line, "0000") || !strcmp(line, "0000\n")) + packet_flush(1); + else if (!strcmp(line, "0001") || !strcmp(line, "0001\n")) + packet_delim(1); + else + packet_write_fmt(1, "%s", line); +} + +static void pack(int argc, const char **argv) +{ + if (argc) { /* read from argv */ + int i; + for (i = 0; i < argc; i++) + pack_line(argv[i]); + } else { /* read from stdin */ + char line[LARGE_PACKET_MAX]; + while (fgets(line, sizeof(line), stdin)) { + pack_line(line); + } + } +} + +static void unpack(void) +{ + struct packet_reader reader; + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); + + while (packet_reader_read(&reader) != PACKET_READ_EOF) { + switch (reader.status) { + case PACKET_READ_EOF: + break; + case PACKET_READ_NORMAL: + printf("%s\n", reader.line); + break; + case PACKET_READ_FLUSH: + printf("0000\n"); + break; + case PACKET_READ_DELIM: + printf("0001\n"); + break; + } + } +} + +static void unpack_sideband(void) +{ + struct packet_reader reader; + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_CHOMP_NEWLINE); + + while (packet_reader_read(&reader) != PACKET_READ_EOF) { + int band; + int fd; + + switch (reader.status) { + case PACKET_READ_EOF: + break; + case PACKET_READ_NORMAL: + band = reader.line[0] & 0xff; + if (band < 1 || band > 2) + die("unexpected side band %d", band); + fd = band; + + write_or_die(fd, reader.line + 1, reader.pktlen - 1); + break; + case PACKET_READ_FLUSH: + return; + case PACKET_READ_DELIM: + break; + } + } +} + +int cmd_main(int argc, const char **argv) +{ + if (argc < 2) + die("too few arguments"); + + if (!strcmp(argv[1], "pack")) + pack(argc - 2, argv + 2); + else if (!strcmp(argv[1], "unpack")) + unpack(); + else if (!strcmp(argv[1], "unpack-sideband")) + unpack_sideband(); + else + die("invalid argument '%s'", argv[1]); + + return 0; +} diff --git a/t/helper/test-reach.c b/t/helper/test-reach.c new file mode 100644 index 0000000000..eb21103998 --- /dev/null +++ b/t/helper/test-reach.c @@ -0,0 +1,130 @@ +#include "test-tool.h" +#include "cache.h" +#include "commit.h" +#include "commit-reach.h" +#include "config.h" +#include "parse-options.h" +#include "ref-filter.h" +#include "string-list.h" +#include "tag.h" + +static void print_sorted_commit_ids(struct commit_list *list) +{ + int i; + struct string_list s = STRING_LIST_INIT_DUP; + + while (list) { + string_list_append(&s, oid_to_hex(&list->item->object.oid)); + list = list->next; + } + + string_list_sort(&s); + + for (i = 0; i < s.nr; i++) + printf("%s\n", s.items[i].string); + + string_list_clear(&s, 0); +} + +int cmd__reach(int ac, const char **av) +{ + struct object_id oid_A, oid_B; + struct commit *A, *B; + struct commit_list *X, *Y; + struct commit **X_array; + int X_nr, X_alloc; + struct strbuf buf = STRBUF_INIT; + struct repository *r = the_repository; + + setup_git_directory(); + + if (ac < 2) + exit(1); + + A = B = NULL; + X = Y = NULL; + X_nr = 0; + X_alloc = 16; + ALLOC_ARRAY(X_array, X_alloc); + + while (strbuf_getline(&buf, stdin) != EOF) { + struct object_id oid; + struct object *o; + struct commit *c; + if (buf.len < 3) + continue; + + if (get_oid_committish(buf.buf + 2, &oid)) + die("failed to resolve %s", buf.buf + 2); + + o = parse_object(r, &oid); + o = deref_tag_noverify(o); + + if (!o) + die("failed to load commit for input %s resulting in oid %s\n", + buf.buf, oid_to_hex(&oid)); + + c = object_as_type(r, o, OBJ_COMMIT, 0); + + if (!c) + die("failed to load commit for input %s resulting in oid %s\n", + buf.buf, oid_to_hex(&oid)); + + switch (buf.buf[0]) { + case 'A': + oidcpy(&oid_A, &oid); + A = c; + break; + + case 'B': + oidcpy(&oid_B, &oid); + B = c; + break; + + case 'X': + commit_list_insert(c, &X); + ALLOC_GROW(X_array, X_nr + 1, X_alloc); + X_array[X_nr++] = c; + break; + + case 'Y': + commit_list_insert(c, &Y); + break; + + default: + die("unexpected start of line: %c", buf.buf[0]); + } + } + strbuf_release(&buf); + + if (!strcmp(av[1], "ref_newer")) + printf("%s(A,B):%d\n", av[1], ref_newer(&oid_A, &oid_B)); + else if (!strcmp(av[1], "in_merge_bases")) + printf("%s(A,B):%d\n", av[1], in_merge_bases(A, B)); + else if (!strcmp(av[1], "is_descendant_of")) + printf("%s(A,X):%d\n", av[1], is_descendant_of(A, X)); + else if (!strcmp(av[1], "get_merge_bases_many")) { + struct commit_list *list = get_merge_bases_many(A, X_nr, X_array); + printf("%s(A,X):\n", av[1]); + print_sorted_commit_ids(list); + } else if (!strcmp(av[1], "reduce_heads")) { + struct commit_list *list = reduce_heads(X); + printf("%s(X):\n", av[1]); + print_sorted_commit_ids(list); + } else if (!strcmp(av[1], "can_all_from_reach")) { + printf("%s(X,Y):%d\n", av[1], can_all_from_reach(X, Y, 1)); + } else if (!strcmp(av[1], "commit_contains")) { + struct ref_filter filter; + struct contains_cache cache; + init_contains_cache(&cache); + + if (ac > 2 && !strcmp(av[2], "--tag")) + filter.with_commit_tag_algo = 1; + else + filter.with_commit_tag_algo = 0; + + printf("%s(_,A,X,_):%d\n", av[1], commit_contains(&filter, A, X, &cache)); + } + + exit(0); +} diff --git a/t/helper/test-read-midx.c b/t/helper/test-read-midx.c new file mode 100644 index 0000000000..831b586d02 --- /dev/null +++ b/t/helper/test-read-midx.c @@ -0,0 +1,51 @@ +#include "test-tool.h" +#include "cache.h" +#include "midx.h" +#include "repository.h" +#include "object-store.h" + +static int read_midx_file(const char *object_dir) +{ + uint32_t i; + struct multi_pack_index *m = load_multi_pack_index(object_dir, 1); + + if (!m) + return 1; + + printf("header: %08x %d %d %d\n", + m->signature, + m->version, + m->num_chunks, + m->num_packs); + + printf("chunks:"); + + if (m->chunk_pack_names) + printf(" pack-names"); + if (m->chunk_oid_fanout) + printf(" oid-fanout"); + if (m->chunk_oid_lookup) + printf(" oid-lookup"); + if (m->chunk_object_offsets) + printf(" object-offsets"); + if (m->chunk_large_offsets) + printf(" large-offsets"); + + printf("\nnum_objects: %d\n", m->num_objects); + + printf("packs:\n"); + for (i = 0; i < m->num_packs; i++) + printf("%s\n", m->pack_names[i]); + + printf("object-dir: %s\n", m->object_dir); + + return 0; +} + +int cmd__read_midx(int argc, const char **argv) +{ + if (argc != 2) + usage("read-midx <object-dir>"); + + return read_midx_file(argv[1]); +} diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index 7c4f43746e..e9e0541276 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -3,6 +3,7 @@ #include "refs.h" #include "worktree.h" #include "object-store.h" +#include "repository.h" static const char *notnull(const char *arg, const char *name) { @@ -23,7 +24,7 @@ static const char **get_store(const char **argv, struct ref_store **refs) if (!argv[0]) { die("ref store required"); } else if (!strcmp(argv[0], "main")) { - *refs = get_main_ref_store(); + *refs = get_main_ref_store(the_repository); } else if (skip_prefix(argv[0], "submodule:", &gitdir)) { struct strbuf sb = STRBUF_INIT; int ret; diff --git a/t/helper/test-repository.c b/t/helper/test-repository.c new file mode 100644 index 0000000000..2762ca6562 --- /dev/null +++ b/t/helper/test-repository.c @@ -0,0 +1,82 @@ +#include "test-tool.h" +#include "cache.h" +#include "commit-graph.h" +#include "commit.h" +#include "config.h" +#include "object-store.h" +#include "object.h" +#include "repository.h" +#include "tree.h" + +static void test_parse_commit_in_graph(const char *gitdir, const char *worktree, + const struct object_id *commit_oid) +{ + struct repository r; + struct commit *c; + struct commit_list *parent; + + repo_init(&r, gitdir, worktree); + + c = lookup_commit(&r, commit_oid); + + if (!parse_commit_in_graph(&r, c)) + die("Couldn't parse commit"); + + printf("%"PRItime, c->date); + for (parent = c->parents; parent; parent = parent->next) + printf(" %s", oid_to_hex(&parent->item->object.oid)); + printf("\n"); + + repo_clear(&r); +} + +static void test_get_commit_tree_in_graph(const char *gitdir, + const char *worktree, + const struct object_id *commit_oid) +{ + struct repository r; + struct commit *c; + struct tree *tree; + + repo_init(&r, gitdir, worktree); + + c = lookup_commit(&r, commit_oid); + + /* + * get_commit_tree_in_graph does not automatically parse the commit, so + * parse it first. + */ + if (!parse_commit_in_graph(&r, c)) + die("Couldn't parse commit"); + tree = get_commit_tree_in_graph(&r, c); + if (!tree) + die("Couldn't get commit tree"); + + printf("%s\n", oid_to_hex(&tree->object.oid)); + + repo_clear(&r); +} + +int cmd__repository(int argc, const char **argv) +{ + if (argc < 2) + die("must have at least 2 arguments"); + if (!strcmp(argv[1], "parse_commit_in_graph")) { + struct object_id oid; + if (argc < 5) + die("not enough arguments"); + if (parse_oid_hex(argv[4], &oid, &argv[4])) + die("cannot parse oid '%s'", argv[4]); + test_parse_commit_in_graph(argv[2], argv[3], &oid); + } else if (!strcmp(argv[1], "get_commit_tree_in_graph")) { + struct object_id oid; + if (argc < 5) + die("not enough arguments"); + if (parse_oid_hex(argv[4], &oid, &argv[4])) + die("cannot parse oid '%s'", argv[4]); + test_get_commit_tree_in_graph(argv[2], argv[3], &oid); + } else { + die("unrecognized '%s'", argv[1]); + } + return 0; +} diff --git a/t/helper/test-scrap-cache-tree.c b/t/helper/test-scrap-cache-tree.c index d26d3e7c8b..393f1604ff 100644 --- a/t/helper/test-scrap-cache-tree.c +++ b/t/helper/test-scrap-cache-tree.c @@ -4,10 +4,10 @@ #include "tree.h" #include "cache-tree.h" -static struct lock_file index_lock; - int cmd__scrap_cache_tree(int ac, const char **av) { + struct lock_file index_lock = LOCK_INIT; + setup_git_directory(); hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); if (read_cache() < 0) diff --git a/t/helper/test-submodule-config.c b/t/helper/test-submodule-config.c index 5c6e4b010d..e2692746df 100644 --- a/t/helper/test-submodule-config.c +++ b/t/helper/test-submodule-config.c @@ -49,9 +49,11 @@ int cmd__submodule_config(int argc, const char **argv) die_usage(argc, argv, "Commit not found."); if (lookup_name) { - submodule = submodule_from_name(&commit_oid, path_or_name); + submodule = submodule_from_name(the_repository, + &commit_oid, path_or_name); } else - submodule = submodule_from_path(&commit_oid, path_or_name); + submodule = submodule_from_path(the_repository, + &commit_oid, path_or_name); if (!submodule) die_usage(argc, argv, "Submodule not found."); @@ -65,7 +67,7 @@ int cmd__submodule_config(int argc, const char **argv) arg += 2; } - submodule_free(); + submodule_free(the_repository); return 0; } diff --git a/t/helper/test-tool.c b/t/helper/test-tool.c index 87066ced62..b87a8c1f22 100644 --- a/t/helper/test-tool.c +++ b/t/helper/test-tool.c @@ -19,6 +19,7 @@ static struct test_cmd cmds[] = { { "genrandom", cmd__genrandom }, { "hashmap", cmd__hashmap }, { "index-version", cmd__index_version }, + { "json-writer", cmd__json_writer }, { "lazy-init-name-hash", cmd__lazy_init_name_hash }, { "match-trees", cmd__match_trees }, { "mergesort", cmd__mergesort }, @@ -26,9 +27,12 @@ static struct test_cmd cmds[] = { { "online-cpus", cmd__online_cpus }, { "path-utils", cmd__path_utils }, { "prio-queue", cmd__prio_queue }, + { "reach", cmd__reach }, { "read-cache", cmd__read_cache }, + { "read-midx", cmd__read_midx }, { "ref-store", cmd__ref_store }, { "regex", cmd__regex }, + { "repository", cmd__repository }, { "revision-walking", cmd__revision_walking }, { "run-command", cmd__run_command }, { "scrap-cache-tree", cmd__scrap_cache_tree }, @@ -41,6 +45,9 @@ static struct test_cmd cmds[] = { { "subprocess", cmd__subprocess }, { "urlmatch-normalization", cmd__urlmatch_normalization }, { "wildmatch", cmd__wildmatch }, +#ifdef GIT_WINDOWS_NATIVE + { "windows-named-pipe", cmd__windows_named_pipe }, +#endif { "write-cache", cmd__write_cache }, }; @@ -48,6 +55,7 @@ int cmd_main(int argc, const char **argv) { int i; + BUG_exit_code = 99; if (argc < 2) die("I need a test name!"); diff --git a/t/helper/test-tool.h b/t/helper/test-tool.h index 7116ddfb94..e074957279 100644 --- a/t/helper/test-tool.h +++ b/t/helper/test-tool.h @@ -1,6 +1,8 @@ #ifndef __TEST_TOOL_H__ #define __TEST_TOOL_H__ +#include "git-compat-util.h" + int cmd__chmtime(int argc, const char **argv); int cmd__config(int argc, const char **argv); int cmd__ctype(int argc, const char **argv); @@ -13,6 +15,7 @@ int cmd__example_decorate(int argc, const char **argv); int cmd__genrandom(int argc, const char **argv); int cmd__hashmap(int argc, const char **argv); int cmd__index_version(int argc, const char **argv); +int cmd__json_writer(int argc, const char **argv); int cmd__lazy_init_name_hash(int argc, const char **argv); int cmd__match_trees(int argc, const char **argv); int cmd__mergesort(int argc, const char **argv); @@ -20,9 +23,12 @@ int cmd__mktemp(int argc, const char **argv); int cmd__online_cpus(int argc, const char **argv); int cmd__path_utils(int argc, const char **argv); int cmd__prio_queue(int argc, const char **argv); +int cmd__reach(int argc, const char **argv); int cmd__read_cache(int argc, const char **argv); +int cmd__read_midx(int argc, const char **argv); int cmd__ref_store(int argc, const char **argv); int cmd__regex(int argc, const char **argv); +int cmd__repository(int argc, const char **argv); int cmd__revision_walking(int argc, const char **argv); int cmd__run_command(int argc, const char **argv); int cmd__scrap_cache_tree(int argc, const char **argv); @@ -35,6 +41,9 @@ int cmd__submodule_config(int argc, const char **argv); int cmd__subprocess(int argc, const char **argv); int cmd__urlmatch_normalization(int argc, const char **argv); int cmd__wildmatch(int argc, const char **argv); +#ifdef GIT_WINDOWS_NATIVE +int cmd__windows_named_pipe(int argc, const char **argv); +#endif int cmd__write_cache(int argc, const char **argv); #endif diff --git a/t/helper/test-windows-named-pipe.c b/t/helper/test-windows-named-pipe.c new file mode 100644 index 0000000000..b4b752b01a --- /dev/null +++ b/t/helper/test-windows-named-pipe.c @@ -0,0 +1,72 @@ +#include "test-tool.h" +#include "git-compat-util.h" +#include "strbuf.h" + +#ifdef GIT_WINDOWS_NATIVE +static const char *usage_string = "<pipe-filename>"; + +#define TEST_BUFSIZE (4096) + +int cmd__windows_named_pipe(int argc, const char **argv) +{ + const char *filename; + struct strbuf pathname = STRBUF_INIT; + int err; + HANDLE h; + BOOL connected; + char buf[TEST_BUFSIZE + 1]; + + if (argc < 2) + goto print_usage; + filename = argv[1]; + if (strchr(filename, '/') || strchr(filename, '\\')) + goto print_usage; + strbuf_addf(&pathname, "//./pipe/%s", filename); + + /* + * Create a single instance of the server side of the named pipe. + * This will allow exactly one client instance to connect to it. + */ + h = CreateNamedPipeA( + pathname.buf, + PIPE_ACCESS_INBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE, + PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + TEST_BUFSIZE, TEST_BUFSIZE, 0, NULL); + if (h == INVALID_HANDLE_VALUE) { + err = err_win_to_posix(GetLastError()); + fprintf(stderr, "CreateNamedPipe failed: %s\n", + strerror(err)); + return err; + } + + connected = ConnectNamedPipe(h, NULL) + ? TRUE + : (GetLastError() == ERROR_PIPE_CONNECTED); + if (!connected) { + err = err_win_to_posix(GetLastError()); + fprintf(stderr, "ConnectNamedPipe failed: %s\n", + strerror(err)); + CloseHandle(h); + return err; + } + + while (1) { + DWORD nbr; + BOOL success = ReadFile(h, buf, TEST_BUFSIZE, &nbr, NULL); + if (!success || nbr == 0) + break; + buf[nbr] = 0; + + write(1, buf, nbr); + } + + DisconnectNamedPipe(h); + CloseHandle(h); + return 0; + +print_usage: + fprintf(stderr, "usage: %s %s\n", argv[0], usage_string); + return 1; +} +#endif diff --git a/t/helper/test-write-cache.c b/t/helper/test-write-cache.c index 017dc30380..8837717d36 100644 --- a/t/helper/test-write-cache.c +++ b/t/helper/test-write-cache.c @@ -2,22 +2,18 @@ #include "cache.h" #include "lockfile.h" -static struct lock_file index_lock; - int cmd__write_cache(int argc, const char **argv) { - int i, cnt = 1, lockfd; + struct lock_file index_lock = LOCK_INIT; + int i, cnt = 1; if (argc == 2) cnt = strtol(argv[1], NULL, 0); setup_git_directory(); read_cache(); for (i = 0; i < cnt; i++) { - lockfd = hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); - if (0 <= lockfd) { - write_locked_index(&the_index, &index_lock, COMMIT_LOCK); - } else { - rollback_lock_file(&index_lock); - } + hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK)) + die("unable to write index file"); } return 0; diff --git a/t/lib-diff-alternative.sh b/t/lib-diff-alternative.sh index 8b4dbf22d2..8d1e408bb5 100644 --- a/t/lib-diff-alternative.sh +++ b/t/lib-diff-alternative.sh @@ -59,9 +59,11 @@ int main(int argc, char **argv) } EOF - cat >expect <<\EOF + file1=$(git rev-parse --short $(git hash-object file1)) + file2=$(git rev-parse --short $(git hash-object file2)) + cat >expect <<EOF diff --git a/file1 b/file2 -index 6faa5a3..e3af329 100644 +index $file1..$file2 100644 --- a/file1 +++ b/file2 @@ -1,26 +1,25 @@ @@ -136,9 +138,11 @@ e f EOF - cat >expect <<\EOF + uniq1=$(git rev-parse --short $(git hash-object uniq1)) + uniq2=$(git rev-parse --short $(git hash-object uniq2)) + cat >expect <<EOF diff --git a/uniq1 b/uniq2 -index b414108..0fdf397 100644 +index $uniq1..$uniq2 100644 --- a/uniq1 +++ b/uniq2 @@ -1,6 +1,6 @@ diff --git a/t/lib-gpg.sh b/t/lib-gpg.sh index a5d3b2cbaa..3fe02876c1 100755 --- a/t/lib-gpg.sh +++ b/t/lib-gpg.sh @@ -38,7 +38,33 @@ then "$TEST_DIRECTORY"/lib-gpg/ownertrust && gpg --homedir "${GNUPGHOME}" </dev/null >/dev/null 2>&1 \ --sign -u committer@example.com && - test_set_prereq GPG + test_set_prereq GPG && + # Available key info: + # * see t/lib-gpg/gpgsm-gen-key.in + # To generate new certificate: + # * no passphrase + # gpgsm --homedir /tmp/gpghome/ \ + # -o /tmp/gpgsm.crt.user \ + # --generate-key \ + # --batch t/lib-gpg/gpgsm-gen-key.in + # To import certificate: + # gpgsm --homedir /tmp/gpghome/ \ + # --import /tmp/gpgsm.crt.user + # To export into a .p12 we can later import: + # gpgsm --homedir /tmp/gpghome/ \ + # -o t/lib-gpg/gpgsm_cert.p12 \ + # --export-secret-key-p12 "committer@example.com" + echo | gpgsm --homedir "${GNUPGHOME}" 2>/dev/null \ + --passphrase-fd 0 --pinentry-mode loopback \ + --import "$TEST_DIRECTORY"/lib-gpg/gpgsm_cert.p12 && + gpgsm --homedir "${GNUPGHOME}" 2>/dev/null -K \ + | grep fingerprint: | cut -d" " -f4 | tr -d '\n' > \ + ${GNUPGHOME}/trustlist.txt && + echo " S relax" >> ${GNUPGHOME}/trustlist.txt && + (gpgconf --kill gpg-agent >/dev/null 2>&1 || : ) && + echo hello | gpgsm --homedir "${GNUPGHOME}" >/dev/null \ + -u committer@example.com -o /dev/null --sign - 2>&1 && + test_set_prereq GPGSM ;; esac fi diff --git a/t/lib-gpg/gpgsm-gen-key.in b/t/lib-gpg/gpgsm-gen-key.in new file mode 100644 index 0000000000..a7fd87c069 --- /dev/null +++ b/t/lib-gpg/gpgsm-gen-key.in @@ -0,0 +1,8 @@ +Key-Type: RSA +Key-Length: 2048 +Key-Usage: sign +Serial: random +Name-DN: CN=C O Mitter, O=Example, SN=C O, GN=Mitter +Name-Email: committer@example.com +Not-Before: 1970-01-01 00:00:00 +Not-After: 3000-01-01 00:00:00 diff --git a/t/lib-gpg/gpgsm_cert.p12 b/t/lib-gpg/gpgsm_cert.p12 Binary files differnew file mode 100644 index 0000000000..94ffad0d31 --- /dev/null +++ b/t/lib-gpg/gpgsm_cert.p12 diff --git a/t/lib-httpd.sh b/t/lib-httpd.sh index 435a37465a..a8729f8232 100644 --- a/t/lib-httpd.sh +++ b/t/lib-httpd.sh @@ -132,6 +132,7 @@ prepare_httpd() { cp "$TEST_PATH"/passwd "$HTTPD_ROOT_PATH" install_script broken-smart-http.sh install_script error.sh + install_script apply-one-time-sed.sh ln -s "$LIB_HTTPD_MODULE_PATH" "$HTTPD_ROOT_PATH/modules" @@ -287,3 +288,24 @@ expect_askpass() { test_cmp "$TRASH_DIRECTORY/askpass-expect" \ "$TRASH_DIRECTORY/askpass-query" } + +strip_access_log() { + sed -e " + s/^.* \"// + s/\"// + s/ [1-9][0-9]*\$// + s/^GET /GET / + " "$HTTPD_ROOT_PATH"/access.log +} + +# Requires one argument: the name of a file containing the expected stripped +# access log entries. +check_access_log() { + sort "$1" >"$1".sorted && + strip_access_log >access.log.stripped && + sort access.log.stripped >access.log.sorted && + if ! test_cmp "$1".sorted access.log.sorted + then + test_cmp "$1" access.log.stripped + fi +} diff --git a/t/lib-httpd/apache.conf b/t/lib-httpd/apache.conf index 724d9ae462..581c010d8f 100644 --- a/t/lib-httpd/apache.conf +++ b/t/lib-httpd/apache.conf @@ -111,9 +111,14 @@ Alias /auth/dumb/ www/auth/dumb/ SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} SetEnv GIT_HTTP_EXPORT_ALL </LocationMatch> +<LocationMatch /one_time_sed/> + SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH} + SetEnv GIT_HTTP_EXPORT_ALL +</LocationMatch> ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1 ScriptAlias /broken_smart/ broken-smart-http.sh/ ScriptAlias /error/ error.sh/ +ScriptAliasMatch /one_time_sed/(.*) apply-one-time-sed.sh/$1 <Directory ${GIT_EXEC_PATH}> Options FollowSymlinks </Directory> @@ -123,6 +128,9 @@ ScriptAlias /error/ error.sh/ <Files error.sh> Options ExecCGI </Files> +<Files apply-one-time-sed.sh> + Options ExecCGI +</Files> <Files ${GIT_EXEC_PATH}/git-http-backend> Options ExecCGI </Files> diff --git a/t/lib-httpd/apply-one-time-sed.sh b/t/lib-httpd/apply-one-time-sed.sh new file mode 100644 index 0000000000..fcef728925 --- /dev/null +++ b/t/lib-httpd/apply-one-time-sed.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# If "one-time-sed" exists in $HTTPD_ROOT_PATH, run sed on the HTTP response, +# using the contents of "one-time-sed" as the sed command to be run. If the +# response was modified as a result, delete "one-time-sed" so that subsequent +# HTTP responses are no longer modified. +# +# This can be used to simulate the effects of the repository changing in +# between HTTP request-response pairs. +if [ -e one-time-sed ]; then + "$GIT_EXEC_PATH/git-http-backend" >out + sed "$(cat one-time-sed)" <out >out_modified + + if diff out out_modified >/dev/null; then + cat out + else + cat out_modified + rm one-time-sed + fi +else + "$GIT_EXEC_PATH/git-http-backend" +fi diff --git a/t/lib-pack.sh b/t/lib-pack.sh index 501078249d..c4d907a450 100644 --- a/t/lib-pack.sh +++ b/t/lib-pack.sh @@ -79,6 +79,18 @@ pack_obj () { ;; esac + # If it's not a delta, we can convince pack-objects to generate a pack + # with just our entry, and then strip off the header (12 bytes) and + # trailer (20 bytes). + if test -z "$2" + then + echo "$1" | git pack-objects --stdout >pack_obj.tmp && + size=$(wc -c <pack_obj.tmp) && + dd if=pack_obj.tmp bs=1 count=$((size - 20 - 12)) skip=12 && + rm -f pack_obj.tmp + return + fi + echo >&2 "BUG: don't know how to print $1${2:+ (from $2)}" return 1 } diff --git a/t/lib-rebase.sh b/t/lib-rebase.sh index 25a77ee5cb..2ca9fb69d6 100644 --- a/t/lib-rebase.sh +++ b/t/lib-rebase.sh @@ -14,8 +14,8 @@ # specified line. # # "<cmd> <lineno>" -- add a line with the specified command -# ("squash", "fixup", "edit", "reword" or "drop") and the SHA1 taken -# from the specified line. +# ("pick", "squash", "fixup", "edit", "reword" or "drop") and the +# SHA1 taken from the specified line. # # "exec_cmd_with_args" -- add an "exec cmd with args" line. # @@ -47,7 +47,7 @@ set_fake_editor () { action=pick for line in $FAKE_LINES; do case $line in - squash|fixup|edit|reword|drop) + pick|squash|fixup|edit|reword|drop) action="$line";; exec*) echo "$line" | sed 's/_/ /g' >> "$1";; diff --git a/t/lib-submodule-update.sh b/t/lib-submodule-update.sh index 1f38a85371..016391723c 100755 --- a/t/lib-submodule-update.sh +++ b/t/lib-submodule-update.sh @@ -755,7 +755,7 @@ test_submodule_recursing_with_args_common() { : >sub1/untrackedfile && test_must_fail $command replace_sub1_with_file && test_superproject_content origin/add_sub1 && - test_submodule_content sub1 origin/add_sub1 + test_submodule_content sub1 origin/add_sub1 && test -f sub1/untracked_file ) ' @@ -781,7 +781,8 @@ test_submodule_recursing_with_args_common() { ( cd submodule_update && git branch -t invalid_sub1 origin/invalid_sub1 && - test_must_fail $command invalid_sub1 && + test_must_fail $command invalid_sub1 2>err && + test_i18ngrep sub1 err && test_superproject_content origin/add_sub1 && test_submodule_content sub1 origin/add_sub1 ) @@ -842,7 +843,7 @@ test_submodule_switch_recursing_with_args () { cd submodule_update && git branch -t add_sub1 origin/add_sub1 && : >sub1 && - echo sub1 >.git/info/exclude + echo sub1 >.git/info/exclude && $command add_sub1 && test_superproject_content origin/add_sub1 && test_submodule_content sub1 origin/add_sub1 @@ -969,7 +970,6 @@ test_submodule_forced_switch_recursing_with_args () { rm -rf .git/modules/sub1 && $command replace_sub1_with_directory && test_superproject_content origin/replace_sub1_with_directory && - test_submodule_content sub1 origin/modify_sub1 test_git_directory_exists sub1 ) ' diff --git a/t/lib-t6000.sh b/t/lib-t6000.sh index 3f2d873fec..b0ed4767e3 100644 --- a/t/lib-t6000.sh +++ b/t/lib-t6000.sh @@ -4,11 +4,11 @@ mkdir -p .git/refs/tags >sed.script -# Answer the sha1 has associated with the tag. The tag must exist in .git/refs/tags +# Answer the sha1 has associated with the tag. The tag must exist under refs/tags tag () { _tag=$1 - test -f ".git/refs/tags/$_tag" || error "tag: \"$_tag\" does not exist" - cat ".git/refs/tags/$_tag" + git rev-parse --verify "refs/tags/$_tag" || + error "tag: \"$_tag\" does not exist" } # Generate a commit using the text specified to make it unique and the tree diff --git a/t/perf/README b/t/perf/README index 21321a0f36..be12090c38 100644 --- a/t/perf/README +++ b/t/perf/README @@ -168,3 +168,28 @@ that While we have tried to make sure that it can cope with embedded whitespace and other special characters, it will not work with multi-line data. + +Rather than tracking the performance by run-time as `test_perf` does, you +may also track output size by using `test_size`. The stdout of the +function should be a single numeric value, which will be captured and +shown in the aggregated output. For example: + + test_perf 'time foo' ' + ./foo >foo.out + ' + + test_size 'output size' + wc -c <foo.out + ' + +might produce output like: + + Test origin HEAD + ------------------------------------------------------------- + 1234.1 time foo 0.37(0.79+0.02) 0.26(0.51+0.02) -29.7% + 1234.2 output size 4.3M 3.6M -14.7% + +The item being measured (and its units) is up to the test; the context +and the test title should make it clear to the user whether bigger or +smaller numbers are better. Unlike test_perf, the test code will only be +run once, since output sizes tend to be more deterministic than timings. diff --git a/t/perf/aggregate.perl b/t/perf/aggregate.perl index 48637ef64b..494907a892 100755 --- a/t/perf/aggregate.perl +++ b/t/perf/aggregate.perl @@ -4,6 +4,7 @@ use lib '../../perl/build/lib'; use strict; use warnings; use JSON; +use Getopt::Long; use Git; sub get_times { @@ -12,70 +13,92 @@ sub get_times { my $line = <$fh>; return undef if not defined $line; close $fh or die "cannot close $name: $!"; - $line =~ /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/ - or die "bad input line: $line"; - my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3; - return ($rt, $4, $5); + # times + if ($line =~ /^(?:(\d+):)?(\d+):(\d+(?:\.\d+)?) (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)$/) { + my $rt = ((defined $1 ? $1 : 0.0)*60+$2)*60+$3; + return ($rt, $4, $5); + # size + } elsif ($line =~ /^\d+$/) { + return $&; + } else { + die "bad input line: $line"; + } +} + +sub relative_change { + my ($r, $firstr) = @_; + if ($firstr > 0) { + return sprintf "%+.1f%%", 100.0*($r-$firstr)/$firstr; + } elsif ($r == 0) { + return "="; + } else { + return "+inf"; + } } sub format_times { my ($r, $u, $s, $firstr) = @_; + # no value means we did not finish the test if (!defined $r) { return "<missing>"; } + # a single value means we have a size, not times + if (!defined $u) { + return format_size($r, $firstr); + } + # otherwise, we have real/user/system times my $out = sprintf "%.2f(%.2f+%.2f)", $r, $u, $s; - if (defined $firstr) { - if ($firstr > 0) { - $out .= sprintf " %+.1f%%", 100.0*($r-$firstr)/$firstr; - } elsif ($r == 0) { - $out .= " ="; - } else { - $out .= " +inf"; - } + $out .= ' ' . relative_change($r, $firstr) if defined $firstr; + return $out; +} + +sub usage { + print <<EOT; +./aggregate.perl [options] [--] [<dir_or_rev>...] [--] [<test_script>...] > + + Options: + --codespeed * Format output for Codespeed + --reponame <str> * Send given reponame to codespeed + --sort-by <str> * Sort output (only "regression" criteria is supported) + --subsection <str> * Use results from given subsection + +EOT + exit(1); +} + +sub human_size { + my $n = shift; + my @units = ('', qw(K M G)); + while ($n > 900 && @units > 1) { + $n /= 1000; + shift @units; } + return $n unless length $units[0]; + return sprintf '%.1f%s', $n, $units[0]; +} + +sub format_size { + my ($size, $first) = @_; + # match the width of a time: 0.00(0.00+0.00) + my $out = sprintf '%15s', human_size($size); + $out .= ' ' . relative_change($size, $first) if defined $first; return $out; } my (@dirs, %dirnames, %dirabbrevs, %prefixes, @tests, $codespeed, $sortby, $subsection, $reponame); + +Getopt::Long::Configure qw/ require_order /; + +my $rc = GetOptions("codespeed" => \$codespeed, + "reponame=s" => \$reponame, + "sort-by=s" => \$sortby, + "subsection=s" => \$subsection); +usage() unless $rc; + while (scalar @ARGV) { my $arg = $ARGV[0]; my $dir; - if ($arg eq "--codespeed") { - $codespeed = 1; - shift @ARGV; - next; - } - if ($arg =~ /--sort-by(?:=(.*))?/) { - shift @ARGV; - if (defined $1) { - $sortby = $1; - } else { - $sortby = shift @ARGV; - if (! defined $sortby) { - die "'--sort-by' requires an argument"; - } - } - next; - } - if ($arg eq "--subsection") { - shift @ARGV; - $subsection = $ARGV[0]; - shift @ARGV; - if (! $subsection) { - die "empty subsection"; - } - next; - } - if ($arg eq "--reponame") { - shift @ARGV; - $reponame = $ARGV[0]; - shift @ARGV; - if (! $reponame) { - die "empty reponame"; - } - next; - } last if -f $arg or $arg eq "--"; if (! -d $arg) { my $rev = Git::command_oneline(qw(rev-parse --verify), $arg); @@ -192,7 +215,14 @@ sub print_default_results { my $firstr; for my $i (0..$#dirs) { my $d = $dirs[$i]; - $times{$prefixes{$d}.$t} = [get_times("$resultsdir/$prefixes{$d}$t.times")]; + my $base = "$resultsdir/$prefixes{$d}$t"; + $times{$prefixes{$d}.$t} = []; + foreach my $type (qw(times size)) { + if (-e "$base.$type") { + $times{$prefixes{$d}.$t} = [get_times("$base.$type")]; + last; + } + } my ($r,$u,$s) = @{$times{$prefixes{$d}.$t}}; my $w = length format_times($r,$u,$s,$firstr); $colwidth[$i] = $w if $w > $colwidth[$i]; @@ -225,7 +255,8 @@ sub print_sorted_results { my ($sortby) = @_; if ($sortby ne "regression") { - die "only 'regression' is supported as '--sort-by' argument"; + print "Only 'regression' is supported as '--sort-by' argument\n"; + usage(); } my @evolutions; diff --git a/t/perf/bisect_regression b/t/perf/bisect_regression new file mode 100755 index 0000000000..a94d9955d0 --- /dev/null +++ b/t/perf/bisect_regression @@ -0,0 +1,73 @@ +#!/bin/sh + +# Read a line coming from `./aggregate.perl --sort-by regression ...` +# and automatically bisect to find the commit responsible for the +# performance regression. +# +# Lines from `./aggregate.perl --sort-by regression ...` look like: +# +# +100.0% p7821-grep-engines-fixed.1 0.04(0.10+0.03) 0.08(0.11+0.08) v2.14.3 v2.15.1 +# +33.3% p7820-grep-engines.1 0.03(0.08+0.02) 0.04(0.08+0.02) v2.14.3 v2.15.1 +# + +die () { + echo >&2 "error: $*" + exit 1 +} + +while [ $# -gt 0 ]; do + arg="$1" + case "$arg" in + --help) + echo "usage: $0 [--config file] [--subsection subsection]" + exit 0 + ;; + --config) + shift + GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1") + export GIT_PERF_CONFIG_FILE + shift ;; + --subsection) + shift + GIT_PERF_SUBSECTION="$1" + export GIT_PERF_SUBSECTION + shift ;; + --*) + die "unrecognised option: '$arg'" ;; + *) + die "unknown argument '$arg'" + ;; + esac +done + +read -r regression subtest oldtime newtime oldrev newrev + +test_script=$(echo "$subtest" | sed -e 's/\(.*\)\.[0-9]*$/\1.sh/') +test_number=$(echo "$subtest" | sed -e 's/.*\.\([0-9]*\)$/\1/') + +# oldtime and newtime are decimal number, not integers + +oldtime=$(echo "$oldtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/') +newtime=$(echo "$newtime" | sed -e 's/^\([0-9]\+\.[0-9]\+\).*$/\1/') + +test $(echo "$newtime" "$oldtime" | awk '{ print ($1 > $2) }') = 1 || + die "New time '$newtime' shoud be greater than old time '$oldtime'" + +tmpdir=$(mktemp -d -t bisect_regression_XXXXXX) || die "Failed to create temp directory" +echo "$oldtime" >"$tmpdir/oldtime" || die "Failed to write to '$tmpdir/oldtime'" +echo "$newtime" >"$tmpdir/newtime" || die "Failed to write to '$tmpdir/newtime'" + +# Bisecting must be performed from the top level directory (even with --no-checkout) +( + toplevel_dir=$(git rev-parse --show-toplevel) || die "Failed to find top level directory" + cd "$toplevel_dir" || die "Failed to cd into top level directory '$toplevel_dir'" + + git bisect start --no-checkout "$newrev" "$oldrev" || die "Failed to start bisecting" + + git bisect run t/perf/bisect_run_script "$test_script" "$test_number" "$tmpdir" + res="$?" + + git bisect reset + + exit "$res" +) diff --git a/t/perf/bisect_run_script b/t/perf/bisect_run_script new file mode 100755 index 0000000000..3ebaf15521 --- /dev/null +++ b/t/perf/bisect_run_script @@ -0,0 +1,53 @@ +#!/bin/sh + +script="$1" +test_number="$2" +info_dir="$3" + +# This aborts the bisection immediately +die () { + echo >&2 "error: $*" + exit 255 +} + +bisect_head=$(git rev-parse --verify BISECT_HEAD) || die "Failed to find BISECT_HEAD ref" + +script_number=$(echo "$script" | sed -e "s/^p\([0-9]*\).*\$/\1/") || die "Failed to get script number for '$script'" + +oldtime=$(cat "$info_dir/oldtime") || die "Failed to access '$info_dir/oldtime'" +newtime=$(cat "$info_dir/newtime") || die "Failed to access '$info_dir/newtime'" + +cd t/perf || die "Failed to cd into 't/perf'" + +result_file="$info_dir/perf_${script_number}_${bisect_head}_results.txt" + +GIT_PERF_DIRS_OR_REVS="$bisect_head" +export GIT_PERF_DIRS_OR_REVS + +# Don't use codespeed +GIT_PERF_CODESPEED_OUTPUT= +GIT_PERF_SEND_TO_CODESPEED= +export GIT_PERF_CODESPEED_OUTPUT +export GIT_PERF_SEND_TO_CODESPEED + +./run "$script" >"$result_file" 2>&1 || die "Failed to run perf test '$script'" + +rtime=$(sed -n "s/^$script_number\.$test_number:.*\([0-9]\+\.[0-9]\+\)(.*).*\$/\1/p" "$result_file") + +echo "newtime: $newtime" +echo "rtime: $rtime" +echo "oldtime: $oldtime" + +# Compare ($newtime - $rtime) with ($rtime - $oldtime) +# Times are decimal number, not integers + +if test $(echo "$newtime" "$rtime" "$oldtime" | awk '{ print ($1 - $2 > $2 - $3) }') = 1 +then + # Current commit is considered "good/old" + echo "$rtime" >"$info_dir/oldtime" + exit 0 +else + # Current commit is considered "bad/new" + echo "$rtime" >"$info_dir/newtime" + exit 1 +fi diff --git a/t/perf/p5311-pack-bitmaps-fetch.sh b/t/perf/p5311-pack-bitmaps-fetch.sh new file mode 100755 index 0000000000..b04575951f --- /dev/null +++ b/t/perf/p5311-pack-bitmaps-fetch.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +test_description='performance of fetches from bitmapped packs' +. ./perf-lib.sh + +test_perf_default_repo + +test_expect_success 'create bitmapped server repo' ' + git config pack.writebitmaps true && + git config pack.writebitmaphashcache true && + git repack -ad +' + +# simulate a fetch from a repository that last fetched N days ago, for +# various values of N. We do so by following the first-parent chain, +# and assume the first entry in the chain that is N days older than the current +# HEAD is where the HEAD would have been then. +for days in 1 2 4 8 16 32 64 128; do + title=$(printf '%10s' "($days days)") + test_expect_success "setup revs from $days days ago" ' + now=$(git log -1 --format=%ct HEAD) && + then=$(($now - ($days * 86400))) && + tip=$(git rev-list -1 --first-parent --until=$then HEAD) && + { + echo HEAD && + echo ^$tip + } >revs + ' + + test_perf "server $title" ' + git pack-objects --stdout --revs \ + --thin --delta-base-offset \ + <revs >tmp.pack + ' + + test_size "size $title" ' + wc -c <tmp.pack + ' + + test_perf "client $title" ' + git index-pack --stdin --fix-thin <tmp.pack + ' +done + +test_done diff --git a/t/perf/perf-lib.sh b/t/perf/perf-lib.sh index e4c343a6b7..11d1922cf5 100644 --- a/t/perf/perf-lib.sh +++ b/t/perf/perf-lib.sh @@ -179,8 +179,8 @@ exit $ret' >&3 2>&4 return "$eval_ret" } - -test_perf () { +test_wrapper_ () { + test_wrapper_func_=$1; shift test_start_ test "$#" = 3 && { test_prereq=$1; shift; } || test_prereq= test "$#" = 2 || @@ -191,35 +191,57 @@ test_perf () { base=$(basename "$0" .sh) echo "$test_count" >>"$perf_results_dir"/$base.subtests echo "$1" >"$perf_results_dir"/$base.$test_count.descr - if test -z "$verbose"; then - printf "%s" "perf $test_count - $1:" - else - echo "perf $test_count - $1:" - fi - for i in $(test_seq 1 $GIT_PERF_REPEAT_COUNT); do - say >&3 "running: $2" - if test_run_perf_ "$2" - then - if test -z "$verbose"; then - printf " %s" "$i" - else - echo "* timing run $i/$GIT_PERF_REPEAT_COUNT:" - fi + base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count" + "$test_wrapper_func_" "$@" + fi + + test_finish_ +} + +test_perf_ () { + if test -z "$verbose"; then + printf "%s" "perf $test_count - $1:" + else + echo "perf $test_count - $1:" + fi + for i in $(test_seq 1 $GIT_PERF_REPEAT_COUNT); do + say >&3 "running: $2" + if test_run_perf_ "$2" + then + if test -z "$verbose"; then + printf " %s" "$i" else - test -z "$verbose" && echo - test_failure_ "$@" - break + echo "* timing run $i/$GIT_PERF_REPEAT_COUNT:" fi - done - if test -z "$verbose"; then - echo " ok" else - test_ok_ "$1" + test -z "$verbose" && echo + test_failure_ "$@" + break fi - base="$perf_results_dir"/"$perf_results_prefix$(basename "$0" .sh)"."$test_count" - "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times + done + if test -z "$verbose"; then + echo " ok" + else + test_ok_ "$1" fi - test_finish_ + "$TEST_DIRECTORY"/perf/min_time.perl test_time.* >"$base".times +} + +test_perf () { + test_wrapper_ test_perf_ "$@" +} + +test_size_ () { + say >&3 "running: $2" + if test_eval_ "$2" 3>"$base".size; then + test_ok_ "$1" + else + test_failure_ "$@" + fi +} + +test_size () { + test_wrapper_ test_size_ "$@" } # We extend test_done to print timings at the end (./run disables this diff --git a/t/perf/run b/t/perf/run index 213da5d6b9..9aaa733c77 100755 --- a/t/perf/run +++ b/t/perf/run @@ -1,21 +1,34 @@ #!/bin/sh -case "$1" in +die () { + echo >&2 "error: $*" + exit 1 +} + +while [ $# -gt 0 ]; do + arg="$1" + case "$arg" in + --) + break ;; --help) - echo "usage: $0 [--config file] [other_git_tree...] [--] [test_scripts]" - exit 0 - ;; + echo "usage: $0 [--config file] [--subsection subsec] [other_git_tree...] [--] [test_scripts]" + exit 0 ;; --config) shift GIT_PERF_CONFIG_FILE=$(cd "$(dirname "$1")"; pwd)/$(basename "$1") export GIT_PERF_CONFIG_FILE shift ;; -esac - -die () { - echo >&2 "error: $*" - exit 1 -} + --subsection) + shift + GIT_PERF_SUBSECTION="$1" + export GIT_PERF_SUBSECTION + shift ;; + --*) + die "unrecognised option: '$arg'" ;; + *) + break ;; + esac +done run_one_dir () { if test $# -eq 0; then @@ -172,9 +185,32 @@ get_subsections "perf" >test-results/run_subsections.names if test $(wc -l <test-results/run_subsections.names) -eq 0 then + if test -n "$GIT_PERF_SUBSECTION" + then + if test -n "$GIT_PERF_CONFIG_FILE" + then + die "no subsections are defined in config file '$GIT_PERF_CONFIG_FILE'" + else + die "subsection '$GIT_PERF_SUBSECTION' defined without a config file" + fi + fi ( run_subsection "$@" ) +elif test -n "$GIT_PERF_SUBSECTION" +then + egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names >/dev/null || + die "subsection '$GIT_PERF_SUBSECTION' not found in '$GIT_PERF_CONFIG_FILE'" + + egrep "^$GIT_PERF_SUBSECTION\$" test-results/run_subsections.names | while read -r subsec + do + ( + GIT_PERF_SUBSECTION="$subsec" + export GIT_PERF_SUBSECTION + echo "======== Run for subsection '$GIT_PERF_SUBSECTION' ========" + run_subsection "$@" + ) + done else while read -r subsec do diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 7fd87dd544..850f651e4e 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -116,7 +116,7 @@ check_sub_test_lib_test () { name="$1" # stdin is the expected output from the test ( cd "$name" && - ! test -s err && + test_must_be_empty err && sed -e 's/^> //' -e 's/Z$//' >expect && test_cmp expect out ) @@ -839,7 +839,7 @@ test_expect_success 'writing tree out with git write-tree' ' ' # we know the shape and contents of the tree and know the object ID for it. -test_expect_success 'validate object ID of a known tree' ' +test_expect_success SHA1 'validate object ID of a known tree' ' test "$tree" = 7bb943559a305bdd6bdee2cef6e5df2413c3d30a ' @@ -882,7 +882,7 @@ test_expect_success 'showing stage with git ls-files --stage' ' git ls-files --stage >current ' -test_expect_success 'validate git ls-files output for a known tree' ' +test_expect_success SHA1 'validate git ls-files output for a known tree' ' cat >expected <<-\EOF && 100644 f87290f8eb2cbbea7857214459a0739927eab154 0 path0 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0 path0sym @@ -900,7 +900,7 @@ test_expect_success 'writing tree out with git write-tree' ' tree=$(git write-tree) ' -test_expect_success 'validate object ID for a known tree' ' +test_expect_success SHA1 'validate object ID for a known tree' ' test "$tree" = 087704a96baf1c2d1c869a8b084481e121c88b5b ' @@ -908,7 +908,7 @@ test_expect_success 'showing tree with git ls-tree' ' git ls-tree $tree >current ' -test_expect_success 'git ls-tree output for a known tree' ' +test_expect_success SHA1 'git ls-tree output for a known tree' ' cat >expected <<-\EOF && 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym @@ -924,7 +924,7 @@ test_expect_success 'showing tree with git ls-tree -r' ' git ls-tree -r $tree >current ' -test_expect_success 'git ls-tree -r output for a known tree' ' +test_expect_success SHA1 'git ls-tree -r output for a known tree' ' cat >expected <<-\EOF && 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym @@ -943,7 +943,7 @@ test_expect_success 'showing tree with git ls-tree -r -t' ' git ls-tree -r -t $tree >current ' -test_expect_success 'git ls-tree -r output for a known tree' ' +test_expect_success SHA1 'git ls-tree -r output for a known tree' ' cat >expected <<-\EOF && 100644 blob f87290f8eb2cbbea7857214459a0739927eab154 path0 120000 blob 15a98433ae33114b085f3eb3bb03b832b3180a01 path0sym @@ -964,7 +964,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' ' ptree=$(git write-tree --prefix=path3) ' -test_expect_success 'validate object ID for a known tree' ' +test_expect_success SHA1 'validate object ID for a known tree' ' test "$ptree" = 21ae8269cacbe57ae09138dcc3a2887f904d02b3 ' @@ -972,7 +972,7 @@ test_expect_success 'writing partial tree out with git write-tree --prefix' ' ptree=$(git write-tree --prefix=path3/subp3) ' -test_expect_success 'validate object ID for a known tree' ' +test_expect_success SHA1 'validate object ID for a known tree' ' test "$ptree" = 3c5e5399f3a333eddecce7a9b9465b63f65f51e2 ' @@ -1006,7 +1006,7 @@ test_expect_success 'git read-tree followed by write-tree should be idempotent' test "$newtree" = "$tree" ' -test_expect_success 'validate git diff-files output for a know cache/work tree state' ' +test_expect_success SHA1 'validate git diff-files output for a know cache/work tree state' ' cat >expected <<\EOF && :100644 100644 f87290f8eb2cbbea7857214459a0739927eab154 0000000000000000000000000000000000000000 M path0 :120000 120000 15a98433ae33114b085f3eb3bb03b832b3180a01 0000000000000000000000000000000000000000 M path0sym @@ -1033,21 +1033,21 @@ test_expect_success 'no diff after checkout and git update-index --refresh' ' ################################################################ P=087704a96baf1c2d1c869a8b084481e121c88b5b -test_expect_success 'git commit-tree records the correct tree in a commit' ' +test_expect_success SHA1 'git commit-tree records the correct tree in a commit' ' commit0=$(echo NO | git commit-tree $P) && tree=$(git show --pretty=raw $commit0 | sed -n -e "s/^tree //p" -e "/^author /q") && test "z$tree" = "z$P" ' -test_expect_success 'git commit-tree records the correct parent in a commit' ' +test_expect_success SHA1 'git commit-tree records the correct parent in a commit' ' commit1=$(echo NO | git commit-tree $P -p $commit0) && parent=$(git show --pretty=raw $commit1 | sed -n -e "s/^parent //p" -e "/^author /q") && test "z$commit0" = "z$parent" ' -test_expect_success 'git commit-tree omits duplicated parent in a commit' ' +test_expect_success SHA1 'git commit-tree omits duplicated parent in a commit' ' commit2=$(echo NO | git commit-tree $P -p $commit0 -p $commit0) && parent=$(git show --pretty=raw $commit2 | sed -n -e "s/^parent //p" -e "/^author /q" | @@ -1081,7 +1081,7 @@ test_expect_success 'very long name in the index handled sanely' ' ( git ls-files -s path4 | sed -e "s/ .*/ /" | - tr -d "\012" + tr -d "\012" && echo "$a" ) | git update-index --index-info && len=$(git ls-files "a*" | wc -c) && diff --git a/t/t0001-init.sh b/t/t0001-init.sh index c413bff9cf..182da069f1 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -167,9 +167,8 @@ test_expect_success 'reinit' ' ) && test_i18ngrep "Initialized empty" again/out1 && test_i18ngrep "Reinitialized existing" again/out2 && - >again/empty && - test_i18ncmp again/empty again/err1 && - test_i18ncmp again/empty again/err2 + test_must_be_empty again/err1 && + test_must_be_empty again/err2 ' test_expect_success 'init with --template' ' @@ -287,6 +286,7 @@ test_expect_success 'init notices EEXIST (2)' ' ' test_expect_success POSIXPERM,SANITY 'init notices EPERM' ' + test_when_finished "chmod +w newdir" && rm -fr newdir && mkdir newdir && chmod -w newdir && @@ -407,7 +407,7 @@ is_hidden () { test_expect_success MINGW '.git hidden' ' rm -rf newdir && ( - unset GIT_DIR GIT_WORK_TREE + sane_unset GIT_DIR GIT_WORK_TREE && mkdir newdir && cd newdir && git init && @@ -419,7 +419,7 @@ test_expect_success MINGW '.git hidden' ' test_expect_success MINGW 'bare git dir not hidden' ' rm -rf newdir && ( - unset GIT_DIR GIT_WORK_TREE GIT_CONFIG + sane_unset GIT_DIR GIT_WORK_TREE GIT_CONFIG && mkdir newdir && cd newdir && git --bare init diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index f19ae4f8cc..22499bce5f 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -34,15 +34,15 @@ test_expect_success 'open-quoted pathname' ' test_expect_success 'setup' ' mkdir -p a/b/d a/c b && ( - echo "[attr]notest !test" - echo "\" d \" test=d" - echo " e test=e" - echo " e\" test=e" - echo "f test=f" - echo "a/i test=a/i" - echo "onoff test -test" - echo "offon -test test" - echo "no notest" + echo "[attr]notest !test" && + echo "\" d \" test=d" && + echo " e test=e" && + echo " e\" test=e" && + echo "f test=f" && + echo "a/i test=a/i" && + echo "onoff test -test" && + echo "offon -test test" && + echo "no notest" && echo "A/e/F test=A/e/F" ) >.gitattributes && ( @@ -51,7 +51,7 @@ test_expect_success 'setup' ' ) >a/.gitattributes && ( echo "h test=a/b/h" && - echo "d/* test=a/b/d/*" + echo "d/* test=a/b/d/*" && echo "d/yes notest" ) >a/b/.gitattributes && ( @@ -208,9 +208,8 @@ test_expect_success 'attribute test: --all option' ' ' test_expect_success 'attribute test: --cached option' ' - : >empty && git check-attr --cached --stdin --all <stdin-all | sort >actual && - test_cmp empty actual && + test_must_be_empty actual && git add .gitattributes a/.gitattributes a/b/.gitattributes && git check-attr --cached --stdin --all <stdin-all | sort >actual && test_cmp specified-all actual @@ -287,7 +286,7 @@ test_expect_success 'bare repository: check that .gitattribute is ignored' ' ( cd bare.git && ( - echo "f test=f" + echo "f test=f" && echo "a/i test=a/i" ) >.gitattributes && attr_check f unspecified && @@ -312,7 +311,7 @@ test_expect_success 'bare repository: test info/attributes' ' ( cd bare.git && ( - echo "f test=f" + echo "f test=f" && echo "a/i test=a/i" ) >info/attributes && attr_check f f && diff --git a/t/t0008-ignores.sh b/t/t0008-ignores.sh index c03f155a35..1744cee5e9 100755 --- a/t/t0008-ignores.sh +++ b/t/t0008-ignores.sh @@ -807,10 +807,9 @@ test_expect_success 'trailing whitespace is ignored' ' cat >expect <<EOF && whitespace/untracked EOF - : >err.expect && git ls-files -o -X ignore whitespace >actual 2>err && test_cmp expect actual && - test_cmp err.expect err + test_must_be_empty err ' test_expect_success !MINGW 'quoting allows trailing whitespace' ' @@ -820,10 +819,9 @@ test_expect_success !MINGW 'quoting allows trailing whitespace' ' >whitespace/untracked && echo "whitespace/trailing\\ \\ " >ignore && echo whitespace/untracked >expect && - : >err.expect && git ls-files -o -X ignore whitespace >actual 2>err && test_cmp expect actual && - test_cmp err.expect err + test_must_be_empty err ' test_expect_success !MINGW,!CYGWIN 'correct handling of backslashes' ' @@ -845,10 +843,9 @@ test_expect_success !MINGW,!CYGWIN 'correct handling of backslashes' ' whitespace/trailing 6 \\a\\Z EOF echo whitespace/untracked >expect && - >err.expect && git ls-files -o -X ignore whitespace >actual 2>err && test_cmp expect actual && - test_cmp err.expect err + test_must_be_empty err ' test_expect_success 'info/exclude trumps core.excludesfile' ' diff --git a/t/t0012-help.sh b/t/t0012-help.sh index 487b92a5de..bc27df7f38 100755 --- a/t/t0012-help.sh +++ b/t/t0012-help.sh @@ -25,6 +25,15 @@ test_expect_success "setup" ' EOF ' +# make sure to exercise these code paths, the output is a bit tricky +# to verify +test_expect_success 'basic help commands' ' + git help >/dev/null && + git help -a >/dev/null && + git help -g >/dev/null && + git help -av >/dev/null +' + test_expect_success "works for commands and guides by default" ' configure_help && git help status && @@ -49,8 +58,23 @@ test_expect_success "--help does not work for guides" " test_i18ncmp expect actual " +test_expect_success 'git help' ' + git help >help.output && + test_i18ngrep "^ clone " help.output && + test_i18ngrep "^ add " help.output && + test_i18ngrep "^ log " help.output && + test_i18ngrep "^ commit " help.output && + test_i18ngrep "^ fetch " help.output +' +test_expect_success 'git help -g' ' + git help -g >help.output && + test_i18ngrep "^ attributes " help.output && + test_i18ngrep "^ everyday " help.output && + test_i18ngrep "^ tutorial " help.output +' + test_expect_success 'generate builtin list' ' - git --list-builtins >builtins + git --list-cmds=builtins >builtins ' while read builtin diff --git a/t/t0019-json-writer.sh b/t/t0019-json-writer.sh new file mode 100755 index 0000000000..3b0c336b38 --- /dev/null +++ b/t/t0019-json-writer.sh @@ -0,0 +1,331 @@ +#!/bin/sh + +test_description='test json-writer JSON generation' +. ./test-lib.sh + +test_expect_success 'unit test of json-writer routines' ' + test-tool json-writer -u +' + +test_expect_success 'trivial object' ' + cat >expect <<-\EOF && + {} + EOF + cat >input <<-\EOF && + object + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'trivial array' ' + cat >expect <<-\EOF && + [] + EOF + cat >input <<-\EOF && + array + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'simple object' ' + cat >expect <<-\EOF && + {"a":"abc","b":42,"c":3.14,"d":true,"e":false,"f":null} + EOF + cat >input <<-\EOF && + object + object-string a abc + object-int b 42 + object-double c 2 3.140 + object-true d + object-false e + object-null f + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'simple array' ' + cat >expect <<-\EOF && + ["abc",42,3.14,true,false,null] + EOF + cat >input <<-\EOF && + array + array-string abc + array-int 42 + array-double 2 3.140 + array-true + array-false + array-null + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'escape quoting string' ' + cat >expect <<-\EOF && + {"a":"abc\\def"} + EOF + cat >input <<-\EOF && + object + object-string a abc\def + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'escape quoting string 2' ' + cat >expect <<-\EOF && + {"a":"abc\"def"} + EOF + cat >input <<-\EOF && + object + object-string a abc"def + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'nested inline object' ' + cat >expect <<-\EOF && + {"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":{"e":false,"f":null}}} + EOF + cat >input <<-\EOF && + object + object-string a abc + object-int b 42 + object-object sub1 + object-double c 2 3.140 + object-true d + object-object sub2 + object-false e + object-null f + end + end + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'nested inline array' ' + cat >expect <<-\EOF && + ["abc",42,[3.14,true,[false,null]]] + EOF + cat >input <<-\EOF && + array + array-string abc + array-int 42 + array-array + array-double 2 3.140 + array-true + array-array + array-false + array-null + end + end + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'nested inline object and array' ' + cat >expect <<-\EOF && + {"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":[false,null]}} + EOF + cat >input <<-\EOF && + object + object-string a abc + object-int b 42 + object-object sub1 + object-double c 2 3.140 + object-true d + object-array sub2 + array-false + array-null + end + end + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'nested inline object and array 2' ' + cat >expect <<-\EOF && + {"a":"abc","b":42,"sub1":{"c":3.14,"d":true,"sub2":[false,{"g":0,"h":1},null]}} + EOF + cat >input <<-\EOF && + object + object-string a abc + object-int b 42 + object-object sub1 + object-double c 2 3.140 + object-true d + object-array sub2 + array-false + array-object + object-int g 0 + object-int h 1 + end + array-null + end + end + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'pretty nested inline object and array 2' ' + sed -e "s/^|//" >expect <<-\EOF && + |{ + | "a": "abc", + | "b": 42, + | "sub1": { + | "c": 3.14, + | "d": true, + | "sub2": [ + | false, + | { + | "g": 0, + | "h": 1 + | }, + | null + | ] + | } + |} + EOF + cat >input <<-\EOF && + object + object-string a abc + object-int b 42 + object-object sub1 + object-double c 2 3.140 + object-true d + object-array sub2 + array-false + array-object + object-int g 0 + object-int h 1 + end + array-null + end + end + end + EOF + test-tool json-writer -p <input >actual && + test_cmp expect actual +' + +test_expect_success 'inline object with no members' ' + cat >expect <<-\EOF && + {"a":"abc","empty":{},"b":42} + EOF + cat >input <<-\EOF && + object + object-string a abc + object-object empty + end + object-int b 42 + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'inline array with no members' ' + cat >expect <<-\EOF && + {"a":"abc","empty":[],"b":42} + EOF + cat >input <<-\EOF && + object + object-string a abc + object-array empty + end + object-int b 42 + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_expect_success 'larger empty example' ' + cat >expect <<-\EOF && + {"a":"abc","empty":[{},{},{},[],{}],"b":42} + EOF + cat >input <<-\EOF && + object + object-string a abc + object-array empty + array-object + end + array-object + end + array-object + end + array-array + end + array-object + end + end + object-int b 42 + end + EOF + test-tool json-writer <input >actual && + test_cmp expect actual +' + +test_lazy_prereq PERLJSON ' + perl -MJSON -e "exit 0" +' + +# As a sanity check, ask Perl to parse our generated JSON and recursively +# dump the resulting data in sorted order. Confirm that that matches our +# expectations. +test_expect_success PERLJSON 'parse JSON using Perl' ' + cat >expect <<-\EOF && + row[0].a abc + row[0].b 42 + row[0].sub1 hash + row[0].sub1.c 3.14 + row[0].sub1.d 1 + row[0].sub1.sub2 array + row[0].sub1.sub2[0] 0 + row[0].sub1.sub2[1] hash + row[0].sub1.sub2[1].g 0 + row[0].sub1.sub2[1].h 1 + row[0].sub1.sub2[2] null + EOF + cat >input <<-\EOF && + object + object-string a abc + object-int b 42 + object-object sub1 + object-double c 2 3.140 + object-true d + object-array sub2 + array-false + array-object + object-int g 0 + object-int h 1 + end + array-null + end + end + end + EOF + test-tool json-writer <input >output.json && + perl "$TEST_DIRECTORY"/t0019/parse_json.perl <output.json >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t0019/parse_json.perl b/t/t0019/parse_json.perl new file mode 100644 index 0000000000..fea87fb81b --- /dev/null +++ b/t/t0019/parse_json.perl @@ -0,0 +1,55 @@ +#!/usr/bin/perl +use strict; +use warnings; +use JSON; + +sub dump_array { + my ($label_in, $ary_ref) = @_; + my @ary = @$ary_ref; + + for ( my $i = 0; $i <= $#{ $ary_ref }; $i++ ) + { + my $label = "$label_in\[$i\]"; + dump_item($label, $ary[$i]); + } +} + +sub dump_hash { + my ($label_in, $obj_ref) = @_; + my %obj = %$obj_ref; + + foreach my $k (sort keys %obj) { + my $label = (length($label_in) > 0) ? "$label_in.$k" : "$k"; + my $value = $obj{$k}; + + dump_item($label, $value); + } +} + +sub dump_item { + my ($label_in, $value) = @_; + if (ref($value) eq 'ARRAY') { + print "$label_in array\n"; + dump_array($label_in, $value); + } elsif (ref($value) eq 'HASH') { + print "$label_in hash\n"; + dump_hash($label_in, $value); + } elsif (ref $value) { + my $bool = $value ? 1 : 0; + print "$label_in $bool\n"; + } elsif (defined $value) { + print "$label_in $value\n"; + } else { + print "$label_in null\n"; + } +} + +my $row = 0; +while (<>) { + my $data = decode_json( $_ ); + my $label = "row[$row]"; + + dump_hash($label, $data); + $row++; +} + diff --git a/t/t0020-crlf.sh b/t/t0020-crlf.sh index 71350e0657..854da0ae16 100755 --- a/t/t0020-crlf.sh +++ b/t/t0020-crlf.sh @@ -98,6 +98,16 @@ test_expect_success 'safecrlf: git diff demotes safecrlf=true to warn' ' ' +test_expect_success 'safecrlf: no warning with safecrlf=false' ' + git config core.autocrlf input && + git config core.safecrlf false && + + for w in I am all CRLF; do echo $w; done | append_cr >allcrlf && + git add allcrlf 2>err && + test_must_be_empty err +' + + test_expect_success 'switch off autocrlf, safecrlf, reset HEAD' ' git config core.autocrlf false && git config core.safecrlf false && @@ -150,7 +160,7 @@ test_expect_success 'checkout with autocrlf=input' ' git config core.autocrlf input && git read-tree --reset -u HEAD && test_must_fail has_cr one && - test_must_fail has_cr two && + test_must_fail has_cr dir/two && git update-index -- one dir/two && test "$one" = $(git hash-object --stdin <one) && test "$two" = $(git hash-object --stdin <dir/two) && diff --git a/t/t0021-conversion.sh b/t/t0021-conversion.sh index 9479a4aaab..308cd28f3b 100755 --- a/t/t0021-conversion.sh +++ b/t/t0021-conversion.sh @@ -583,7 +583,7 @@ test_expect_success PERL 'process filter should restart after unexpected write f git checkout --quiet --no-progress . 2>git-stderr.log && grep "smudge write error at" git-stderr.log && - grep "error: external filter" git-stderr.log && + test_i18ngrep "error: external filter" git-stderr.log && cat >expected.log <<-EOF && START @@ -785,7 +785,7 @@ test_expect_success PERL 'missing file in delayed checkout' ' cd repo && git init && echo "*.a filter=bug" >.gitattributes && - cp "$TEST_ROOT/test.o" missing-delay.a + cp "$TEST_ROOT/test.o" missing-delay.a && git add . && git commit -m "test commit" ) && @@ -807,7 +807,7 @@ test_expect_success PERL 'invalid file in delayed checkout' ' git init && echo "*.a filter=bug" >.gitattributes && cp "$TEST_ROOT/test.o" invalid-delay.a && - cp "$TEST_ROOT/test.o" unfiltered + cp "$TEST_ROOT/test.o" unfiltered && git add . && git commit -m "test commit" ) && diff --git a/t/t0028-working-tree-encoding.sh b/t/t0028-working-tree-encoding.sh new file mode 100755 index 0000000000..7e87b5a200 --- /dev/null +++ b/t/t0028-working-tree-encoding.sh @@ -0,0 +1,249 @@ +#!/bin/sh + +test_description='working-tree-encoding conversion via gitattributes' + +. ./test-lib.sh + +GIT_TRACE_WORKING_TREE_ENCODING=1 && export GIT_TRACE_WORKING_TREE_ENCODING + +test_expect_success 'setup test files' ' + git config core.eol lf && + + text="hallo there!\ncan you read me?" && + echo "*.utf16 text working-tree-encoding=utf-16" >.gitattributes && + printf "$text" >test.utf8.raw && + printf "$text" | iconv -f UTF-8 -t UTF-16 >test.utf16.raw && + printf "$text" | iconv -f UTF-8 -t UTF-32 >test.utf32.raw && + + # Line ending tests + printf "one\ntwo\nthree\n" >lf.utf8.raw && + printf "one\r\ntwo\r\nthree\r\n" >crlf.utf8.raw && + + # BOM tests + printf "\0a\0b\0c" >nobom.utf16be.raw && + printf "a\0b\0c\0" >nobom.utf16le.raw && + printf "\376\777\0a\0b\0c" >bebom.utf16be.raw && + printf "\777\376a\0b\0c\0" >lebom.utf16le.raw && + printf "\0\0\0a\0\0\0b\0\0\0c" >nobom.utf32be.raw && + printf "a\0\0\0b\0\0\0c\0\0\0" >nobom.utf32le.raw && + printf "\0\0\376\777\0\0\0a\0\0\0b\0\0\0c" >bebom.utf32be.raw && + printf "\777\376\0\0a\0\0\0b\0\0\0c\0\0\0" >lebom.utf32le.raw && + + # Add only UTF-16 file, we will add the UTF-32 file later + cp test.utf16.raw test.utf16 && + cp test.utf32.raw test.utf32 && + git add .gitattributes test.utf16 && + git commit -m initial +' + +test_expect_success 'ensure UTF-8 is stored in Git' ' + test_when_finished "rm -f test.utf16.git" && + + git cat-file -p :test.utf16 >test.utf16.git && + test_cmp_bin test.utf8.raw test.utf16.git +' + +test_expect_success 're-encode to UTF-16 on checkout' ' + test_when_finished "rm -f test.utf16.raw" && + + rm test.utf16 && + git checkout test.utf16 && + test_cmp_bin test.utf16.raw test.utf16 +' + +test_expect_success 'check $GIT_DIR/info/attributes support' ' + test_when_finished "rm -f test.utf32.git" && + test_when_finished "git reset --hard HEAD" && + + echo "*.utf32 text working-tree-encoding=utf-32" >.git/info/attributes && + git add test.utf32 && + + git cat-file -p :test.utf32 >test.utf32.git && + test_cmp_bin test.utf8.raw test.utf32.git +' + +for i in 16 32 +do + test_expect_success "check prohibited UTF-${i} BOM" ' + test_when_finished "git reset --hard HEAD" && + + echo "*.utf${i}be text working-tree-encoding=utf-${i}be" >>.gitattributes && + echo "*.utf${i}le text working-tree-encoding=utf-${i}LE" >>.gitattributes && + + # Here we add a UTF-16 (resp. UTF-32) files with BOM (big/little-endian) + # but we tell Git to treat it as UTF-16BE/UTF-16LE (resp. UTF-32). + # In these cases the BOM is prohibited. + cp bebom.utf${i}be.raw bebom.utf${i}be && + test_must_fail git add bebom.utf${i}be 2>err.out && + test_i18ngrep "fatal: BOM is prohibited .* utf-${i}be" err.out && + test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out && + + cp lebom.utf${i}le.raw lebom.utf${i}be && + test_must_fail git add lebom.utf${i}be 2>err.out && + test_i18ngrep "fatal: BOM is prohibited .* utf-${i}be" err.out && + test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out && + + cp bebom.utf${i}be.raw bebom.utf${i}le && + test_must_fail git add bebom.utf${i}le 2>err.out && + test_i18ngrep "fatal: BOM is prohibited .* utf-${i}LE" err.out && + test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out && + + cp lebom.utf${i}le.raw lebom.utf${i}le && + test_must_fail git add lebom.utf${i}le 2>err.out && + test_i18ngrep "fatal: BOM is prohibited .* utf-${i}LE" err.out && + test_i18ngrep "use UTF-${i} as working-tree-encoding" err.out + ' + + test_expect_success "check required UTF-${i} BOM" ' + test_when_finished "git reset --hard HEAD" && + + echo "*.utf${i} text working-tree-encoding=utf-${i}" >>.gitattributes && + + cp nobom.utf${i}be.raw nobom.utf${i} && + test_must_fail git add nobom.utf${i} 2>err.out && + test_i18ngrep "fatal: BOM is required .* utf-${i}" err.out && + test_i18ngrep "use UTF-${i}BE or UTF-${i}LE" err.out && + + cp nobom.utf${i}le.raw nobom.utf${i} && + test_must_fail git add nobom.utf${i} 2>err.out && + test_i18ngrep "fatal: BOM is required .* utf-${i}" err.out && + test_i18ngrep "use UTF-${i}BE or UTF-${i}LE" err.out + ' + + test_expect_success "eol conversion for UTF-${i} encoded files on checkout" ' + test_when_finished "rm -f crlf.utf${i}.raw lf.utf${i}.raw" && + test_when_finished "git reset --hard HEAD^" && + + cat lf.utf8.raw | iconv -f UTF-8 -t UTF-${i} >lf.utf${i}.raw && + cat crlf.utf8.raw | iconv -f UTF-8 -t UTF-${i} >crlf.utf${i}.raw && + cp crlf.utf${i}.raw eol.utf${i} && + + cat >expectIndexLF <<-EOF && + i/lf w/-text attr/text eol.utf${i} + EOF + + git add eol.utf${i} && + git commit -m eol && + + # UTF-${i} with CRLF (Windows line endings) + rm eol.utf${i} && + git -c core.eol=crlf checkout eol.utf${i} && + test_cmp_bin crlf.utf${i}.raw eol.utf${i} && + + # Although the file has CRLF in the working tree, + # ensure LF in the index + git ls-files --eol eol.utf${i} >actual && + test_cmp expectIndexLF actual && + + # UTF-${i} with LF (Unix line endings) + rm eol.utf${i} && + git -c core.eol=lf checkout eol.utf${i} && + test_cmp_bin lf.utf${i}.raw eol.utf${i} && + + # The file LF in the working tree, ensure LF in the index + git ls-files --eol eol.utf${i} >actual && + test_cmp expectIndexLF actual + ' +done + +test_expect_success 'check unsupported encodings' ' + test_when_finished "git reset --hard HEAD" && + + echo "*.set text working-tree-encoding" >.gitattributes && + printf "set" >t.set && + test_must_fail git add t.set 2>err.out && + test_i18ngrep "true/false are no valid working-tree-encodings" err.out && + + echo "*.unset text -working-tree-encoding" >.gitattributes && + printf "unset" >t.unset && + git add t.unset && + + echo "*.empty text working-tree-encoding=" >.gitattributes && + printf "empty" >t.empty && + git add t.empty && + + echo "*.garbage text working-tree-encoding=garbage" >.gitattributes && + printf "garbage" >t.garbage && + test_must_fail git add t.garbage 2>err.out && + test_i18ngrep "failed to encode" err.out +' + +test_expect_success 'error if encoding round trip is not the same during refresh' ' + BEFORE_STATE=$(git rev-parse HEAD) && + test_when_finished "git reset --hard $BEFORE_STATE" && + + # Add and commit a UTF-16 file but skip the "working-tree-encoding" + # filter. Consequently, the in-repo representation is UTF-16 and not + # UTF-8. This simulates a Git version that has no working tree encoding + # support. + echo "*.utf16le text working-tree-encoding=utf-16le" >.gitattributes && + echo "hallo" >nonsense.utf16le && + TEST_HASH=$(git hash-object --no-filters -w nonsense.utf16le) && + git update-index --add --cacheinfo 100644 $TEST_HASH nonsense.utf16le && + COMMIT=$(git commit-tree -p $(git rev-parse HEAD) -m "plain commit" $(git write-tree)) && + git update-ref refs/heads/master $COMMIT && + + test_must_fail git checkout HEAD^ 2>err.out && + test_i18ngrep "error: .* overwritten by checkout:" err.out +' + +test_expect_success 'error if encoding garbage is already in Git' ' + BEFORE_STATE=$(git rev-parse HEAD) && + test_when_finished "git reset --hard $BEFORE_STATE" && + + # Skip the UTF-16 filter for the added file + # This simulates a Git version that has no checkoutEncoding support + cp nobom.utf16be.raw nonsense.utf16 && + TEST_HASH=$(git hash-object --no-filters -w nonsense.utf16) && + git update-index --add --cacheinfo 100644 $TEST_HASH nonsense.utf16 && + COMMIT=$(git commit-tree -p $(git rev-parse HEAD) -m "plain commit" $(git write-tree)) && + git update-ref refs/heads/master $COMMIT && + + git diff 2>err.out && + test_i18ngrep "error: BOM is required" err.out +' + +test_lazy_prereq ICONV_SHIFT_JIS ' + iconv -f UTF-8 -t SHIFT-JIS </dev/null +' + +test_expect_success ICONV_SHIFT_JIS 'check roundtrip encoding' ' + test_when_finished "rm -f roundtrip.shift roundtrip.utf16" && + test_when_finished "git reset --hard HEAD" && + + text="hallo there!\nroundtrip test here!" && + printf "$text" | iconv -f UTF-8 -t SHIFT-JIS >roundtrip.shift && + printf "$text" | iconv -f UTF-8 -t UTF-16 >roundtrip.utf16 && + echo "*.shift text working-tree-encoding=SHIFT-JIS" >>.gitattributes && + + # SHIFT-JIS encoded files are round-trip checked by default... + GIT_TRACE=1 git add .gitattributes roundtrip.shift 2>&1 | + grep "Checking roundtrip encoding for SHIFT-JIS" && + git reset && + + # ... unless we overwrite the Git config! + ! GIT_TRACE=1 git -c core.checkRoundtripEncoding=garbage \ + add .gitattributes roundtrip.shift 2>&1 | + grep "Checking roundtrip encoding for SHIFT-JIS" && + git reset && + + # UTF-16 encoded files should not be round-trip checked by default... + ! GIT_TRACE=1 git add roundtrip.utf16 2>&1 | + grep "Checking roundtrip encoding for UTF-16" && + git reset && + + # ... unless we tell Git to check it! + GIT_TRACE=1 git -c core.checkRoundtripEncoding="UTF-16, UTF-32" \ + add roundtrip.utf16 2>&1 | + grep "Checking roundtrip encoding for utf-16" && + git reset && + + # ... unless we tell Git to check it! + # (here we also check that the casing of the encoding is irrelevant) + GIT_TRACE=1 git -c core.checkRoundtripEncoding="UTF-32, utf-16" \ + add roundtrip.utf16 2>&1 | + grep "Checking roundtrip encoding for utf-16" && + git reset +' + +test_done diff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh index bbf3e39e3d..5ce47e8af5 100755 --- a/t/t0030-stripspace.sh +++ b/t/t0030-stripspace.sh @@ -110,31 +110,30 @@ test_expect_success \ test_expect_success \ 'only consecutive blank lines should be completely removed' ' - > expect && printf "\n" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "\n\n\n" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "$sss\n$sss\n$sss\n" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "$sss$sss\n$sss\n\n" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "\n$sss\n$sss$sss\n" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "$sss$sss$sss$sss\n\n\n" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "\n$sss$sss$sss$sss\n\n" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "\n\n$sss$sss$sss$sss\n" | git stripspace >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success \ @@ -321,22 +320,20 @@ test_expect_success \ test_expect_success \ 'spaces with newline at end should be replaced with empty string' ' - printf "" >expect && - echo | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && echo "$sss" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && echo "$sss$sss" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && echo "$sss$sss$sss" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && echo "$sss$sss$sss$sss" | git stripspace >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success \ @@ -350,19 +347,17 @@ test_expect_success \ test_expect_success \ 'spaces without newline at end should be replaced with empty string' ' - printf "" >expect && - printf "" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "$sss$sss" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "$sss$sss$sss" | git stripspace >actual && - test_cmp expect actual && + test_must_be_empty actual && printf "$sss$sss$sss$sss" | git stripspace >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success \ diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 04d474c84f..5b0560fa20 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -286,11 +286,9 @@ test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' ' test_cmp expect output ' ->expect - test_expect_success 'OPT_CALLBACK() and callback errors work' ' test_must_fail test-parse-options --no-length >output 2>output.err && - test_i18ncmp expect output && + test_must_be_empty output && test_must_be_empty output.err ' diff --git a/t/t0051-windows-named-pipe.sh b/t/t0051-windows-named-pipe.sh new file mode 100755 index 0000000000..10ac92d225 --- /dev/null +++ b/t/t0051-windows-named-pipe.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +test_description='Windows named pipes' + +. ./test-lib.sh + +test_expect_success MINGW 'o_append write to named pipe' ' + GIT_TRACE="$(pwd)/expect" git status >/dev/null 2>&1 && + { test-tool windows-named-pipe t0051 >actual 2>&1 & } && + pid=$! && + sleep 1 && + GIT_TRACE=//./pipe/t0051 git status >/dev/null 2>warning && + wait $pid && + test_cmp expect actual +' + +test_done diff --git a/t/t0060-path-utils.sh b/t/t0060-path-utils.sh index f46e3c4995..cd74c0a471 100755 --- a/t/t0060-path-utils.sh +++ b/t/t0060-path-utils.sh @@ -330,6 +330,9 @@ test_submodule_relative_url "(null)" "../foo" "../submodule" "../submodule" test_submodule_relative_url "(null)" "./foo/bar" "../submodule" "foo/submodule" test_submodule_relative_url "(null)" "./foo" "../submodule" "submodule" test_submodule_relative_url "(null)" "//somewhere else/repo" "../subrepo" "//somewhere else/subrepo" +test_submodule_relative_url "(null)" "//somewhere else/repo" "../../subrepo" "//subrepo" +test_submodule_relative_url "(null)" "//somewhere else/repo" "../../../subrepo" "/subrepo" +test_submodule_relative_url "(null)" "//somewhere else/repo" "../../../../subrepo" "subrepo" test_submodule_relative_url "(null)" "$(pwd)/subsuper_update_r" "../subsubsuper_update_r" "$(pwd)/subsubsuper_update_r" test_submodule_relative_url "(null)" "$(pwd)/super_update_r2" "../subsuper_update_r" "$(pwd)/subsuper_update_r" test_submodule_relative_url "(null)" "$(pwd)/." "../." "$(pwd)/." @@ -344,9 +347,105 @@ test_submodule_relative_url "(null)" "file:///tmp/repo" "../subrepo" "file:///tm test_submodule_relative_url "(null)" "foo/bar" "../submodule" "foo/submodule" test_submodule_relative_url "(null)" "foo" "../submodule" "submodule" test_submodule_relative_url "(null)" "helper:://hostname/repo" "../subrepo" "helper:://hostname/subrepo" +test_submodule_relative_url "(null)" "helper:://hostname/repo" "../../subrepo" "helper:://subrepo" +test_submodule_relative_url "(null)" "helper:://hostname/repo" "../../../subrepo" "helper::/subrepo" +test_submodule_relative_url "(null)" "helper:://hostname/repo" "../../../../subrepo" "helper::subrepo" +test_submodule_relative_url "(null)" "helper:://hostname/repo" "../../../../../subrepo" "helper:subrepo" +test_submodule_relative_url "(null)" "helper:://hostname/repo" "../../../../../../subrepo" ".:subrepo" test_submodule_relative_url "(null)" "ssh://hostname/repo" "../subrepo" "ssh://hostname/subrepo" +test_submodule_relative_url "(null)" "ssh://hostname/repo" "../../subrepo" "ssh://subrepo" +test_submodule_relative_url "(null)" "ssh://hostname/repo" "../../../subrepo" "ssh:/subrepo" +test_submodule_relative_url "(null)" "ssh://hostname/repo" "../../../../subrepo" "ssh:subrepo" +test_submodule_relative_url "(null)" "ssh://hostname/repo" "../../../../../subrepo" ".:subrepo" test_submodule_relative_url "(null)" "ssh://hostname:22/repo" "../subrepo" "ssh://hostname:22/subrepo" test_submodule_relative_url "(null)" "user@host:path/to/repo" "../subrepo" "user@host:path/to/subrepo" test_submodule_relative_url "(null)" "user@host:repo" "../subrepo" "user@host:subrepo" +test_submodule_relative_url "(null)" "user@host:repo" "../../subrepo" ".:subrepo" + +test_expect_success 'match .gitmodules' ' + test-tool path-utils is_dotgitmodules \ + .gitmodules \ + \ + .git${u200c}modules \ + \ + .Gitmodules \ + .gitmoduleS \ + \ + ".gitmodules " \ + ".gitmodules." \ + ".gitmodules " \ + ".gitmodules. " \ + ".gitmodules ." \ + ".gitmodules.." \ + ".gitmodules " \ + ".gitmodules. " \ + ".gitmodules . " \ + ".gitmodules ." \ + \ + ".Gitmodules " \ + ".Gitmodules." \ + ".Gitmodules " \ + ".Gitmodules. " \ + ".Gitmodules ." \ + ".Gitmodules.." \ + ".Gitmodules " \ + ".Gitmodules. " \ + ".Gitmodules . " \ + ".Gitmodules ." \ + \ + GITMOD~1 \ + gitmod~1 \ + GITMOD~2 \ + gitmod~3 \ + GITMOD~4 \ + \ + "GITMOD~1 " \ + "gitmod~2." \ + "GITMOD~3 " \ + "gitmod~4. " \ + "GITMOD~1 ." \ + "gitmod~2 " \ + "GITMOD~3. " \ + "gitmod~4 . " \ + \ + GI7EBA~1 \ + gi7eba~9 \ + \ + GI7EB~10 \ + GI7EB~11 \ + GI7EB~99 \ + GI7EB~10 \ + GI7E~100 \ + GI7E~101 \ + GI7E~999 \ + ~1000000 \ + ~9999999 \ + \ + --not \ + ".gitmodules x" \ + ".gitmodules .x" \ + \ + " .gitmodules" \ + \ + ..gitmodules \ + \ + gitmodules \ + \ + .gitmodule \ + \ + ".gitmodules x " \ + ".gitmodules .x" \ + \ + GI7EBA~ \ + GI7EBA~0 \ + GI7EBA~~1 \ + GI7EBA~X \ + Gx7EBA~1 \ + GI7EBX~1 \ + \ + GI7EB~1 \ + GI7EB~01 \ + GI7EB~1X +' test_done diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index d03149be9f..3e131c5325 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -11,7 +11,6 @@ cat >hello-script <<-EOF #!$SHELL_PATH cat hello-script EOF ->empty test_expect_success 'start_command reports ENOENT' ' test-tool run-command start-command-ENOENT ./does-not-exist @@ -23,7 +22,7 @@ test_expect_success 'run_command can run a command' ' test-tool run-command run-command ./hello.sh >actual 2>err && test_cmp hello-script actual && - test_cmp empty err + test_must_be_empty err ' test_expect_success !MINGW 'run_command can run a script without a #! line' ' @@ -34,7 +33,7 @@ test_expect_success !MINGW 'run_command can run a script without a #! line' ' test-tool run-command run-command ./hello >actual 2>err && test_cmp hello-script actual && - test_cmp empty err + test_must_be_empty err ' test_expect_success 'run_command does not try to execute a directory' ' @@ -47,7 +46,7 @@ test_expect_success 'run_command does not try to execute a directory' ' PATH=$PWD/bin1:$PWD/bin2:$PATH \ test-tool run-command run-command greet >actual 2>err && test_cmp bin2/greet actual && - test_cmp empty err + test_must_be_empty err ' test_expect_success POSIXPERM 'run_command passes over non-executable file' ' @@ -64,7 +63,7 @@ test_expect_success POSIXPERM 'run_command passes over non-executable file' ' PATH=$PWD/bin1:$PWD/bin2:$PATH \ test-tool run-command run-command greet >actual 2>err && test_cmp bin2/greet actual && - test_cmp empty err + test_must_be_empty err ' test_expect_success POSIXPERM 'run_command reports EACCES' ' @@ -145,7 +144,7 @@ test_trace () { expect="$1" shift GIT_TRACE=1 test-tool run-command "$@" run-command true 2>&1 >/dev/null | \ - sed 's/.* run_command: //' >actual && + sed -e 's/.* run_command: //' -e '/trace: .*/d' >actual && echo "$expect true" >expect && test_cmp expect actual } diff --git a/t/t0070-fundamental.sh b/t/t0070-fundamental.sh index 23fbe6434a..7b111a56fd 100755 --- a/t/t0070-fundamental.sh +++ b/t/t0070-fundamental.sh @@ -19,8 +19,8 @@ test_expect_success 'mktemp to nonexistent directory prints filename' ' test_expect_success POSIXPERM,SANITY 'mktemp to unwritable directory prints filename' ' mkdir cannotwrite && - chmod -w cannotwrite && test_when_finished "chmod +w cannotwrite" && + chmod -w cannotwrite && test_must_fail test-tool mktemp cannotwrite/testXXXXXX 2>err && grep "cannotwrite/test" err ' diff --git a/t/t0090-cache-tree.sh b/t/t0090-cache-tree.sh index 4ae0995cd9..94fcb4a78e 100755 --- a/t/t0090-cache-tree.sh +++ b/t/t0090-cache-tree.sh @@ -9,7 +9,7 @@ cache-tree extension. cmp_cache_tree () { test-tool dump-cache-tree | sed -e '/#(ref)/d' >actual && - sed "s/$_x40/SHA/" <actual >filtered && + sed "s/$OID_REGEX/SHA/" <actual >filtered && test_cmp "$1" filtered } @@ -156,11 +156,29 @@ test_expect_success PERL 'commit --interactive gives cache-tree on partial commi return 44; } EOT - (echo p; echo 1; echo; echo s; echo n; echo y; echo q) | + test_write_lines p 1 "" s n y q | git commit --interactive -m foo && test_cache_tree ' +test_expect_success PERL 'commit -p with shrinking cache-tree' ' + mkdir -p deep/subdir && + echo content >deep/subdir/file && + git add deep && + git commit -m add && + git rm -r deep && + + before=$(wc -c <.git/index) && + git commit -m delete -p && + after=$(wc -c <.git/index) && + + # double check that the index shrank + test $before -gt $after && + + # and that our index was not corrupted + git fsck +' + test_expect_success 'commit in child dir has cache-tree' ' mkdir dir && >dir/child.t && @@ -239,7 +257,7 @@ test_expect_success 'no phantom error when switching trees' ' >newdir/one && git add newdir/one && git checkout 2>errors && - ! test -s errors + test_must_be_empty errors ' test_expect_success 'switching trees does not invalidate shared index' ' diff --git a/t/t0203-gettext-setlocale-sanity.sh b/t/t0203-gettext-setlocale-sanity.sh index 71b0d74b4d..0ce1f22eff 100755 --- a/t/t0203-gettext-setlocale-sanity.sh +++ b/t/t0203-gettext-setlocale-sanity.sh @@ -11,7 +11,7 @@ test_expect_success 'git show a ISO-8859-1 commit under C locale' ' . "$TEST_DIRECTORY"/t3901/8859-1.txt && test_commit "iso-c-commit" iso-under-c && git show >out 2>err && - ! test -s err && + test_must_be_empty err && grep -q "iso-c-commit" out ' @@ -19,7 +19,7 @@ test_expect_success GETTEXT_LOCALE 'git show a ISO-8859-1 commit under a UTF-8 l . "$TEST_DIRECTORY"/t3901/8859-1.txt && test_commit "iso-utf8-commit" iso-under-utf8 && LANGUAGE=is LC_ALL="$is_IS_locale" git show >out 2>err && - ! test -s err && + test_must_be_empty err && grep -q "iso-utf8-commit" out ' diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh index 03bd31e9f2..82eaaea0f4 100755 --- a/t/t0300-credentials.sh +++ b/t/t0300-credentials.sh @@ -294,8 +294,7 @@ test_expect_success 'helpers can abort the process' ' -c credential.helper="!f() { echo quit=1; }; f" \ -c credential.helper="verbatim foo bar" \ credential fill >stdout && - >expect && - test_cmp expect stdout + test_must_be_empty stdout ' test_expect_success 'empty helper spec resets helper list' ' diff --git a/t/t0410-partial-clone.sh b/t/t0410-partial-clone.sh index cc18b75c03..b35bc89f1e 100755 --- a/t/t0410-partial-clone.sh +++ b/t/t0410-partial-clone.sh @@ -23,7 +23,15 @@ promise_and_delete () { delete_object repo "$HASH" } +test_expect_success 'extensions.partialclone without filter' ' + test_create_repo server && + git clone --filter="blob:none" "file://$(pwd)/server" client && + git -C client config --unset core.partialclonefilter && + git -C client fetch origin +' + test_expect_success 'missing reflog object, but promised by a commit, passes fsck' ' + rm -rf repo && test_create_repo repo && test_commit -C repo my_commit && @@ -173,7 +181,7 @@ test_expect_success 'rev-list stops traversal at missing and promised commit' ' git -C repo config core.repositoryformatversion 1 && git -C repo config extensions.partialclone "arbitrary string" && - git -C repo rev-list --exclude-promisor-objects --objects bar >out && + GIT_TEST_COMMIT_GRAPH=0 git -C repo rev-list --exclude-promisor-objects --objects bar >out && grep $(git -C repo rev-parse bar) out && ! grep $FOO out ' @@ -263,28 +271,91 @@ test_expect_success 'rev-list accepts missing and promised objects on command li git -C repo rev-list --exclude-promisor-objects --objects "$COMMIT" "$TREE" "$BLOB" ' -test_expect_success 'gc does not repack promisor objects' ' +test_expect_success 'gc repacks promisor objects separately from non-promisor objects' ' rm -rf repo && test_create_repo repo && - test_commit -C repo my_commit && + test_commit -C repo one && + test_commit -C repo two && - TREE_HASH=$(git -C repo rev-parse HEAD^{tree}) && - HASH=$(printf "$TREE_HASH\n" | pack_as_from_promisor) && + TREE_ONE=$(git -C repo rev-parse one^{tree}) && + printf "$TREE_ONE\n" | pack_as_from_promisor && + TREE_TWO=$(git -C repo rev-parse two^{tree}) && + printf "$TREE_TWO\n" | pack_as_from_promisor && git -C repo config core.repositoryformatversion 1 && git -C repo config extensions.partialclone "arbitrary string" && git -C repo gc && - # Ensure that the promisor packfile still exists, and remove it - test -e repo/.git/objects/pack/pack-$HASH.pack && - rm repo/.git/objects/pack/pack-$HASH.* && - - # Ensure that the single other pack contains the commit, but not the tree + # Ensure that exactly one promisor packfile exists, and that it + # contains the trees but not the commits + ls repo/.git/objects/pack/pack-*.promisor >promisorlist && + test_line_count = 1 promisorlist && + PROMISOR_PACKFILE=$(sed "s/.promisor/.pack/" <promisorlist) && + git verify-pack $PROMISOR_PACKFILE -v >out && + grep "$TREE_ONE" out && + grep "$TREE_TWO" out && + ! grep "$(git -C repo rev-parse one)" out && + ! grep "$(git -C repo rev-parse two)" out && + + # Remove the promisor packfile and associated files + rm $(sed "s/.promisor//" <promisorlist).* && + + # Ensure that the single other pack contains the commits, but not the + # trees ls repo/.git/objects/pack/pack-*.pack >packlist && test_line_count = 1 packlist && git verify-pack repo/.git/objects/pack/pack-*.pack -v >out && - grep "$(git -C repo rev-parse HEAD)" out && - ! grep "$TREE_HASH" out + grep "$(git -C repo rev-parse one)" out && + grep "$(git -C repo rev-parse two)" out && + ! grep "$TREE_ONE" out && + ! grep "$TREE_TWO" out +' + +test_expect_success 'gc does not repack promisor objects if there are none' ' + rm -rf repo && + test_create_repo repo && + test_commit -C repo one && + + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + git -C repo gc && + + # Ensure that only one pack exists + ls repo/.git/objects/pack/pack-*.pack >packlist && + test_line_count = 1 packlist +' + +repack_and_check () { + rm -rf repo2 && + cp -r repo repo2 && + git -C repo2 repack $1 -d && + git -C repo2 fsck && + + git -C repo2 cat-file -e $2 && + git -C repo2 cat-file -e $3 +} + +test_expect_success 'repack -d does not irreversibly delete promisor objects' ' + rm -rf repo && + test_create_repo repo && + git -C repo config core.repositoryformatversion 1 && + git -C repo config extensions.partialclone "arbitrary string" && + + git -C repo commit --allow-empty -m one && + git -C repo commit --allow-empty -m two && + git -C repo commit --allow-empty -m three && + git -C repo commit --allow-empty -m four && + ONE=$(git -C repo rev-parse HEAD^^^) && + TWO=$(git -C repo rev-parse HEAD^^) && + THREE=$(git -C repo rev-parse HEAD^) && + + printf "$TWO\n" | pack_as_from_promisor && + printf "$THREE\n" | pack_as_from_promisor && + delete_object repo "$ONE" && + + repack_and_check -a "$TWO" "$THREE" && + repack_and_check -A "$TWO" "$THREE" && + repack_and_check -l "$TWO" "$THREE" ' test_expect_success 'gc stops traversal when a missing but promised object is reached' ' diff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh index 3c4d2d6045..013c5a7bc3 100755 --- a/t/t1000-read-tree-m-3way.sh +++ b/t/t1000-read-tree-m-3way.sh @@ -128,7 +128,7 @@ cat >expected <<\EOF EOF check_result () { - git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current && + git ls-files --stage | sed -e 's/ '"$OID_REGEX"' / X /' >current && test_cmp expected current } diff --git a/t/t1001-read-tree-m-2way.sh b/t/t1001-read-tree-m-2way.sh index 5ededd8e40..1057a96b24 100755 --- a/t/t1001-read-tree-m-2way.sh +++ b/t/t1001-read-tree-m-2way.sh @@ -30,7 +30,7 @@ read_tree_twoway () { compare_change () { sed -n >current \ -e '/^--- /d; /^+++ /d; /^@@ /d;' \ - -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /p' \ + -e 's/^\([-+][0-7][0-7][0-7][0-7][0-7][0-7]\) '"$OID_REGEX"' /\1 X /p' \ "$1" test_cmp expected current } diff --git a/t/t1002-read-tree-m-u-2way.sh b/t/t1002-read-tree-m-u-2way.sh index 7ca2e65d10..9c05f5e1f5 100755 --- a/t/t1002-read-tree-m-u-2way.sh +++ b/t/t1002-read-tree-m-u-2way.sh @@ -16,7 +16,7 @@ compare_change () { -e '1{/^diff --git /d;}' \ -e '2{/^index /d;}' \ -e '/^--- /d; /^+++ /d; /^@@ /d;' \ - -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$_x40"' /\1 X /' "$1" + -e 's/^\(.[0-7][0-7][0-7][0-7][0-7][0-7]\) '"$OID_REGEX"' /\1 X /' "$1" test_cmp expected current } diff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh index c7ce5d8bb5..c13578a635 100755 --- a/t/t1004-read-tree-m-u-wf.sh +++ b/t/t1004-read-tree-m-u-wf.sh @@ -179,6 +179,8 @@ test_expect_success 'funny symlink in work tree' ' test_expect_success SANITY 'funny symlink in work tree, un-unlink-able' ' + test_when_finished "chmod u+w a 2>/dev/null; rm -fr a b" && + rm -fr a b && git reset --hard && @@ -188,10 +190,6 @@ test_expect_success SANITY 'funny symlink in work tree, un-unlink-able' ' ' -# clean-up from the above test -chmod a+w a 2>/dev/null -rm -fr a b - test_expect_success 'D/F setup' ' git reset --hard && @@ -212,10 +210,10 @@ test_expect_success 'D/F' ' read_tree_u_must_succeed -m -u branch-point side-b side-a && git ls-files -u >actual && ( - a=$(git rev-parse branch-point:subdir/file2) - b=$(git rev-parse side-a:subdir/file2/another) - echo "100644 $a 1 subdir/file2" - echo "100644 $a 2 subdir/file2" + a=$(git rev-parse branch-point:subdir/file2) && + b=$(git rev-parse side-a:subdir/file2/another) && + echo "100644 $a 1 subdir/file2" && + echo "100644 $a 2 subdir/file2" && echo "100644 $b 3 subdir/file2/another" ) >expect && test_cmp expect actual diff --git a/t/t1005-read-tree-reset.sh b/t/t1005-read-tree-reset.sh index 074568500a..83b09e1310 100755 --- a/t/t1005-read-tree-reset.sh +++ b/t/t1005-read-tree-reset.sh @@ -33,7 +33,7 @@ test_expect_success 'reset should remove remnants from a failed merge' ' git ls-files -s >expect && sha1=$(git rev-parse :new) && ( - echo "100644 $sha1 1 old" + echo "100644 $sha1 1 old" && echo "100644 $sha1 3 old" ) | git update-index --index-info && >old && @@ -48,7 +48,7 @@ test_expect_success 'two-way reset should remove remnants too' ' git ls-files -s >expect && sha1=$(git rev-parse :new) && ( - echo "100644 $sha1 1 old" + echo "100644 $sha1 1 old" && echo "100644 $sha1 3 old" ) | git update-index --index-info && >old && @@ -63,7 +63,7 @@ test_expect_success 'Porcelain reset should remove remnants too' ' git ls-files -s >expect && sha1=$(git rev-parse :new) && ( - echo "100644 $sha1 1 old" + echo "100644 $sha1 1 old" && echo "100644 $sha1 3 old" ) | git update-index --index-info && >old && @@ -78,7 +78,7 @@ test_expect_success 'Porcelain checkout -f should remove remnants too' ' git ls-files -s >expect && sha1=$(git rev-parse :new) && ( - echo "100644 $sha1 1 old" + echo "100644 $sha1 1 old" && echo "100644 $sha1 3 old" ) | git update-index --index-info && >old && @@ -93,7 +93,7 @@ test_expect_success 'Porcelain checkout -f HEAD should remove remnants too' ' git ls-files -s >expect && sha1=$(git rev-parse :new) && ( - echo "100644 $sha1 1 old" + echo "100644 $sha1 1 old" && echo "100644 $sha1 3 old" ) | git update-index --index-info && >old && diff --git a/t/t1006-cat-file.sh b/t/t1006-cat-file.sh index 2ac3b940c6..7f19d591f2 100755 --- a/t/t1006-cat-file.sh +++ b/t/t1006-cat-file.sh @@ -236,8 +236,8 @@ test_expect_success "--batch-check for an empty line" ' ' test_expect_success 'empty --batch-check notices missing object' ' - echo "$_z40 missing" >expect && - echo "$_z40" | git cat-file --batch-check="" >actual && + echo "$ZERO_OID missing" >expect && + echo "$ZERO_OID" | git cat-file --batch-check="" >actual && test_cmp expect actual ' @@ -294,8 +294,8 @@ test_expect_success 'setup blobs which are likely to delta' ' test_expect_success 'confirm that neither loose blob is a delta' ' cat >expect <<-EOF && - $_z40 - $_z40 + $ZERO_OID + $ZERO_OID EOF git cat-file --batch-check="%(deltabase)" <blobs >actual && test_cmp expect actual @@ -550,8 +550,8 @@ test_expect_success 'git cat-file --batch --follow-symlink returns correct sha a test_expect_success 'cat-file --batch-all-objects shows all objects' ' # make new repos so we know the full set of objects; we will # also make sure that there are some packed and some loose - # objects, some referenced and some not, and that there are - # some available only via alternates. + # objects, some referenced and some not, some duplicates, and that + # there are some available only via alternates. git init all-one && ( cd all-one && @@ -567,10 +567,23 @@ test_expect_success 'cat-file --batch-all-objects shows all objects' ' cd all-two && echo local-unref | git hash-object -w --stdin ) >>expect.unsorted && + git -C all-two rev-parse HEAD:file | + git -C all-two pack-objects .git/objects/pack/pack && sort <expect.unsorted >expect && git -C all-two cat-file --batch-all-objects \ --batch-check="%(objectname)" >actual && test_cmp expect actual ' +# The only user-visible difference is that the objects are no longer sorted, +# and the resulting sort order is undefined. So we can only check that it +# produces the same objects as the ordered case, but that at least exercises +# the code. +test_expect_success 'cat-file --unordered works' ' + git -C all-two cat-file --batch-all-objects --unordered \ + --batch-check="%(objectname)" >actual.unsorted && + sort <actual.unsorted >actual && + test_cmp expect actual +' + test_done diff --git a/t/t1007-hash-object.sh b/t/t1007-hash-object.sh index 532682f51c..a37753047e 100755 --- a/t/t1007-hash-object.sh +++ b/t/t1007-hash-object.sh @@ -9,13 +9,13 @@ echo_without_newline() { } test_blob_does_not_exist() { - test_expect_success 'blob does not exist in database' " + test_expect_success SHA1 'blob does not exist in database' " test_must_fail git cat-file blob $1 " } test_blob_exists() { - test_expect_success 'blob exists in database' " + test_expect_success SHA1 'blob exists in database' " git cat-file blob $1 " } @@ -73,19 +73,19 @@ test_expect_success "Can't use --path with --no-filters" ' push_repo -test_expect_success 'hash a file' ' +test_expect_success SHA1 'hash a file' ' test $hello_sha1 = $(git hash-object hello) ' test_blob_does_not_exist $hello_sha1 -test_expect_success 'hash from stdin' ' +test_expect_success SHA1 'hash from stdin' ' test $example_sha1 = $(git hash-object --stdin < example) ' test_blob_does_not_exist $example_sha1 -test_expect_success 'hash a file and write to database' ' +test_expect_success SHA1 'hash a file and write to database' ' test $hello_sha1 = $(git hash-object -w hello) ' @@ -161,7 +161,7 @@ pop_repo for args in "-w --stdin" "--stdin -w"; do push_repo - test_expect_success "hash from stdin and write to database ($args)" ' + test_expect_success SHA1 "hash from stdin and write to database ($args)" ' test $example_sha1 = $(git hash-object $args < example) ' @@ -176,14 +176,14 @@ example" sha1s="$hello_sha1 $example_sha1" -test_expect_success "hash two files with names on stdin" ' +test_expect_success SHA1 "hash two files with names on stdin" ' test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object --stdin-paths)" ' for args in "-w --stdin-paths" "--stdin-paths -w"; do push_repo - test_expect_success "hash two files with names on stdin and write to database ($args)" ' + test_expect_success SHA1 "hash two files with names on stdin and write to database ($args)" ' test "$sha1s" = "$(echo_without_newline "$filenames" | git hash-object $args)" ' diff --git a/t/t1008-read-tree-overlay.sh b/t/t1008-read-tree-overlay.sh index 4c50ed955e..cf96016844 100755 --- a/t/t1008-read-tree-overlay.sh +++ b/t/t1008-read-tree-overlay.sh @@ -23,7 +23,7 @@ test_expect_success setup ' test_expect_success 'multi-read' ' read_tree_must_succeed initial master side && - (echo a; echo b/c) >expect && + test_write_lines a b/c >expect && git ls-files >actual && test_cmp expect actual ' diff --git a/t/t1011-read-tree-sparse-checkout.sh b/t/t1011-read-tree-sparse-checkout.sh index 0c6f48f302..ba71b159ba 100755 --- a/t/t1011-read-tree-sparse-checkout.sh +++ b/t/t1011-read-tree-sparse-checkout.sh @@ -227,12 +227,11 @@ test_expect_success 'index removal and worktree narrowing at the same time' ' ' test_expect_success 'read-tree --reset removes outside worktree' ' - >empty && echo init.t >.git/info/sparse-checkout && git checkout -f top && git reset --hard removed && git ls-files sub/added >result && - test_cmp empty result + test_must_be_empty result ' test_expect_success 'print errors when failed to update worktree' ' diff --git a/t/t1012-read-tree-df.sh b/t/t1012-read-tree-df.sh index a6a04b6b90..57f0770df1 100755 --- a/t/t1012-read-tree-df.sh +++ b/t/t1012-read-tree-df.sh @@ -32,7 +32,7 @@ settree () { checkindex () { git ls-files -s | - sed "s|^[0-7][0-7]* $_x40 \([0-3]\) |\1 |" >current && + sed "s|^[0-7][0-7]* $OID_REGEX \([0-3]\) |\1 |" >current && cat >expect && test_cmp expect current } diff --git a/t/t1015-read-index-unmerged.sh b/t/t1015-read-index-unmerged.sh new file mode 100755 index 0000000000..55d22da32c --- /dev/null +++ b/t/t1015-read-index-unmerged.sh @@ -0,0 +1,123 @@ +#!/bin/sh + +test_description='Test various callers of read_index_unmerged' +. ./test-lib.sh + +test_expect_success 'setup modify/delete + directory/file conflict' ' + test_create_repo df_plus_modify_delete && + ( + cd df_plus_modify_delete && + + test_write_lines a b c d e f g h >letters && + git add letters && + git commit -m initial && + + git checkout -b modify && + # Throw in letters.txt for sorting order fun + # ("letters.txt" sorts between "letters" and "letters/file") + echo i >>letters && + echo "version 2" >letters.txt && + git add letters letters.txt && + git commit -m modified && + + git checkout -b delete HEAD^ && + git rm letters && + mkdir letters && + >letters/file && + echo "version 1" >letters.txt && + git add letters letters.txt && + git commit -m deleted + ) +' + +test_expect_success 'read-tree --reset cleans unmerged entries' ' + test_when_finished "git -C df_plus_modify_delete clean -f" && + test_when_finished "git -C df_plus_modify_delete reset --hard" && + ( + cd df_plus_modify_delete && + + git checkout delete^0 && + test_must_fail git merge modify && + + git read-tree --reset HEAD && + git ls-files -u >conflicts && + test_must_be_empty conflicts + ) +' + +test_expect_success 'One reset --hard cleans unmerged entries' ' + test_when_finished "git -C df_plus_modify_delete clean -f" && + test_when_finished "git -C df_plus_modify_delete reset --hard" && + ( + cd df_plus_modify_delete && + + git checkout delete^0 && + test_must_fail git merge modify && + + git reset --hard && + test_path_is_missing .git/MERGE_HEAD && + git ls-files -u >conflicts && + test_must_be_empty conflicts + ) +' + +test_expect_success 'setup directory/file conflict + simple edit/edit' ' + test_create_repo df_plus_edit_edit && + ( + cd df_plus_edit_edit && + + test_seq 1 10 >numbers && + git add numbers && + git commit -m initial && + + git checkout -b d-edit && + mkdir foo && + echo content >foo/bar && + git add foo && + echo 11 >>numbers && + git add numbers && + git commit -m "directory and edit" && + + git checkout -b f-edit d-edit^1 && + echo content >foo && + git add foo && + echo eleven >>numbers && + git add numbers && + git commit -m "file and edit" + ) +' + +test_expect_success 'git merge --abort succeeds despite D/F conflict' ' + test_when_finished "git -C df_plus_edit_edit clean -f" && + test_when_finished "git -C df_plus_edit_edit reset --hard" && + ( + cd df_plus_edit_edit && + + git checkout f-edit^0 && + test_must_fail git merge d-edit^0 && + + git merge --abort && + test_path_is_missing .git/MERGE_HEAD && + git ls-files -u >conflicts && + test_must_be_empty conflicts + ) +' + +test_expect_success 'git am --skip succeeds despite D/F conflict' ' + test_when_finished "git -C df_plus_edit_edit clean -f" && + test_when_finished "git -C df_plus_edit_edit reset --hard" && + ( + cd df_plus_edit_edit && + + git checkout f-edit^0 && + git format-patch -1 d-edit && + test_must_fail git am -3 0001*.patch && + + git am --skip && + test_path_is_missing .git/rebase-apply && + git ls-files -u >conflicts && + test_must_be_empty conflicts + ) +' + +test_done diff --git a/t/t1020-subdirectory.sh b/t/t1020-subdirectory.sh index df3183ea1a..c2df75e495 100755 --- a/t/t1020-subdirectory.sh +++ b/t/t1020-subdirectory.sh @@ -148,7 +148,7 @@ test_expect_success 'GIT_PREFIX for built-ins' ' ( cd dir && echo "change" >two && - GIT_EXTERNAL_DIFF=./diff git diff >../actual + GIT_EXTERNAL_DIFF=./diff git diff >../actual && git checkout -- two ) && test_cmp expect actual diff --git a/t/t1050-large.sh b/t/t1050-large.sh index f9eb143f43..1a9b21b293 100755 --- a/t/t1050-large.sh +++ b/t/t1050-large.sh @@ -108,7 +108,7 @@ test_expect_success 'packsize limit' ' test-tool genrandom "c" $(( 128 * 1024 )) >mid3 && git add mid1 mid2 mid3 && - count=0 + count=0 && for pi in .git/objects/pack/pack-*.idx do test -f "$pi" && count=$(( $count + 1 )) @@ -116,8 +116,8 @@ test_expect_success 'packsize limit' ' test $count = 2 && ( - git hash-object --stdin <mid1 - git hash-object --stdin <mid2 + git hash-object --stdin <mid1 && + git hash-object --stdin <mid2 && git hash-object --stdin <mid3 ) | sort >expect && diff --git a/t/t1090-sparse-checkout-scope.sh b/t/t1090-sparse-checkout-scope.sh index 1f61eb3e88..25d7c700f6 100755 --- a/t/t1090-sparse-checkout-scope.sh +++ b/t/t1090-sparse-checkout-scope.sh @@ -31,6 +31,20 @@ test_expect_success 'perform sparse checkout of master' ' test_path_is_file c ' +test_expect_success 'checkout -b checkout.optimizeNewBranch interaction' ' + cp .git/info/sparse-checkout .git/info/sparse-checkout.bak && + test_when_finished " + mv -f .git/info/sparse-checkout.bak .git/info/sparse-checkout + git checkout master + " && + echo "/b" >>.git/info/sparse-checkout && + test "$(git ls-files -t b)" = "S b" && + git -c checkout.optimizeNewBranch=true checkout -b fast && + test "$(git ls-files -t b)" = "S b" && + git checkout -b slow && + test "$(git ls-files -t b)" = "H b" +' + test_expect_success 'merge feature branch into sparse checkout of master' ' git merge feature && test_path_is_file a && diff --git a/t/t1300-repo-config.sh b/t/t1300-config.sh index e95b1e67da..cdf1fed5d1 100755 --- a/t/t1300-repo-config.sh +++ b/t/t1300-config.sh @@ -108,6 +108,7 @@ bar = foo [beta] baz = multiple \ lines +foo = bar EOF test_expect_success 'unset with cont. lines' ' @@ -118,6 +119,7 @@ cat > expect <<\EOF [alpha] bar = foo [beta] +foo = bar EOF test_expect_success 'unset with cont. lines is correct' 'test_cmp expect .git/config' @@ -344,12 +346,9 @@ test_expect_success 'working --list' ' git config --list > output && test_cmp expect output ' -cat > expect << EOF -EOF - test_expect_success '--list without repo produces empty output' ' git --git-dir=nonexistent config --list >output && - test_cmp expect output + test_must_be_empty output ' cat > expect << EOF @@ -740,7 +739,7 @@ test_expect_success bool ' do git config --bool --get bool.true$i >>result git config --bool --get bool.false$i >>result - done && + done && test_cmp expect result' test_expect_success 'invalid bool (--get)' ' @@ -886,7 +885,7 @@ EOF test_expect_success !MINGW 'get --path copes with unset $HOME' ' ( - unset HOME; + sane_unset HOME && test_must_fail git config --get --path path.home \ >result 2>msg && git config --get --path path.normal >>result && @@ -931,6 +930,36 @@ test_expect_success 'get --expiry-date' ' test_must_fail git config --expiry-date date.invalid1 ' +test_expect_success 'get --type=color' ' + rm .git/config && + git config foo.color "red" && + git config --get --type=color foo.color >actual.raw && + test_decode_color <actual.raw >actual && + echo "<RED>" >expect && + test_cmp expect actual +' + +cat >expect << EOF +[foo] + color = red +EOF + +test_expect_success 'set --type=color' ' + rm .git/config && + git config --type=color foo.color "red" && + test_cmp expect .git/config +' + +test_expect_success 'get --type=color barfs on non-color' ' + echo "[foo]bar=not-a-color" >.git/config && + test_must_fail git config --get --type=color foo.bar +' + +test_expect_success 'set --type=color barfs on non-color' ' + test_must_fail git config --type=color foo.color "not-a-color" 2>error && + test_i18ngrep "cannot parse color" error +' + cat > expect << EOF [quote] leading = " test" @@ -1186,6 +1215,93 @@ test_expect_success 'last one wins: three level vars' ' test_cmp expect actual ' +test_expect_success 'old-fashioned settings are case insensitive' ' + test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" && + + cat >testConfig_actual <<-EOF && + [V.A] + r = value1 + EOF + q_to_tab >testConfig_expect <<-EOF && + [V.A] + Qr = value2 + EOF + git config -f testConfig_actual "v.a.r" value2 && + test_cmp testConfig_expect testConfig_actual && + + cat >testConfig_actual <<-EOF && + [V.A] + r = value1 + EOF + q_to_tab >testConfig_expect <<-EOF && + [V.A] + QR = value2 + EOF + git config -f testConfig_actual "V.a.R" value2 && + test_cmp testConfig_expect testConfig_actual && + + cat >testConfig_actual <<-EOF && + [V.A] + r = value1 + EOF + q_to_tab >testConfig_expect <<-EOF && + [V.A] + r = value1 + Qr = value2 + EOF + git config -f testConfig_actual "V.A.r" value2 && + test_cmp testConfig_expect testConfig_actual && + + cat >testConfig_actual <<-EOF && + [V.A] + r = value1 + EOF + q_to_tab >testConfig_expect <<-EOF && + [V.A] + r = value1 + Qr = value2 + EOF + git config -f testConfig_actual "v.A.r" value2 && + test_cmp testConfig_expect testConfig_actual +' + +test_expect_success 'setting different case sensitive subsections ' ' + test_when_finished "rm -f testConfig testConfig_expect testConfig_actual" && + + cat >testConfig_actual <<-EOF && + [V "A"] + R = v1 + [K "E"] + Y = v1 + [a "b"] + c = v1 + [d "e"] + f = v1 + EOF + q_to_tab >testConfig_expect <<-EOF && + [V "A"] + Qr = v2 + [K "E"] + Qy = v2 + [a "b"] + Qc = v2 + [d "e"] + f = v1 + [d "E"] + Qf = v2 + EOF + # exact match + git config -f testConfig_actual a.b.c v2 && + # match section and subsection, key is cased differently. + git config -f testConfig_actual K.E.y v2 && + # section and key are matched case insensitive, but subsection needs + # to match; When writing out new values only the key is adjusted + git config -f testConfig_actual v.A.r v2 && + # subsection is not matched: + git config -f testConfig_actual d.E.f v2 && + test_cmp testConfig_expect testConfig_actual +' + for VAR in a .a a. a.0b a."b c". a."b c".0d do test_expect_success "git -c $VAR=VAL rejects invalid '$VAR'" ' @@ -1411,7 +1527,7 @@ test_expect_success 'urlmatch with wildcard' ' ' # good section hygiene -test_expect_failure 'unsetting the last key in a section removes header' ' +test_expect_success '--unset last key removes section (except if commented)' ' cat >.git/config <<-\EOF && # some generic comment on the configuration file itself # a comment specific to this "section" section. @@ -1425,13 +1541,86 @@ test_expect_failure 'unsetting the last key in a section removes header' ' cat >expect <<-\EOF && # some generic comment on the configuration file itself + # a comment specific to this "section" section. + [section] + # some intervening lines + # that should also be dropped + + # please be careful when you update the above variable EOF git config --unset section.key && - test_cmp expect .git/config + test_cmp expect .git/config && + + cat >.git/config <<-\EOF && + [section] + key = value + [next-section] + EOF + + cat >expect <<-\EOF && + [next-section] + EOF + + git config --unset section.key && + test_cmp expect .git/config && + + q_to_tab >.git/config <<-\EOF && + [one] + Qkey = "multiline \ + QQ# with comment" + [two] + key = true + EOF + git config --unset two.key && + ! grep two .git/config && + + q_to_tab >.git/config <<-\EOF && + [one] + Qkey = "multiline \ + QQ# with comment" + [one] + key = true + EOF + git config --unset-all one.key && + test_line_count = 0 .git/config && + + q_to_tab >.git/config <<-\EOF && + [one] + Qkey = true + Q# a comment not at the start + [two] + Qkey = true + EOF + git config --unset two.key && + grep two .git/config && + + q_to_tab >.git/config <<-\EOF && + [one] + Qkey = not [two "subsection"] + [two "subsection"] + [two "subsection"] + Qkey = true + [TWO "subsection"] + [one] + EOF + git config --unset two.subsection.key && + test "not [two subsection]" = "$(git config one.key)" && + test_line_count = 3 .git/config +' + +test_expect_success '--unset-all removes section if empty & uncommented' ' + cat >.git/config <<-\EOF && + [section] + key = value1 + key = value2 + EOF + + git config --unset-all section.key && + test_line_count = 0 .git/config ' -test_expect_failure 'adding a key into an empty section reuses header' ' +test_expect_success 'adding a key into an empty section reuses header' ' cat >.git/config <<-\EOF && [section] EOF @@ -1611,4 +1800,88 @@ test_expect_success '--local requires a repo' ' test_expect_code 128 nongit git config --local foo.bar ' +cat >.git/config <<-\EOF && +[core] +foo = true +number = 10 +big = 1M +EOF + +test_expect_success 'identical modern --type specifiers are allowed' ' + git config --type=int --type=int core.big >actual && + echo 1048576 >expect && + test_cmp expect actual +' + +test_expect_success 'identical legacy --type specifiers are allowed' ' + git config --int --int core.big >actual && + echo 1048576 >expect && + test_cmp expect actual +' + +test_expect_success 'identical mixed --type specifiers are allowed' ' + git config --int --type=int core.big >actual && + echo 1048576 >expect && + test_cmp expect actual +' + +test_expect_success 'non-identical modern --type specifiers are not allowed' ' + test_must_fail git config --type=int --type=bool core.big 2>error && + test_i18ngrep "only one type at a time" error +' + +test_expect_success 'non-identical legacy --type specifiers are not allowed' ' + test_must_fail git config --int --bool core.big 2>error && + test_i18ngrep "only one type at a time" error +' + +test_expect_success 'non-identical mixed --type specifiers are not allowed' ' + test_must_fail git config --type=int --bool core.big 2>error && + test_i18ngrep "only one type at a time" error +' + +test_expect_success '--type allows valid type specifiers' ' + echo "true" >expect && + git config --type=bool core.foo >actual && + test_cmp expect actual +' + +test_expect_success '--no-type unsets type specifiers' ' + echo "10" >expect && + git config --type=bool --no-type core.number >actual && + test_cmp expect actual +' + +test_expect_success 'unset type specifiers may be reset to conflicting ones' ' + echo 1048576 >expect && + git config --type=bool --no-type --type=int core.big >actual && + test_cmp expect actual +' + +test_expect_success '--type rejects unknown specifiers' ' + test_must_fail git config --type=nonsense core.foo 2>error && + test_i18ngrep "unrecognized --type argument" error +' + +test_expect_success '--replace-all does not invent newlines' ' + q_to_tab >.git/config <<-\EOF && + [abc]key + QkeepSection + [xyz] + Qkey = 1 + [abc] + Qkey = a + EOF + q_to_tab >expect <<-\EOF && + [abc] + QkeepSection + [xyz] + Qkey = 1 + [abc] + Qkey = b + EOF + git config --replace-all abc.key b && + test_cmp .git/config expect +' + test_done diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index f035ee40a3..635918505d 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh @@ -310,7 +310,7 @@ test_expect_success 'include cycles are detected' ' cycle EOF test_must_fail git config --get-all test.value 2>stderr && - grep "exceeded maximum include depth" stderr + test_i18ngrep "exceeded maximum include depth" stderr ' test_done diff --git a/t/t1306-xdg-files.sh b/t/t1306-xdg-files.sh index 8b14ab187c..21e139a313 100755 --- a/t/t1306-xdg-files.sh +++ b/t/t1306-xdg-files.sh @@ -114,11 +114,10 @@ test_expect_success 'Exclusion in a non-XDG global ignore file' ' ' test_expect_success 'Checking XDG ignore file when HOME is unset' ' - >expected && (sane_unset HOME && git config --unset core.excludesfile && git ls-files --exclude-standard --ignored >actual) && - test_cmp expected actual + test_must_be_empty actual ' test_expect_success 'Checking attributes in the XDG attributes file' ' @@ -132,10 +131,9 @@ test_expect_success 'Checking attributes in the XDG attributes file' ' ' test_expect_success 'Checking XDG attributes when HOME is unset' ' - >expected && (sane_unset HOME && git check-attr -a f >actual) && - test_cmp expected actual + test_must_be_empty actual ' test_expect_success '$XDG_CONFIG_HOME overrides $HOME/.config/git/attributes' ' diff --git a/t/t1307-config-blob.sh b/t/t1307-config-blob.sh index eed31ffa30..37dc689d8c 100755 --- a/t/t1307-config-blob.sh +++ b/t/t1307-config-blob.sh @@ -73,4 +73,8 @@ test_expect_success 'can parse blob ending with CR' ' test_cmp expect actual ' +test_expect_success 'config --blob outside of a repository is an error' ' + test_must_fail nongit git config --blob=foo --list +' + test_done diff --git a/t/t1308-config-set.sh b/t/t1308-config-set.sh index 3e00d1af01..d0a2727b85 100755 --- a/t/t1308-config-set.sh +++ b/t/t1308-config-set.sh @@ -233,7 +233,7 @@ test_expect_success 'check line errors for malformed values' ' test_expect_success 'error on modifying repo config without repo' ' nongit test_must_fail git config a.b c 2>err && - grep "not in a git directory" err + test_i18ngrep "not in a git directory" err ' cmdline_config="'foo.bar=from-cmdline'" diff --git a/t/t1310-config-default.sh b/t/t1310-config-default.sh new file mode 100755 index 0000000000..6049d91708 --- /dev/null +++ b/t/t1310-config-default.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +test_description='Test git config in different settings (with --default)' + +. ./test-lib.sh + +test_expect_success 'uses --default when entry missing' ' + echo quux >expect && + git config -f config --default=quux core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'does not use --default when entry present' ' + echo bar >expect && + git -c core.foo=bar config --default=baz core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'canonicalizes --default with appropriate type' ' + echo true >expect && + git config -f config --default=yes --bool core.foo >actual && + test_cmp expect actual +' + +test_expect_success 'dies when --default cannot be parsed' ' + test_must_fail git config -f config --type=expiry-date --default=x --get \ + not.a.section 2>error && + test_i18ngrep "failed to format default config value" error +' + +test_expect_success 'does not allow --default without --get' ' + test_must_fail git config --default=quux --unset a.section >output 2>&1 && + test_i18ngrep "\-\-default is only applicable to" output +' + +test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index 664a3a4e4e..7c8df20955 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -6,7 +6,7 @@ test_description='Test git update-ref and basic ref logging' . ./test-lib.sh -Z=$_z40 +Z=$ZERO_OID m=refs/heads/master n_dir=refs/heads/gu @@ -390,7 +390,7 @@ test_expect_success 'Query "master@{2005-05-26 23:33:01}" (middle of history wit test_when_finished "rm -f o e" && git rev-parse --verify "master@{2005-05-26 23:33:01}" >o 2>e && test $B = $(cat o) && - test "warning: Log for ref $m has gap after $gd." = "$(cat e)" + test_i18ngrep -F "warning: log for ref $m has gap after $gd" e ' test_expect_success 'Query "master@{2005-05-26 23:38:00}" (middle of history)' ' test_when_finished "rm -f o e" && @@ -408,7 +408,7 @@ test_expect_success 'Query "master@{2005-05-28}" (past end of history)' ' test_when_finished "rm -f o e" && git rev-parse --verify "master@{2005-05-28}" >o 2>e && test $D = $(cat o) && - test "warning: Log for ref $m unexpectedly ended on $ld." = "$(cat e)" + test_i18ngrep -F "warning: log for ref $m unexpectedly ended on $ld" e ' rm -f .git/$m .git/logs/$m expect @@ -457,6 +457,66 @@ test_expect_success 'git cat-file blob master@{2005-05-26 23:42}:F (expect OTHER test OTHER = $(git cat-file blob "master@{2005-05-26 23:42}:F") ' +# Test adding and deleting pseudorefs + +test_expect_success 'given old value for missing pseudoref, do not create' ' + test_must_fail git update-ref PSEUDOREF $A $B 2>err && + test_path_is_missing .git/PSEUDOREF && + test_i18ngrep "could not read ref" err +' + +test_expect_success 'create pseudoref' ' + git update-ref PSEUDOREF $A && + test $A = $(cat .git/PSEUDOREF) +' + +test_expect_success 'overwrite pseudoref with no old value given' ' + git update-ref PSEUDOREF $B && + test $B = $(cat .git/PSEUDOREF) +' + +test_expect_success 'overwrite pseudoref with correct old value' ' + git update-ref PSEUDOREF $C $B && + test $C = $(cat .git/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 = $(cat .git/PSEUDOREF) && + test_i18ngrep "unexpected object ID" err +' + +test_expect_success 'delete pseudoref' ' + git update-ref -d PSEUDOREF && + test_path_is_missing .git/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 = $(cat .git/PSEUDOREF) && + test_i18ngrep "unexpected object ID" err +' + +test_expect_success 'delete pseudoref with correct old value' ' + git update-ref -d PSEUDOREF $A && + test_path_is_missing .git/PSEUDOREF +' + +test_expect_success 'create pseudoref with old OID zero' ' + git update-ref PSEUDOREF $A $Z && + test $A = $(cat .git/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 = $(cat .git/PSEUDOREF) && + test_i18ngrep "already exists" err +' + +# Test --stdin + a=refs/heads/a b=refs/heads/b c=refs/heads/c @@ -590,7 +650,7 @@ test_expect_success 'stdin fails with duplicate refs' ' create $a $m EOF test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err + test_i18ngrep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed" err ' test_expect_success 'stdin create ref works' ' @@ -992,7 +1052,7 @@ test_expect_success 'stdin -z fails option with unknown name' ' test_expect_success 'stdin -z fails with duplicate refs' ' printf $F "create $a" "$m" "create $b" "$m" "create $a" "$m" >stdin && test_must_fail git update-ref -z --stdin <stdin 2>err && - grep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed." err + test_i18ngrep "fatal: multiple updates for ref '"'"'$a'"'"' not allowed" err ' test_expect_success 'stdin -z create ref works' ' @@ -1223,7 +1283,7 @@ test_expect_success 'fails with duplicate HEAD update' ' update HEAD $B EOF test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: multiple updates for '\''HEAD'\'' (including one via its referent .refs/heads/target1.) are not allowed" err && + test_i18ngrep "fatal: multiple updates for '\''HEAD'\'' (including one via its referent .refs/heads/target1.) are not allowed" err && echo "refs/heads/target1" >expect && git symbolic-ref HEAD >actual && test_cmp expect actual && @@ -1240,7 +1300,7 @@ test_expect_success 'fails with duplicate ref update via symref' ' update refs/heads/symref2 $B EOF test_must_fail git update-ref --stdin <stdin 2>err && - grep "fatal: multiple updates for '\''refs/heads/target2'\'' (including one via symref .refs/heads/symref2.) are not allowed" err && + test_i18ngrep "fatal: multiple updates for '\''refs/heads/target2'\'' (including one via symref .refs/heads/symref2.) are not allowed" err && echo "refs/heads/target2" >expect && git symbolic-ref refs/heads/symref2 >actual && test_cmp expect actual && diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh index 9e782a8122..a4ebb0b65f 100755 --- a/t/t1401-symbolic-ref.sh +++ b/t/t1401-symbolic-ref.sh @@ -65,7 +65,7 @@ reset_to_sane test_expect_success 'symbolic-ref fails to delete real ref' ' echo "fatal: Cannot delete refs/heads/foo, not a symbolic ref" >expect && test_must_fail git symbolic-ref -d refs/heads/foo >actual 2>&1 && - test_path_is_file .git/refs/heads/foo && + git rev-parse --verify refs/heads/foo && test_cmp expect actual ' reset_to_sane diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh index 30354fd26c..5d955c3bff 100755 --- a/t/t1403-show-ref.sh +++ b/t/t1403-show-ref.sh @@ -26,26 +26,22 @@ test_expect_success 'show-ref' ' git show-ref refs/tags/A >actual && test_cmp expect actual && - >expect && - test_must_fail git show-ref D >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'show-ref -q' ' - >expect && - git show-ref -q A >actual && - test_cmp expect actual && + test_must_be_empty actual && git show-ref -q tags/A >actual && - test_cmp expect actual && + test_must_be_empty actual && git show-ref -q refs/tags/A >actual && - test_cmp expect actual && + test_must_be_empty actual && test_must_fail git show-ref -q D >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'show-ref --verify' ' @@ -54,32 +50,28 @@ test_expect_success 'show-ref --verify' ' git show-ref --verify refs/tags/A >actual && test_cmp expect actual && - >expect && - test_must_fail git show-ref --verify A >actual && - test_cmp expect actual && + test_must_be_empty actual && test_must_fail git show-ref --verify tags/A >actual && - test_cmp expect actual && + test_must_be_empty actual && test_must_fail git show-ref --verify D >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'show-ref --verify -q' ' - >expect && - git show-ref --verify -q refs/tags/A >actual && - test_cmp expect actual && + test_must_be_empty actual && test_must_fail git show-ref --verify -q A >actual && - test_cmp expect actual && + test_must_be_empty actual && test_must_fail git show-ref --verify -q tags/A >actual && - test_cmp expect actual && + test_must_be_empty actual && test_must_fail git show-ref --verify -q D >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'show-ref -d' ' @@ -113,19 +105,17 @@ test_expect_success 'show-ref -d' ' git show-ref -d --verify refs/heads/master >actual && test_cmp expect actual && - >expect && - test_must_fail git show-ref -d --verify master >actual && - test_cmp expect actual && + test_must_be_empty actual && test_must_fail git show-ref -d --verify heads/master >actual && - test_cmp expect actual && + test_must_be_empty actual && test_must_fail git show-ref --verify -d A C >actual && - test_cmp expect actual && + test_must_be_empty actual && test_must_fail git show-ref --verify -d tags/A tags/C >actual && - test_cmp expect actual + test_must_be_empty actual ' @@ -178,10 +168,8 @@ test_expect_success 'show-ref --verify HEAD' ' git show-ref --verify HEAD >actual && test_cmp expect actual && - >expect && - git show-ref --verify -q HEAD >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'show-ref --verify with dangling ref' ' diff --git a/t/t1404-update-ref-errors.sh b/t/t1404-update-ref-errors.sh index 3a887b5113..51a4f4c0ac 100755 --- a/t/t1404-update-ref-errors.sh +++ b/t/t1404-update-ref-errors.sh @@ -27,7 +27,7 @@ test_update_rejected () { fi && printf "create $prefix/%s $C\n" $create >input && test_must_fail git update-ref --stdin <input 2>output.err && - grep -F "$error" output.err && + test_i18ngrep -F "$error" output.err && git for-each-ref $prefix >actual && test_cmp unchanged actual } @@ -103,7 +103,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 && + test_i18ncmp expected-err 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 @@ -559,9 +559,9 @@ test_expect_success 'no bogus intermediate values during delete' ' { # 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 longer than - # it takes for us to do the check then delete it: - git -c core.packedrefstimeout=3000 update-ref -d $prefix/foo & + # 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 diff --git a/t/t1407-worktree-ref-store.sh b/t/t1407-worktree-ref-store.sh index 2211f9831f..4623ae15c4 100755 --- a/t/t1407-worktree-ref-store.sh +++ b/t/t1407-worktree-ref-store.sh @@ -50,13 +50,13 @@ test_expect_success 'create_symref(FOO, refs/heads/master)' ' ' test_expect_success 'for_each_reflog()' ' - echo $_z40 > .git/logs/PSEUDO-MAIN && + echo $ZERO_OID > .git/logs/PSEUDO-MAIN && mkdir -p .git/logs/refs/bisect && - echo $_z40 > .git/logs/refs/bisect/random && + echo $ZERO_OID > .git/logs/refs/bisect/random && - echo $_z40 > .git/worktrees/wt/logs/PSEUDO-WT && + echo $ZERO_OID > .git/worktrees/wt/logs/PSEUDO-WT && mkdir -p .git/worktrees/wt/logs/refs/bisect && - echo $_z40 > .git/worktrees/wt/logs/refs/bisect/wt-random && + echo $ZERO_OID > .git/worktrees/wt/logs/refs/bisect/wt-random && $RWT for-each-reflog | cut -c 42- | sort >actual && cat >expected <<-\EOF && diff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh index 553e26d9ce..388b0611d8 100755 --- a/t/t1410-reflog.sh +++ b/t/t1410-reflog.sh @@ -290,9 +290,8 @@ test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' ' # same as before, but we only create a reflog for "one" if # it already exists, which it does not git -c core.logallrefupdates=false branch one master && - : >expect && git log -g --format="%gd %gs" one >actual && - test_cmp expect actual + test_must_be_empty actual ' # Triggering the bug detected by this test requires a newline to fall @@ -339,8 +338,8 @@ test_expect_failure 'reflog with non-commit entries displays all entries' ' ' test_expect_success 'reflog expire operates on symref not referrent' ' - git branch -l the_symref && - git branch -l 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" && diff --git a/t/t1411-reflog-show.sh b/t/t1411-reflog-show.sh index 596907758d..985daf1def 100755 --- a/t/t1411-reflog-show.sh +++ b/t/t1411-reflog-show.sh @@ -136,13 +136,12 @@ test_expect_success '--date magic does not override explicit @{0} syntax' ' test_cmp expect actual ' -: >expect test_expect_success 'empty reflog file' ' git branch empty && git reflog expire --expire=all refs/heads/empty && git log -g empty >actual && - test_cmp expect actual + test_must_be_empty actual ' # This guards against the alternative of showing the diffs vs. the @@ -159,9 +158,9 @@ test_expect_success 'git log -g -p shows diffs vs. parents' ' git log -1 -p HEAD^ >log.one && git log -1 -p HEAD >log.two && ( - cat log.one; echo - cat log.two; echo - cat log.one; echo + cat log.one && echo && + cat log.two && echo && + cat log.one && echo && cat log.two ) >expect && test_cmp expect actual diff --git a/t/t1450-fsck.sh b/t/t1450-fsck.sh index cb4b66e29d..0f2dd26f74 100755 --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@ -16,8 +16,7 @@ test_expect_success setup ' git checkout HEAD^0 && test_commit B fileB two && git tag -d A B && - git reflog expire --expire=now --all && - >empty + git reflog expire --expire=now --all ' test_expect_success 'loose objects borrowed from alternate are not missing' ' @@ -29,12 +28,12 @@ test_expect_success 'loose objects borrowed from alternate are not missing' ' test_commit C fileC one && git fsck --no-dangling >../actual 2>&1 ) && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'HEAD is part of refs, valid objects appear valid' ' git fsck >actual 2>&1 && - test_cmp empty actual + test_must_be_empty actual ' # Corruption tests follow. Make sure to remove all traces of the @@ -346,12 +345,12 @@ test_expect_success 'tag with NUL in header' ' test_expect_success 'cleaned up' ' git fsck >actual 2>&1 && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'rev-list --verify-objects' ' git rev-list --verify-objects --all >/dev/null 2>out && - test_cmp empty out + test_must_be_empty out ' test_expect_success 'rev-list --verify-objects with bad sha1' ' @@ -372,7 +371,7 @@ test_expect_success 'rev-list --verify-objects with bad sha1' ' test_might_fail git rev-list --verify-objects refs/heads/bogus >/dev/null 2>out && cat out && - grep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out + test_i18ngrep -q "error: sha1 mismatch 63ffffffffffffffffffffffffffffffffffffff" out ' test_expect_success 'force fsck to ignore double author' ' @@ -713,7 +712,7 @@ test_expect_success 'fsck notices dangling objects' ' test_expect_success 'fsck $name notices bogus $name' ' test_must_fail git fsck bogus && - test_must_fail git fsck $_z40 + test_must_fail git fsck $ZERO_OID ' test_expect_success 'bogus head does not fallback to all heads' ' @@ -723,7 +722,7 @@ test_expect_success 'bogus head does not fallback to all heads' ' blob=$(git rev-parse :foo) && test_when_finished "git rm --cached foo" && remove_object $blob && - test_must_fail git fsck $_z40 >out 2>&1 && + test_must_fail git fsck $ZERO_OID >out 2>&1 && ! grep $blob out ' diff --git a/t/t1501-work-tree.sh b/t/t1501-work-tree.sh index 02cf2013fc..3498d3d55e 100755 --- a/t/t1501-work-tree.sh +++ b/t/t1501-work-tree.sh @@ -41,7 +41,7 @@ test_expect_success 'setup: helper for testing rev-parse' ' # rev-parse --show-prefix should output # a single newline when at the top of the work tree, # but we test for that separately. - test -z "$4" && ! test -s actual.prefix || + test -z "$4" && test_must_be_empty actual.prefix || test_cmp expected.prefix actual.prefix fi } @@ -238,10 +238,10 @@ test_expect_success '_gently() groks relative GIT_DIR & GIT_WORK_TREE' ' test_expect_success 'diff-index respects work tree under .git dir' ' cat >diff-index-cached.expected <<-EOF && - :000000 100644 $_z40 $EMPTY_BLOB A sub/dir/tracked + :000000 100644 $ZERO_OID $EMPTY_BLOB A sub/dir/tracked EOF cat >diff-index.expected <<-EOF && - :000000 100644 $_z40 $_z40 A sub/dir/tracked + :000000 100644 $ZERO_OID $ZERO_OID A sub/dir/tracked EOF ( @@ -257,7 +257,7 @@ test_expect_success 'diff-index respects work tree under .git dir' ' test_expect_success 'diff-files respects work tree under .git dir' ' cat >diff-files.expected <<-EOF && - :100644 100644 $EMPTY_BLOB $_z40 M sub/dir/tracked + :100644 100644 $EMPTY_BLOB $ZERO_OID M sub/dir/tracked EOF ( @@ -431,4 +431,16 @@ test_expect_success 'error out gracefully on invalid $GIT_WORK_TREE' ' ) ' +test_expect_success 'refs work with relative gitdir and work tree' ' + git init relative && + git -C relative commit --allow-empty -m one && + git -C relative commit --allow-empty -m two && + + GIT_DIR=relative/.git GIT_WORK_TREE=relative git reset HEAD^ && + + git -C relative log -1 --format=%s >actual && + echo one >expect && + test_cmp expect actual +' + test_done diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh index 93c77eac45..fa3e499641 100755 --- a/t/t1507-rev-parse-upstream.sh +++ b/t/t1507-rev-parse-upstream.sh @@ -123,9 +123,9 @@ test_expect_success 'checkout -b new my-side@{u} forks from the same' ' test_expect_success 'merge my-side@{u} records the correct name' ' ( - cd clone || exit - git checkout master || exit - git branch -D new ;# can fail but is ok + cd clone && + git checkout master && + test_might_fail git branch -D new && git branch -t new my-side@{u} && git merge -s ours new@{u} && git show -s --pretty=tformat:%s >actual && @@ -138,8 +138,7 @@ test_expect_success 'branch -d other@{u}' ' git checkout -t -b other master && git branch -d @{u} && git for-each-ref refs/heads/master >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'checkout other@{u}' ' diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh index e6854b828e..9974457f56 100755 --- a/t/t1510-repo-setup.sh +++ b/t/t1510-repo-setup.sh @@ -234,15 +234,14 @@ test_expect_success '#0: nonbare repo, no explicit configuration' ' try_repo 0 unset unset unset "" unset \ .git "$here/0" "$here/0" "(null)" \ .git "$here/0" "$here/0" sub/ 2>message && - ! test -s message + test_must_be_empty message ' test_expect_success '#1: GIT_WORK_TREE without explicit GIT_DIR is accepted' ' - mkdir -p wt && try_repo 1 "$here" unset unset "" unset \ "$here/1/.git" "$here" "$here" 1/ \ "$here/1/.git" "$here" "$here" 1/sub/ 2>message && - ! test -s message + test_must_be_empty message ' test_expect_success '#2: worktree defaults to cwd with explicit GIT_DIR' ' @@ -269,7 +268,7 @@ test_expect_success '#4: core.worktree without GIT_DIR set is accepted' ' try_case 4 unset unset \ .git "$here/4/sub" "$here/4" "(null)" \ "$here/4/.git" "$here/4/sub" "$here/4/sub" "(null)" 2>message && - ! test -s message + test_must_be_empty message ' test_expect_success '#5: core.worktree + GIT_WORK_TREE is accepted' ' @@ -280,7 +279,7 @@ test_expect_success '#5: core.worktree + GIT_WORK_TREE is accepted' ' try_repo 5a .. unset "$here/5a" "" unset \ "$here/5a/.git" "$here" "$here" 5a/ \ "$here/5a/.git" "$here/5a" "$here/5a" sub/ && - ! test -s message + test_must_be_empty message ' test_expect_success '#6: setting GIT_DIR brings core.worktree to life' ' @@ -377,7 +376,7 @@ test_expect_success '#9: GIT_WORK_TREE accepted with gitfile' ' try_repo 9 wt unset unset gitfile unset \ "$here/9.git" "$here/9/wt" "$here/9" "(null)" \ "$here/9.git" "$here/9/sub/wt" "$here/9/sub" "(null)" 2>message && - ! test -s message + test_must_be_empty message ' test_expect_success '#10: GIT_DIR can point to gitfile' ' @@ -403,7 +402,7 @@ test_expect_success '#12: core.worktree with gitfile is accepted' ' try_repo 12 unset unset "$here/12" gitfile unset \ "$here/12.git" "$here/12" "$here/12" "(null)" \ "$here/12.git" "$here/12" "$here/12" sub/ 2>message && - ! test -s message + test_must_be_empty message ' test_expect_success '#13: core.worktree+GIT_WORK_TREE accepted (with gitfile)' ' @@ -411,7 +410,7 @@ test_expect_success '#13: core.worktree+GIT_WORK_TREE accepted (with gitfile)' ' try_repo 13 non-existent-too unset non-existent gitfile unset \ "$here/13.git" "$here/13/non-existent-too" "$here/13" "(null)" \ "$here/13.git" "$here/13/sub/non-existent-too" "$here/13/sub" "(null)" 2>message && - ! test -s message + test_must_be_empty message ' # case #14. @@ -566,7 +565,7 @@ test_expect_success '#17: GIT_WORK_TREE without explicit GIT_DIR is accepted (ba try_repo 17c "$here/17c" unset unset "" true \ .git "$here/17c" "$here/17c" "(null)" \ "$here/17c/.git" "$here/17c" "$here/17c" sub/ 2>message && - ! test -s message + test_must_be_empty message ' test_expect_success '#18: bare .git named by GIT_DIR has no worktree' ' @@ -595,7 +594,7 @@ test_expect_success '#20a: core.worktree without GIT_DIR accepted (inside .git)' "$here/20a/.git" "$here/20a" "$here/20a" .git/wt/ && try_case 20a/.git/wt/sub unset unset \ "$here/20a/.git" "$here/20a" "$here/20a" .git/wt/sub/ && - ! test -s message + test_must_be_empty message ' test_expect_success '#20b/c: core.worktree and core.bare conflict' ' @@ -627,7 +626,7 @@ test_expect_success '#21: setup, core.worktree warns before overriding core.bare export GIT_WORK_TREE && git status >/dev/null ) 2>message && - ! test -s message + test_must_be_empty message ' run_wt_tests 21 @@ -743,7 +742,7 @@ test_expect_success '#25: GIT_WORK_TREE accepted if GIT_DIR unset (bare gitfile try_repo 25 "$here/25" unset unset gitfile true \ "$here/25.git" "$here/25" "$here/25" "(null)" \ "$here/25.git" "$here/25" "$here/25" "sub/" 2>message && - ! test -s message + test_must_be_empty message ' test_expect_success '#26: bare repo has no worktree (GIT_DIR -> gitfile case)' ' @@ -781,7 +780,7 @@ test_expect_success '#29: setup' ' export GIT_WORK_TREE && git status ) 2>message && - ! test -s message + test_must_be_empty message ' run_wt_tests 29 gitfile diff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh index 711704ba5a..e4d5b56014 100755 --- a/t/t1512-rev-parse-disambiguation.sh +++ b/t/t1512-rev-parse-disambiguation.sh @@ -22,14 +22,20 @@ one tagged as v1.0.0. They all have one regular file each. . ./test-lib.sh +if ! test_have_prereq SHA1 +then + skip_all='not using SHA-1 for objects' + test_done +fi + test_expect_success 'blob and tree' ' test_tick && ( for i in 0 1 2 3 4 5 6 7 8 9 do echo $i - done - echo + done && + echo && echo b1rwzyc3 ) >a0blgqsjc && @@ -216,7 +222,7 @@ test_expect_success 'more history' ' test_might_fail git rm -f a0blgqsjc && ( - git cat-file blob $side:f5518nwu + git cat-file blob $side:f5518nwu && echo j3l0i9s6 ) >ab2gs879 && git add ab2gs879 && @@ -361,4 +367,25 @@ test_expect_success 'core.disambiguate does not override context' ' git -c core.disambiguate=committish rev-parse $sha1^{tree} ' +test_expect_success C_LOCALE_OUTPUT 'ambiguous commits are printed by type first, then hash order' ' + test_must_fail git rev-parse 0000 2>stderr && + grep ^hint: stderr >hints && + grep 0000 hints >objects && + cat >expected <<-\EOF && + tag + commit + tree + blob + EOF + awk "{print \$3}" <objects >objects.types && + uniq <objects.types >objects.types.uniq && + test_cmp expected objects.types.uniq && + for type in tag commit tree blob + do + grep $type objects >$type.objects && + sort $type.objects >$type.objects.sorted && + test_cmp $type.objects.sorted $type.objects + done +' + test_done diff --git a/t/t1600-index.sh b/t/t1600-index.sh index c4422312f4..42962ed7d4 100755 --- a/t/t1600-index.sh +++ b/t/t1600-index.sh @@ -41,8 +41,7 @@ test_expect_success 'no warning with bogus GIT_INDEX_VERSION and existing index' GIT_INDEX_VERSION=1 && export GIT_INDEX_VERSION && git add a 2>actual.err && - >expect.err && - test_i18ncmp expect.err actual.err + test_must_be_empty actual.err ) ' diff --git a/t/t1601-index-bogus.sh b/t/t1601-index-bogus.sh index 73cc9323cd..4171f1e141 100755 --- a/t/t1601-index-bogus.sh +++ b/t/t1601-index-bogus.sh @@ -4,7 +4,7 @@ test_description='test handling of bogus index entries' . ./test-lib.sh test_expect_success 'create tree with null sha1' ' - tree=$(printf "160000 commit $_z40\\tbroken\\n" | git mktree) + tree=$(printf "160000 commit $ZERO_OID\\tbroken\\n" | git mktree) ' test_expect_success 'read-tree refuses to read null sha1' ' diff --git a/t/t1700-split-index.sh b/t/t1700-split-index.sh index e4f4c4df4e..b3b4d83eaf 100755 --- a/t/t1700-split-index.sh +++ b/t/t1700-split-index.sh @@ -143,9 +143,7 @@ test_expect_success 'remove file not in base index' ' test_expect_success 'remove file in base index' ' git update-index --force-remove one && git ls-files --stage >ls-files.actual && - cat >ls-files.expect <<-EOF && - EOF - test_cmp ls-files.expect ls-files.actual && + test_must_be_empty ls-files.actual && test-tool dump-split-index .git/index | sed "/^own/d" >actual && cat >expect <<-EOF && @@ -426,7 +424,7 @@ test_expect_success 'writing split index with null sha1 does not write cache tre git commit -m "commit" && { git ls-tree HEAD && - printf "160000 commit $_z40\\tbroken\\n" + printf "160000 commit $ZERO_OID\\tbroken\\n" } >broken-tree && echo "add broken entry" >msg && @@ -435,7 +433,7 @@ test_expect_success 'writing split index with null sha1 does not write cache tre commit=$(git commit-tree $tree -p HEAD <msg) && git update-ref HEAD "$commit" && GIT_ALLOW_NULL_SHA1=1 git reset --hard && - (test-tool dump-cache-tree >cache-tree.out || true) && + test_might_fail test-tool dump-cache-tree >cache-tree.out && test_line_count = 0 cache-tree.out ' diff --git a/t/t2011-checkout-invalid-head.sh b/t/t2011-checkout-invalid-head.sh index c5501b008c..0e8d56aa76 100755 --- a/t/t2011-checkout-invalid-head.sh +++ b/t/t2011-checkout-invalid-head.sh @@ -15,7 +15,7 @@ test_expect_success 'checkout should not start branch from a tree' ' ' test_expect_success 'checkout master from invalid HEAD' ' - echo $_z40 >.git/HEAD && + echo $ZERO_OID >.git/HEAD && git checkout master -- ' diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh index 6ef15738e4..8f86b5f4b2 100755 --- a/t/t2013-checkout-submodule.sh +++ b/t/t2013-checkout-submodule.sh @@ -44,7 +44,7 @@ test_expect_success '"checkout <submodule>" honors diff.ignoreSubmodules' ' git config diff.ignoreSubmodules dirty && echo x> submodule/untracked && git checkout HEAD >actual 2>&1 && - ! test -s actual + test_must_be_empty actual ' test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .gitmodules' ' @@ -52,7 +52,7 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .gitm git config -f .gitmodules submodule.submodule.path submodule && git config -f .gitmodules submodule.submodule.ignore untracked && git checkout HEAD >actual 2>&1 && - ! test -s actual + test_must_be_empty actual ' test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/config' ' @@ -60,7 +60,7 @@ test_expect_success '"checkout <submodule>" honors submodule.*.ignore from .git/ git config submodule.submodule.path submodule && git config submodule.submodule.ignore all && git checkout HEAD >actual 2>&1 && - ! test -s actual + test_must_be_empty actual ' KNOWN_FAILURE_DIRECTORY_SUBMODULE_CONFLICTS=1 diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh index 9cd0ac4ba3..47aeb0b167 100755 --- a/t/t2016-checkout-patch.sh +++ b/t/t2016-checkout-patch.sh @@ -20,33 +20,33 @@ test_expect_success PERL 'setup' ' test_expect_success PERL 'saying "n" does nothing' ' set_and_save_state dir/foo work head && - (echo n; echo n) | git checkout -p && + test_write_lines n n | git checkout -p && verify_saved_state bar && verify_saved_state dir/foo ' test_expect_success PERL 'git checkout -p' ' - (echo n; echo y) | git checkout -p && + test_write_lines n y | git checkout -p && verify_saved_state bar && verify_state dir/foo head head ' test_expect_success PERL 'git checkout -p with staged changes' ' set_state dir/foo work index && - (echo n; echo y) | git checkout -p && + test_write_lines n y | git checkout -p && verify_saved_state bar && verify_state dir/foo index index ' test_expect_success PERL 'git checkout -p HEAD with NO staged changes: abort' ' set_and_save_state dir/foo work head && - (echo n; echo y; echo n) | git checkout -p HEAD && + test_write_lines n y n | git checkout -p HEAD && verify_saved_state bar && verify_saved_state dir/foo ' test_expect_success PERL 'git checkout -p HEAD with NO staged changes: apply' ' - (echo n; echo y; echo y) | git checkout -p HEAD && + test_write_lines n y y | git checkout -p HEAD && verify_saved_state bar && verify_state dir/foo head head ' @@ -54,14 +54,14 @@ test_expect_success PERL 'git checkout -p HEAD with NO staged changes: apply' ' test_expect_success PERL '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 - (echo n; echo y; echo n) | git checkout -p HEAD && + test_write_lines n y n | git checkout -p HEAD && verify_saved_state bar && verify_state dir/foo head head ' test_expect_success PERL 'git checkout -p HEAD^' ' # the third n is to get out in case it mistakenly does not apply - (echo n; echo y; echo n) | git checkout -p HEAD^ && + test_write_lines n y n | git checkout -p HEAD^ && verify_saved_state bar && verify_state dir/foo parent parent ' @@ -69,7 +69,7 @@ test_expect_success PERL 'git checkout -p HEAD^' ' test_expect_success PERL 'git checkout -p handles deletion' ' set_state dir/foo work index && rm dir/foo && - (echo n; echo y) | git checkout -p && + test_write_lines n y | git checkout -p && verify_saved_state bar && verify_state dir/foo index index ' @@ -81,21 +81,21 @@ test_expect_success PERL 'git checkout -p handles deletion' ' test_expect_success PERL 'path limiting works: dir' ' set_state dir/foo work head && - (echo y; echo n) | git checkout -p dir && + test_write_lines y n | git checkout -p dir && verify_saved_state bar && verify_state dir/foo head head ' test_expect_success PERL 'path limiting works: -- dir' ' set_state dir/foo work head && - (echo y; echo n) | git checkout -p -- dir && + test_write_lines y n | git checkout -p -- dir && verify_saved_state bar && verify_state dir/foo head head ' test_expect_success PERL 'path limiting works: HEAD^ -- dir' ' # the third n is to get out in case it mistakenly does not apply - (echo y; echo n; echo n) | git checkout -p HEAD^ -- dir && + test_write_lines y n n | git checkout -p HEAD^ -- dir && verify_saved_state bar && verify_state dir/foo parent parent ' @@ -103,7 +103,7 @@ test_expect_success PERL 'path limiting works: HEAD^ -- dir' ' test_expect_success PERL '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 - (echo y; echo n; echo n) | (cd dir && git checkout -p foo) && + test_write_lines y n n | (cd dir && git checkout -p foo) && verify_saved_state bar && verify_state dir/foo head head ' diff --git a/t/t2022-checkout-paths.sh b/t/t2022-checkout-paths.sh index e74d58b9e1..fc3eb43b89 100755 --- a/t/t2022-checkout-paths.sh +++ b/t/t2022-checkout-paths.sh @@ -73,8 +73,8 @@ test_expect_success 'do not touch files that are already up-to-date' ' git checkout HEAD -- file1 file2 && echo one >expect && test_cmp expect file1 && - echo "1000000000 file2" >expect && - test-tool chmtime -v +0 file2 >actual && + echo "1000000000" >expect && + test-tool chmtime --get file2 >actual && test_cmp expect actual ' diff --git a/t/t2024-checkout-dwim.sh b/t/t2024-checkout-dwim.sh index 3e5ac81bd2..69b6774d10 100755 --- a/t/t2024-checkout-dwim.sh +++ b/t/t2024-checkout-dwim.sh @@ -23,6 +23,11 @@ test_branch_upstream () { test_cmp expect.upstream actual.upstream } +status_uno_is_clean () { + git status -uno --porcelain >status.actual && + test_must_be_empty status.actual +} + test_expect_success 'setup' ' test_commit my_master && git init repo_a && @@ -55,6 +60,7 @@ test_expect_success 'checkout of non-existing branch fails' ' test_might_fail git branch -D xyzzy && test_must_fail git checkout xyzzy && + status_uno_is_clean && test_must_fail git rev-parse --verify refs/heads/xyzzy && test_branch master ' @@ -64,15 +70,52 @@ test_expect_success 'checkout of branch from multiple remotes fails #1' ' test_might_fail git branch -D foo && test_must_fail git checkout foo && + status_uno_is_clean && test_must_fail git rev-parse --verify refs/heads/foo && test_branch master ' +test_expect_success 'checkout of branch from multiple remotes fails with advice' ' + git checkout -B master && + test_might_fail git branch -D foo && + test_must_fail git checkout foo 2>stderr && + test_branch master && + status_uno_is_clean && + test_i18ngrep "^hint: " stderr && + test_must_fail git -c advice.checkoutAmbiguousRemoteBranchName=false \ + checkout foo 2>stderr && + test_branch master && + status_uno_is_clean && + test_i18ngrep ! "^hint: " stderr +' + +test_expect_success PERL 'checkout -p with multiple remotes does not print advice' ' + git checkout -B master && + test_might_fail git branch -D foo && + + git checkout -p foo 2>stderr && + test_i18ngrep ! "^hint: " stderr && + status_uno_is_clean +' + +test_expect_success 'checkout of branch from multiple remotes succeeds with checkout.defaultRemote #1' ' + git checkout -B master && + status_uno_is_clean && + test_might_fail git branch -D foo && + + git -c checkout.defaultRemote=repo_a checkout foo && + status_uno_is_clean && + test_branch foo && + test_cmp_rev remotes/repo_a/foo HEAD && + test_branch_upstream foo repo_a foo +' + test_expect_success 'checkout of branch from a single remote succeeds #1' ' git checkout -B master && test_might_fail git branch -D bar && git checkout bar && + status_uno_is_clean && test_branch bar && test_cmp_rev remotes/repo_a/bar HEAD && test_branch_upstream bar repo_a bar @@ -83,6 +126,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' ' test_might_fail git branch -D baz && git checkout baz && + status_uno_is_clean && test_branch baz && test_cmp_rev remotes/other_b/baz HEAD && test_branch_upstream baz repo_b baz @@ -90,6 +134,7 @@ test_expect_success 'checkout of branch from a single remote succeeds #2' ' test_expect_success '--no-guess suppresses branch auto-vivification' ' git checkout -B master && + status_uno_is_clean && test_might_fail git branch -D bar && test_must_fail git checkout --no-guess bar && @@ -99,6 +144,7 @@ test_expect_success '--no-guess suppresses branch auto-vivification' ' test_expect_success 'setup more remotes with unconventional refspecs' ' git checkout -B master && + status_uno_is_clean && git init repo_c && ( cd repo_c && @@ -128,27 +174,33 @@ test_expect_success 'setup more remotes with unconventional refspecs' ' test_expect_success 'checkout of branch from multiple remotes fails #2' ' git checkout -B master && + status_uno_is_clean && test_might_fail git branch -D bar && test_must_fail git checkout bar && + status_uno_is_clean && test_must_fail git rev-parse --verify refs/heads/bar && test_branch master ' test_expect_success 'checkout of branch from multiple remotes fails #3' ' git checkout -B master && + status_uno_is_clean && test_might_fail git branch -D baz && test_must_fail git checkout baz && + status_uno_is_clean && test_must_fail git rev-parse --verify refs/heads/baz && test_branch master ' test_expect_success 'checkout of branch from a single remote succeeds #3' ' git checkout -B master && + status_uno_is_clean && test_might_fail git branch -D spam && git checkout spam && + status_uno_is_clean && test_branch spam && test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD && test_branch_upstream spam repo_c spam @@ -156,9 +208,11 @@ test_expect_success 'checkout of branch from a single remote succeeds #3' ' test_expect_success 'checkout of branch from a single remote succeeds #4' ' git checkout -B master && + status_uno_is_clean && test_might_fail git branch -D eggs && git checkout eggs && + status_uno_is_clean && test_branch eggs && test_cmp_rev refs/repo_d/eggs HEAD && test_branch_upstream eggs repo_d eggs @@ -166,32 +220,38 @@ test_expect_success 'checkout of branch from a single remote succeeds #4' ' test_expect_success 'checkout of branch with a file having the same name fails' ' git checkout -B master && + status_uno_is_clean && test_might_fail git branch -D spam && >spam && test_must_fail git checkout spam && + status_uno_is_clean && test_must_fail git rev-parse --verify refs/heads/spam && test_branch master ' test_expect_success 'checkout of branch with a file in subdir having the same name fails' ' git checkout -B master && + status_uno_is_clean && test_might_fail git branch -D spam && >spam && mkdir sub && mv spam sub/spam && test_must_fail git -C sub checkout spam && + status_uno_is_clean && test_must_fail git rev-parse --verify refs/heads/spam && test_branch master ' test_expect_success 'checkout <branch> -- succeeds, even if a file with the same name exists' ' git checkout -B master && + status_uno_is_clean && test_might_fail git branch -D spam && >spam && git checkout spam -- && + status_uno_is_clean && test_branch spam && test_cmp_rev refs/remotes/extra_dir/repo_c/extra_dir/spam HEAD && test_branch_upstream spam repo_c spam @@ -200,6 +260,7 @@ test_expect_success 'checkout <branch> -- succeeds, even if a file with the same test_expect_success 'loosely defined local base branch is reported correctly' ' git checkout master && + status_uno_is_clean && git branch strict && git branch loose && git commit --allow-empty -m "a bit more" && @@ -210,7 +271,9 @@ test_expect_success 'loosely defined local base branch is reported correctly' ' test_config branch.loose.merge master && git checkout strict | sed -e "s/strict/BRANCHNAME/g" >expect && + status_uno_is_clean && git checkout loose | sed -e "s/loose/BRANCHNAME/g" >actual && + status_uno_is_clean && test_cmp expect actual ' diff --git a/t/t2025-worktree-add.sh b/t/t2025-worktree-add.sh index d0d2e4f7ec..286bba35d8 100755 --- a/t/t2025-worktree-add.sh +++ b/t/t2025-worktree-add.sh @@ -198,13 +198,25 @@ test_expect_success '"add" with <branch> omitted' ' test_cmp_rev HEAD bat ' -test_expect_success '"add" auto-vivify does not clobber existing branch' ' - test_commit c1 && - test_commit c2 && - git branch precious HEAD~1 && - test_must_fail git worktree add precious && - test_cmp_rev HEAD~1 precious && - test_path_is_missing precious +test_expect_success '"add" checks out existing branch of dwimd name' ' + git branch dwim HEAD~1 && + git worktree add dwim && + test_cmp_rev HEAD~1 dwim && + ( + cd dwim && + test_cmp_rev HEAD dwim + ) +' + +test_expect_success '"add <path>" dwim fails with checked out branch' ' + git checkout -b test-branch && + test_must_fail git worktree add test-branch && + test_path_is_missing test-branch +' + +test_expect_success '"add --force" with existing dwimd name doesnt die' ' + git checkout test-branch && + git worktree add --force test-branch ' test_expect_success '"add" no auto-vivify with --detach and <branch> omitted' ' @@ -240,6 +252,11 @@ test_expect_success 'add -B' ' test_cmp_rev master^ poodle ' +test_expect_success 'add --quiet' ' + git worktree add --quiet another-worktree master 2>actual && + test_must_be_empty actual +' + test_expect_success 'local clone from linked checkout' ' git clone --local here here-clone && ( cd here-clone && git fsck ) @@ -390,6 +407,26 @@ test_expect_success '"add" <path> <branch> dwims' ' ) ' +test_expect_success '"add" <path> <branch> dwims with checkout.defaultRemote' ' + test_when_finished rm -rf repo_upstream repo_dwim foo && + setup_remote_repo repo_upstream repo_dwim && + git init repo_dwim && + ( + cd repo_dwim && + git remote add repo_upstream2 ../repo_upstream && + git fetch repo_upstream2 && + test_must_fail git worktree add ../foo foo && + git -c checkout.defaultRemote=repo_upstream worktree add ../foo foo && + git status -uno --porcelain >status.actual && + test_must_be_empty status.actual + ) && + ( + cd foo && + test_branch_upstream foo repo_upstream foo && + test_cmp_rev refs/remotes/repo_upstream/foo refs/heads/foo + ) +' + test_expect_success 'git worktree add does not match remote' ' test_when_finished rm -rf repo_a repo_b foo && setup_remote_repo repo_a repo_b && @@ -465,7 +502,7 @@ post_checkout_hook () { test_expect_success '"add" invokes post-checkout hook (branch)' ' post_checkout_hook && { - echo $_z40 $(git rev-parse HEAD) 1 && + echo $ZERO_OID $(git rev-parse HEAD) 1 && echo $(pwd)/.git/worktrees/gumby && echo $(pwd)/gumby } >hook.expect && @@ -476,7 +513,7 @@ test_expect_success '"add" invokes post-checkout hook (branch)' ' test_expect_success '"add" invokes post-checkout hook (detached)' ' post_checkout_hook && { - echo $_z40 $(git rev-parse HEAD) 1 && + echo $ZERO_OID $(git rev-parse HEAD) 1 && echo $(pwd)/.git/worktrees/grumpy && echo $(pwd)/grumpy } >hook.expect && @@ -494,7 +531,7 @@ test_expect_success '"add --no-checkout" suppresses post-checkout hook' ' test_expect_success '"add" in other worktree invokes post-checkout hook' ' post_checkout_hook && { - echo $_z40 $(git rev-parse HEAD) 1 && + echo $ZERO_OID $(git rev-parse HEAD) 1 && echo $(pwd)/.git/worktrees/guppy && echo $(pwd)/guppy } >hook.expect && @@ -506,7 +543,7 @@ test_expect_success '"add" in bare repo invokes post-checkout hook' ' rm -rf bare && git clone --bare . bare && { - echo $_z40 $(git --git-dir=bare rev-parse HEAD) 1 && + echo $ZERO_OID $(git --git-dir=bare rev-parse HEAD) 1 && echo $(pwd)/bare/worktrees/goozy && echo $(pwd)/goozy } >hook.expect && @@ -515,4 +552,22 @@ test_expect_success '"add" in bare repo invokes post-checkout hook' ' test_cmp hook.expect goozy/hook.actual ' +test_expect_success '"add" an existing but missing worktree' ' + git worktree add --detach pneu && + test_must_fail git worktree add --detach pneu && + rm -fr pneu && + test_must_fail git worktree add --detach pneu && + git worktree add --force --detach pneu +' + +test_expect_success '"add" an existing locked but missing worktree' ' + git worktree add --detach gnoo && + git worktree lock gnoo && + test_when_finished "git worktree unlock gnoo || :" && + rm -fr gnoo && + test_must_fail git worktree add --detach gnoo && + test_must_fail git worktree add --force --detach gnoo && + git worktree add --force --force --detach gnoo +' + test_done diff --git a/t/t2027-worktree-list.sh b/t/t2027-worktree-list.sh index 720063bf0d..bb6fb9b12c 100755 --- a/t/t2027-worktree-list.sh +++ b/t/t2027-worktree-list.sh @@ -116,7 +116,7 @@ test_expect_success 'broken main worktree still at the top' ' git worktree add linked && cat >expected <<-EOF && worktree $(pwd) - HEAD $_z40 + HEAD $ZERO_OID EOF cd linked && diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh index 5d5b3632ba..33c0337733 100755 --- a/t/t2028-worktree-move.sh +++ b/t/t2028-worktree-move.sh @@ -72,12 +72,11 @@ test_expect_success 'move locked worktree' ' ' test_expect_success 'move worktree' ' - toplevel="$(pwd)" && git worktree move source destination && test_path_is_missing source && git worktree list --porcelain >out && - grep "^worktree.*/destination" out && - ! grep "^worktree.*/source" out && + grep "^worktree.*/destination$" out && + ! grep "^worktree.*/source$" out && git -C destination log --format=%s >actual2 && echo init >expected2 && test_cmp expected2 actual2 @@ -93,12 +92,26 @@ test_expect_success 'move worktree to another dir' ' test_when_finished "git worktree move some-dir/destination destination" && test_path_is_missing destination && git worktree list --porcelain >out && - grep "^worktree.*/some-dir/destination" out && + grep "^worktree.*/some-dir/destination$" out && git -C some-dir/destination log --format=%s >actual2 && echo init >expected2 && test_cmp expected2 actual2 ' +test_expect_success 'move locked worktree (force)' ' + test_when_finished " + git worktree unlock flump || : + git worktree remove flump || : + git worktree unlock ploof || : + git worktree remove ploof || : + " && + git worktree add --detach flump && + git worktree lock flump && + test_must_fail git worktree move flump ploof" && + test_must_fail git worktree move --force flump ploof" && + git worktree move --force --force flump ploof +' + test_expect_success 'remove main worktree' ' test_must_fail git worktree remove . ' @@ -142,4 +155,34 @@ test_expect_success 'NOT remove missing-but-locked worktree' ' test_path_is_dir .git/worktrees/gone-but-locked ' +test_expect_success 'proper error when worktree not found' ' + for i in noodle noodle/bork + do + test_must_fail git worktree lock $i 2>err && + test_i18ngrep "not a working tree" err || return 1 + done +' + +test_expect_success 'remove locked worktree (force)' ' + git worktree add --detach gumby && + test_when_finished "git worktree remove gumby || :" && + git worktree lock gumby && + test_when_finished "git worktree unlock gumby || :" && + test_must_fail git worktree remove gumby && + test_must_fail git worktree remove --force gumby && + git worktree remove --force --force gumby +' + +test_expect_success 'remove cleans up .git/worktrees when empty' ' + git init moog && + ( + cd moog && + test_commit bim && + git worktree add --detach goom && + test_path_exists .git/worktrees && + git worktree remove goom && + test_path_is_missing .git/worktrees + ) +' + test_done diff --git a/t/t2103-update-index-ignore-missing.sh b/t/t2103-update-index-ignore-missing.sh index 332694e7d3..0114f05228 100755 --- a/t/t2103-update-index-ignore-missing.sh +++ b/t/t2103-update-index-ignore-missing.sh @@ -32,7 +32,7 @@ test_expect_success basics ' test_create_repo xyzzy && cd xyzzy && >file && - git add file + git add file && git commit -m "sub initial" ) && git add xyzzy && diff --git a/t/t2107-update-index-basic.sh b/t/t2107-update-index-basic.sh index 1db7e6a1ab..2242cd098e 100755 --- a/t/t2107-update-index-basic.sh +++ b/t/t2107-update-index-basic.sh @@ -37,7 +37,7 @@ test_expect_success '--cacheinfo does not accept blob null sha1' ' echo content >file && git add file && git rev-parse :file >expect && - test_must_fail git update-index --cacheinfo 100644 $_z40 file && + test_must_fail git update-index --cacheinfo 100644 $ZERO_OID file && git rev-parse :file >actual && test_cmp expect actual ' @@ -47,7 +47,7 @@ test_expect_success '--cacheinfo does not accept gitlink null sha1' ' (cd submodule && test_commit foo) && git add submodule && git rev-parse :submodule >expect && - test_must_fail git update-index --cacheinfo 160000 $_z40 submodule && + test_must_fail git update-index --cacheinfo 160000 $ZERO_OID submodule && git rev-parse :submodule >actual && test_cmp expect actual ' diff --git a/t/t2200-add-update.sh b/t/t2200-add-update.sh index 314c73c5a7..f764b7e3f5 100755 --- a/t/t2200-add-update.sh +++ b/t/t2200-add-update.sh @@ -88,9 +88,8 @@ test_expect_success 'non-qualified update in subdir updates from the root' ' echo even more >>sub2 && git add -u ) && - : >expect && git diff-files --name-only >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'replace a file with a symlink' ' diff --git a/t/t2201-add-update-typechange.sh b/t/t2201-add-update-typechange.sh index 954fc51e5b..a4eec0a346 100755 --- a/t/t2201-add-update-typechange.sh +++ b/t/t2201-add-update-typechange.sh @@ -75,35 +75,35 @@ test_expect_success modify ' git ls-tree -r HEAD | sed -e "s/^/:/" -e " / caskly/{ - s/ caskly/ $_z40 D&/ + s/ caskly/ $ZERO_OID D&/ s/blob/000000/ } / nitfol/{ - s/ nitfol/ $_z40 $T_letter&/ + s/ nitfol/ $ZERO_OID $T_letter&/ s/blob/100644/ } / rezrov.bozbar/{ - s/ rezrov.bozbar/ $_z40 D&/ + s/ rezrov.bozbar/ $ZERO_OID D&/ s/blob/000000/ } / xyzzy/{ - s/ xyzzy/ $_z40 D&/ + s/ xyzzy/ $ZERO_OID D&/ s/blob/000000/ } / yomin/{ - s/ yomin/ $_z40 T&/ + s/ yomin/ $ZERO_OID T&/ s/blob/160000/ } " } >expect && { cat expect - echo ":100644 160000 $_empty $_z40 T yonk" - echo ":100644 000000 $_empty $_z40 D zifmia" + echo ":100644 160000 $_empty $ZERO_OID T yonk" + echo ":100644 000000 $_empty $ZERO_OID D zifmia" } >expect-files && { cat expect - echo ":000000 160000 $_z40 $_z40 A yonk" + echo ":000000 160000 $ZERO_OID $ZERO_OID A yonk" } >expect-index && { echo "100644 $_empty 0 nitfol" diff --git a/t/t2202-add-addremove.sh b/t/t2202-add-addremove.sh index 6a5a3166b1..9ee659098c 100755 --- a/t/t2202-add-addremove.sh +++ b/t/t2202-add-addremove.sh @@ -6,12 +6,12 @@ test_description='git add --all' test_expect_success setup ' ( - echo .gitignore + echo .gitignore && echo will-remove ) >expect && ( - echo actual - echo expect + echo actual && + echo expect && echo ignored ) >.gitignore && git --literal-pathspecs add --all && @@ -25,10 +25,10 @@ test_expect_success setup ' test_expect_success 'git add --all' ' ( - echo .gitignore - echo not-ignored - echo "M .gitignore" - echo "A not-ignored" + echo .gitignore && + echo not-ignored && + echo "M .gitignore" && + echo "A not-ignored" && echo "D will-remove" ) >expect && >ignored && @@ -48,8 +48,7 @@ test_expect_success 'Just "git add" is a no-op' ' >will-not-be-added && git add && git diff-index --name-status --cached HEAD >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_done diff --git a/t/t2203-add-intent.sh b/t/t2203-add-intent.sh index 78236dc7d8..68e54d5c44 100755 --- a/t/t2203-add-intent.sh +++ b/t/t2203-add-intent.sh @@ -27,12 +27,12 @@ test_expect_success 'git status' ' test_expect_success 'git status with porcelain v2' ' git status --porcelain=v2 | grep -v "^?" >actual && - nam1=d00491fd7e5bb6fa28c517a0bb32b8b506539d4d && - nam2=ce013625030ba8dba906f756967f9e9ca394464a && + nam1=$(echo 1 | git hash-object --stdin) && + nam2=$(git hash-object elif) && cat >expect <<-EOF && - 1 DA N... 100644 000000 100644 $nam1 $_z40 1.t - 1 A. N... 000000 100644 100644 $_z40 $nam2 elif - 1 .A N... 000000 000000 100644 $_z40 $_z40 file + 1 DA N... 100644 000000 100644 $nam1 $ZERO_OID 1.t + 1 A. N... 000000 100644 100644 $ZERO_OID $nam2 elif + 1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID file EOF test_cmp expect actual ' @@ -70,8 +70,7 @@ test_expect_success 'i-t-a entry is simply ignored' ' git commit -m second && test $(git ls-tree HEAD -- nitfol | wc -l) = 0 && test $(git diff --name-only HEAD -- nitfol | wc -l) = 1 && - test $(git diff --name-only --ita-invisible-in-index HEAD -- nitfol | wc -l) = 0 && - test $(git diff --name-only --ita-invisible-in-index -- nitfol | wc -l) = 1 + test $(git diff --name-only -- nitfol | wc -l) = 1 ' test_expect_success 'can commit with an unrelated i-t-a entry in index' ' @@ -99,13 +98,13 @@ test_expect_success 'cache-tree invalidates i-t-a paths' ' : >dir/bar && git add -N dir/bar && - git diff --cached --name-only >actual && + git diff --name-only >actual && echo dir/bar >expect && test_cmp expect actual && git write-tree >/dev/null && - git diff --cached --name-only >actual && + git diff --name-only >actual && echo dir/bar >expect && test_cmp expect actual ' @@ -181,12 +180,23 @@ test_expect_success 'rename detection finds the right names' ' EOF test_cmp expected.2 actual.2 && - hash=12f00e90b6ef79117ce6e650416b8cf517099b78 && + hash=$(git hash-object third) && git status --porcelain=v2 | grep -v "^?" >actual.3 && cat >expected.3 <<-EOF && 2 .R N... 100644 100644 100644 $hash $hash R100 third first EOF - test_cmp expected.3 actual.3 + test_cmp expected.3 actual.3 && + + git diff --stat >actual.4 && + cat >expected.4 <<-EOF && + first => third | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) + EOF + test_cmp expected.4 actual.4 && + + git diff --cached --stat >actual.5 && + test_must_be_empty actual.5 + ) ' @@ -212,7 +222,7 @@ test_expect_success 'double rename detection in status' ' EOF test_cmp expected.2 actual.2 && - hash=12f00e90b6ef79117ce6e650416b8cf517099b78 && + hash=$(git hash-object third) && git status --porcelain=v2 | grep -v "^?" >actual.3 && cat >expected.3 <<-EOF && 2 R. N... 100644 100644 100644 $hash $hash R100 second first @@ -222,5 +232,45 @@ test_expect_success 'double rename detection in status' ' ) ' -test_done +test_expect_success 'diff-files/diff-cached shows ita as new/not-new files' ' + git reset --hard && + echo new >new-ita && + git add -N new-ita && + git diff --summary >actual && + echo " create mode 100644 new-ita" >expected && + test_cmp expected actual && + git diff --cached --summary >actual2 && + test_must_be_empty actual2 +' + +test_expect_success '"diff HEAD" includes ita as new files' ' + git reset --hard && + echo new >new-ita && + git add -N new-ita && + git diff HEAD >actual && + cat >expected <<-\EOF && + diff --git a/new-ita b/new-ita + new file mode 100644 + index 0000000..3e75765 + --- /dev/null + +++ b/new-ita + @@ -0,0 +1 @@ + +new + EOF + test_cmp expected actual +' + +test_expect_success 'apply --intent-to-add' ' + git reset --hard && + echo new >new-ita && + git add -N new-ita && + git diff >expected && + grep "new file" expected && + git reset --hard && + git apply --intent-to-add expected && + git diff >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t2204-add-ignored.sh b/t/t2204-add-ignored.sh index 8340ac2f07..2e07365bbb 100755 --- a/t/t2204-add-ignored.sh +++ b/t/t2204-add-ignored.sh @@ -31,7 +31,7 @@ do rm -f .git/index && test_must_fail git add "$i" 2>err && git ls-files "$i" >out && - ! test -s out + test_must_be_empty out ' test_expect_success "complaints for ignored $i output" ' @@ -42,7 +42,7 @@ do rm -f .git/index && test_must_fail git add "$i" file 2>err && git ls-files "$i" >out && - ! test -s out + test_must_be_empty out ' test_expect_success "complaints for ignored $i with unignored file output" ' test_i18ngrep -e "Use -f if" err @@ -57,7 +57,7 @@ do cd dir && test_must_fail git add "$i" 2>err && git ls-files "$i" >out && - ! test -s out + test_must_be_empty out ) ' @@ -77,7 +77,7 @@ do cd sub && test_must_fail git add "$i" 2>err && git ls-files "$i" >out && - ! test -s out + test_must_be_empty out ) ' diff --git a/t/t3000-ls-files-others.sh b/t/t3000-ls-files-others.sh index c525656b2c..afd4756134 100755 --- a/t/t3000-ls-files-others.sh +++ b/t/t3000-ls-files-others.sh @@ -84,7 +84,7 @@ test_expect_success SYMLINKS 'ls-files --others with symlinked submodule' ' ) && ( cd super && - "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" ../sub sub + "$SHELL_PATH" "$TEST_DIRECTORY/../contrib/workdir/git-new-workdir" ../sub sub && git ls-files --others --exclude-standard >../actual ) && echo sub/ >expect && diff --git a/t/t3001-ls-files-others-exclude.sh b/t/t3001-ls-files-others-exclude.sh index 3fc484e8c3..1ec7cb57c7 100755 --- a/t/t3001-ls-files-others-exclude.sh +++ b/t/t3001-ls-files-others-exclude.sh @@ -210,8 +210,7 @@ test_expect_success 'subdirectory ignore (toplevel)' ' cd top && git ls-files -o --exclude-standard ) >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'subdirectory ignore (l1/l2)' ' @@ -219,8 +218,7 @@ test_expect_success 'subdirectory ignore (l1/l2)' ' cd top/l1/l2 && git ls-files -o --exclude-standard ) >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'subdirectory ignore (l1)' ' @@ -228,8 +226,7 @@ test_expect_success 'subdirectory ignore (l1)' ' cd top/l1 && git ls-files -o --exclude-standard ) >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'show/hide empty ignored directory (setup)' ' @@ -251,8 +248,7 @@ test_expect_success 'hide empty ignored directory with --no-empty-directory' ' cd top && git ls-files -o -i --exclude l1 --directory --no-empty-directory ) >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'show/hide empty ignored sub-directory (setup)' ' @@ -277,14 +273,12 @@ test_expect_success 'hide empty ignored sub-directory with --no-empty-directory' cd top && git ls-files -o -i --exclude l1 --directory --no-empty-directory ) >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'pattern matches prefix completely' ' - : >expect && git ls-files -i -o --exclude "/three/a.3[abc]" >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'ls-files with "**" patterns' ' @@ -300,9 +294,8 @@ EOF test_expect_success 'ls-files with "**" patterns and no slashes' ' - : >expect && git ls-files -o -i --exclude "one**a.1" >actual && - test_cmp expect actual + test_must_be_empty actual ' test_done diff --git a/t/t3004-ls-files-basic.sh b/t/t3004-ls-files-basic.sh index 9c7adbdbe1..9fd5a1f188 100755 --- a/t/t3004-ls-files-basic.sh +++ b/t/t3004-ls-files-basic.sh @@ -8,16 +8,14 @@ command-line arguments. . ./test-lib.sh ->empty - test_expect_success 'ls-files in empty repository' ' git ls-files >actual && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'ls-files with nonexistent path' ' git ls-files doesnotexist >actual && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'ls-files with nonsense option' ' diff --git a/t/t3005-ls-files-relative.sh b/t/t3005-ls-files-relative.sh index 377869432e..209b4c7cd8 100755 --- a/t/t3005-ls-files-relative.sh +++ b/t/t3005-ls-files-relative.sh @@ -44,13 +44,13 @@ test_expect_success 'ls-files -c' ' cd top/sub && for f in ../y* do - echo "error: pathspec $sq$f$sq did not match any file(s) known to git." + echo "error: pathspec $sq$f$sq did not match any file(s) known to git" done >expect.err && echo "Did you forget to ${sq}git add${sq}?" >>expect.err && ls ../x* >expect.out && test_must_fail git ls-files -c --error-unmatch ../[xy]* >actual.out 2>actual.err && test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.err actual.err ) ' @@ -59,13 +59,13 @@ test_expect_success 'ls-files -o' ' cd top/sub && for f in ../x* do - echo "error: pathspec $sq$f$sq did not match any file(s) known to git." + echo "error: pathspec $sq$f$sq did not match any file(s) known to git" done >expect.err && echo "Did you forget to ${sq}git add${sq}?" >>expect.err && ls ../y* >expect.out && test_must_fail git ls-files -o --error-unmatch ../[xy]* >actual.out 2>actual.err && test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_i18ncmp expect.err actual.err ) ' diff --git a/t/t3006-ls-files-long.sh b/t/t3006-ls-files-long.sh index 202ad658b8..e109c3fbfb 100755 --- a/t/t3006-ls-files-long.sh +++ b/t/t3006-ls-files-long.sh @@ -29,7 +29,7 @@ test_expect_success 'overly-long path does not replace another by mistake' ' printf "$pat" "$blob_a" "$path_a" "$blob_z" "$path_z" | git update-index --add --index-info && ( - echo "$path_a" + echo "$path_a" && echo "$path_z" ) >expect && git ls-files >actual && diff --git a/t/t3008-ls-files-lazy-init-name-hash.sh b/t/t3008-ls-files-lazy-init-name-hash.sh index 08af596ba6..64f047332b 100755 --- a/t/t3008-ls-files-lazy-init-name-hash.sh +++ b/t/t3008-ls-files-lazy-init-name-hash.sh @@ -14,10 +14,10 @@ LAZY_THREAD_COST=2000 test_expect_success 'no buffer overflow in lazy_init_name_hash' ' ( - test_seq $LAZY_THREAD_COST | sed "s/^/a_/" - echo b/b/b - test_seq $LAZY_THREAD_COST | sed "s/^/c_/" - test_seq 50 | sed "s/^/d_/" | tr "\n" "/"; echo d + test_seq $LAZY_THREAD_COST | sed "s/^/a_/" && + echo b/b/b && + test_seq $LAZY_THREAD_COST | sed "s/^/c_/" && + test_seq 50 | sed "s/^/d_/" | tr "\n" "/" && echo d ) | sed "s/^/100644 $EMPTY_BLOB /" | git update-index --index-info && diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index 3563e77b37..ff641b348a 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -36,15 +36,15 @@ test_expect_success 'setup 1' ' test_tick && git commit -m "master modifies a and d/e" && c1=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o1 a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o1 d/e" - echo "100644 $o1 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 blob $o1 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o1 d/e" && + echo "100644 $o1 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" ) >expected && test_cmp expected actual @@ -54,15 +54,15 @@ test_expect_success 'setup 2' ' rm -rf [abcd] && git checkout side && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o0 a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 $o0 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o0 0 d/e" ) >expected && test_cmp expected actual && @@ -75,15 +75,15 @@ test_expect_success 'setup 2' ' test_tick && git commit -m "side modifies a" && c2=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o2 a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 $o2 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 blob $o2 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o2 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o0 0 d/e" ) >expected && test_cmp expected actual @@ -93,15 +93,15 @@ test_expect_success 'setup 3' ' rm -rf [abcd] && git checkout df-1 && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o0 a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 $o0 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o0 0 d/e" ) >expected && test_cmp expected actual && @@ -112,15 +112,15 @@ test_expect_success 'setup 3' ' test_tick && git commit -m "df-1 makes b/c" && c3=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o0 a" - echo "100644 blob $o3 b/c" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 $o0 0 a" - echo "100644 $o3 0 b/c" - echo "100644 $o0 0 c" + echo "100644 blob $o0 a" && + echo "100644 blob $o3 b/c" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o3 0 b/c" && + echo "100644 $o0 0 c" && echo "100644 $o0 0 d/e" ) >expected && test_cmp expected actual @@ -130,15 +130,15 @@ test_expect_success 'setup 4' ' rm -rf [abcd] && git checkout df-2 && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o0 a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 $o0 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o0 0 d/e" ) >expected && test_cmp expected actual && @@ -149,15 +149,15 @@ test_expect_success 'setup 4' ' test_tick && git commit -m "df-2 makes a/c" && c4=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o4 a/c" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 $o4 0 a/c" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 blob $o4 a/c" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o4 0 a/c" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o0 0 d/e" ) >expected && test_cmp expected actual @@ -167,15 +167,15 @@ test_expect_success 'setup 5' ' rm -rf [abcd] && git checkout remove && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o0 a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 $o0 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o0 0 d/e" ) >expected && test_cmp expected actual && @@ -190,13 +190,13 @@ test_expect_success 'setup 5' ' test_tick && git commit -m "remove removes b and modifies a" && c5=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o5 a" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 $o5 0 a" - echo "100644 $o0 0 c" + echo "100644 blob $o5 a" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o5 0 a" && + echo "100644 $o0 0 c" && echo "100644 $o0 0 d/e" ) >expected && test_cmp expected actual @@ -207,15 +207,15 @@ test_expect_success 'setup 6' ' rm -rf [abcd] && git checkout df-3 && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o0 a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 $o0 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o0 0 d/e" ) >expected && test_cmp expected actual && @@ -226,15 +226,15 @@ test_expect_success 'setup 6' ' test_tick && git commit -m "df-3 makes d" && c6=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o0 a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o6 d" - echo "100644 $o0 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o6 d" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o6 0 d" ) >expected && test_cmp expected actual @@ -286,11 +286,11 @@ test_expect_success 'merge-recursive result' ' git ls-files -s >actual && ( - echo "100644 $o0 1 a" - echo "100644 $o2 2 a" - echo "100644 $o1 3 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 $o0 1 a" && + echo "100644 $o2 2 a" && + echo "100644 $o1 3 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" ) >expected && test_cmp expected actual @@ -325,10 +325,10 @@ test_expect_success 'merge-recursive remove conflict' ' git ls-files -s >actual && ( - echo "100644 $o0 1 a" - echo "100644 $o1 2 a" - echo "100644 $o5 3 a" - echo "100644 $o0 0 c" + echo "100644 $o0 1 a" && + echo "100644 $o1 2 a" && + echo "100644 $o5 3 a" && + echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" ) >expected && test_cmp expected actual @@ -347,9 +347,9 @@ test_expect_success 'merge-recursive result' ' git ls-files -s >actual && ( - echo "100644 $o1 0 a" - echo "100644 $o3 0 b/c" - echo "100644 $o0 0 c" + echo "100644 $o1 0 a" && + echo "100644 $o3 0 b/c" && + echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" ) >expected && test_cmp expected actual @@ -369,11 +369,11 @@ test_expect_success 'merge-recursive d/f conflict result' ' git ls-files -s >actual && ( - echo "100644 $o0 1 a" - echo "100644 $o1 2 a" - echo "100644 $o4 0 a/c" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 $o0 1 a" && + echo "100644 $o1 2 a" && + echo "100644 $o4 0 a/c" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" ) >expected && test_cmp expected actual @@ -393,11 +393,11 @@ test_expect_success 'merge-recursive d/f conflict result the other way' ' git ls-files -s >actual && ( - echo "100644 $o0 1 a" - echo "100644 $o1 3 a" - echo "100644 $o4 0 a/c" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 $o0 1 a" && + echo "100644 $o1 3 a" && + echo "100644 $o4 0 a/c" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" ) >expected && test_cmp expected actual @@ -417,11 +417,11 @@ test_expect_success 'merge-recursive d/f conflict result' ' git ls-files -s >actual && ( - echo "100644 $o1 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" - echo "100644 $o6 3 d" - echo "100644 $o0 1 d/e" + echo "100644 $o1 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o6 3 d" && + echo "100644 $o0 1 d/e" && echo "100644 $o1 2 d/e" ) >expected && test_cmp expected actual @@ -441,11 +441,11 @@ test_expect_success 'merge-recursive d/f conflict result' ' git ls-files -s >actual && ( - echo "100644 $o1 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" - echo "100644 $o6 2 d" - echo "100644 $o0 1 d/e" + echo "100644 $o1 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o6 2 d" && + echo "100644 $o0 1 d/e" && echo "100644 $o1 3 d/e" ) >expected && test_cmp expected actual @@ -465,13 +465,13 @@ test_expect_success 'reset and bind merge' ' git read-tree --prefix=M/ master && git ls-files -s >actual && ( - echo "100644 $o1 0 M/a" - echo "100644 $o0 0 M/b" - echo "100644 $o0 0 M/c" - echo "100644 $o1 0 M/d/e" - echo "100644 $o1 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 $o1 0 M/a" && + echo "100644 $o0 0 M/b" && + echo "100644 $o0 0 M/c" && + echo "100644 $o1 0 M/d/e" && + echo "100644 $o1 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" ) >expected && test_cmp expected actual && @@ -479,17 +479,17 @@ test_expect_success 'reset and bind merge' ' git read-tree --prefix=a1/ master && git ls-files -s >actual && ( - echo "100644 $o1 0 M/a" - echo "100644 $o0 0 M/b" - echo "100644 $o0 0 M/c" - echo "100644 $o1 0 M/d/e" - echo "100644 $o1 0 a" - echo "100644 $o1 0 a1/a" - echo "100644 $o0 0 a1/b" - echo "100644 $o0 0 a1/c" - echo "100644 $o1 0 a1/d/e" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" + echo "100644 $o1 0 M/a" && + echo "100644 $o0 0 M/b" && + echo "100644 $o0 0 M/c" && + echo "100644 $o1 0 M/d/e" && + echo "100644 $o1 0 a" && + echo "100644 $o1 0 a1/a" && + echo "100644 $o0 0 a1/b" && + echo "100644 $o0 0 a1/c" && + echo "100644 $o1 0 a1/d/e" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" ) >expected && test_cmp expected actual && @@ -497,21 +497,21 @@ test_expect_success 'reset and bind merge' ' git read-tree --prefix=z/ master && git ls-files -s >actual && ( - echo "100644 $o1 0 M/a" - echo "100644 $o0 0 M/b" - echo "100644 $o0 0 M/c" - echo "100644 $o1 0 M/d/e" - echo "100644 $o1 0 a" - echo "100644 $o1 0 a1/a" - echo "100644 $o0 0 a1/b" - echo "100644 $o0 0 a1/c" - echo "100644 $o1 0 a1/d/e" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" - echo "100644 $o1 0 d/e" - echo "100644 $o1 0 z/a" - echo "100644 $o0 0 z/b" - echo "100644 $o0 0 z/c" + echo "100644 $o1 0 M/a" && + echo "100644 $o0 0 M/b" && + echo "100644 $o0 0 M/c" && + echo "100644 $o1 0 M/d/e" && + echo "100644 $o1 0 a" && + echo "100644 $o1 0 a1/a" && + echo "100644 $o0 0 a1/b" && + echo "100644 $o0 0 a1/c" && + echo "100644 $o1 0 a1/d/e" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" && + echo "100644 $o1 0 z/a" && + echo "100644 $o0 0 z/b" && + echo "100644 $o0 0 z/c" && echo "100644 $o1 0 z/d/e" ) >expected && test_cmp expected actual @@ -589,8 +589,8 @@ test_expect_success 'merge-recursive simple w/submodule result' ' git ls-files -s >actual && ( - echo "100644 $o5 0 a" - echo "100644 $o0 0 c" + echo "100644 $o5 0 a" && + echo "100644 $o0 0 c" && echo "160000 $c1 0 d" ) >expected && test_cmp expected actual @@ -601,13 +601,13 @@ test_expect_success 'merge-recursive copy vs. rename' ' git merge rename && ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 blob $o0 e" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" - echo "100644 $o0 0 d/e" + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 blob $o0 e" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" && echo "100644 $o0 0 e" ) >expected && test_cmp expected actual @@ -617,17 +617,17 @@ test_expect_failure 'merge-recursive rename vs. rename/symlink' ' git checkout -f rename && git merge rename-ln && - ( git ls-tree -r HEAD ; git ls-files -s ) >actual && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && ( - echo "120000 blob $oln a" - echo "100644 blob $o0 b" - echo "100644 blob $o0 c" - echo "100644 blob $o0 d/e" - echo "100644 blob $o0 e" - echo "120000 $oln 0 a" - echo "100644 $o0 0 b" - echo "100644 $o0 0 c" - echo "100644 $o0 0 d/e" + echo "120000 blob $oln a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 blob $o0 e" && + echo "120000 $oln 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" && echo "100644 $o0 0 e" ) >expected && test_cmp expected actual diff --git a/t/t3031-merge-criscross.sh b/t/t3031-merge-criscross.sh index e59b0a32d6..3824756a02 100755 --- a/t/t3031-merge-criscross.sh +++ b/t/t3031-merge-criscross.sh @@ -88,7 +88,7 @@ test_expect_success 'setup repo with criss-cross history' ' git branch G ' -test_expect_success 'recursive merge between F and G, causes segfault' ' +test_expect_success 'recursive merge between F and G does not cause segfault' ' git merge F ' diff --git a/t/t3034-merge-recursive-rename-options.sh b/t/t3034-merge-recursive-rename-options.sh index b9c4028496..3d9fae68c4 100755 --- a/t/t3034-merge-recursive-rename-options.sh +++ b/t/t3034-merge-recursive-rename-options.sh @@ -309,4 +309,22 @@ test_expect_success 'last wins in --find-renames=<m> --rename-threshold=<n>' ' check_threshold_0 ' +test_expect_success 'merge.renames disables rename detection' ' + git read-tree --reset -u HEAD && + git -c merge.renames=false merge-recursive $tail && + check_no_renames +' + +test_expect_success 'merge.renames defaults to diff.renames' ' + git read-tree --reset -u HEAD && + git -c diff.renames=false merge-recursive $tail && + check_no_renames +' + +test_expect_success 'merge.renames overrides diff.renames' ' + git read-tree --reset -u HEAD && + test_must_fail git -c diff.renames=false -c merge.renames=true merge-recursive $tail && + $check_50 +' + test_done diff --git a/t/t3035-merge-sparse.sh b/t/t3035-merge-sparse.sh new file mode 100755 index 0000000000..c4b4a94324 --- /dev/null +++ b/t/t3035-merge-sparse.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +test_description='merge with sparse files' + +. ./test-lib.sh + +# test_file $filename $content +test_file () { + echo "$2" > "$1" && + git add "$1" +} + +# test_commit_this $message_and_tag +test_commit_this () { + git commit -m "$1" && + git tag "$1" +} + +test_expect_success 'setup' ' + test_file checked-out init && + test_file modify_delete modify_delete_init && + test_commit_this init && + test_file modify_delete modify_delete_theirs && + test_commit_this theirs && + git reset --hard init && + git rm modify_delete && + test_commit_this ours && + git config core.sparseCheckout true && + echo "/checked-out" >.git/info/sparse-checkout && + git reset --hard && + ! git merge theirs +' + +test_expect_success 'reset --hard works after the conflict' ' + git reset --hard +' + +test_expect_success 'is reset properly' ' + git status --porcelain -- modify_delete >out && + test_must_be_empty out && + test_path_is_missing modify_delete +' + +test_expect_success 'setup: conflict back' ' + ! git merge theirs +' + +test_expect_success 'Merge abort works after the conflict' ' + git merge --abort +' + +test_expect_success 'is aborted properly' ' + git status --porcelain -- modify_delete >out && + test_must_be_empty out && + test_path_is_missing modify_delete +' + +test_done diff --git a/t/t3050-subprojects-fetch.sh b/t/t3050-subprojects-fetch.sh index 2f5f41a012..f1f09abdd9 100755 --- a/t/t3050-subprojects-fetch.sh +++ b/t/t3050-subprojects-fetch.sh @@ -21,10 +21,10 @@ test_expect_success setup ' test_expect_success clone ' git clone "file://$(pwd)/.git" cloned && - (git rev-parse HEAD; git ls-files -s) >expected && + (git rev-parse HEAD && git ls-files -s) >expected && ( cd cloned && - (git rev-parse HEAD; git ls-files -s) >../actual + (git rev-parse HEAD && git ls-files -s) >../actual ) && test_cmp expected actual ' @@ -40,11 +40,11 @@ test_expect_success advance ' ' test_expect_success fetch ' - (git rev-parse HEAD; git ls-files -s) >expected && + (git rev-parse HEAD && git ls-files -s) >expected && ( cd cloned && git pull && - (git rev-parse HEAD; git ls-files -s) >../actual + (git rev-parse HEAD && git ls-files -s) >../actual ) && test_cmp expected actual ' diff --git a/t/t3070-wildmatch.sh b/t/t3070-wildmatch.sh index dce102130f..46aca0af10 100755 --- a/t/t3070-wildmatch.sh +++ b/t/t3070-wildmatch.sh @@ -101,8 +101,7 @@ match_with_ls_files() { match_stdout_stderr_cmp=" tr -d '\0' <actual.raw >actual && - >expect.err && - test_cmp expect.err actual.err && + test_must_be_empty actual.err && test_cmp expect actual" if test "$match_expect" = 'E' diff --git a/t/t3100-ls-tree-restrict.sh b/t/t3100-ls-tree-restrict.sh index 325114f8fe..18baf49a49 100755 --- a/t/t3100-ls-tree-restrict.sh +++ b/t/t3100-ls-tree-restrict.sh @@ -32,7 +32,7 @@ test_expect_success \ echo $tree' test_output () { - sed -e "s/ $_x40 / X /" <current >check + sed -e "s/ $OID_REGEX / X /" <current >check test_cmp expected check } diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh index 327ded4000..12bf31022a 100755 --- a/t/t3101-ls-tree-dirname.sh +++ b/t/t3101-ls-tree-dirname.sh @@ -40,7 +40,7 @@ test_expect_success 'setup' ' ' test_output () { - sed -e "s/ $_x40 / X /" <current >check && + sed -e "s/ $OID_REGEX / X /" <current >check && test_cmp expected check } diff --git a/t/t3102-ls-tree-wildcards.sh b/t/t3102-ls-tree-wildcards.sh index e804377f1c..1e16c6b8ea 100755 --- a/t/t3102-ls-tree-wildcards.sh +++ b/t/t3102-ls-tree-wildcards.sh @@ -23,7 +23,7 @@ test_expect_success 'ls-tree outside prefix' ' cat >expect <<-EOF && 100644 blob $EMPTY_BLOB ../a[a]/three EOF - ( cd aa && git ls-tree -r HEAD "../a[a]"; ) >actual && + ( cd aa && git ls-tree -r HEAD "../a[a]" ) >actual && test_cmp expect actual ' diff --git a/t/t3103-ls-tree-misc.sh b/t/t3103-ls-tree-misc.sh index 09dcf043fd..14520913af 100755 --- a/t/t3103-ls-tree-misc.sh +++ b/t/t3103-ls-tree-misc.sh @@ -17,7 +17,8 @@ test_expect_success 'setup' ' ' test_expect_success 'ls-tree fails with non-zero exit code on broken tree' ' - rm -f .git/objects/5f/cffbd6e4c5c5b8d81f5e9314b20e338e3ffff5 && + tree=$(git rev-parse HEAD:a) && + rm -f .git/objects/$(echo $tree | sed -e "s,^\(..\),\1/,") && test_must_fail git ls-tree -r HEAD ' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 6c0b7ea4ad..93f21ab078 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -6,6 +6,7 @@ test_description='git branch assorted tests' . ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh test_expect_success 'prepare a trivial repository' ' echo Hello >A && @@ -46,11 +47,11 @@ test_expect_success 'git branch HEAD should fail' ' ' cat >expect <<EOF -$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master +$ZERO_OID $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF -test_expect_success 'git branch -l d/e/f should create a branch and a log' ' +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 branch -l d/e/f && + git -c core.logallrefupdates=false branch --create-reflog d/e/f && test_path_is_file .git/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 @@ -81,7 +82,7 @@ test_expect_success 'git branch -m dumps usage' ' test_expect_success 'git branch -m m broken_symref should work' ' test_when_finished "git branch -D broken_symref" && - git branch -l m && + git branch --create-reflog m && git symbolic-ref refs/heads/broken_symref refs/heads/i_am_broken && git branch -m m broken_symref && git reflog exists refs/heads/broken_symref && @@ -89,13 +90,13 @@ test_expect_success 'git branch -m m broken_symref should work' ' ' test_expect_success 'git branch -m m m/m should work' ' - git branch -l m && + git branch --create-reflog m && git branch -m m m/m && git reflog exists refs/heads/m/m ' test_expect_success 'git branch -m n/n n should work' ' - git branch -l n/n && + git branch --create-reflog n/n && git branch -m n/n n && git reflog exists refs/heads/n ' @@ -233,34 +234,34 @@ test_expect_success 'git branch -M master2 master2 should work when master is ch test_expect_success 'git branch -v -d t should work' ' git branch t && - test_path_is_file .git/refs/heads/t && + git rev-parse --verify refs/heads/t && git branch -v -d t && - test_path_is_missing .git/refs/heads/t + test_must_fail git rev-parse --verify refs/heads/t ' test_expect_success 'git branch -v -m t s should work' ' git branch t && - test_path_is_file .git/refs/heads/t && + git rev-parse --verify refs/heads/t && git branch -v -m t s && - test_path_is_missing .git/refs/heads/t && - test_path_is_file .git/refs/heads/s && + test_must_fail git rev-parse --verify refs/heads/t && + git rev-parse --verify refs/heads/s && git branch -d s ' test_expect_success 'git branch -m -d t s should fail' ' git branch t && - test_path_is_file .git/refs/heads/t && + git rev-parse refs/heads/t && test_must_fail git branch -m -d t s && git branch -d t && - test_path_is_missing .git/refs/heads/t + test_must_fail git rev-parse refs/heads/t ' test_expect_success 'git branch --list -d t should fail' ' git branch t && - test_path_is_file .git/refs/heads/t && + git rev-parse refs/heads/t && test_must_fail git branch --list -d t && git branch -d t && - test_path_is_missing .git/refs/heads/t + test_must_fail git rev-parse refs/heads/t ' test_expect_success 'git branch --list -v with --abbrev' ' @@ -377,9 +378,9 @@ 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 branch -l s/s && + git branch --create-reflog s/s && git reflog exists refs/heads/s/s && - git branch -l s/t && + git branch --create-reflog s/t && git reflog exists refs/heads/s/t && git branch -d s/t && git branch -m s/s s && @@ -443,7 +444,7 @@ test_expect_success 'git branch --copy dumps usage' ' ' test_expect_success 'git branch -c d e should work' ' - git branch -l d && + git branch --create-reflog d && git reflog exists refs/heads/d && git config branch.d.dummy Hello && git branch -c d e && @@ -458,7 +459,7 @@ test_expect_success 'git branch -c d e should work' ' ' test_expect_success 'git branch --copy is a synonym for -c' ' - git branch -l copy && + git branch --create-reflog copy && git reflog exists refs/heads/copy && git config branch.copy.dummy Hello && git branch --copy copy copy-to && @@ -485,7 +486,7 @@ test_expect_success 'git branch -c ee ef should copy ee to create branch ef' ' ' test_expect_success 'git branch -c f/f g/g should work' ' - git branch -l f/f && + git branch --create-reflog f/f && git reflog exists refs/heads/f/f && git config branch.f/f.dummy Hello && git branch -c f/f g/g && @@ -496,7 +497,7 @@ test_expect_success 'git branch -c f/f g/g should work' ' ' test_expect_success 'git branch -c m2 m2 should work' ' - git branch -l m2 && + git branch --create-reflog m2 && git reflog exists refs/heads/m2 && git config branch.m2.dummy Hello && git branch -c m2 m2 && @@ -505,18 +506,18 @@ test_expect_success 'git branch -c m2 m2 should work' ' ' test_expect_success 'git branch -c zz zz/zz should fail' ' - git branch -l zz && + git branch --create-reflog zz && git reflog exists refs/heads/zz && test_must_fail git branch -c zz zz/zz ' test_expect_success 'git branch -c b/b b should fail' ' - git branch -l b/b && + git branch --create-reflog b/b && test_must_fail git branch -c b/b b ' test_expect_success 'git branch -C o/q o/p should work when o/p exists' ' - git branch -l o/q && + git branch --create-reflog o/q && git reflog exists refs/heads/o/q && git reflog exists refs/heads/o/p && git branch -C o/q o/p @@ -569,10 +570,10 @@ test_expect_success 'git branch -C master5 master5 should work when master is ch ' test_expect_success 'git branch -C ab cd should overwrite existing config for cd' ' - git branch -l cd && + git branch --create-reflog cd && git reflog exists refs/heads/cd && git config branch.cd.dummy CD && - git branch -l ab && + git branch --create-reflog ab && git reflog exists refs/heads/ab && git config branch.ab.dummy AB && git branch -C ab cd && @@ -684,7 +685,7 @@ test_expect_success 'renaming a symref is not allowed' ' ' test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' ' - git branch -l u && + 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 @@ -884,7 +885,7 @@ test_expect_success 'test --unset-upstream on a particular branch' ' test_must_fail git config branch.my14.merge ' -test_expect_success '--set-upstream fails' ' +test_expect_success 'disabled option --set-upstream fails' ' test_must_fail git branch --set-upstream origin/master ' @@ -900,7 +901,7 @@ test_expect_success '--set-upstream-to notices an error to set branch as own ups # Keep this test last, as it changes the current branch cat >expect <<EOF -$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master +$ZERO_OID $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master EOF test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' ' GIT_COMMITTER_DATE="2005-05-26 23:30" \ @@ -1246,6 +1247,29 @@ test_expect_success '--merged is incompatible with --no-merged' ' test_must_fail git branch --merged HEAD --no-merged HEAD ' +test_expect_success '--list during rebase' ' + test_when_finished "reset_rebase" && + git checkout master && + FAKE_LINES="1 edit 2" && + export FAKE_LINES && + set_fake_editor && + git rebase -i HEAD~2 && + git branch --list >actual && + test_i18ngrep "rebasing master" actual +' + +test_expect_success '--list during rebase from detached HEAD' ' + test_when_finished "reset_rebase && git checkout master" && + git checkout master^0 && + oid=$(git rev-parse --short HEAD) && + FAKE_LINES="1 edit 2" && + export FAKE_LINES && + set_fake_editor && + git rebase -i HEAD~2 && + git branch --list >actual && + test_i18ngrep "rebasing detached HEAD $oid" actual +' + test_expect_success 'tracking with unexpected .fetch refspec' ' rm -rf a b c d && git init a && @@ -1281,4 +1305,50 @@ test_expect_success 'tracking with unexpected .fetch refspec' ' ) ' +test_expect_success 'configured committerdate sort' ' + git init sort && + ( + cd sort && + git config branch.sort committerdate && + test_commit initial && + git checkout -b a && + test_commit a && + git checkout -b c && + test_commit c && + git checkout -b b && + test_commit b && + git branch >actual && + cat >expect <<-\EOF && + master + a + c + * b + EOF + test_cmp expect actual + ) +' + +test_expect_success 'option override configured sort' ' + ( + cd sort && + git config branch.sort committerdate && + git branch --sort=refname >actual && + cat >expect <<-\EOF && + a + * b + c + master + EOF + test_cmp expect actual + ) +' + +test_expect_success 'invalid sort parameter in configuration' ' + ( + cd sort && + git config branch.sort "v:notvalid" && + test_must_fail git branch + ) +' + test_done diff --git a/t/t3201-branch-contains.sh b/t/t3201-branch-contains.sh index 0ef1b6fdcc..0ea4fc4694 100755 --- a/t/t3201-branch-contains.sh +++ b/t/t3201-branch-contains.sh @@ -48,16 +48,14 @@ test_expect_success 'branch --contains master' ' test_expect_success 'branch --no-contains=master' ' git branch --no-contains=master >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'branch --no-contains master' ' git branch --no-contains master >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' @@ -94,8 +92,7 @@ test_expect_success 'branch --contains with pattern implies --list' ' test_expect_success 'branch --no-contains with pattern implies --list' ' git branch --no-contains=master master >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' @@ -123,8 +120,7 @@ test_expect_success 'branch --merged with pattern implies --list' ' test_expect_success 'side: branch --no-merged' ' git branch --no-merged >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' @@ -152,8 +148,7 @@ test_expect_success 'master: branch --no-merged' ' test_expect_success 'branch --no-merged with pattern implies --list' ' git branch --no-merged=master master >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' diff --git a/t/t3206-range-diff.sh b/t/t3206-range-diff.sh new file mode 100755 index 0000000000..d481f29259 --- /dev/null +++ b/t/t3206-range-diff.sh @@ -0,0 +1,201 @@ +#!/bin/sh + +test_description='range-diff tests' + +. ./test-lib.sh + +# Note that because of the range-diff's heuristics, test_commit does more +# harm than good. We need some real history. + +test_expect_success 'setup' ' + git fast-import < "$TEST_DIRECTORY"/t3206/history.export +' + +test_expect_success 'simple A..B A..C (unmodified)' ' + git range-diff --no-color master..topic master..unmodified \ + >actual && + cat >expected <<-EOF && + 1: 4de457d = 1: 35b9b25 s/5/A/ + 2: fccce22 = 2: de345ab s/4/A/ + 3: 147e64e = 3: 9af6654 s/11/B/ + 4: a63e992 = 4: 2901f77 s/12/B/ + EOF + test_cmp expected actual +' + +test_expect_success 'simple B...C (unmodified)' ' + git range-diff --no-color topic...unmodified >actual && + # same "expected" as above + test_cmp expected actual +' + +test_expect_success 'simple A B C (unmodified)' ' + git range-diff --no-color master topic unmodified >actual && + # same "expected" as above + test_cmp expected actual +' + +test_expect_success 'trivial reordering' ' + git range-diff --no-color master topic reordered >actual && + cat >expected <<-EOF && + 1: 4de457d = 1: aca177a s/5/A/ + 3: 147e64e = 2: 14ad629 s/11/B/ + 4: a63e992 = 3: ee58208 s/12/B/ + 2: fccce22 = 4: 307b27a s/4/A/ + EOF + test_cmp expected actual +' + +test_expect_success 'removed a commit' ' + git range-diff --no-color master topic removed >actual && + cat >expected <<-EOF && + 1: 4de457d = 1: 7657159 s/5/A/ + 2: fccce22 < -: ------- s/4/A/ + 3: 147e64e = 2: 43d84d3 s/11/B/ + 4: a63e992 = 3: a740396 s/12/B/ + EOF + test_cmp expected actual +' + +test_expect_success 'added a commit' ' + git range-diff --no-color master topic added >actual && + cat >expected <<-EOF && + 1: 4de457d = 1: 2716022 s/5/A/ + 2: fccce22 = 2: b62accd s/4/A/ + -: ------- > 3: df46cfa s/6/A/ + 3: 147e64e = 4: 3e64548 s/11/B/ + 4: a63e992 = 5: 12b4063 s/12/B/ + EOF + test_cmp expected actual +' + +test_expect_success 'new base, A B C' ' + git range-diff --no-color master topic rebased >actual && + cat >expected <<-EOF && + 1: 4de457d = 1: cc9c443 s/5/A/ + 2: fccce22 = 2: c5d9641 s/4/A/ + 3: 147e64e = 3: 28cc2b6 s/11/B/ + 4: a63e992 = 4: 5628ab7 s/12/B/ + EOF + test_cmp expected actual +' + +test_expect_success 'new base, B...C' ' + # this syntax includes the commits from master! + git range-diff --no-color topic...rebased >actual && + cat >expected <<-EOF && + -: ------- > 1: a31b12e unrelated + 1: 4de457d = 2: cc9c443 s/5/A/ + 2: fccce22 = 3: c5d9641 s/4/A/ + 3: 147e64e = 4: 28cc2b6 s/11/B/ + 4: a63e992 = 5: 5628ab7 s/12/B/ + EOF + test_cmp expected actual +' + +test_expect_success 'changed commit' ' + git range-diff --no-color topic...changed >actual && + cat >expected <<-EOF && + 1: 4de457d = 1: a4b3333 s/5/A/ + 2: fccce22 = 2: f51d370 s/4/A/ + 3: 147e64e ! 3: 0559556 s/11/B/ + @@ -10,7 +10,7 @@ + 9 + 10 + -11 + -+B + ++BB + 12 + 13 + 14 + 4: a63e992 ! 4: d966c5c s/12/B/ + @@ -8,7 +8,7 @@ + @@ + 9 + 10 + - B + + BB + -12 + +B + 13 + EOF + test_cmp expected actual +' + +test_expect_success 'changed message' ' + git range-diff --no-color topic...changed-message >actual && + sed s/Z/\ /g >expected <<-EOF && + 1: 4de457d = 1: f686024 s/5/A/ + 2: fccce22 ! 2: 4ab067d s/4/A/ + @@ -2,6 +2,8 @@ + Z + Z s/4/A/ + Z + + Also a silly comment here! + + + Z diff --git a/file b/file + Z --- a/file + Z +++ b/file + 3: 147e64e = 3: b9cb956 s/11/B/ + 4: a63e992 = 4: 8add5f1 s/12/B/ + EOF + test_cmp expected actual +' + +test_expect_success 'dual-coloring' ' + sed -e "s|^:||" >expect <<-\EOF && + :<YELLOW>1: a4b3333 = 1: f686024 s/5/A/<RESET> + :<RED>2: f51d370 <RESET><YELLOW>!<RESET><GREEN> 2: 4ab067d<RESET><YELLOW> s/4/A/<RESET> + : <REVERSE><CYAN>@@ -2,6 +2,8 @@<RESET> + : <RESET> + : s/4/A/<RESET> + : <RESET> + : <REVERSE><GREEN>+<RESET><BOLD> Also a silly comment here!<RESET> + : <REVERSE><GREEN>+<RESET> + : diff --git a/file b/file<RESET> + : --- a/file<RESET> + : +++ b/file<RESET> + :<RED>3: 0559556 <RESET><YELLOW>!<RESET><GREEN> 3: b9cb956<RESET><YELLOW> s/11/B/<RESET> + : <REVERSE><CYAN>@@ -10,7 +10,7 @@<RESET> + : 9<RESET> + : 10<RESET> + : <RED> -11<RESET> + : <REVERSE><RED>-<RESET><FAINT;GREEN>+BB<RESET> + : <REVERSE><GREEN>+<RESET><BOLD;GREEN>+B<RESET> + : 12<RESET> + : 13<RESET> + : 14<RESET> + :<RED>4: d966c5c <RESET><YELLOW>!<RESET><GREEN> 4: 8add5f1<RESET><YELLOW> s/12/B/<RESET> + : <REVERSE><CYAN>@@ -8,7 +8,7 @@<RESET> + : <CYAN> @@<RESET> + : 9<RESET> + : 10<RESET> + : <REVERSE><RED>-<RESET><FAINT> BB<RESET> + : <REVERSE><GREEN>+<RESET><BOLD> B<RESET> + : <RED> -12<RESET> + : <GREEN> +B<RESET> + : 13<RESET> + EOF + git range-diff changed...changed-message --color --dual-color >actual.raw && + test_decode_color >actual <actual.raw && + test_cmp expect actual +' + +for prev in topic master..topic +do + test_expect_success "format-patch --range-diff=$prev" ' + git format-patch --stdout --cover-letter --range-diff=$prev \ + master..unmodified >actual && + grep "= 1: .* s/5/A" actual && + grep "= 2: .* s/4/A" actual && + grep "= 3: .* s/11/B" actual && + grep "= 4: .* s/12/B" actual + ' +done + +test_expect_success 'format-patch --range-diff as commentary' ' + git format-patch --stdout --range-diff=HEAD~1 HEAD~1 >actual && + test_i18ngrep "^Range-diff:$" actual +' + +test_done diff --git a/t/t3206/history.export b/t/t3206/history.export new file mode 100644 index 0000000000..b8ffff0940 --- /dev/null +++ b/t/t3206/history.export @@ -0,0 +1,604 @@ +blob +mark :1 +data 51 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 + +reset refs/heads/removed +commit refs/heads/removed +mark :2 +author Thomas Rast <trast@inf.ethz.ch> 1374424921 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374484724 +0200 +data 8 +initial +M 100644 :1 file + +blob +mark :3 +data 51 +1 +2 +3 +4 +A +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/topic +mark :4 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +data 7 +s/5/A/ +from :2 +M 100644 :3 file + +blob +mark :5 +data 51 +1 +2 +3 +A +A +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/topic +mark :6 +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +data 7 +s/4/A/ +from :4 +M 100644 :5 file + +blob +mark :7 +data 50 +1 +2 +3 +A +A +6 +7 +8 +9 +10 +B +12 +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/topic +mark :8 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +data 8 +s/11/B/ +from :6 +M 100644 :7 file + +blob +mark :9 +data 49 +1 +2 +3 +A +A +6 +7 +8 +9 +10 +B +B +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/topic +mark :10 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +data 8 +s/12/B/ +from :8 +M 100644 :9 file + +blob +mark :11 +data 10 +unrelated + +commit refs/heads/master +mark :12 +author Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485127 +0200 +data 10 +unrelated +from :2 +M 100644 :11 otherfile + +commit refs/heads/rebased +mark :13 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485137 +0200 +data 7 +s/5/A/ +from :12 +M 100644 :3 file + +commit refs/heads/rebased +mark :14 +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200 +data 7 +s/4/A/ +from :13 +M 100644 :5 file + +commit refs/heads/rebased +mark :15 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200 +data 8 +s/11/B/ +from :14 +M 100644 :7 file + +commit refs/heads/rebased +mark :16 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485138 +0200 +data 8 +s/12/B/ +from :15 +M 100644 :9 file + +commit refs/heads/added +mark :17 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200 +data 7 +s/5/A/ +from :2 +M 100644 :3 file + +commit refs/heads/added +mark :18 +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200 +data 7 +s/4/A/ +from :17 +M 100644 :5 file + +blob +mark :19 +data 51 +1 +2 +3 +A +A +A +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/added +mark :20 +author Thomas Rast <trast@inf.ethz.ch> 1374485186 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200 +data 7 +s/6/A/ +from :18 +M 100644 :19 file + +blob +mark :21 +data 50 +1 +2 +3 +A +A +A +7 +8 +9 +10 +B +12 +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/added +mark :22 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200 +data 8 +s/11/B/ +from :20 +M 100644 :21 file + +blob +mark :23 +data 49 +1 +2 +3 +A +A +A +7 +8 +9 +10 +B +B +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/added +mark :24 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485341 +0200 +data 8 +s/12/B/ +from :22 +M 100644 :23 file + +commit refs/heads/reordered +mark :25 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200 +data 7 +s/5/A/ +from :2 +M 100644 :3 file + +blob +mark :26 +data 50 +1 +2 +3 +4 +A +6 +7 +8 +9 +10 +B +12 +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/reordered +mark :27 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200 +data 8 +s/11/B/ +from :25 +M 100644 :26 file + +blob +mark :28 +data 49 +1 +2 +3 +4 +A +6 +7 +8 +9 +10 +B +B +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/reordered +mark :29 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200 +data 8 +s/12/B/ +from :27 +M 100644 :28 file + +commit refs/heads/reordered +mark :30 +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485350 +0200 +data 7 +s/4/A/ +from :29 +M 100644 :9 file + +commit refs/heads/changed +mark :31 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200 +data 7 +s/5/A/ +from :2 +M 100644 :3 file + +commit refs/heads/changed +mark :32 +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200 +data 7 +s/4/A/ +from :31 +M 100644 :5 file + +blob +mark :33 +data 51 +1 +2 +3 +A +A +6 +7 +8 +9 +10 +BB +12 +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/changed +mark :34 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200 +data 8 +s/11/B/ +from :32 +M 100644 :33 file + +blob +mark :35 +data 50 +1 +2 +3 +A +A +6 +7 +8 +9 +10 +BB +B +13 +14 +15 +16 +17 +18 +19 +20 + +commit refs/heads/changed +mark :36 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485507 +0200 +data 8 +s/12/B/ +from :34 +M 100644 :35 file + +commit refs/heads/changed-message +mark :37 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200 +data 7 +s/5/A/ +from :2 +M 100644 :3 file + +commit refs/heads/changed-message +mark :38 +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485530 +0200 +data 35 +s/4/A/ + +Also a silly comment here! +from :37 +M 100644 :5 file + +commit refs/heads/changed-message +mark :39 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200 +data 8 +s/11/B/ +from :38 +M 100644 :7 file + +commit refs/heads/changed-message +mark :40 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485536 +0200 +data 8 +s/12/B/ +from :39 +M 100644 :9 file + +commit refs/heads/unmodified +mark :41 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200 +data 7 +s/5/A/ +from :2 +M 100644 :3 file + +commit refs/heads/unmodified +mark :42 +author Thomas Rast <trast@inf.ethz.ch> 1374485024 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485631 +0200 +data 7 +s/4/A/ +from :41 +M 100644 :5 file + +commit refs/heads/unmodified +mark :43 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200 +data 8 +s/11/B/ +from :42 +M 100644 :7 file + +commit refs/heads/unmodified +mark :44 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374485632 +0200 +data 8 +s/12/B/ +from :43 +M 100644 :9 file + +commit refs/heads/removed +mark :45 +author Thomas Rast <trast@inf.ethz.ch> 1374485014 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200 +data 7 +s/5/A/ +from :2 +M 100644 :3 file + +commit refs/heads/removed +mark :46 +author Thomas Rast <trast@inf.ethz.ch> 1374485036 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200 +data 8 +s/11/B/ +from :45 +M 100644 :26 file + +commit refs/heads/removed +mark :47 +author Thomas Rast <trast@inf.ethz.ch> 1374485044 +0200 +committer Thomas Rast <trast@inf.ethz.ch> 1374486061 +0200 +data 8 +s/12/B/ +from :46 +M 100644 :28 file + +reset refs/heads/removed +from :47 + diff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh index afa27ffe2d..9ea5fa4fd2 100755 --- a/t/t3210-pack-refs.sh +++ b/t/t3210-pack-refs.sh @@ -127,7 +127,7 @@ test_expect_success 'explicit pack-refs with dangling packed reference' ' git reflog expire --expire=all --all && git prune --expire=all && git pack-refs --all 2>result && - test_cmp /dev/null result + test_must_be_empty result ' test_expect_success 'delete ref with dangling packed version' ' @@ -139,7 +139,7 @@ test_expect_success 'delete ref with dangling packed version' ' git reflog expire --expire=all --all && git prune --expire=all && git branch -d lamb 2>result && - test_cmp /dev/null result + test_must_be_empty result ' test_expect_success 'delete ref while another dangling packed ref' ' @@ -150,7 +150,7 @@ test_expect_success 'delete ref while another dangling packed ref' ' git reflog expire --expire=all --all && git prune --expire=all && git branch -d lamb 2>result && - test_cmp /dev/null result + test_must_be_empty result ' test_expect_success 'pack ref directly below refs/' ' @@ -186,7 +186,7 @@ test_expect_success 'notice d/f conflict with existing directory' ' test_expect_success 'existing directory reports concrete ref' ' test_must_fail git branch foo 2>stderr && - grep refs/heads/foo/bar/baz stderr + test_i18ngrep refs/heads/foo/bar/baz stderr ' test_expect_success 'notice d/f conflict with existing ref' ' @@ -231,9 +231,9 @@ test_expect_success 'timeout if packed-refs.lock exists' ' test_expect_success 'retry acquiring packed-refs.lock' ' LOCK=.git/packed-refs.lock && >"$LOCK" && - test_when_finished "wait; rm -f $LOCK" && + test_when_finished "wait && rm -f $LOCK" && { - ( sleep 1 ; rm -f $LOCK ) & + ( sleep 1 && rm -f $LOCK ) & } && git -c core.packedrefstimeout=3000 pack-refs --all --prune ' diff --git a/t/t3301-notes.sh b/t/t3301-notes.sh index 2d200fdf36..84bbf88cf9 100755 --- a/t/t3301-notes.sh +++ b/t/t3301-notes.sh @@ -481,10 +481,8 @@ test_expect_success 'list specific note with "git notes list <object>"' ' ' test_expect_success 'listing non-existing notes fails' ' - cat >expect <<-EOF && - EOF test_must_fail git notes list HEAD >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'append to existing note with "git notes append"' ' @@ -914,7 +912,7 @@ test_expect_success 'git notes copy --stdin' ' ${indent} ${indent}yet another note EOF - (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \ + (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) && echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | git notes copy --stdin && git log -2 >actual && @@ -939,7 +937,7 @@ test_expect_success 'git notes copy --for-rewrite (unconfigured)' ' EOF test_commit 14th && test_commit 15th && - (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \ + (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) && echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | git notes copy --for-rewrite=foo && git log -2 >actual && @@ -972,7 +970,7 @@ test_expect_success 'git notes copy --for-rewrite (enabled)' ' EOF test_config notes.rewriteMode overwrite && test_config notes.rewriteRef "refs/notes/*" && - (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^); \ + (echo $(git rev-parse HEAD~3) $(git rev-parse HEAD^) && echo $(git rev-parse HEAD~2) $(git rev-parse HEAD)) | git notes copy --for-rewrite=foo && git log -2 >actual && @@ -1059,7 +1057,7 @@ test_expect_success 'git notes copy --for-rewrite (append two to one)' ' git notes add -f -m"append 2" HEAD^^ && test_config notes.rewriteMode concatenate && test_config notes.rewriteRef "refs/notes/*" && - (echo $(git rev-parse HEAD^) $(git rev-parse HEAD); + (echo $(git rev-parse HEAD^) $(git rev-parse HEAD) && echo $(git rev-parse HEAD^^) $(git rev-parse HEAD)) | git notes copy --for-rewrite=foo && git log -1 >actual && diff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh index ab946a5153..d60588ec8f 100755 --- a/t/t3308-notes-merge.sh +++ b/t/t3308-notes-merge.sh @@ -183,7 +183,7 @@ test_expect_success 'merge empty notes ref (z => y)' ' git notes add -m "foo" && git notes remove && git notes >output_notes_z && - test_cmp /dev/null output_notes_z && + test_must_be_empty output_notes_z && # Do the merge (z => y) git config core.notesRef refs/notes/y && git notes merge z && diff --git a/t/t3310-notes-merge-manual-resolve.sh b/t/t3310-notes-merge-manual-resolve.sh index 9c1bf6eb3d..2dea846e25 100755 --- a/t/t3310-notes-merge-manual-resolve.sh +++ b/t/t3310-notes-merge-manual-resolve.sh @@ -337,7 +337,7 @@ EOF git notes merge --commit && # No .git/NOTES_MERGE_* files left test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && - test_cmp /dev/null output && + test_must_be_empty output && # Merge commit has pre-merge y and pre-merge z as parents test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" && test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" && @@ -399,7 +399,7 @@ test_expect_success 'abort notes merge' ' git notes merge --abort && # No .git/NOTES_MERGE_* files left test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && - test_cmp /dev/null output && + test_must_be_empty output && # m has not moved (still == y) test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" && # Verify that other notes refs has not changed (w, x, y and z) @@ -466,7 +466,7 @@ EOF git notes merge --commit && # No .git/NOTES_MERGE_* files left test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && - test_cmp /dev/null output && + test_must_be_empty output && # Merge commit has pre-merge y and pre-merge z as parents test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" && test "$(git rev-parse refs/notes/m^2)" = "$(cat pre_merge_z)" && @@ -541,9 +541,9 @@ EOF test "$(git rev-parse refs/notes/y)" = "$(git rev-parse NOTES_MERGE_PARTIAL^1)" && test "$(git rev-parse refs/notes/m)" != "$(git rev-parse NOTES_MERGE_PARTIAL^1)" && # Mention refs/notes/m, and its current and expected value in output - grep -q "refs/notes/m" output && - grep -q "$(git rev-parse refs/notes/m)" output && - grep -q "$(git rev-parse NOTES_MERGE_PARTIAL^1)" output && + test_i18ngrep -q "refs/notes/m" output && + test_i18ngrep -q "$(git rev-parse refs/notes/m)" output && + test_i18ngrep -q "$(git rev-parse NOTES_MERGE_PARTIAL^1)" output && # Verify that other notes refs has not changed (w, x, y and z) verify_notes w && verify_notes x && @@ -555,7 +555,7 @@ test_expect_success 'resolve situation by aborting the notes merge' ' git notes merge --abort && # No .git/NOTES_MERGE_* files left test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null && - test_cmp /dev/null output && + test_must_be_empty output && # m has not moved (still == w) test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" && # Verify that other notes refs has not changed (w, x, y and z) diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 72d9564747..3996ee0135 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -200,10 +200,10 @@ test_expect_success 'rebase -q is quiet' ' test_expect_success 'Rebase a commit that sprinkles CRs in' ' ( - echo "One" - echo "TwoQ" - echo "Three" - echo "FQur" + echo "One" && + echo "TwoQ" && + echo "Three" && + echo "FQur" && echo "Five" ) | q_to_cr >CR && git add CR && diff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh new file mode 100755 index 0000000000..e0b5111993 --- /dev/null +++ b/t/t3401-rebase-and-am-rename.sh @@ -0,0 +1,213 @@ +#!/bin/sh + +test_description='git rebase + directory rename tests' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_expect_success 'setup testcase where directory rename should be detected' ' + test_create_repo dir-rename && + ( + cd dir-rename && + + mkdir x && + test_seq 1 10 >x/a && + test_seq 11 20 >x/b && + test_seq 21 30 >x/c && + test_write_lines a b c d e f g h i >l && + git add x l && + git commit -m "Initial" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv x y && + git mv l letters && + git commit -m "Rename x to y, l to letters" && + + git checkout B && + echo j >>l && + test_seq 31 40 >x/d && + git add l x/d && + git commit -m "Modify l, add x/d" + ) +' + +test_expect_success 'rebase --interactive: directory rename detected' ' + ( + cd dir-rename && + + git checkout B^0 && + + set_fake_editor && + FAKE_LINES="1" git rebase --interactive A && + + git ls-files -s >out && + test_line_count = 5 out && + + test_path_is_file y/d && + test_path_is_missing x/d + ) +' + +test_expect_failure 'rebase (am): directory rename detected' ' + ( + cd dir-rename && + + git checkout B^0 && + + git rebase A && + + git ls-files -s >out && + test_line_count = 5 out && + + test_path_is_file y/d && + test_path_is_missing x/d + ) +' + +test_expect_success 'rebase --merge: directory rename detected' ' + ( + cd dir-rename && + + git checkout B^0 && + + git rebase --merge A && + + git ls-files -s >out && + test_line_count = 5 out && + + test_path_is_file y/d && + test_path_is_missing x/d + ) +' + +test_expect_failure 'am: directory rename detected' ' + ( + cd dir-rename && + + git checkout A^0 && + + git format-patch -1 B && + + git am --3way 0001*.patch && + + git ls-files -s >out && + test_line_count = 5 out && + + test_path_is_file y/d && + test_path_is_missing x/d + ) +' + +test_expect_success 'setup testcase where directory rename should NOT be detected' ' + test_create_repo no-dir-rename && + ( + cd no-dir-rename && + + mkdir x && + test_seq 1 10 >x/a && + test_seq 11 20 >x/b && + test_seq 21 30 >x/c && + echo original >project_info && + git add x project_info && + git commit -m "Initial" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo v2 >project_info && + git add project_info && + git commit -m "Modify project_info" && + + git checkout B && + mkdir y && + git mv x/c y/c && + echo v1 >project_info && + git add project_info && + git commit -m "Rename x/c to y/c, modify project_info" + ) +' + +test_expect_success 'rebase --interactive: NO directory rename' ' + test_when_finished "git -C no-dir-rename rebase --abort" && + ( + cd no-dir-rename && + + git checkout B^0 && + + set_fake_editor && + test_must_fail env FAKE_LINES="1" git rebase --interactive A && + + git ls-files -s >out && + test_line_count = 6 out && + + test_path_is_file x/a && + test_path_is_file x/b && + test_path_is_missing x/c + ) +' + +test_expect_success 'rebase (am): NO directory rename' ' + test_when_finished "git -C no-dir-rename rebase --abort" && + ( + cd no-dir-rename && + + git checkout B^0 && + + set_fake_editor && + test_must_fail git rebase A && + + git ls-files -s >out && + test_line_count = 6 out && + + test_path_is_file x/a && + test_path_is_file x/b && + test_path_is_missing x/c + ) +' + +test_expect_success 'rebase --merge: NO directory rename' ' + test_when_finished "git -C no-dir-rename rebase --abort" && + ( + cd no-dir-rename && + + git checkout B^0 && + + set_fake_editor && + test_must_fail git rebase --merge A && + + git ls-files -s >out && + test_line_count = 6 out && + + test_path_is_file x/a && + test_path_is_file x/b && + test_path_is_missing x/c + ) +' + +test_expect_success 'am: NO directory rename' ' + test_when_finished "git -C no-dir-rename am --abort" && + ( + cd no-dir-rename && + + git checkout A^0 && + + git format-patch -1 B && + + test_must_fail git am --3way 0001*.patch && + + git ls-files -s >out && + test_line_count = 6 out && + + test_path_is_file x/a && + test_path_is_file x/b && + test_path_is_missing x/c + ) +' + +test_done diff --git a/t/t3402-rebase-merge.sh b/t/t3402-rebase-merge.sh index 488945e007..a1ec501a87 100755 --- a/t/t3402-rebase-merge.sh +++ b/t/t3402-rebase-merge.sh @@ -25,7 +25,7 @@ test_expect_success setup ' git commit -a -m"master updates a bit more." && git checkout side && - (echo "0 $T" ; cat original) >renamed && + (echo "0 $T" && cat original) >renamed && git add renamed && git update-index --force-remove original && git commit -a -m"side renames and edits." && @@ -143,7 +143,7 @@ test_expect_success 'rebase -s funny -Xopt' ' git checkout -b test-funny master^ && test_commit funny && ( - PATH=./test-bin:$PATH + PATH=./test-bin:$PATH && git rebase -s funny -Xopt master ) && test -f funny.was.run diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 756de26c19..86bba5ed7c 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -119,6 +119,15 @@ test_expect_success 'rebase -i with exec allows git commands in subdirs' ' ) ' +test_expect_success 'rebase -i sets work tree properly' ' + test_when_finished "rm -rf subdir" && + test_when_finished "test_might_fail git rebase --abort" && + mkdir subdir && + git rebase -x "(cd subdir && git rev-parse --show-toplevel)" HEAD^ \ + >actual && + ! grep "/subdir$" actual +' + test_expect_success 'rebase -i with the exec command checks tree cleanness' ' git checkout master && set_fake_editor && @@ -264,11 +273,18 @@ test_expect_success 'retain authorship' ' ' test_expect_success 'retain authorship w/ conflicts' ' + oGIT_AUTHOR_NAME=$GIT_AUTHOR_NAME && + test_when_finished "GIT_AUTHOR_NAME=\$oGIT_AUTHOR_NAME" && + git reset --hard twerp && test_commit a conflict a conflict-a && git reset --hard twerp && - GIT_AUTHOR_NAME=AttributeMe \ + + GIT_AUTHOR_NAME=AttributeMe && + export GIT_AUTHOR_NAME && test_commit b conflict b conflict-b && + GIT_AUTHOR_NAME=$oGIT_AUTHOR_NAME && + set_fake_editor && test_must_fail git rebase -i conflict-a && echo resolved >conflict && @@ -509,7 +525,7 @@ test_expect_success 'interrupted squash works as expected' ' one=$(git rev-parse HEAD~3) && set_fake_editor && test_must_fail env FAKE_LINES="1 squash 3 2" git rebase -i HEAD~3 && - (echo one; echo two; echo four) > conflict && + test_write_lines one two four > conflict && git add conflict && test_must_fail git rebase --continue && echo resolved > conflict && @@ -523,10 +539,10 @@ test_expect_success 'interrupted squash works as expected (case 2)' ' one=$(git rev-parse HEAD~3) && set_fake_editor && test_must_fail env FAKE_LINES="3 squash 1 2" git rebase -i HEAD~3 && - (echo one; echo four) > conflict && + test_write_lines one four > conflict && git add conflict && test_must_fail git rebase --continue && - (echo one; echo two; echo four) > conflict && + test_write_lines one two four > conflict && git add conflict && test_must_fail git rebase --continue && echo resolved > conflict && @@ -553,15 +569,16 @@ test_expect_success '--continue tries to commit, even for "edit"' ' ' test_expect_success 'aborted --continue does not squash commits after "edit"' ' + test_when_finished "git rebase --abort" && old=$(git rev-parse HEAD) && test_tick && set_fake_editor && FAKE_LINES="edit 1" git rebase -i HEAD^ && echo "edited again" > file7 && git add file7 && - test_must_fail env FAKE_COMMIT_MESSAGE=" " git rebase --continue && - test $old = $(git rev-parse HEAD) && - git rebase --abort + echo all the things >>conflict && + test_must_fail git rebase --continue && + test $old = $(git rev-parse HEAD) ' test_expect_success 'auto-amend only edited commits after "edit"' ' @@ -717,7 +734,7 @@ test_expect_success 'avoid unnecessary reset' ' set_fake_editor && git rebase -i HEAD~4 && test $HEAD = $(git rev-parse HEAD) && - MTIME=$(test-tool chmtime -v +0 file3 | sed 's/[^0-9].*$//') && + MTIME=$(test-tool chmtime --get file3) && test 123456789 = $MTIME ' @@ -779,16 +796,15 @@ test_expect_success 'always cherry-pick with --no-ff' ' git tag original-no-ff-branch && set_fake_editor && git rebase -i --no-ff A && - touch empty && for p in 0 1 2 do test ! $(git rev-parse HEAD~$p) = $(git rev-parse original-no-ff-branch~$p) && git diff HEAD~$p original-no-ff-branch~$p > out && - test_cmp empty out + test_must_be_empty out done && test $(git rev-parse HEAD~3) = $(git rev-parse original-no-ff-branch~3) && git diff HEAD~3 original-no-ff-branch~3 > out && - test_cmp empty out + test_must_be_empty out ' test_expect_success 'set up commits with funny messages' ' @@ -971,7 +987,45 @@ test_expect_success 'rebase -i --root fixup root commit' ' test 0 = $(git cat-file commit HEAD | grep -c ^parent\ ) ' +test_expect_success 'rebase -i --root reword root commit' ' + test_when_finished "test_might_fail git rebase --abort" && + git checkout -b reword-root-branch master && + set_fake_editor && + FAKE_LINES="reword 1 2" FAKE_COMMIT_MESSAGE="A changed" \ + git rebase -i --root && + git show HEAD^ | grep "A changed" && + test -z "$(git show -s --format=%p HEAD^)" +' + +test_expect_success 'rebase -i --root when root has untracked file confilct' ' + test_when_finished "reset_rebase" && + git checkout -b failing-root-pick A && + echo x >file2 && + git rm file1 && + git commit -m "remove file 1 add file 2" && + echo z >file1 && + set_fake_editor && + test_must_fail env FAKE_LINES="1 2" git rebase -i --root && + rm file1 && + git rebase --continue && + test "$(git log -1 --format=%B)" = "remove file 1 add file 2" && + test "$(git rev-list --count HEAD)" = 2 +' + +test_expect_success 'rebase -i --root reword root when root has untracked file conflict' ' + test_when_finished "reset_rebase" && + echo z>file1 && + set_fake_editor && + test_must_fail env FAKE_LINES="reword 1 2" \ + FAKE_COMMIT_MESSAGE="Modified A" git rebase -i --root && + rm file1 && + FAKE_COMMIT_MESSAGE="Reworded A" git rebase --continue && + test "$(git log -1 --format=%B HEAD^)" = "Reworded A" && + test "$(git rev-list --count HEAD)" = 2 +' + test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-interactive rebase' ' + git checkout reword-root-branch && git reset --hard && git checkout conflict-branch && set_fake_editor && @@ -1192,7 +1246,7 @@ rebase_setup_and_clean () { test_might_fail git branch -D $1 && test_might_fail git rebase --abort " && - git checkout -b $1 master + git checkout -b $1 ${2:-master} } test_expect_success 'drop' ' @@ -1204,10 +1258,6 @@ test_expect_success 'drop' ' test A = $(git cat-file commit HEAD^^ | sed -ne \$p) ' -cat >expect <<EOF -Successfully rebased and updated refs/heads/missing-commit. -EOF - test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' ' test_config rebase.missingCommitsCheck ignore && rebase_setup_and_clean missing-commit && @@ -1215,7 +1265,9 @@ test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' ' FAKE_LINES="1 2 3 4" \ git rebase -i --root 2>actual && test D = $(git cat-file commit HEAD | sed -ne \$p) && - test_i18ncmp expect actual + test_i18ngrep \ + "Successfully rebased and updated refs/heads/missing-commit" \ + actual ' cat >expect <<EOF @@ -1227,15 +1279,24 @@ To avoid this message, use "drop" to explicitly remove a commit. Use 'git config rebase.missingCommitsCheck' to change the level of warnings. The possible behaviours are: ignore, warn, error. +Rebasing (1/4) +Rebasing (2/4) +Rebasing (3/4) +Rebasing (4/4) Successfully rebased and updated refs/heads/missing-commit. EOF +cr_to_nl () { + tr '\015' '\012' +} + test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' ' test_config rebase.missingCommitsCheck warn && rebase_setup_and_clean missing-commit && set_fake_editor && FAKE_LINES="1 2 3 4" \ - git rebase -i --root 2>actual && + git rebase -i --root 2>actual.2 && + cr_to_nl <actual.2 >actual && test_i18ncmp expect actual && test D = $(git cat-file commit HEAD | sed -ne \$p) ' @@ -1362,4 +1423,24 @@ test_expect_success 'rebase -i --gpg-sign=<key-id> overrides commit.gpgSign' ' test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err ' +test_expect_success 'valid author header after --root swap' ' + rebase_setup_and_clean author-header no-conflict-branch && + set_fake_editor && + git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit && + git cat-file commit HEAD | grep ^author >expected && + FAKE_LINES="5 1" git rebase -i --root && + git cat-file commit HEAD^ | grep ^author >actual && + test_cmp expected actual +' + +test_expect_success 'valid author header when author contains single quote' ' + rebase_setup_and_clean author-header no-conflict-branch && + set_fake_editor && + git commit --amend --author="Au ${SQ}thor <author@example.com>" --no-edit && + git cat-file commit HEAD | grep ^author >expected && + FAKE_LINES="2" git rebase -i HEAD~2 && + git cat-file commit HEAD | grep ^author >actual && + test_cmp expected actual +' + test_done diff --git a/t/t3405-rebase-malformed.sh b/t/t3405-rebase-malformed.sh index cb7c6de84a..da94dddc86 100755 --- a/t/t3405-rebase-malformed.sh +++ b/t/t3405-rebase-malformed.sh @@ -77,19 +77,14 @@ test_expect_success 'rebase commit with diff in message' ' ' test_expect_success 'rebase -m commit with empty message' ' - test_must_fail git rebase -m master empty-message-merge && - git rebase --abort && - git rebase -m --allow-empty-message master empty-message-merge + git rebase -m master empty-message-merge ' test_expect_success 'rebase -i commit with empty message' ' git checkout diff-in-message && set_fake_editor && - test_must_fail env FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \ - git rebase -i HEAD^ && - git rebase --abort && - FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \ - git rebase -i --allow-empty-message HEAD^ + env FAKE_COMMIT_MESSAGE=" " FAKE_LINES="reword 1" \ + git rebase -i HEAD^ ' test_done diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index e364c12622..13f5688135 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -330,4 +330,23 @@ test_expect_success 'wrapped original subject' ' test $base = $parent ' +test_expect_success 'abort last squash' ' + test_when_finished "test_might_fail git rebase --abort" && + test_when_finished "git checkout master" && + + git checkout -b some-squashes && + git commit --allow-empty -m first && + git commit --allow-empty --squash HEAD && + git commit --allow-empty -m second && + git commit --allow-empty --squash HEAD && + + test_must_fail git -c core.editor="grep -q ^pick" \ + rebase -ki --autosquash HEAD~4 && + : do not finish the squash, but resolve it manually && + git commit --allow-empty --amend -m edited-first && + git rebase --skip && + git show >actual && + ! grep first actual +' + test_done diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 9214d0bb51..25099d715c 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -60,7 +60,7 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' EOF chmod +x test-bin/git-merge-funny && ( - PATH=./test-bin:$PATH + PATH=./test-bin:$PATH && test_must_fail git rebase -s funny -Xopt master topic ) && test -f funny.was.run && @@ -68,7 +68,39 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' echo "Resolved" >F2 && git add F2 && ( - PATH=./test-bin:$PATH + PATH=./test-bin:$PATH && + git rebase --continue + ) && + test -f funny.was.run +' + +test_expect_success 'rebase -i --continue handles merge strategy and options' ' + rm -fr .git/rebase-* && + git reset --hard commit-new-file-F2-on-topic-branch && + test_commit "commit-new-file-F3-on-topic-branch-for-dash-i" F3 32 && + test_when_finished "rm -fr test-bin funny.was.run funny.args" && + mkdir test-bin && + cat >test-bin/git-merge-funny <<-EOF && + #!$SHELL_PATH + echo "\$@" >>funny.args + case "\$1" in --opt) ;; *) exit 2 ;; esac + case "\$2" in --foo) ;; *) exit 2 ;; esac + case "\$4" in --) ;; *) exit 2 ;; esac + shift 2 && + >funny.was.run && + exec git merge-recursive "\$@" + EOF + chmod +x test-bin/git-merge-funny && + ( + PATH=./test-bin:$PATH && + test_must_fail git rebase -i -s funny -Xopt -Xfoo master topic + ) && + test -f funny.was.run && + rm funny.was.run && + echo "Resolved" >F2 && + git add F2 && + ( + PATH=./test-bin:$PATH && git rebase --continue ) && test -f funny.was.run @@ -88,6 +120,57 @@ test_expect_success 'rebase passes merge strategy options correctly' ' git rebase --continue ' +test_expect_success '--skip after failed fixup cleans commit message' ' + test_when_finished "test_might_fail git rebase --abort" && + git checkout -b with-conflicting-fixup && + test_commit wants-fixup && + test_commit "fixup! wants-fixup" wants-fixup.t 1 wants-fixup-1 && + test_commit "fixup! wants-fixup" wants-fixup.t 2 wants-fixup-2 && + test_commit "fixup! wants-fixup" wants-fixup.t 3 wants-fixup-3 && + test_must_fail env FAKE_LINES="1 fixup 2 squash 4" \ + git rebase -i HEAD~4 && + + : now there is a conflict, and comments in the commit message && + git show HEAD >out && + grep "fixup! wants-fixup" out && + + : skip and continue && + echo "cp \"\$1\" .git/copy.txt" | write_script copy-editor.sh && + (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) && + + : the user should not have had to edit the commit message && + test_path_is_missing .git/copy.txt && + + : now the comments in the commit message should have been cleaned up && + git show HEAD >out && + ! grep "fixup! wants-fixup" out && + + : now, let us ensure that "squash" is handled correctly && + git reset --hard wants-fixup-3 && + test_must_fail env FAKE_LINES="1 squash 4 squash 2 squash 4" \ + git rebase -i HEAD~4 && + + : the first squash failed, but there are two more in the chain && + (test_set_editor "$PWD/copy-editor.sh" && + test_must_fail git rebase --skip) && + + : not the final squash, no need to edit the commit message && + test_path_is_missing .git/copy.txt && + + : The first squash was skipped, therefore: && + git show HEAD >out && + test_i18ngrep "# This is a combination of 2 commits" out && + test_i18ngrep "# This is the commit message #2:" out && + + (test_set_editor "$PWD/copy-editor.sh" && git rebase --skip) && + git show HEAD >out && + test_i18ngrep ! "# This is a combination" out && + + : Final squash failed, but there was still a squash && + test_i18ngrep "# This is a combination of 2 commits" .git/copy.txt && + test_i18ngrep "# This is the commit message #2:" .git/copy.txt +' + test_expect_success 'setup rerere database' ' rm -fr .git/rebase-* && git reset --hard commit-new-file-F3-on-topic-branch && diff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh index e243700660..0c4eefec76 100755 --- a/t/t3420-rebase-autostash.sh +++ b/t/t3420-rebase-autostash.sh @@ -202,7 +202,7 @@ testrebase () { echo dirty >>file3 && test_must_fail git rebase$type related-onto-branch && test_path_is_file $dotest/autostash && - ! grep dirty file3 && + test_path_is_missing file3 && rm -rf $dotest && git reset --hard && git checkout feature-branch @@ -216,7 +216,7 @@ testrebase () { echo dirty >>file3 && test_must_fail git rebase$type related-onto-branch && test_path_is_file $dotest/autostash && - ! grep dirty file3 && + test_path_is_missing file3 && echo "conflicting-plus-goodbye" >file2 && git add file2 && git rebase --continue && @@ -233,7 +233,7 @@ testrebase () { echo dirty >>file3 && test_must_fail git rebase$type related-onto-branch && test_path_is_file $dotest/autostash && - ! grep dirty file3 && + test_path_is_missing file3 && git rebase --skip && test_path_is_missing $dotest/autostash && grep dirty file3 && @@ -248,7 +248,7 @@ testrebase () { echo dirty >>file3 && test_must_fail git rebase$type related-onto-branch && test_path_is_file $dotest/autostash && - ! grep dirty file3 && + test_path_is_missing file3 && git rebase --abort && test_path_is_missing $dotest/autostash && grep dirty file3 && diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index 68fe2003ef..99b2aac921 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -199,7 +199,7 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m +test_run_rebase success -m test_run_rebase success -i test_run_rebase failure -p @@ -214,9 +214,10 @@ test_run_rebase () { " } test_run_rebase success '' -test_run_rebase failure -m -test_run_rebase failure -i +test_run_rebase success -m +test_run_rebase success -i test_run_rebase failure -p +test_run_rebase success --rebase-merges # m # / @@ -327,9 +328,9 @@ test_run_rebase () { test_cmp_rev c HEAD " } -test_run_rebase failure '' -test_run_rebase failure -m -test_run_rebase failure -i +test_run_rebase success '' +test_run_rebase success -m +test_run_rebase success -i test_run_rebase failure -p test_run_rebase () { diff --git a/t/t3422-rebase-incompatible-options.sh b/t/t3422-rebase-incompatible-options.sh new file mode 100755 index 0000000000..bb78a6ec86 --- /dev/null +++ b/t/t3422-rebase-incompatible-options.sh @@ -0,0 +1,88 @@ +#!/bin/sh + +test_description='test if rebase detects and aborts on incompatible options' +. ./test-lib.sh + +test_expect_success 'setup' ' + test_seq 2 9 >foo && + git add foo && + git commit -m orig && + + git branch A && + git branch B && + + git checkout A && + test_seq 1 9 >foo && + git add foo && + git commit -m A && + + git checkout B && + echo "q qfoo();" | q_to_tab >>foo && + git add foo && + git commit -m B +' + +# +# Rebase has lots of useful options like --whitepsace=fix, which are +# actually all built in terms of flags to git-am. Since neither +# --merge nor --interactive (nor any options that imply those two) use +# git-am, using them together will result in flags like --whitespace=fix +# being ignored. Make sure rebase warns the user and aborts instead. +# + +test_rebase_am_only () { + opt=$1 + shift + test_expect_success "$opt incompatible with --merge" " + git checkout B^0 && + test_must_fail git rebase $opt --merge A + " + + test_expect_success "$opt incompatible with --strategy=ours" " + git checkout B^0 && + test_must_fail git rebase $opt --strategy=ours A + " + + test_expect_success "$opt incompatible with --strategy-option=ours" " + git checkout B^0 && + test_must_fail git rebase $opt --strategy-option=ours A + " + + test_expect_success "$opt incompatible with --interactive" " + git checkout B^0 && + test_must_fail git rebase $opt --interactive A + " + + test_expect_success "$opt incompatible with --exec" " + git checkout B^0 && + test_must_fail git rebase $opt --exec 'true' A + " + +} + +test_rebase_am_only --whitespace=fix +test_rebase_am_only --ignore-whitespace +test_rebase_am_only --committer-date-is-author-date +test_rebase_am_only -C4 + +test_expect_success '--preserve-merges incompatible with --signoff' ' + git checkout B^0 && + test_must_fail git rebase --preserve-merges --signoff A +' + +test_expect_success '--preserve-merges incompatible with --rebase-merges' ' + git checkout B^0 && + test_must_fail git rebase --preserve-merges --rebase-merges A +' + +test_expect_success '--rebase-merges incompatible with --strategy' ' + git checkout B^0 && + test_must_fail git rebase --rebase-merges -s resolve A +' + +test_expect_success '--rebase-merges incompatible with --strategy-option' ' + git checkout B^0 && + test_must_fail git rebase --rebase-merges -Xignore-space-change A +' + +test_done diff --git a/t/t3423-rebase-reword.sh b/t/t3423-rebase-reword.sh new file mode 100755 index 0000000000..6963750794 --- /dev/null +++ b/t/t3423-rebase-reword.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +test_description='git rebase interactive with rewording' + +. ./test-lib.sh + +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_expect_success 'setup' ' + test_commit master file-1 test && + + git checkout -b stuff && + + test_commit feature_a file-2 aaa && + test_commit feature_b file-2 ddd +' + +test_expect_success 'reword without issues functions as intended' ' + test_when_finished "reset_rebase" && + + git checkout stuff^0 && + + set_fake_editor && + FAKE_LINES="pick 1 reword 2" FAKE_COMMIT_MESSAGE="feature_b_reworded" \ + git rebase -i -v master && + + test "$(git log -1 --format=%B)" = "feature_b_reworded" && + test $(git rev-list --count HEAD) = 3 +' + +test_expect_success 'reword after a conflict preserves commit' ' + test_when_finished "reset_rebase" && + + git checkout stuff^0 && + + set_fake_editor && + test_must_fail env FAKE_LINES="reword 2" \ + git rebase -i -v master && + + git checkout --theirs file-2 && + git add file-2 && + FAKE_COMMIT_MESSAGE="feature_b_reworded" git rebase --continue && + + test "$(git log -1 --format=%B)" = "feature_b_reworded" && + test $(git rev-list --count HEAD) = 2 +' + +test_done diff --git a/t/t3428-rebase-signoff.sh b/t/t3428-rebase-signoff.sh index 2afb564701..f6993b7e14 100755 --- a/t/t3428-rebase-signoff.sh +++ b/t/t3428-rebase-signoff.sh @@ -12,6 +12,13 @@ cat >file <<EOF a EOF +# 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 + # Expected commit message after rebase --signoff cat >expected-signed <<EOF first @@ -43,4 +50,35 @@ test_expect_success 'rebase --no-signoff does not add a sign-off line' ' test_cmp expected-unsigned actual ' +test_expect_success 'rebase --exec --signoff adds a sign-off line' ' + test_when_finished "rm exec" && + 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_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_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_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_done diff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh new file mode 100755 index 0000000000..aa7bfc88ec --- /dev/null +++ b/t/t3430-rebase-merges.sh @@ -0,0 +1,399 @@ +#!/bin/sh +# +# Copyright (c) 2018 Johannes E. Schindelin +# + +test_description='git rebase -i --rebase-merges + +This test runs git rebase "interactively", retaining the branch structure by +recreating merge commits. + +Initial setup: + + -- B -- (first) + / \ + A - C - D - E - H (master) + \ \ / + \ F - G (second) + \ + Conflicting-G +' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-rebase.sh + +test_cmp_graph () { + cat >expect && + git log --graph --boundary --format=%s "$@" >output && + sed "s/ *$//" <output >output.trimmed && + test_cmp expect output.trimmed +} + +test_expect_success 'setup' ' + write_script replace-editor.sh <<-\EOF && + mv "$1" "$(git rev-parse --git-path ORIGINAL-TODO)" + cp script-from-scratch "$1" + EOF + + test_commit A && + git checkout -b first && + test_commit B && + git checkout master && + test_commit C && + test_commit D && + git merge --no-commit B && + test_tick && + git commit -m E && + git tag -m E E && + git checkout -b second C && + test_commit F && + test_commit G && + git checkout master && + git merge --no-commit G && + test_tick && + git commit -m H && + git tag -m H H && + git checkout A && + test_commit conflicting-G G.t +' + +test_expect_success 'create completely different structure' ' + cat >script-from-scratch <<-\EOF && + label onto + + # onebranch + pick G + pick D + label onebranch + + # second + reset onto + pick B + label second + + reset onto + merge -C H second + merge onebranch # Merge the topic branch '\''onebranch'\'' + EOF + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + git rebase -i -r A master && + test_cmp_graph <<-\EOF + * Merge the topic branch '\''onebranch'\'' + |\ + | * D + | * G + * | H + |\ \ + | |/ + |/| + | * B + |/ + * A + EOF +' + +test_expect_success 'generate correct todo list' ' + cat >expect <<-\EOF && + label onto + + reset onto + pick d9df450 B + label E + + reset onto + pick 5dee784 C + label branch-point + pick ca2c861 F + pick 088b00a G + label H + + reset branch-point # C + pick 12bd07b D + merge -C 2051b56 E # E + merge -C 233d48a H # H + + EOF + + grep -v "^#" <.git/ORIGINAL-TODO >output && + test_cmp expect output +' + +test_expect_success '`reset` refuses to overwrite untracked files' ' + git checkout -b refuse-to-reset && + test_commit dont-overwrite-untracked && + git checkout @{-1} && + : >dont-overwrite-untracked.t && + echo "reset refs/tags/dont-overwrite-untracked" >script-from-scratch && + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_must_fail git rebase -r HEAD && + git rebase --abort +' + +test_expect_success 'failed `merge -C` writes patch (may be rescheduled, too)' ' + test_when_finished "test_might_fail git rebase --abort" && + git checkout -b conflicting-merge A && + + : fail because of conflicting untracked file && + >G.t && + echo "merge -C H G" >script-from-scratch && + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + test_must_fail git rebase -ir HEAD && + grep "^merge -C .* G$" .git/rebase-merge/done && + grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && + test_path_is_file .git/rebase-merge/patch && + + : fail because of merge conflict && + rm G.t .git/rebase-merge/patch && + git reset --hard conflicting-G && + test_must_fail git rebase --continue && + ! grep "^merge -C .* G$" .git/rebase-merge/git-rebase-todo && + test_path_is_file .git/rebase-merge/patch +' + +SQ="'" +test_expect_success 'failed `merge <branch>` does not crash' ' + test_when_finished "test_might_fail git rebase --abort" && + git checkout conflicting-G && + + echo "merge G" >script-from-scratch && + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + test_must_fail git rebase -ir HEAD && + ! grep "^merge G$" .git/rebase-merge/git-rebase-todo && + grep "^Merge branch ${SQ}G${SQ}$" .git/rebase-merge/message +' + +test_expect_success 'with a branch tip that was cherry-picked already' ' + git checkout -b already-upstream master && + base="$(git rev-parse --verify HEAD)" && + + test_commit A1 && + test_commit A2 && + git reset --hard $base && + test_commit B1 && + test_tick && + git merge -m "Merge branch A" A2 && + + git checkout -b upstream-with-a2 $base && + test_tick && + git cherry-pick A2 && + + git checkout already-upstream && + test_tick && + git rebase -i -r upstream-with-a2 && + test_cmp_graph upstream-with-a2.. <<-\EOF + * Merge branch A + |\ + | * A1 + * | B1 + |/ + o A2 + EOF +' + +test_expect_success 'do not rebase cousins unless asked for' ' + git checkout -b cousins master && + before="$(git rev-parse --verify HEAD)" && + test_tick && + git rebase -r HEAD^ && + test_cmp_rev HEAD $before && + test_tick && + git rebase --rebase-merges=rebase-cousins HEAD^ && + test_cmp_graph HEAD^.. <<-\EOF + * Merge the topic branch '\''onebranch'\'' + |\ + | * D + | * G + |/ + o H + EOF +' + +test_expect_success 'refs/rewritten/* is worktree-local' ' + git worktree add wt && + cat >wt/script-from-scratch <<-\EOF && + label xyz + exec GIT_DIR=../.git git rev-parse --verify refs/rewritten/xyz >a || : + exec git rev-parse --verify refs/rewritten/xyz >b + EOF + + test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" && + git -C wt rebase -i HEAD && + test_must_be_empty wt/a && + test_cmp_rev HEAD "$(cat wt/b)" +' + +test_expect_success 'post-rewrite hook and fixups work for merges' ' + git checkout -b post-rewrite && + test_commit same1 && + git reset --hard HEAD^ && + test_commit same2 && + git merge -m "to fix up" same1 && + echo same old same old >same2.t && + test_tick && + git commit --fixup HEAD same2.t && + fixup="$(git rev-parse HEAD)" && + + mkdir -p .git/hooks && + test_when_finished "rm .git/hooks/post-rewrite" && + echo "cat >actual" | write_script .git/hooks/post-rewrite && + + test_tick && + git rebase -i --autosquash -r HEAD^^^ && + printf "%s %s\n%s %s\n%s %s\n%s %s\n" >expect $(git rev-parse \ + $fixup^^2 HEAD^2 \ + $fixup^^ HEAD^ \ + $fixup^ HEAD \ + $fixup HEAD) && + test_cmp expect actual +' + +test_expect_success 'refuse to merge ancestors of HEAD' ' + echo "merge HEAD^" >script-from-scratch && + test_config -C wt sequence.editor \""$PWD"/replace-editor.sh\" && + before="$(git rev-parse HEAD)" && + git rebase -i HEAD && + test_cmp_rev HEAD $before +' + +test_expect_success 'root commits' ' + git checkout --orphan unrelated && + (GIT_AUTHOR_NAME="Parsnip" GIT_AUTHOR_EMAIL="root@example.com" \ + test_commit second-root) && + test_commit third-root && + cat >script-from-scratch <<-\EOF && + pick third-root + label first-branch + reset [new root] + pick second-root + merge first-branch # Merge the 3rd root + EOF + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + git rebase -i --force --root -r && + test "Parsnip" = "$(git show -s --format=%an HEAD^)" && + test $(git rev-parse second-root^0) != $(git rev-parse HEAD^) && + test $(git rev-parse second-root:second-root.t) = \ + $(git rev-parse HEAD^:second-root.t) && + test_cmp_graph HEAD <<-\EOF && + * Merge the 3rd root + |\ + | * third-root + * second-root + EOF + + : fast forward if possible && + before="$(git rev-parse --verify HEAD)" && + test_might_fail git config --unset sequence.editor && + test_tick && + git rebase -i --root -r && + test_cmp_rev HEAD $before +' + +test_expect_success 'a "merge" into a root commit is a fast-forward' ' + head=$(git rev-parse HEAD) && + cat >script-from-scratch <<-EOF && + reset [new root] + merge $head + EOF + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + git rebase -i -r HEAD^ && + test_cmp_rev HEAD $head +' + +test_expect_success 'A root commit can be a cousin, treat it that way' ' + git checkout --orphan khnum && + test_commit yama && + git checkout -b asherah master && + test_commit shamkat && + git merge --allow-unrelated-histories khnum && + test_tick && + git rebase -f -r HEAD^ && + ! test_cmp_rev HEAD^2 khnum && + test_cmp_graph HEAD^.. <<-\EOF && + * Merge branch '\''khnum'\'' into asherah + |\ + | * yama + o shamkat + EOF + test_tick && + git rebase --rebase-merges=rebase-cousins HEAD^ && + test_cmp_graph HEAD^.. <<-\EOF + * Merge branch '\''khnum'\'' into asherah + |\ + | * yama + |/ + o shamkat + EOF +' + +test_expect_success 'labels that are object IDs are rewritten' ' + git checkout -b third B && + test_commit I && + third=$(git rev-parse HEAD) && + git checkout -b labels master && + git merge --no-commit third && + test_tick && + git commit -m "Merge commit '\''$third'\'' into labels" && + echo noop >script-from-scratch && + test_config sequence.editor \""$PWD"/replace-editor.sh\" && + test_tick && + git rebase -i -r A && + grep "^label $third-" .git/ORIGINAL-TODO && + ! grep "^label $third$" .git/ORIGINAL-TODO +' + +test_expect_success 'octopus merges' ' + git checkout -b three && + test_commit before-octopus && + test_commit three && + git checkout -b two HEAD^ && + test_commit two && + git checkout -b one HEAD^ && + test_commit one && + test_tick && + (GIT_AUTHOR_NAME="Hank" GIT_AUTHOR_EMAIL="hank@sea.world" \ + git merge -m "Tüntenfüsch" two three) && + + : fast forward if possible && + before="$(git rev-parse --verify HEAD)" && + test_tick && + git rebase -i -r HEAD^^ && + test_cmp_rev HEAD $before && + + test_tick && + git rebase -i --force -r HEAD^^ && + test "Hank" = "$(git show -s --format=%an HEAD)" && + test "$before" != $(git rev-parse HEAD) && + test_cmp_graph HEAD^^.. <<-\EOF + *-. Tüntenfüsch + |\ \ + | | * three + | * | two + | |/ + * | one + |/ + o before-octopus + EOF +' + +test_expect_success 'with --autosquash and --exec' ' + git checkout -b with-exec H && + echo Booh >B.t && + test_tick && + git commit --fixup B B.t && + write_script show.sh <<-\EOF && + subject="$(git show -s --format=%s HEAD)" + content="$(git diff HEAD^! | tail -n 1)" + echo "$subject: $content" + EOF + test_tick && + git rebase -ir --autosquash --exec ./show.sh A >actual && + grep "B: +Booh" actual && + grep "E: +Booh" actual && + grep "G: +G" actual +' + +test_done diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index ccbc118514..d1c68af8c5 100755 --- a/t/t3501-revert-cherry-pick.sh +++ b/t/t3501-revert-cherry-pick.sh @@ -141,7 +141,7 @@ test_expect_success 'cherry-pick "-" works with arguments' ' test_cmp expect actual ' -test_expect_failure 'cherry-pick works with dirty renamed file' ' +test_expect_success 'cherry-pick works with dirty renamed file' ' test_commit to-rename && git checkout -b unrelated && test_commit unrelated && @@ -150,9 +150,8 @@ test_expect_failure 'cherry-pick works with dirty renamed file' ' test_tick && git commit -m renamed && echo modified >renamed && - test_must_fail git cherry-pick refs/heads/unrelated >out && - test_i18ngrep "Refusing to lose dirty file at renamed" out && - test $(git rev-parse :0:renamed) = $(git rev-parse HEAD^:to-rename.t) && + git cherry-pick refs/heads/unrelated >out && + test $(git rev-parse :0:renamed) = $(git rev-parse HEAD~2:to-rename.t) && grep -q "^modified$" renamed ' diff --git a/t/t3507-cherry-pick-conflict.sh b/t/t3507-cherry-pick-conflict.sh index 7c5ad08626..0db166152a 100755 --- a/t/t3507-cherry-pick-conflict.sh +++ b/t/t3507-cherry-pick-conflict.sh @@ -392,4 +392,17 @@ test_expect_success 'commit --amend -s places the sign-off at the right place' ' test_cmp expect actual ' +test_expect_success 'cherry-pick preserves sparse-checkout' ' + pristine_detach initial && + test_config core.sparseCheckout true && + test_when_finished " + echo \"/*\" >.git/info/sparse-checkout + git read-tree --reset -u HEAD + rm .git/info/sparse-checkout" && + echo /unrelated >.git/info/sparse-checkout && + git read-tree --reset -u HEAD && + test_must_fail git cherry-pick -Xours picked>actual && + test_i18ngrep ! "Changes not staged for commit:" actual +' + test_done diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 9f93445f1e..c84eeefdc9 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -103,7 +103,8 @@ test_expect_success '--quit cleans up sequencer state' ' pristine_detach initial && test_expect_code 1 git cherry-pick base..picked && git cherry-pick --quit && - test_path_is_missing .git/sequencer + test_path_is_missing .git/sequencer && + test_path_is_missing .git/CHERRY_PICK_HEAD ' test_expect_success '--quit keeps HEAD and conflicted index intact' ' @@ -122,7 +123,7 @@ test_expect_success '--quit keeps HEAD and conflicted index intact' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && test_cmp expect actual ' @@ -132,6 +133,7 @@ test_expect_success '--abort to cancel multiple cherry-pick' ' test_expect_code 1 git cherry-pick base..anotherpick && git cherry-pick --abort && test_path_is_missing .git/sequencer && + test_path_is_missing .git/CHERRY_PICK_HEAD && test_cmp_rev initial HEAD && git update-index --refresh && git diff-index --exit-code HEAD @@ -142,6 +144,7 @@ test_expect_success '--abort to cancel single cherry-pick' ' test_expect_code 1 git cherry-pick picked && git cherry-pick --abort && test_path_is_missing .git/sequencer && + test_path_is_missing .git/CHERRY_PICK_HEAD && test_cmp_rev initial HEAD && git update-index --refresh && git diff-index --exit-code HEAD @@ -162,6 +165,7 @@ test_expect_success 'cherry-pick --abort to cancel multiple revert' ' test_expect_code 1 git revert base..picked && git cherry-pick --abort && test_path_is_missing .git/sequencer && + test_path_is_missing .git/CHERRY_PICK_HEAD && test_cmp_rev anotherpick HEAD && git update-index --refresh && git diff-index --exit-code HEAD @@ -220,7 +224,7 @@ test_expect_success 'cherry-pick still writes sequencer state when one commit is { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && cat >expect <<-\EOF && OBJID @@ -239,6 +243,7 @@ test_expect_success '--abort after last commit in sequence' ' test_expect_code 1 git cherry-pick base..picked && git cherry-pick --abort && test_path_is_missing .git/sequencer && + test_path_is_missing .git/CHERRY_PICK_HEAD && test_cmp_rev initial HEAD && git update-index --refresh && git diff-index --exit-code HEAD @@ -247,9 +252,9 @@ test_expect_success '--abort after last commit in sequence' ' test_expect_success 'cherry-pick does not implicitly stomp an existing operation' ' pristine_detach initial && test_expect_code 1 git cherry-pick base..anotherpick && - test-tool chmtime -v +0 .git/sequencer >expect && + test-tool chmtime --get .git/sequencer >expect && test_expect_code 128 git cherry-pick unrelatedpick && - test-tool chmtime -v +0 .git/sequencer >actual && + test-tool chmtime --get .git/sequencer >actual && test_cmp expect actual ' @@ -317,7 +322,7 @@ test_expect_success '--continue after resolving conflicts' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual.log && test_cmp expect foo && test_cmp expect.log actual.log @@ -334,7 +339,7 @@ test_expect_success '--continue after resolving conflicts and committing' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && cat >expect <<-\EOF && OBJID @@ -480,11 +485,16 @@ test_expect_success 'malformed instruction sheet 2' ' test_expect_code 128 git cherry-pick --continue ' -test_expect_success 'empty commit set' ' +test_expect_success 'empty commit set (no commits to walk)' ' pristine_detach initial && test_expect_code 128 git cherry-pick base..base ' +test_expect_success 'empty commit set (culled during walk)' ' + pristine_detach initial && + test_expect_code 128 git cherry-pick -2 --author=no.such.author base +' + test_expect_success 'malformed instruction sheet 3' ' pristine_detach initial && test_expect_code 1 git cherry-pick base..anotherpick && diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index b8fbdefcdc..04e5d42bd3 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -14,15 +14,13 @@ test_expect_success \ git add -- foo bar baz 'space embedded' -q && git commit -m 'add normal files'" -if test_have_prereq !MINGW && touch -- 'tab embedded' 'newline -embedded' 2>/dev/null -then - test_set_prereq FUNNYNAMES -else +if test_have_prereq !FUNNYNAMES; then say 'Your filesystem does not allow tabs in filenames.' fi test_expect_success FUNNYNAMES 'add files with funny names' " + touch -- 'tab embedded' 'newline +embedded' && git add -- 'tab embedded' 'newline embedded' && git commit -m 'add files with tabs and newlines' @@ -382,7 +380,7 @@ test_expect_success 'rm does not complain when no .gitmodules file is found' ' git submodule update && git rm .gitmodules && git rm submod >actual 2>actual.err && - ! test -s actual.err && + test_must_be_empty actual.err && ! test -d submod && ! test -f submod/.git && git status -s -uno >actual && @@ -400,7 +398,7 @@ test_expect_success 'rm will error out on a modified .gitmodules file unless sta git diff-files --quiet -- submod && git add .gitmodules && git rm submod >actual 2>actual.err && - ! test -s actual.err && + test_must_be_empty actual.err && ! test -d submod && ! test -f submod/.git && git status -s -uno >actual && @@ -694,7 +692,7 @@ test_expect_success 'checking out a commit after submodule removal needs manual test_cmp expected actual && rm -rf submod && git status -s -uno --ignore-submodules=none >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'rm of d/f when d has become a non-directory' ' diff --git a/t/t3700-add.sh b/t/t3700-add.sh index 07af05d7ae..37729ba258 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -156,9 +156,9 @@ test_expect_success 'git add with filemode=0, symlinks=0, and unmerged entries' test_expect_success 'git add with filemode=0, symlinks=0 prefers stage 2 over stage 1' ' git rm --cached -f file symlink && ( - echo "100644 $(git hash-object -w stage1) 1 file" - echo "100755 $(git hash-object -w stage2) 2 file" - echo "100644 $(printf 1 | git hash-object -w -t blob --stdin) 1 symlink" + echo "100644 $(git hash-object -w stage1) 1 file" && + echo "100755 $(git hash-object -w stage2) 2 file" && + echo "100644 $(printf 1 | git hash-object -w -t blob --stdin) 1 symlink" && echo "120000 $(printf 2 | git hash-object -w -t blob --stdin) 2 symlink" ) | git update-index --index-info && git config core.filemode 0 && @@ -188,9 +188,8 @@ test_expect_success 'git add --refresh with pathspec' ' git add foo bar baz && H=$(git rev-parse :foo) && git rm -f foo && echo "100644 $H 3 foo" | git update-index --index-info && test-tool chmtime -60 bar baz && - >expect && git add --refresh bar >actual && - test_cmp expect actual && + test_must_be_empty actual && git diff-files --name-only >actual && ! grep bar actual&& @@ -265,7 +264,7 @@ test_expect_success 'git add to resolve conflicts on otherwise ignored path' ' git reset --hard && H=$(git rev-parse :1/2/a) && ( - echo "100644 $H 1 track-this" + echo "100644 $H 1 track-this" && echo "100644 $H 3 track-this" ) | git update-index --index-info && echo track-this >>.gitignore && diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index b170fb02b8..609fbfdc31 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -46,13 +46,13 @@ test_expect_success 'setup expected' ' ' test_expect_success 'diff works (initial)' ' - (echo d; echo 1) | git add -i >output && + test_write_lines d 1 | git add -i >output && sed -ne "/new file/,/content/p" <output >diff && diff_cmp expected diff ' test_expect_success 'revert works (initial)' ' git add file && - (echo r; echo 1) | git add -i && + test_write_lines r 1 | git add -i && git ls-files >output && ! grep . output ' @@ -83,13 +83,13 @@ test_expect_success 'setup expected' ' ' test_expect_success 'diff works (commit)' ' - (echo d; echo 1) | git add -i >output && + test_write_lines d 1 | git add -i >output && sed -ne "/^index/,/content/p" <output >diff && diff_cmp expected diff ' test_expect_success 'revert works (commit)' ' git add file && - (echo r; echo 1) | git add -i && + test_write_lines r 1 | git add -i && git add -i </dev/null >output && grep "unchanged *+3/-0 file" output ' @@ -102,7 +102,7 @@ test_expect_success 'setup expected' ' test_expect_success 'dummy edit works' ' test_set_editor : && - (echo e; echo a) | git add -p && + test_write_lines e a | git add -p && git diff > diff && diff_cmp expected diff ' @@ -127,7 +127,7 @@ test_expect_success 'setup fake editor' ' test_expect_success 'bad edit rejected' ' git reset && - (echo e; echo n; echo d) | git add -p >output && + test_write_lines e n d | git add -p >output && grep "hunk does not apply" output ' @@ -140,7 +140,7 @@ test_expect_success 'setup patch' ' test_expect_success 'garbage edit rejected' ' git reset && - (echo e; echo n; echo d) | git add -p >output && + test_write_lines e n d | git add -p >output && grep "hunk does not apply" output ' @@ -170,7 +170,50 @@ test_expect_success 'setup expected' ' ' test_expect_success 'real edit works' ' - (echo e; echo n; echo d) | git add -p && + test_write_lines e n d | git add -p && + git diff >output && + diff_cmp expected output +' + +test_expect_success 'setup file' ' + test_write_lines a "" b "" c >file && + git add file && + test_write_lines a "" d "" c >file +' + +test_expect_success 'setup patch' ' + SP=" " && + NULL="" && + cat >patch <<-EOF + @@ -1,4 +1,4 @@ + a + $NULL + -b + +f + $SP + c + EOF +' + +test_expect_success 'setup expected' ' + cat >expected <<-EOF + diff --git a/file b/file + index b5dd6c9..f910ae9 100644 + --- a/file + +++ b/file + @@ -1,5 +1,5 @@ + a + $SP + -f + +d + $SP + c + EOF +' + +test_expect_success 'edit can strip spaces from empty context lines' ' + test_write_lines e n q | git add -p 2>error && + test_must_be_empty error && git diff >output && diff_cmp expected output ' diff --git a/t/t3702-add-edit.sh b/t/t3702-add-edit.sh index 3cb74ca296..c6af7f82b5 100755 --- a/t/t3702-add-edit.sh +++ b/t/t3702-add-edit.sh @@ -40,7 +40,6 @@ test_expect_success 'setup' ' cat > expected-patch << EOF diff --git a/file b/file -index b9834b5..9020acb 100644 --- a/file +++ b/file @@ -1,11 +1,6 @@ @@ -80,7 +79,6 @@ EOF cat > expected << EOF diff --git a/file b/file -index b9834b5..ef6e94c 100644 --- a/file +++ b/file @@ -1,10 +1,12 @@ @@ -100,7 +98,7 @@ EOF echo "#!$SHELL_PATH" >fake-editor.sh cat >> fake-editor.sh <<\EOF -mv -f "$1" orig-patch && +egrep -v '^index' "$1" >orig-patch && mv -f patch "$1" EOF @@ -113,7 +111,8 @@ test_expect_success 'add -e' ' git add -e && test_cmp second-part file && test_cmp orig-patch expected-patch && - git diff --cached > out && + git diff --cached >actual && + grep -v index actual >out && test_cmp out expected ' diff --git a/t/t3903-stash.sh b/t/t3903-stash.sh index aefde7b172..6450bc6698 100755 --- a/t/t3903-stash.sh +++ b/t/t3903-stash.sh @@ -724,9 +724,9 @@ test_expect_success 'store updates stash ref and reflog' ' git add bazzy && STASH_ID=$(git stash create) && git reset --hard && - ! grep quux bazzy && + test_path_is_missing bazzy && git stash store -m quuxery $STASH_ID && - test $(cat .git/refs/stash) = $STASH_ID && + test $(git rev-parse stash) = $STASH_ID && git reflog --format=%H stash| grep $STASH_ID && git stash pop && grep quux bazzy diff --git a/t/t3904-stash-patch.sh b/t/t3904-stash-patch.sh index 83744f8c93..9546b6f8a4 100755 --- a/t/t3904-stash-patch.sh +++ b/t/t3904-stash-patch.sh @@ -29,14 +29,14 @@ test_expect_success 'setup' ' test_expect_success 'saying "n" does nothing' ' set_state HEAD HEADfile_work HEADfile_index && set_state dir/foo work index && - (echo n; echo n; echo n) | test_must_fail git stash save -p && + test_write_lines n n n | test_must_fail git stash save -p && verify_state HEAD HEADfile_work HEADfile_index && verify_saved_state bar && verify_state dir/foo work index ' test_expect_success 'git stash -p' ' - (echo y; echo n; echo y) | git stash save -p && + test_write_lines y n y | git stash save -p && verify_state HEAD committed HEADfile_index && verify_saved_state bar && verify_state dir/foo head index && @@ -51,7 +51,7 @@ test_expect_success 'git stash -p --no-keep-index' ' set_state HEAD HEADfile_work HEADfile_index && set_state bar bar_work bar_index && set_state dir/foo work index && - (echo y; echo n; echo y) | git stash save -p --no-keep-index && + test_write_lines y n y | git stash save -p --no-keep-index && verify_state HEAD committed committed && verify_state bar bar_work dummy && verify_state dir/foo head head && @@ -66,7 +66,7 @@ test_expect_success 'git stash --no-keep-index -p' ' set_state HEAD HEADfile_work HEADfile_index && set_state bar bar_work bar_index && set_state dir/foo work index && - (echo y; echo n; echo y) | git stash save --no-keep-index -p && + test_write_lines y n y | git stash save --no-keep-index -p && verify_state HEAD committed committed && verify_state dir/foo head head && verify_state bar bar_work dummy && diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index 3ea5b9bb3f..597b0637d1 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -35,24 +35,26 @@ test_expect_success 'stash save --include-untracked cleaned the untracked files' test_cmp expect actual ' +tracked=$(git rev-parse --short $(echo 1 | git hash-object --stdin)) +untracked=$(git rev-parse --short $(echo untracked | git hash-object --stdin)) cat > expect.diff <<EOF diff --git a/HEAD b/HEAD new file mode 100644 -index 0000000..d00491f +index 0000000..$tracked --- /dev/null +++ b/HEAD @@ -0,0 +1 @@ +1 diff --git a/file2 b/file2 new file mode 100644 -index 0000000..d00491f +index 0000000..$tracked --- /dev/null +++ b/file2 @@ -0,0 +1 @@ +1 diff --git a/untracked/untracked b/untracked/untracked new file mode 100644 -index 0000000..5a72eb2 +index 0000000..$untracked --- /dev/null +++ b/untracked/untracked @@ -0,0 +1 @@ @@ -109,10 +111,11 @@ test_expect_success 'stash save -u dirty index' ' git stash -u ' +blob=$(git rev-parse --short $(echo 4 | git hash-object --stdin)) cat > expect <<EOF diff --git a/file3 b/file3 new file mode 100644 -index 0000000..b8626c4 +index 0000000..$blob --- /dev/null +++ b/file3 @@ -0,0 +1 @@ diff --git a/t/t3910-mac-os-precompose.sh b/t/t3910-mac-os-precompose.sh index 26dd5b7f78..54ce19e353 100755 --- a/t/t3910-mac-os-precompose.sh +++ b/t/t3910-mac-os-precompose.sh @@ -187,9 +187,8 @@ test_expect_failure 'handle existing decomposed filenames' ' echo content >"verbatim.$Adiarnfd" && git -c core.precomposeunicode=false add "verbatim.$Adiarnfd" && git commit -m "existing decomposed file" && - >expect && git ls-files --exclude-standard -o "verbatim*" >untracked && - test_cmp expect untracked + test_must_be_empty untracked ' # Test if the global core.precomposeunicode stops autosensing diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh index a07816d560..c16486a9d4 100755 --- a/t/t4001-diff-rename.sh +++ b/t/t4001-diff-rename.sh @@ -138,6 +138,18 @@ test_expect_success 'favour same basenames over different ones' ' test_i18ngrep "renamed: .*path1 -> subdir/path1" out ' +test_expect_success 'test diff.renames=true for git status' ' + git -c diff.renames=true status >out && + test_i18ngrep "renamed: .*path1 -> subdir/path1" out +' + +test_expect_success 'test diff.renames=false for git status' ' + git -c diff.renames=false status >out && + test_i18ngrep ! "renamed: .*path1 -> subdir/path1" out && + test_i18ngrep "new file: .*subdir/path1" out && + test_i18ngrep "deleted: .*[^/]path1" out +' + test_expect_success 'favour same basenames even with minor differences' ' git show HEAD:path1 | sed "s/15/16/" > subdir/path1 && git status >out && @@ -168,7 +180,7 @@ test_expect_success 'setup for many rename source candidates' ' git add "path??" && test_tick && git commit -m "hundred" && - (cat path1; echo new) >new-path && + (cat path1 && echo new) >new-path && echo old >>path1 && git add new-path path1 && git diff -l 4 -C -C --cached --name-status >actual 2>actual.err && diff --git a/t/t4002-diff-basic.sh b/t/t4002-diff-basic.sh index a5e8b83083..3a6c21e825 100755 --- a/t/t4002-diff-basic.sh +++ b/t/t4002-diff-basic.sh @@ -131,7 +131,7 @@ cmp_diff_files_output () { # object ID for the changed files because it wants you to look at the # filesystem. sed <"$2" >.test-tmp \ - -e '/^:000000 /d;s/'$_x40'\( [MCRNDU][0-9]*\) /'$_z40'\1 /' && + -e '/^:000000 /d;s/'$OID_REGEX'\( [MCRNDU][0-9]*\) /'$ZERO_OID'\1 /' && test_cmp "$1" .test-tmp } diff --git a/t/t4006-diff-mode.sh b/t/t4006-diff-mode.sh index 76f643b2c2..a8e01eccd1 100755 --- a/t/t4006-diff-mode.sh +++ b/t/t4006-diff-mode.sh @@ -8,7 +8,7 @@ test_description='Test mode change diffs. ' . ./test-lib.sh -sed_script='s/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' +sed_script='s/\(:100644 100755\) \('"$OID_REGEX"'\) \2 /\1 X X /' test_expect_success 'setup' ' echo frotz >rezrov && diff --git a/t/t4007-rename-3.sh b/t/t4007-rename-3.sh index dae327fabb..b187b7f6c6 100755 --- a/t/t4007-rename-3.sh +++ b/t/t4007-rename-3.sh @@ -17,6 +17,7 @@ test_expect_success 'prepare reference tree' ' echo $tree ' +blob=$(git hash-object "$TEST_DIRECTORY/diff-lib/COPYING") test_expect_success 'prepare work tree' ' cp path0/COPYING path1/COPYING && git update-index --add --remove path0/COPYING path1/COPYING @@ -26,8 +27,8 @@ test_expect_success 'prepare work tree' ' # path1 both have COPYING and the latter is a copy of path0/COPYING. # Comparing the full tree with cache should tell us so. -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 C100 path0/COPYING path1/COPYING +cat >expected <<EOF +:100644 100644 $blob $blob C100 path0/COPYING path1/COPYING EOF test_expect_success 'copy detection' ' @@ -46,8 +47,8 @@ test_expect_success 'copy detection, cached' ' # path1/COPYING suddenly appearing from nowhere, not detected as # a copy from path0/COPYING. -cat >expected <<\EOF -:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A path1/COPYING +cat >expected <<EOF +:000000 100644 $ZERO_OID $blob A path1/COPYING EOF test_expect_success 'copy, limited to a subtree' ' @@ -64,8 +65,8 @@ test_expect_success 'tweak work tree' ' # path0/COPYING. Showing the full tree with cache should tell us about # the rename. -cat >expected <<\EOF -:100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 path0/COPYING path1/COPYING +cat >expected <<EOF +:100644 100644 $blob $blob R100 path0/COPYING path1/COPYING EOF test_expect_success 'rename detection' ' @@ -78,8 +79,8 @@ test_expect_success 'rename detection' ' # path0/COPYING. When we say we care only about path1, we should just # see path1/COPYING appearing from nowhere. -cat >expected <<\EOF -:000000 100644 0000000000000000000000000000000000000000 6ff87c4664981e4397625791c8ea3bbb5f2279a3 A path1/COPYING +cat >expected <<EOF +:000000 100644 $ZERO_OID $blob A path1/COPYING EOF test_expect_success 'rename, limited to a subtree' ' diff --git a/t/t4008-diff-break-rewrite.sh b/t/t4008-diff-break-rewrite.sh index 9dd1bc5e16..b1ccd4102e 100755 --- a/t/t4008-diff-break-rewrite.sh +++ b/t/t4008-diff-break-rewrite.sh @@ -27,29 +27,32 @@ Further, with -B and -M together, these should turn into two renames. test_expect_success setup ' cat "$TEST_DIRECTORY"/diff-lib/README >file0 && cat "$TEST_DIRECTORY"/diff-lib/COPYING >file1 && + blob0_id=$(git hash-object file0) && + blob1_id=$(git hash-object file1) && git update-index --add file0 file1 && git tag reference $(git write-tree) ' test_expect_success 'change file1 with copy-edit of file0 and remove file0' ' sed -e "s/git/GIT/" file0 >file1 && + blob2_id=$(git hash-object file1) && rm -f file0 && git update-index --remove file0 file1 ' test_expect_success 'run diff with -B (#1)' ' git diff-index -B --cached reference >current && - cat >expect <<-\EOF && - :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100 file1 + cat >expect <<-EOF && + :100644 000000 $blob0_id $ZERO_OID D file0 + :100644 100644 $blob1_id $blob2_id M100 file1 EOF compare_diff_raw expect current ' test_expect_success 'run diff with -B and -M (#2)' ' git diff-index -B -M reference >current && - cat >expect <<-\EOF && - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec R100 file0 file1 + cat >expect <<-EOF && + :100644 100644 $blob0_id $blob2_id R100 file0 file1 EOF compare_diff_raw expect current ' @@ -66,18 +69,18 @@ test_expect_success 'swap file0 and file1' ' test_expect_success 'run diff with -B (#3)' ' git diff-index -B reference >current && - cat >expect <<-\EOF && - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 6ff87c4664981e4397625791c8ea3bbb5f2279a3 M100 file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100 file1 + cat >expect <<-EOF && + :100644 100644 $blob0_id $blob1_id M100 file0 + :100644 100644 $blob1_id $blob0_id M100 file1 EOF compare_diff_raw expect current ' test_expect_success 'run diff with -B and -M (#4)' ' git diff-index -B -M reference >current && - cat >expect <<-\EOF && - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 6ff87c4664981e4397625791c8ea3bbb5f2279a3 R100 file1 file0 - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 R100 file0 file1 + cat >expect <<-EOF && + :100644 100644 $blob1_id $blob1_id R100 file1 file0 + :100644 100644 $blob0_id $blob0_id R100 file0 file1 EOF compare_diff_raw expect current ' @@ -85,14 +88,15 @@ test_expect_success 'run diff with -B and -M (#4)' ' test_expect_success 'make file0 into something completely different' ' rm -f file0 && test_ln_s_add frotz file0 && + slink_id=$(printf frotz | git hash-object --stdin) && git update-index file1 ' test_expect_success 'run diff with -B (#5)' ' git diff-index -B reference >current && - cat >expect <<-\EOF && - :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M100 file1 + cat >expect <<-EOF && + :100644 120000 $blob0_id $slink_id T file0 + :100644 100644 $blob1_id $blob0_id M100 file1 EOF compare_diff_raw expect current ' @@ -103,9 +107,9 @@ test_expect_success 'run diff with -B -M (#6)' ' # file0 changed from regular to symlink. file1 is the same as the preimage # of file0. Because the change does not make file0 disappear, file1 is # denoted as a copy of file0 - cat >expect <<-\EOF && - :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T file0 - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 548142c327a6790ff8821d67c2ee1eff7a656b52 C file0 file1 + cat >expect <<-EOF && + :100644 120000 $blob0_id $slink_id T file0 + :100644 100644 $blob0_id $blob0_id C file0 file1 EOF compare_diff_raw expect current ' @@ -115,9 +119,9 @@ test_expect_success 'run diff with -M (#7)' ' # This should not mistake file0 as the copy source of new file1 # due to type differences. - cat >expect <<-\EOF && - :100644 120000 548142c327a6790ff8821d67c2ee1eff7a656b52 67be421f88824578857624f7b3dc75e99a8a1481 T file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 548142c327a6790ff8821d67c2ee1eff7a656b52 M file1 + cat >expect <<-EOF && + :100644 120000 $blob0_id $slink_id T file0 + :100644 100644 $blob1_id $blob0_id M file1 EOF compare_diff_raw expect current ' @@ -128,25 +132,26 @@ test_expect_success 'file1 edited to look like file0 and file0 rename-edited to git checkout-index -f -u -a && sed -e "s/git/GIT/" file0 >file1 && sed -e "s/git/GET/" file0 >file2 && + blob3_id=$(git hash-object file2) && rm -f file0 && git update-index --add --remove file0 file1 file2 ' test_expect_success 'run diff with -B (#8)' ' git diff-index -B reference >current && - cat >expect <<-\EOF && - :100644 000000 548142c327a6790ff8821d67c2ee1eff7a656b52 0000000000000000000000000000000000000000 D file0 - :100644 100644 6ff87c4664981e4397625791c8ea3bbb5f2279a3 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec M100 file1 - :000000 100644 0000000000000000000000000000000000000000 69a939f651686f56322566e2fd76715947a24162 A file2 + cat >expect <<-EOF && + :100644 000000 $blob0_id $ZERO_OID D file0 + :100644 100644 $blob1_id $blob2_id M100 file1 + :000000 100644 $ZERO_OID $blob3_id A file2 EOF compare_diff_raw expect current ' test_expect_success 'run diff with -B -C (#9)' ' git diff-index -B -C reference >current && - cat >expect <<-\EOF && - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 2fbedd0b5d4b8126e4750c3bee305e8ff79f80ec C095 file0 file1 - :100644 100644 548142c327a6790ff8821d67c2ee1eff7a656b52 69a939f651686f56322566e2fd76715947a24162 R095 file0 file2 + cat >expect <<-EOF && + :100644 100644 $blob0_id $blob2_id C095 file0 file1 + :100644 100644 $blob0_id $blob3_id R095 file0 file2 EOF compare_diff_raw expect current ' diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh index 35b35a81c8..281f8fad0c 100755 --- a/t/t4010-diff-pathspec.sh +++ b/t/t4010-diff-pathspec.sh @@ -74,8 +74,7 @@ test_expect_success 'diff-tree pathspec' ' tree2=$(git write-tree) && echo "$tree2" && git diff-tree -r --name-only $tree $tree2 -- pa path1/a >current && - >expected && - test_cmp expected current + test_must_be_empty current ' test_expect_success 'diff-tree with wildcard shows dir also matches' ' @@ -111,10 +110,10 @@ test_expect_success 'diff-tree -r with wildcard' ' test_expect_success 'setup submodules' ' test_tick && git init submod && - ( cd submod && test_commit first; ) && + ( cd submod && test_commit first ) && git add submod && git commit -m first && - ( cd submod && test_commit second; ) && + ( cd submod && test_commit second ) && git add submod && git commit -m second ' diff --git a/t/t4011-diff-symlink.sh b/t/t4011-diff-symlink.sh index cf0f3a1ee7..5ae19b987d 100755 --- a/t/t4011-diff-symlink.sh +++ b/t/t4011-diff-symlink.sh @@ -126,7 +126,7 @@ test_expect_success SYMLINKS 'diff symlinks with non-existing targets' ' ln -s take\ over brain && test_must_fail git diff --no-index pinky brain >output 2>output.err && grep narf output && - ! test -s output.err + test_must_be_empty output.err ' test_expect_success SYMLINKS 'setup symlinks with attributes' ' @@ -139,11 +139,13 @@ test_expect_success SYMLINKS 'setup symlinks with attributes' ' test_expect_success SYMLINKS 'symlinks do not respect userdiff config by path' ' cat >expect <<-\EOF && diff --git a/file.bin b/file.bin - index e69de29..d95f3ad 100644 - Binary files a/file.bin and b/file.bin differ + new file mode 100644 + index 0000000..d95f3ad + Binary files /dev/null and b/file.bin differ diff --git a/link.bin b/link.bin - index e69de29..dce41ec 120000 - --- a/link.bin + new file mode 120000 + index 0000000..dce41ec + --- /dev/null +++ b/link.bin @@ -0,0 +1 @@ +file.bin diff --git a/t/t4012-diff-binary.sh b/t/t4012-diff-binary.sh index 0a8af76aab..6579c81216 100755 --- a/t/t4012-diff-binary.sh +++ b/t/t4012-diff-binary.sh @@ -102,10 +102,8 @@ test_expect_success 'apply binary patch' ' test_expect_success 'diff --no-index with binary creation' ' echo Q | q_to_nul >binary && - (: hide error code from diff, which just indicates differences - git diff --binary --no-index /dev/null binary >current || - true - ) && + # hide error code from diff, which just indicates differences + test_might_fail git diff --binary --no-index /dev/null binary >current && rm binary && git apply --binary <current && echo Q >expected && diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index f8d853595b..73f7038253 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -140,7 +140,7 @@ do expect="$TEST_DIRECTORY/t4013/diff.$test" actual="$pfx-diff.$test" - test_expect_success "git $cmd # magic is ${magic:-"(not used)"}" ' + test_expect_success "git $cmd # magic is ${magic:-(not used)}" ' { echo "$ git $cmd" case "$magic" in diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh index 482112ca33..909c743c13 100755 --- a/t/t4014-format-patch.sh +++ b/t/t4014-format-patch.sh @@ -578,7 +578,11 @@ test_expect_success 'excessive subject' ' rm -rf patches/ && git checkout side && + before=$(git hash-object file) && + before=$(git rev-parse --short $before) && for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >>file && + after=$(git hash-object file) && + after=$(git rev-parse --short $after) && git update-index file && git commit -m "This is an excessively long subject line for a message due to the habit some projects have of not having a short, one-line subject at the start of the commit message, but rather sticking a whole paragraph right at the start as the only thing in the commit message. It had better not become the filename for the patch." && git format-patch -o patches/ master..side && @@ -586,7 +590,6 @@ test_expect_success 'excessive subject' ' ' test_expect_success 'cover-letter inherits diff options' ' - git mv file foo && git commit -m foo && git format-patch --no-renames --cover-letter -1 && @@ -616,7 +619,7 @@ test_expect_success 'shortlog of cover-letter wraps overly-long onelines' ' ' cat > expect << EOF -index 40f36c6..2dc5c23 100644 +index $before..$after 100644 --- a/file +++ b/file @@ -13,4 +13,20 @@ C @@ -640,7 +643,7 @@ test_expect_success 'format-patch respects -U' ' cat > expect << EOF diff --git a/file b/file -index 40f36c6..2dc5c23 100644 +index $before..$after 100644 --- a/file +++ b/file @@ -14,3 +14,19 @@ C @@ -1523,14 +1526,14 @@ test_expect_success 'cover letter auto user override' ' test_expect_success 'format-patch --zero-commit' ' git format-patch --zero-commit --stdout v2..v1 >patch2 && grep "^From " patch2 | sort | uniq >actual && - echo "From $_z40 Mon Sep 17 00:00:00 2001" >expect && + echo "From $ZERO_OID Mon Sep 17 00:00:00 2001" >expect && test_cmp expect actual ' test_expect_success 'From line has expected format' ' git format-patch --stdout v2..v1 >patch2 && grep "^From " patch2 >from && - grep "^From $_x40 Mon Sep 17 00:00:00 2001$" patch2 >filtered && + grep "^From $OID_REGEX Mon Sep 17 00:00:00 2001$" patch2 >filtered && test_cmp from filtered ' @@ -1551,13 +1554,15 @@ test_expect_success 'format-patch -o overrides format.outputDirectory' ' test_expect_success 'format-patch --base' ' git checkout side && - git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual && + git format-patch --stdout --base=HEAD~3 -1 | tail -n 7 >actual1 && + git format-patch --stdout --base=HEAD~3 HEAD~.. | tail -n 7 >actual2 && echo >expected && echo "base-commit: $(git rev-parse HEAD~3)" >>expected && echo "prerequisite-patch-id: $(git show --patch HEAD~2 | git patch-id --stable | awk "{print \$1}")" >>expected && echo "prerequisite-patch-id: $(git show --patch HEAD~1 | git patch-id --stable | awk "{print \$1}")" >>expected && signature >> expected && - test_cmp expected actual + test_cmp expected actual1 && + test_cmp expected actual2 ' test_expect_success 'format-patch --base errors out when base commit is in revision list' ' @@ -1661,6 +1666,15 @@ test_expect_success 'format-patch --base with --attach' ' test_write_lines 1 2 >expect && test_cmp expect actual ' +test_expect_success 'format-patch --attach cover-letter only is non-multipart' ' + test_when_finished "rm -fr patches" && + git format-patch -o patches --cover-letter --attach=mimemime --base=HEAD~ -1 && + ! egrep "^--+mimemime" patches/0000*.patch && + egrep "^--+mimemime$" patches/0001*.patch >output && + test_line_count = 2 output && + egrep "^--+mimemime--$" patches/0001*.patch >output && + test_line_count = 1 output +' test_expect_success 'format-patch --pretty=mboxrd' ' sp=" " && @@ -1703,4 +1717,38 @@ test_expect_success 'format-patch --pretty=mboxrd' ' test_cmp expect actual ' +test_expect_success 'interdiff: setup' ' + git checkout -b boop master && + test_commit fnorp blorp && + test_commit fleep blorp +' + +test_expect_success 'interdiff: cover-letter' ' + sed "y/q/ /" >expect <<-\EOF && + +fleep + --q + EOF + git format-patch --cover-letter --interdiff=boop~2 -1 boop && + test_i18ngrep "^Interdiff:$" 0000-cover-letter.patch && + test_i18ngrep ! "^Interdiff:$" 0001-fleep.patch && + sed "1,/^@@ /d; /^-- $/q" <0000-cover-letter.patch >actual && + test_cmp expect actual +' + +test_expect_success 'interdiff: reroll-count' ' + git format-patch --cover-letter --interdiff=boop~2 -v2 -1 boop && + test_i18ngrep "^Interdiff ..* v1:$" v2-0000-cover-letter.patch +' + +test_expect_success 'interdiff: solo-patch' ' + cat >expect <<-\EOF && + +fleep + + EOF + git format-patch --interdiff=boop~2 -1 boop && + test_i18ngrep "^Interdiff:$" 0001-fleep.patch && + sed "1,/^ @@ /d; /^$/q" <0001-fleep.patch >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4015-diff-whitespace.sh b/t/t4015-diff-whitespace.sh index 41facf7abf..a9fb226c5a 100755 --- a/t/t4015-diff-whitespace.sh +++ b/t/t4015-diff-whitespace.sh @@ -93,21 +93,20 @@ test_expect_success 'another test, without options' ' git diff >out && test_cmp expect out && - >expect && git diff -w >out && - test_cmp expect out && + test_must_be_empty out && git diff -w -b >out && - test_cmp expect out && + test_must_be_empty out && git diff -w --ignore-space-at-eol >out && - test_cmp expect out && + test_must_be_empty out && git diff -w -b --ignore-space-at-eol >out && - test_cmp expect out && + test_must_be_empty out && git diff -w --ignore-cr-at-eol >out && - test_cmp expect out && + test_must_be_empty out && tr "Q_" "\015 " <<-\EOF >expect && diff --git a/x b/x @@ -182,8 +181,7 @@ test_expect_success 'ignore-blank-lines: only new lines' ' test_seq 5 | sed "/3/i\\ " >x && git diff --ignore-blank-lines >out && - >expect && - test_cmp expect out + test_must_be_empty out ' test_expect_success 'ignore-blank-lines: only new lines with space' ' @@ -192,8 +190,7 @@ test_expect_success 'ignore-blank-lines: only new lines with space' ' test_seq 5 | sed "/3/i\\ " >x && git diff -w --ignore-blank-lines >out && - >expect && - test_cmp expect out + test_must_be_empty out ' test_expect_success 'ignore-blank-lines: after change' ' @@ -779,8 +776,6 @@ test_expect_success 'checkdiff allows new blank lines' ' git diff --check ' -cat <<EOF >expect -EOF test_expect_success 'whitespace-only changes not reported' ' git reset --hard && echo >x "hello world" && @@ -788,7 +783,7 @@ test_expect_success 'whitespace-only changes not reported' ' git commit -m "hello 1" && echo >x "hello world" && git diff -b >actual && - test_cmp expect actual + test_must_be_empty actual ' cat <<EOF >expect @@ -1305,7 +1300,7 @@ test_expect_success 'detect blocks of moved code' ' ' -test_expect_success 'detect permutations inside moved code -- dimmed_zebra' ' +test_expect_success 'detect permutations inside moved code -- dimmed-zebra' ' # reuse setup from test before! test_config color.diff.oldMoved "magenta" && test_config color.diff.newMoved "cyan" && @@ -1315,7 +1310,7 @@ test_expect_success 'detect permutations inside moved code -- dimmed_zebra' ' test_config color.diff.newMovedDimmed "normal cyan" && test_config color.diff.oldMovedAlternativeDimmed "normal blue" && test_config color.diff.newMovedAlternativeDimmed "normal yellow" && - git diff HEAD --no-renames --color-moved=dimmed_zebra --color >actual.raw && + git diff HEAD --no-renames --color-moved=dimmed-zebra --color >actual.raw && grep -v "index" actual.raw | test_decode_color >actual && cat <<-\EOF >expected && <BOLD>diff --git a/lines.txt b/lines.txt<RESET> diff --git a/t/t4018/php-abstract-class b/t/t4018/php-abstract-class new file mode 100644 index 0000000000..5213e12494 --- /dev/null +++ b/t/t4018/php-abstract-class @@ -0,0 +1,4 @@ +abstract class RIGHT +{ + const FOO = 'ChangeMe'; +} diff --git a/t/t4018/php-class b/t/t4018/php-class new file mode 100644 index 0000000000..7785b6303c --- /dev/null +++ b/t/t4018/php-class @@ -0,0 +1,4 @@ +class RIGHT +{ + const FOO = 'ChangeMe'; +} diff --git a/t/t4018/php-final-class b/t/t4018/php-final-class new file mode 100644 index 0000000000..69f5710552 --- /dev/null +++ b/t/t4018/php-final-class @@ -0,0 +1,4 @@ +final class RIGHT +{ + const FOO = 'ChangeMe'; +} diff --git a/t/t4018/php-function b/t/t4018/php-function new file mode 100644 index 0000000000..35717c51c3 --- /dev/null +++ b/t/t4018/php-function @@ -0,0 +1,4 @@ +function RIGHT() +{ + return 'ChangeMe'; +} diff --git a/t/t4018/php-interface b/t/t4018/php-interface new file mode 100644 index 0000000000..86b49ad5d9 --- /dev/null +++ b/t/t4018/php-interface @@ -0,0 +1,4 @@ +interface RIGHT +{ + public function foo($ChangeMe); +} diff --git a/t/t4018/php-method b/t/t4018/php-method new file mode 100644 index 0000000000..03af1a6d9d --- /dev/null +++ b/t/t4018/php-method @@ -0,0 +1,7 @@ +class Klass +{ + public static function RIGHT() + { + return 'ChangeMe'; + } +} diff --git a/t/t4018/php-trait b/t/t4018/php-trait new file mode 100644 index 0000000000..65b8c82a61 --- /dev/null +++ b/t/t4018/php-trait @@ -0,0 +1,7 @@ +trait RIGHT +{ + public function foo($ChangeMe) + { + return 'foo'; + } +} diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh index a5019759bc..c6135c7548 100755 --- a/t/t4019-diff-wserror.sh +++ b/t/t4019-diff-wserror.sh @@ -260,7 +260,7 @@ test_expect_success 'trailing empty lines (2)' ' echo "F -whitespace" >.gitattributes && git diff --check >output && - ! test -s output + test_must_be_empty output ' diff --git a/t/t4020-diff-external.sh b/t/t4020-diff-external.sh index 044620186d..e009826fcb 100755 --- a/t/t4020-diff-external.sh +++ b/t/t4020-diff-external.sh @@ -13,6 +13,8 @@ test_expect_success setup ' test_tick && echo second >file && + before=$(git hash-object file) && + before=$(git rev-parse --short $before) && git add file && git commit -m second && @@ -26,7 +28,7 @@ test_expect_success 'GIT_EXTERNAL_DIFF environment' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z100644 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -55,7 +57,7 @@ test_expect_success SYMLINKS 'typechange diff' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z120000 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -73,7 +75,7 @@ test_expect_success 'diff.external' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z100644 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -104,7 +106,7 @@ test_expect_success 'diff attribute' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z100644 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -137,7 +139,7 @@ test_expect_success 'diff attribute' ' read path oldfile oldhex oldmode newfile newhex newmode && test "z$path" = zfile && test "z$oldmode" = z100644 && - test "z$newhex" = "z$_z40" && + test "z$newhex" = "z$ZERO_OID" && test "z$newmode" = z100644 && oh=$(git rev-parse --verify HEAD:file) && test "z$oh" = "z$oldhex" @@ -180,9 +182,13 @@ test_expect_success 'no diff with -diff' ' echo NULZbetweenZwords | perl -pe 'y/Z/\000/' > file test_expect_success 'force diff with "diff"' ' + after=$(git hash-object file) && + after=$(git rev-parse --short $after) && echo >.gitattributes "file diff" && git diff >actual && - test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual + sed -e "s/^index .*/index $before..$after 100644/" \ + "$TEST_DIRECTORY"/t4020/diff.NUL >expected-diff && + test_cmp expected-diff actual ' test_expect_success 'GIT_EXTERNAL_DIFF with more than one changed files' ' @@ -237,7 +243,7 @@ test_expect_success 'diff --cached' ' git update-index --assume-unchanged file && echo second >file && git diff --cached >actual && - test_cmp "$TEST_DIRECTORY"/t4020/diff.NUL actual + test_cmp expected-diff actual ' test_expect_success 'clean up crlf leftovers' ' diff --git a/t/t4022-diff-rewrite.sh b/t/t4022-diff-rewrite.sh index cb51d9f9d4..6d1c3d949c 100755 --- a/t/t4022-diff-rewrite.sh +++ b/t/t4022-diff-rewrite.sh @@ -13,6 +13,8 @@ test_expect_success setup ' "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM" \ <"$TEST_DIRECTORY"/../COPYING >test && echo "to be deleted" >test2 && + blob=$(git hash-object test2) && + blob=$(git rev-parse --short $blob) && git add test2 ' @@ -27,7 +29,7 @@ test_expect_success 'detect rewrite' ' cat >expect <<EOF diff --git a/test2 b/test2 deleted file mode 100644 -index 4202011..0000000 +index $blob..0000000 --- a/test2 +++ /dev/null @@ -1 +0,0 @@ @@ -43,7 +45,7 @@ test_expect_success 'show deletion diff without -D' ' cat >expect <<EOF diff --git a/test2 b/test2 deleted file mode 100644 -index 4202011..0000000 +index $blob..0000000 EOF test_expect_success 'suppress deletion diff with -D' ' diff --git a/t/t4024-diff-optimize-common.sh b/t/t4024-diff-optimize-common.sh index 7e76018296..6b44ce1493 100755 --- a/t/t4024-diff-optimize-common.sh +++ b/t/t4024-diff-optimize-common.sh @@ -127,17 +127,17 @@ test_expect_success setup ' for n in $sample do - ( zs $n ; echo a ) >file-a$n && - ( echo b; zs $n; echo ) >file-b$n && - ( printf c; zs $n ) >file-c$n && - ( echo d; zs $n ) >file-d$n && + ( zs $n && echo a ) >file-a$n && + ( echo b && zs $n && echo ) >file-b$n && + ( printf c && zs $n ) >file-c$n && + ( echo d && zs $n ) >file-d$n && git add file-a$n file-b$n file-c$n file-d$n && - ( zs $n ; echo A ) >file-a$n && - ( echo B; zs $n; echo ) >file-b$n && - ( printf C; zs $n ) >file-c$n && - ( echo D; zs $n ) >file-d$n && + ( zs $n && echo A ) >file-a$n && + ( echo B && zs $n && echo ) >file-b$n && + ( printf C && zs $n ) >file-c$n && + ( echo D && zs $n ) >file-d$n && expect_pattern $n || return 1 diff --git a/t/t4025-hunk-header.sh b/t/t4025-hunk-header.sh index 7a3dbc1ea2..fa44e78869 100755 --- a/t/t4025-hunk-header.sh +++ b/t/t4025-hunk-header.sh @@ -12,12 +12,12 @@ NS="$N$N$N$N$N$N$N$N$N$N$N$N$N" test_expect_success setup ' ( - echo "A $NS" + echo "A $NS" && for c in B C D E F G H I J K do echo " $c" - done - echo "L $NS" + done && + echo "L $NS" && for c in M N O P Q R S T U V do echo " $c" @@ -34,7 +34,7 @@ test_expect_success 'hunk header truncation with an overly long line' ' git diff | sed -n -e "s/^.*@@//p" >actual && ( - echo " A $N$N$N$N$N$N$N$N$N2" + echo " A $N$N$N$N$N$N$N$N$N2" && echo " L $N$N$N$N$N$N$N$N$N1" ) >expected && test_cmp actual expected diff --git a/t/t4027-diff-submodule.sh b/t/t4027-diff-submodule.sh index 2ffd11a142..9aa8e2b39b 100755 --- a/t/t4027-diff-submodule.sh +++ b/t/t4027-diff-submodule.sh @@ -31,7 +31,7 @@ test_expect_success setup ' cd sub && git rev-list HEAD ) && - echo ":160000 160000 $3 $_z40 M sub" >expect && + echo ":160000 160000 $3 $ZERO_OID M sub" >expect && subtip=$3 subprev=$2 ' @@ -104,19 +104,19 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match)' expect_from_to >expect.body $subprev $subprev-dirty && test_cmp expect.body actual.body && git diff --ignore-submodules HEAD >actual2 && - ! test -s actual2 && + test_must_be_empty actual2 && git diff --ignore-submodules=untracked HEAD >actual3 && sed -e "1,/^@@/d" actual3 >actual3.body && expect_from_to >expect.body $subprev $subprev-dirty && test_cmp expect.body actual3.body && git diff --ignore-submodules=dirty HEAD >actual4 && - ! test -s actual4 + test_must_be_empty actual4 ' test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) [.gitmodules]' ' git config diff.ignoreSubmodules dirty && git diff HEAD >actual && - ! test -s actual && + test_must_be_empty actual && git config --add -f .gitmodules submodule.subname.ignore none && git config --add -f .gitmodules submodule.subname.path sub && git diff HEAD >actual && @@ -126,7 +126,7 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) git config -f .gitmodules submodule.subname.ignore all && git config -f .gitmodules submodule.subname.path sub && git diff HEAD >actual2 && - ! test -s actual2 && + test_must_be_empty actual2 && git config -f .gitmodules submodule.subname.ignore untracked && git diff HEAD >actual3 && sed -e "1,/^@@/d" actual3 >actual3.body && @@ -134,7 +134,7 @@ test_expect_success 'git diff HEAD with dirty submodule (work tree, refs match) test_cmp expect.body actual3.body && git config -f .gitmodules submodule.subname.ignore dirty && git diff HEAD >actual4 && - ! test -s actual4 && + test_must_be_empty actual4 && git config submodule.subname.ignore none && git config submodule.subname.path sub && git diff HEAD >actual && @@ -172,24 +172,24 @@ test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match)' expect_from_to >expect.body $subprev $subprev-dirty && test_cmp expect.body actual.body && git diff --ignore-submodules=all HEAD >actual2 && - ! test -s actual2 && + test_must_be_empty actual2 && git diff --ignore-submodules=untracked HEAD >actual3 && - ! test -s actual3 && + test_must_be_empty actual3 && git diff --ignore-submodules=dirty HEAD >actual4 && - ! test -s actual4 + test_must_be_empty actual4 ' test_expect_success 'git diff HEAD with dirty submodule (untracked, refs match) [.gitmodules]' ' git config --add -f .gitmodules submodule.subname.ignore all && git config --add -f .gitmodules submodule.subname.path sub && git diff HEAD >actual2 && - ! test -s actual2 && + test_must_be_empty actual2 && git config -f .gitmodules submodule.subname.ignore untracked && git diff HEAD >actual3 && - ! test -s actual3 && + test_must_be_empty actual3 && git config -f .gitmodules submodule.subname.ignore dirty && git diff HEAD >actual4 && - ! test -s actual4 && + test_must_be_empty actual4 && git config submodule.subname.ignore none && git config submodule.subname.path sub && git diff HEAD >actual && @@ -211,7 +211,7 @@ test_expect_success 'git diff between submodule commits' ' expect_from_to >expect.body $subtip $subprev && test_cmp expect.body actual.body && git diff --ignore-submodules HEAD^..HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'git diff between submodule commits [.gitmodules]' ' @@ -227,7 +227,7 @@ test_expect_success 'git diff between submodule commits [.gitmodules]' ' test_cmp expect.body actual.body && git config -f .gitmodules submodule.subname.ignore all && git diff HEAD^..HEAD >actual && - ! test -s actual && + test_must_be_empty actual && git config submodule.subname.ignore dirty && git config submodule.subname.path sub && git diff HEAD^..HEAD >actual && @@ -239,10 +239,9 @@ test_expect_success 'git diff between submodule commits [.gitmodules]' ' ' test_expect_success 'git diff (empty submodule dir)' ' - : >empty && rm -rf sub/* sub/.git && git diff > actual.empty && - test_cmp empty actual.empty + test_must_be_empty actual.empty ' test_expect_success 'conflicted submodule setup' ' @@ -250,7 +249,7 @@ test_expect_success 'conflicted submodule setup' ' # 39 efs c=fffffffffffffffffffffffffffffffffffffff && ( - echo "000000 $_z40 0 sub" && + echo "000000 $ZERO_OID 0 sub" && echo "160000 1$c 1 sub" && echo "160000 2$c 2 sub" && echo "160000 3$c 3 sub" @@ -265,7 +264,7 @@ index 2ffffff,3ffffff..0000000 ++Subproject commit 0000000000000000000000000000000000000000'\'' && hh=$(git rev-parse HEAD) && - sed -e "s/$_z40/$hh/" expect.nosub >expect.withsub + sed -e "s/$ZERO_OID/$hh/" expect.nosub >expect.withsub ' diff --git a/t/t4029-diff-trailing-space.sh b/t/t4029-diff-trailing-space.sh index 3ccc237a8d..32b6e9a4e7 100755 --- a/t/t4029-diff-trailing-space.sh +++ b/t/t4029-diff-trailing-space.sh @@ -6,7 +6,7 @@ test_description='diff honors config option, diff.suppressBlankEmpty' . ./test-lib.sh -cat <<\EOF > exp || +cat <<\EOF >expected || diff --git a/f b/f index 5f6a263..8cb8bae 100644 --- a/f @@ -18,22 +18,26 @@ index 5f6a263..8cb8bae 100644 EOF exit 1 -test_expect_success \ - "$test_description" \ - 'printf "\nx\n" > f && - git add f && - git commit -q -m. f && - printf "\ny\n" > f && - git config --bool diff.suppressBlankEmpty true && - git diff f > actual && - test_cmp exp actual && - perl -i.bak -p -e "s/^\$/ /" exp && - git config --bool diff.suppressBlankEmpty false && - git diff f > actual && - test_cmp exp actual && - git config --bool --unset diff.suppressBlankEmpty && - git diff f > actual && - test_cmp exp actual - ' +test_expect_success "$test_description" ' + printf "\nx\n" > f && + before=$(git hash-object f) && + before=$(git rev-parse --short $before) && + git add f && + git commit -q -m. f && + printf "\ny\n" > f && + after=$(git hash-object f) && + after=$(git rev-parse --short $after) && + sed -e "s/^index .*/index $before..$after 100644/" expected >exp && + git config --bool diff.suppressBlankEmpty true && + git diff f > actual && + test_cmp exp actual && + perl -i.bak -p -e "s/^\$/ /" exp && + git config --bool diff.suppressBlankEmpty false && + git diff f > actual && + test_cmp exp actual && + git config --bool --unset diff.suppressBlankEmpty && + git diff f > actual && + test_cmp exp actual +' test_done diff --git a/t/t4030-diff-textconv.sh b/t/t4030-diff-textconv.sh index aad6c7f78d..4cb9f0e523 100755 --- a/t/t4030-diff-textconv.sh +++ b/t/t4030-diff-textconv.sh @@ -148,7 +148,8 @@ test_expect_success 'diffstat does not run textconv' ' # restore working setup echo file diff=foo >.gitattributes -cat >expect.typechange <<'EOF' +symlink=$(git rev-parse --short $(printf frotz | git hash-object --stdin)) +cat >expect.typechange <<EOF --- a/file +++ /dev/null @@ -1,2 +0,0 @@ @@ -156,7 +157,7 @@ cat >expect.typechange <<'EOF' -1 diff --git a/file b/file new file mode 120000 -index 0000000..67be421 +index 0000000..$symlink --- /dev/null +++ b/file @@ -0,0 +1 @@ diff --git a/t/t4039-diff-assume-unchanged.sh b/t/t4039-diff-assume-unchanged.sh index 23c0e357a7..53ac44b0f0 100755 --- a/t/t4039-diff-assume-unchanged.sh +++ b/t/t4039-diff-assume-unchanged.sh @@ -34,9 +34,8 @@ test_expect_success POSIXPERM 'find-copies-harder is not confused by mode bits' git add exec && git commit -m exec && git update-index --assume-unchanged exec && - >expect && git diff-files --find-copies-harder -- exec >actual && - test_cmp expect actual + test_must_be_empty actual ' test_done diff --git a/t/t4041-diff-submodule-option.sh b/t/t4041-diff-submodule-option.sh index 058ee0829d..619bf97098 100755 --- a/t/t4041-diff-submodule-option.sh +++ b/t/t4041-diff-submodule-option.sh @@ -257,9 +257,7 @@ test_expect_success 'typechanged submodule(blob->submodule)' ' commit_file sm1 && test_expect_success 'submodule is up to date' ' git diff-index -p --submodule=log HEAD >actual && - cat >expected <<-EOF && - EOF - test_cmp expected actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked content' ' @@ -273,17 +271,17 @@ test_expect_success 'submodule contains untracked content' ' test_expect_success 'submodule contains untracked content (untracked ignored)' ' git diff-index -p --ignore-submodules=untracked --submodule=log HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked content (dirty ignored)' ' git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked content (all ignored)' ' git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked and modifed content' ' @@ -308,13 +306,13 @@ test_expect_success 'submodule contains untracked and modifed content (untracked test_expect_success 'submodule contains untracked and modifed content (dirty ignored)' ' echo new > sm1/foo6 && git diff-index -p --ignore-submodules=dirty --submodule=log HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked and modifed content (all ignored)' ' echo new > sm1/foo6 && git diff-index -p --ignore-submodules --submodule=log HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains modifed content' ' @@ -368,7 +366,7 @@ test_expect_success 'modified submodule contains untracked content (dirty ignore test_expect_success 'modified submodule contains untracked content (all ignored)' ' git diff-index -p --ignore-submodules=all --submodule=log HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'modified submodule contains untracked and modifed content' ' @@ -407,7 +405,7 @@ test_expect_success 'modified submodule contains untracked and modifed content ( test_expect_success 'modified submodule contains untracked and modifed content (all ignored)' ' echo modification >> sm1/foo6 && git diff-index -p --ignore-submodules --submodule=log HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'modified submodule contains modifed content' ' @@ -498,7 +496,7 @@ test_expect_success 'given commit --submodule=short' ' test_expect_success 'setup .git file for sm2' ' (cd sm2 && REAL="$(pwd)/../.real" && - mv .git "$REAL" + mv .git "$REAL" && echo "gitdir: $REAL" >.git) ' @@ -527,7 +525,7 @@ test_expect_success 'diff --submodule with objects referenced by alternates' ' git commit -m "sub a" ) && (cd sub_alt && - sha1_before=$(git rev-parse --short HEAD) + sha1_before=$(git rev-parse --short HEAD) && echo b >b && git add b && git commit -m b && diff --git a/t/t4042-diff-textconv-caching.sh b/t/t4042-diff-textconv-caching.sh index 04a44d5c61..bf33aedf4b 100755 --- a/t/t4042-diff-textconv-caching.sh +++ b/t/t4042-diff-textconv-caching.sh @@ -15,9 +15,13 @@ test_expect_success 'setup' ' echo bar content 1 >bar.bin && git add . && git commit -m one && + foo1=$(git rev-parse --short HEAD:foo.bin) && + bar1=$(git rev-parse --short HEAD:bar.bin) && echo foo content 2 >foo.bin && echo bar content 2 >bar.bin && git commit -a -m two && + foo2=$(git rev-parse --short HEAD:foo.bin) && + bar2=$(git rev-parse --short HEAD:bar.bin) && echo "*.bin diff=magic" >.gitattributes && git config diff.magic.textconv ./helper && git config diff.magic.cachetextconv true @@ -25,14 +29,14 @@ test_expect_success 'setup' ' cat >expect <<EOF diff --git a/bar.bin b/bar.bin -index fcf9166..28283d5 100644 +index $bar1..$bar2 100644 --- a/bar.bin +++ b/bar.bin @@ -1 +1 @@ -converted: bar content 1 +converted: bar content 2 diff --git a/foo.bin b/foo.bin -index d5b9fe3..1345db2 100644 +index $foo1..$foo2 100644 --- a/foo.bin +++ b/foo.bin @@ -1 +1 @@ @@ -59,7 +63,7 @@ test_expect_success 'cached textconv does not run helper' ' cat >expect <<EOF diff --git a/bar.bin b/bar.bin -index fcf9166..28283d5 100644 +index $bar1..$bar2 100644 --- a/bar.bin +++ b/bar.bin @@ -1,2 +1,2 @@ @@ -67,7 +71,7 @@ index fcf9166..28283d5 100644 -converted: bar content 1 +converted: bar content 2 diff --git a/foo.bin b/foo.bin -index d5b9fe3..1345db2 100644 +index $foo1..$foo2 100644 --- a/foo.bin +++ b/foo.bin @@ -1,2 +1,2 @@ @@ -84,7 +88,7 @@ test_expect_success 'changing textconv invalidates cache' ' cat >expect <<EOF diff --git a/bar.bin b/bar.bin -index fcf9166..28283d5 100644 +index $bar1..$bar2 100644 --- a/bar.bin +++ b/bar.bin @@ -1,2 +1,2 @@ @@ -92,7 +96,7 @@ index fcf9166..28283d5 100644 -converted: bar content 1 +converted: bar content 2 diff --git a/foo.bin b/foo.bin -index d5b9fe3..1345db2 100644 +index $foo1..$foo2 100644 --- a/foo.bin +++ b/foo.bin @@ -1 +1 @@ diff --git a/t/t4044-diff-index-unique-abbrev.sh b/t/t4044-diff-index-unique-abbrev.sh index d5ce72be63..647905e01f 100755 --- a/t/t4044-diff-index-unique-abbrev.sh +++ b/t/t4044-diff-index-unique-abbrev.sh @@ -3,6 +3,12 @@ test_description='test unique sha1 abbreviation on "index from..to" line' . ./test-lib.sh +if ! test_have_prereq SHA1 +then + skip_all='not using SHA-1 for objects' + test_done +fi + cat >expect_initial <<EOF 100644 blob 51d2738463ea4ca66f8691c91e33ce64b7d41bb1 foo EOF diff --git a/t/t4045-diff-relative.sh b/t/t4045-diff-relative.sh index 6471a68701..36f8ed8a81 100755 --- a/t/t4045-diff-relative.sh +++ b/t/t4045-diff-relative.sh @@ -8,6 +8,7 @@ test_expect_success 'setup' ' echo content >file1 && mkdir subdir && echo other content >subdir/file2 && + blob=$(git hash-object subdir/file2) && git add . && git commit -m one ' @@ -17,10 +18,11 @@ check_diff () { shift expect=$1 shift + short_blob=$(git rev-parse --short $blob) cat >expected <<-EOF diff --git a/$expect b/$expect new file mode 100644 - index 0000000..25c05ef + index 0000000..$short_blob --- /dev/null +++ b/$expect @@ -0,0 +1 @@ @@ -68,7 +70,7 @@ check_raw () { expect=$1 shift cat >expected <<-EOF - :000000 100644 0000000000000000000000000000000000000000 25c05ef3639d2d270e7fe765a67668f098092bc5 A $expect + :000000 100644 0000000000000000000000000000000000000000 $blob A $expect EOF test_expect_success "--raw $*" " git -C '$dir' diff --no-abbrev --raw $* HEAD^ >actual && diff --git a/t/t4046-diff-unmerged.sh b/t/t4046-diff-unmerged.sh index d0f14475ca..ff7cfd884a 100755 --- a/t/t4046-diff-unmerged.sh +++ b/t/t4046-diff-unmerged.sh @@ -37,7 +37,7 @@ test_expect_success 'diff-files -0' ' for path in $paths do >"$path" && - echo ":000000 100644 $_z40 $_z40 U $path" + echo ":000000 100644 $ZERO_OID $ZERO_OID U $path" done >diff-files-0.expect && git diff-files -0 >diff-files-0.actual && test_cmp diff-files-0.expect diff-files-0.actual @@ -47,9 +47,9 @@ test_expect_success 'diff-files -1' ' for path in $paths do >"$path" && - echo ":000000 100644 $_z40 $_z40 U $path" && + echo ":000000 100644 $ZERO_OID $ZERO_OID U $path" && case "$path" in - x??) echo ":100644 100644 $blob1 $_z40 M $path" + x??) echo ":100644 100644 $blob1 $ZERO_OID M $path" esac done >diff-files-1.expect && git diff-files -1 >diff-files-1.actual && @@ -60,9 +60,9 @@ test_expect_success 'diff-files -2' ' for path in $paths do >"$path" && - echo ":000000 100644 $_z40 $_z40 U $path" && + echo ":000000 100644 $ZERO_OID $ZERO_OID U $path" && case "$path" in - ?x?) echo ":100644 100644 $blob2 $_z40 M $path" + ?x?) echo ":100644 100644 $blob2 $ZERO_OID M $path" esac done >diff-files-2.expect && git diff-files -2 >diff-files-2.actual && @@ -75,9 +75,9 @@ test_expect_success 'diff-files -3' ' for path in $paths do >"$path" && - echo ":000000 100644 $_z40 $_z40 U $path" && + echo ":000000 100644 $ZERO_OID $ZERO_OID U $path" && case "$path" in - ??x) echo ":100644 100644 $blob3 $_z40 M $path" + ??x) echo ":100644 100644 $blob3 $ZERO_OID M $path" esac done >diff-files-3.expect && git diff-files -3 >diff-files-3.actual && diff --git a/t/t4047-diff-dirstat.sh b/t/t4047-diff-dirstat.sh index 447a8ffa3a..7fec2cb9cd 100755 --- a/t/t4047-diff-dirstat.sh +++ b/t/t4047-diff-dirstat.sh @@ -940,7 +940,7 @@ test_expect_success 'diff.dirstat=0,lines' ' test_expect_success '--dirstat=future_param,lines,0 should fail loudly' ' test_must_fail git diff --dirstat=future_param,lines,0 HEAD^..HEAD >actual_diff_dirstat 2>actual_error && test_debug "cat actual_error" && - test_cmp /dev/null actual_diff_dirstat && + test_must_be_empty actual_diff_dirstat && test_i18ngrep -q "future_param" actual_error && test_i18ngrep -q "\--dirstat" actual_error ' @@ -948,7 +948,7 @@ test_expect_success '--dirstat=future_param,lines,0 should fail loudly' ' test_expect_success '--dirstat=dummy1,cumulative,2dummy should report both unrecognized parameters' ' test_must_fail git diff --dirstat=dummy1,cumulative,2dummy HEAD^..HEAD >actual_diff_dirstat 2>actual_error && test_debug "cat actual_error" && - test_cmp /dev/null actual_diff_dirstat && + test_must_be_empty actual_diff_dirstat && test_i18ngrep -q "dummy1" actual_error && test_i18ngrep -q "2dummy" actual_error && test_i18ngrep -q "\--dirstat" actual_error diff --git a/t/t4051-diff-function-context.sh b/t/t4051-diff-function-context.sh index 2d76a971c4..4838a1df8b 100755 --- a/t/t4051-diff-function-context.sh +++ b/t/t4051-diff-function-context.sh @@ -174,7 +174,7 @@ test_expect_success ' context does not include other functions' ' ' test_expect_success ' context does not include preceding empty lines' ' - test "$(first_context_line <long_common_tail.diff.diff)" != " " + test "$(first_context_line <long_common_tail.diff)" != " " ' check_diff changed_hello_appended 'changed function plus appended function' diff --git a/t/t4054-diff-bogus-tree.sh b/t/t4054-diff-bogus-tree.sh index 18f42c5fff..fcae82fffa 100755 --- a/t/t4054-diff-bogus-tree.sh +++ b/t/t4054-diff-bogus-tree.sh @@ -19,37 +19,37 @@ test_expect_success 'create tree with matching file' ' ' test_expect_success 'raw diff shows null sha1 (addition)' ' - echo ":000000 100644 $_z40 $_z40 A foo" >expect && + echo ":000000 100644 $ZERO_OID $ZERO_OID A foo" >expect && git diff-tree $EMPTY_TREE $bogus_tree >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (removal)' ' - echo ":100644 000000 $_z40 $_z40 D foo" >expect && + echo ":100644 000000 $ZERO_OID $ZERO_OID D foo" >expect && git diff-tree $bogus_tree $EMPTY_TREE >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (modification)' ' - echo ":100644 100644 $blob $_z40 M foo" >expect && + echo ":100644 100644 $blob $ZERO_OID M foo" >expect && git diff-tree $good_tree $bogus_tree >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (other direction)' ' - echo ":100644 100644 $_z40 $blob M foo" >expect && + echo ":100644 100644 $ZERO_OID $blob M foo" >expect && git diff-tree $bogus_tree $good_tree >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (reverse)' ' - echo ":100644 100644 $_z40 $blob M foo" >expect && + echo ":100644 100644 $ZERO_OID $blob M foo" >expect && git diff-tree -R $good_tree $bogus_tree >actual && test_cmp expect actual ' test_expect_success 'raw diff shows null sha1 (index)' ' - echo ":100644 100644 $_z40 $blob M foo" >expect && + echo ":100644 100644 $ZERO_OID $blob M foo" >expect && git diff-index $bogus_tree >actual && test_cmp expect actual ' diff --git a/t/t4058-diff-duplicates.sh b/t/t4058-diff-duplicates.sh index 0a23242cb6..c24ee175ef 100755 --- a/t/t4058-diff-duplicates.sh +++ b/t/t4058-diff-duplicates.sh @@ -59,12 +59,12 @@ test_expect_success 'create trees with duplicate entries' ' test_expect_success 'diff-tree between trees' ' { - printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" && - printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" && - printf ":000000 100644 $_z40 $blob_two A\touter/inner\n" && - printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" && - printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" && - printf ":100644 000000 $blob_two $_z40 D\touter/inner\n" + printf ":000000 100644 $ZERO_OID $blob_two A\touter/inner\n" && + printf ":000000 100644 $ZERO_OID $blob_two A\touter/inner\n" && + printf ":000000 100644 $ZERO_OID $blob_two A\touter/inner\n" && + printf ":100644 000000 $blob_two $ZERO_OID D\touter/inner\n" && + printf ":100644 000000 $blob_two $ZERO_OID D\touter/inner\n" && + printf ":100644 000000 $blob_two $ZERO_OID D\touter/inner\n" } >expect && git diff-tree -r --no-abbrev one two >actual && test_cmp expect actual diff --git a/t/t4060-diff-submodule-option-diff-format.sh b/t/t4060-diff-submodule-option-diff-format.sh index 4b168d0ed7..9dcb69df5c 100755 --- a/t/t4060-diff-submodule-option-diff-format.sh +++ b/t/t4060-diff-submodule-option-diff-format.sh @@ -392,9 +392,7 @@ test_expect_success 'typechanged submodule(blob->submodule)' ' commit_file sm1 && test_expect_success 'submodule is up to date' ' git diff-index -p --submodule=diff HEAD >actual && - cat >expected <<-EOF && - EOF - test_cmp expected actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked content' ' @@ -408,17 +406,17 @@ test_expect_success 'submodule contains untracked content' ' test_expect_success 'submodule contains untracked content (untracked ignored)' ' git diff-index -p --ignore-submodules=untracked --submodule=diff HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked content (dirty ignored)' ' git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked content (all ignored)' ' git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked and modified content' ' @@ -458,13 +456,13 @@ test_expect_success 'submodule contains untracked and modified content (untracke test_expect_success 'submodule contains untracked and modified content (dirty ignored)' ' echo new > sm1/foo6 && git diff-index -p --ignore-submodules=dirty --submodule=diff HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains untracked and modified content (all ignored)' ' echo new > sm1/foo6 && git diff-index -p --ignore-submodules --submodule=diff HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'submodule contains modified content' ' @@ -549,7 +547,7 @@ test_expect_success 'modified submodule contains untracked content (dirty ignore test_expect_success 'modified submodule contains untracked content (all ignored)' ' git diff-index -p --ignore-submodules=all --submodule=diff HEAD >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'modified submodule contains untracked and modified content' ' @@ -609,7 +607,7 @@ test_expect_success 'modified submodule contains untracked and modified content test_expect_success 'modified submodule contains untracked and modified content (all ignored)' ' echo modification >> sm1/foo6 && git diff-index -p --ignore-submodules --submodule=diff HEAD >actual && - ! test -s actual + test_must_be_empty actual ' # NOT OK @@ -721,7 +719,7 @@ test_expect_success 'given commit' ' test_expect_success 'setup .git file for sm2' ' (cd sm2 && REAL="$(pwd)/../.real" && - mv .git "$REAL" + mv .git "$REAL" && echo "gitdir: $REAL" >.git) ' diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh index ce8567f496..b99e65c086 100755 --- a/t/t4116-apply-reverse.sh +++ b/t/t4116-apply-reverse.sh @@ -42,7 +42,7 @@ test_expect_success 'apply in reverse' ' git reset --hard second && git apply --reverse --binary --index patch && git diff >diff && - test_cmp /dev/null diff + test_must_be_empty diff ' diff --git a/t/t4121-apply-diffs.sh b/t/t4121-apply-diffs.sh index aff551a1d7..66368effd5 100755 --- a/t/t4121-apply-diffs.sh +++ b/t/t4121-apply-diffs.sh @@ -27,6 +27,6 @@ test_expect_success 'setup' \ test_expect_success \ 'check if contextually independent diffs for the same file apply' \ - '( git diff test~2 test~1; git diff test~1 test~0 )| git apply' + '( git diff test~2 test~1 && git diff test~1 test~0 )| git apply' test_done diff --git a/t/t4124-apply-ws-rule.sh b/t/t4124-apply-ws-rule.sh index 4fc27c51f7..7e32237a2a 100755 --- a/t/t4124-apply-ws-rule.sh +++ b/t/t4124-apply-ws-rule.sh @@ -100,7 +100,7 @@ test_expect_success 'whitespace=warn, default rule' ' test_expect_success 'whitespace=error-all, default rule' ' test_must_fail apply_patch --whitespace=error-all && - ! test -s target + test_must_be_empty target ' diff --git a/t/t4132-apply-removal.sh b/t/t4132-apply-removal.sh index a2bc1cd37d..fec1d6fa51 100755 --- a/t/t4132-apply-removal.sh +++ b/t/t4132-apply-removal.sh @@ -49,8 +49,7 @@ test_expect_success setup ' sed -e "s/TS0/$timeGMT/" -e "s/TS1/$epocGMT/" <d >removeGMT.patch && sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest2/" <d >removeWest2.patch && - echo something >something && - >empty + echo something >something ' for patch in *.patch @@ -81,7 +80,7 @@ do git add file && git apply --index $patch && test -f file && - test_cmp empty file + test_must_be_empty file ;; remove*) # must remove the file diff --git a/t/t4135-apply-weird-filenames.sh b/t/t4135-apply-weird-filenames.sh index c7c688fcc4..6bc3fb97a7 100755 --- a/t/t4135-apply-weird-filenames.sh +++ b/t/t4135-apply-weird-filenames.sh @@ -15,15 +15,7 @@ test_expect_success 'setup' ' git checkout -f preimage^0 && git read-tree -u --reset HEAD && git update-index --refresh - } && - - test_when_finished "rm -f \"tab embedded.txt\"" && - test_when_finished "rm -f '\''\"quoteembedded\".txt'\''" && - if test_have_prereq !MINGW && - touch -- "tab embedded.txt" '\''"quoteembedded".txt'\'' - then - test_set_prereq FUNNYNAMES - fi + } ' try_filename() { diff --git a/t/t4150-am.sh b/t/t4150-am.sh index 1eccfb71d0..55b577d919 100755 --- a/t/t4150-am.sh +++ b/t/t4150-am.sh @@ -69,13 +69,15 @@ test_expect_success 'setup: messages' ' EOF - cat >scissors-msg <<-\EOF && - Test git-am with scissors line + cat >msg-without-scissors-line <<-\EOF && + Test that git-am --scissors cuts at the scissors line This line should be included in the commit message. EOF - cat - scissors-msg >no-scissors-msg <<-\EOF && + printf "Subject: " >subject-prefix && + + cat - subject-prefix msg-without-scissors-line >msg-with-scissors-line <<-\EOF && This line should not be included in the commit message with --scissors enabled. - - >8 - - remove everything above this line - - >8 - - @@ -140,26 +142,25 @@ test_expect_success setup ' echo "# User $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL>" && echo "# Date $test_tick 25200" && echo "# $(git show --pretty="%aD" -s second)" && - echo "# Node ID $_z40" && - echo "# Parent $_z40" && + echo "# Node ID $ZERO_OID" && + echo "# Parent $ZERO_OID" && cat msg && echo && git diff-tree --no-commit-id -p second } >patch1-hg.eml && - echo scissors-file >scissors-file && - git add scissors-file && - git commit -F scissors-msg && - git tag scissors && - git format-patch --stdout scissors^ >scissors-patch.eml && + echo file >file && + git add file && + git commit -F msg-without-scissors-line && + git tag expected-for-scissors && git reset --hard HEAD^ && - echo no-scissors-file >no-scissors-file && - git add no-scissors-file && - git commit -F no-scissors-msg && - git tag no-scissors && - git format-patch --stdout no-scissors^ >no-scissors-patch.eml && + echo file >file && + git add file && + git commit -F msg-with-scissors-line && + git tag expected-for-no-scissors && + git format-patch --stdout expected-for-no-scissors^ >patch-with-scissors-line.eml && git reset --hard HEAD^ && sed -n -e "3,\$p" msg >file && @@ -416,10 +417,10 @@ test_expect_success 'am --scissors cuts the message at the scissors line' ' rm -fr .git/rebase-apply && git reset --hard && git checkout second && - git am --scissors scissors-patch.eml && + git am --scissors patch-with-scissors-line.eml && test_path_is_missing .git/rebase-apply && - git diff --exit-code scissors && - test_cmp_rev scissors HEAD + git diff --exit-code expected-for-scissors && + test_cmp_rev expected-for-scissors HEAD ' test_expect_success 'am --no-scissors overrides mailinfo.scissors' ' @@ -427,10 +428,10 @@ test_expect_success 'am --no-scissors overrides mailinfo.scissors' ' git reset --hard && git checkout second && test_config mailinfo.scissors true && - git am --no-scissors no-scissors-patch.eml && + git am --no-scissors patch-with-scissors-line.eml && test_path_is_missing .git/rebase-apply && - git diff --exit-code no-scissors && - test_cmp_rev no-scissors HEAD + git diff --exit-code expected-for-no-scissors && + test_cmp_rev expected-for-no-scissors HEAD ' test_expect_success 'setup: new author and committer' ' @@ -651,7 +652,7 @@ test_expect_success 'am -3 -q is quiet' ' git checkout -f lorem2 && git reset base3way --hard && git am -3 -q lorem-move.patch >output.out 2>&1 && - ! test -s output.out + test_must_be_empty output.out ' test_expect_success 'am pauses on conflict' ' @@ -874,7 +875,7 @@ test_expect_success 'am -q is quiet' ' git checkout first && test_tick && git am -q <patch1 >output.out 2>&1 && - ! test -s output.out + test_must_be_empty output.out ' test_expect_success 'am empty-file does not infloop' ' diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh index deafaa3e07..55b7750ade 100755 --- a/t/t4200-rerere.sh +++ b/t/t4200-rerere.sh @@ -166,7 +166,7 @@ test_expect_success 'first postimage wins' ' git commit -q -a -m "prefer first over second" && test -f $rr/postimage && - oldmtimepost=$(test-tool chmtime -v -60 $rr/postimage | cut -f 1) && + oldmtimepost=$(test-tool chmtime --get -60 $rr/postimage) && git checkout -b third master && git show second^:a1 | sed "s/To die: t/To die! T/" >a1 && @@ -179,7 +179,7 @@ test_expect_success 'first postimage wins' ' ' test_expect_success 'rerere updates postimage timestamp' ' - newmtimepost=$(test-tool chmtime -v +0 $rr/postimage | cut -f 1) && + newmtimepost=$(test-tool chmtime --get $rr/postimage) && test $oldmtimepost -lt $newmtimepost ' @@ -243,7 +243,7 @@ rerere_gc_custom_expiry_test () { five_days="$1" right_now="$2" test_expect_success "rerere gc with custom expiry ($five_days, $right_now)" ' rm -fr .git/rr-cache && - rr=.git/rr-cache/$_z40 && + rr=.git/rr-cache/$ZERO_OID && mkdir -p "$rr" && >"$rr/preimage" && >"$rr/postimage" && @@ -267,8 +267,7 @@ rerere_gc_custom_expiry_test () { git -c "gc.rerereresolved=$right_now" \ -c "gc.rerereunresolved=$right_now" rerere gc && find .git/rr-cache -type f | sort >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' } @@ -512,7 +511,7 @@ test_expect_success 'multiple identical conflicts' ' count_pre_post 2 0 && # Pretend that the conflicts were made quite some time ago - find .git/rr-cache/ -type f | xargs test-tool chmtime -172800 && + test-tool chmtime -172800 $(find .git/rr-cache/ -type f) && # Unresolved entries have not expired yet git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && @@ -536,9 +535,8 @@ test_expect_success 'multiple identical conflicts' ' # We resolved file1 and file2 git rerere && - >expect && git rerere remaining >actual && - test_cmp expect actual && + test_must_be_empty actual && # We must have recorded both of them count_pre_post 2 2 && @@ -548,9 +546,8 @@ test_expect_success 'multiple identical conflicts' ' test_must_fail git merge six.1 && git rerere && - >expect && git rerere remaining >actual && - test_cmp expect actual && + test_must_be_empty actual && concat_insert short 6.1 6.2 >file1.expect && concat_insert long 6.1 6.2 >file2.expect && @@ -568,7 +565,7 @@ test_expect_success 'multiple identical conflicts' ' git rerere && # Pretend that the resolutions are old again - find .git/rr-cache/ -type f | xargs test-tool chmtime -172800 && + test-tool chmtime -172800 $(find .git/rr-cache/ -type f) && # Resolved entries have not expired yet git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc && @@ -580,4 +577,98 @@ test_expect_success 'multiple identical conflicts' ' count_pre_post 0 0 ' +test_expect_success 'rerere with unexpected conflict markers does not crash' ' + git reset --hard && + + git checkout -b branch-1 master && + echo "bar" >test && + git add test && + git commit -q -m two && + + git reset --hard && + git checkout -b branch-2 master && + echo "foo" >test && + git add test && + git commit -q -a -m one && + + test_must_fail git merge branch-1 && + echo "<<<<<<< a" >test && + git rerere && + + git rerere clear +' + +test_expect_success 'rerere with inner conflict markers' ' + git reset --hard && + + git checkout -b A master && + echo "bar" >test && + git add test && + git commit -q -m two && + echo "baz" >test && + git add test && + git commit -q -m three && + + git reset --hard && + git checkout -b B master && + echo "foo" >test && + git add test && + git commit -q -a -m one && + + test_must_fail git merge A~ && + git add test && + git commit -q -m "will solve conflicts later" && + test_must_fail git merge A && + + echo "resolved" >test && + git add test && + git commit -q -m "solved conflict" && + + echo "resolved" >expect && + + git reset --hard HEAD~~ && + test_must_fail git merge A~ && + git add test && + git commit -q -m "will solve conflicts later" && + test_must_fail git merge A && + cat test >actual && + test_cmp expect actual && + + git add test && + git commit -m "rerere solved conflict" && + git reset --hard HEAD~ && + test_must_fail git merge A && + cat test >actual && + test_cmp expect actual +' + +test_expect_success 'setup simple stage 1 handling' ' + test_create_repo stage_1_handling && + ( + cd stage_1_handling && + + test_seq 1 10 >original && + git add original && + git commit -m original && + + git checkout -b A master && + git mv original A && + git commit -m "rename to A" && + + git checkout -b B master && + git mv original B && + git commit -m "rename to B" + ) +' + +test_expect_success 'test simple stage 1 handling' ' + ( + cd stage_1_handling && + + git config rerere.enabled true && + git checkout A^0 && + test_must_fail git merge B^0 + ) +' + test_done diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index ff6649ed9a..d3a7ce6bbb 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -59,7 +59,7 @@ test_expect_success 'setup' ' fuzz() { file=$1 && sed " - s/$_x40/OBJECT_NAME/g + s/$OID_REGEX/OBJECT_NAME/g s/$_x35/OBJID/g s/^ \{6\}[CTa].*/ SUBJECT/g s/^ \{8\}[^ ].*/ CONTINUATION/g @@ -192,7 +192,7 @@ test_expect_success 'shortlog with revision pseudo options' ' test_expect_success 'shortlog with --output=<file>' ' git shortlog --output=shortlog -1 master >output && - test ! -s output && + test_must_be_empty output && test_line_count = 3 shortlog ' diff --git a/t/t4202-log.sh b/t/t4202-log.sh index 25b1f8cc73..153a506151 100755 --- a/t/t4202-log.sh +++ b/t/t4202-log.sh @@ -340,10 +340,9 @@ test_expect_success PCRE 'log -F -E --perl-regexp --grep=<pcre> uses PCRE' ' ' test_expect_success 'log with grep.patternType configuration' ' - >expect && git -c grep.patterntype=fixed \ log -1 --pretty=tformat:%s --grep=s.c.nd >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'log with grep.patternType configuration and command line' ' @@ -1556,12 +1555,28 @@ test_expect_success GPG 'setup signed branch' ' git commit -S -m signed_commit ' +test_expect_success GPGSM 'setup signed branch x509' ' + test_when_finished "git reset --hard && git checkout master" && + git checkout -b signed-x509 master && + echo foo >foo && + git add foo && + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + git commit -S -m signed_commit +' + test_expect_success GPG 'log --graph --show-signature' ' git log --graph --show-signature -n1 signed >actual && grep "^| gpg: Signature made" actual && grep "^| gpg: Good signature" actual ' +test_expect_success GPGSM 'log --graph --show-signature x509' ' + git log --graph --show-signature -n1 signed-x509 >actual && + grep "^| gpgsm: Signature made" actual && + grep "^| gpgsm: Good signature" actual +' + test_expect_success GPG 'log --graph --show-signature for merged tag' ' test_when_finished "git reset --hard && git checkout master" && git checkout -b plain master && @@ -1581,6 +1596,27 @@ test_expect_success GPG 'log --graph --show-signature for merged tag' ' grep "^| | gpg: Good signature" actual ' +test_expect_success GPGSM 'log --graph --show-signature for merged tag x509' ' + test_when_finished "git reset --hard && git checkout master" && + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + git checkout -b plain-x509 master && + echo aaa >bar && + git add bar && + git commit -m bar_commit && + git checkout -b tagged-x509 master && + echo bbb >baz && + git add baz && + git commit -m baz_commit && + git tag -s -m signed_tag_msg signed_tag_x509 && + git checkout plain-x509 && + git merge --no-ff -m msg signed_tag_x509 && + git log --graph --show-signature -n1 plain-x509 >actual && + grep "^|\\\ merged tag" actual && + grep "^| | gpgsm: Signature made" actual && + grep "^| | gpgsm: Good signature" actual +' + test_expect_success GPG '--no-show-signature overrides --show-signature' ' git log -1 --show-signature --no-show-signature signed >actual && ! grep "^gpg:" actual @@ -1625,9 +1661,8 @@ test_expect_success 'log diagnoses bogus HEAD' ' ' test_expect_success 'log does not default to HEAD when rev input is given' ' - >expect && git log --branches=does-not-exist >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'set up --source tests' ' diff --git a/t/t4203-mailmap.sh b/t/t4203-mailmap.sh index 0dd8b65d7c..43b1522ea2 100755 --- a/t/t4203-mailmap.sh +++ b/t/t4203-mailmap.sh @@ -461,11 +461,9 @@ test_expect_success 'Grep author with log.mailmap' ' test_cmp expect actual ' ->expect - test_expect_success 'Only grep replaced author with --use-mailmap' ' git log --use-mailmap --author "<cto@coompany.xx>" >actual && - test_cmp expect actual + test_must_be_empty actual ' # git blame diff --git a/t/t4205-log-pretty-formats.sh b/t/t4205-log-pretty-formats.sh index 591f35daaf..978a8a66ff 100755 --- a/t/t4205-log-pretty-formats.sh +++ b/t/t4205-log-pretty-formats.sh @@ -516,22 +516,22 @@ test_expect_success 'log decoration properly follows tag chain' ' git commit --amend -m shorter && git log --no-walk --tags --pretty="%H %d" --decorate=full >actual && cat <<-EOF >expected && - $head1 (tag: refs/tags/tag2) $head2 (tag: refs/tags/message-one) $old_head1 (tag: refs/tags/message-two) + $head1 (tag: refs/tags/tag2) EOF - sort actual >actual1 && + sort -k3 actual >actual1 && test_cmp expected actual1 ' test_expect_success 'clean log decoration' ' git log --no-walk --tags --pretty="%H %D" --decorate=full >actual && cat >expected <<-EOF && - $head1 tag: refs/tags/tag2 $head2 tag: refs/tags/message-one $old_head1 tag: refs/tags/message-two + $head1 tag: refs/tags/tag2 EOF - sort actual >actual1 && + sort -k3 actual >actual1 && test_cmp expected actual1 ' @@ -598,4 +598,27 @@ test_expect_success ':only and :unfold work together' ' test_cmp expect actual ' +test_expect_success 'trailer parsing not fooled by --- line' ' + git commit --allow-empty -F - <<-\EOF && + this is the subject + + This is the body. The message has a "---" line which would confuse a + message+patch parser. But here we know we have only a commit message, + so we get it right. + + trailer: wrong + --- + This is more body. + + trailer: right + EOF + + { + echo "trailer: right" && + echo + } >expect && + git log --no-walk --format="%(trailers)" >actual && + test_cmp expect actual +' + test_done diff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh index a1705f70cf..4c8f3b8e1b 100755 --- a/t/t4208-log-magic-pathspec.sh +++ b/t/t4208-log-magic-pathspec.sh @@ -25,6 +25,32 @@ test_expect_success '"git log :/a -- " should not be ambiguous' ' git log :/a -- ' +test_expect_success '"git log :/detached -- " should find a commit only in HEAD' ' + test_when_finished "git checkout master" && + git checkout --detach && + # Must manually call `test_tick` instead of using `test_commit`, + # because the latter additionally creates a tag, which would make + # the commit reachable not only via HEAD. + test_tick && + git commit --allow-empty -m detached && + test_tick && + git commit --allow-empty -m something-else && + git log :/detached -- +' + +test_expect_success '"git log :/detached -- " should not find an orphaned commit' ' + test_must_fail git log :/detached -- +' + +test_expect_success '"git log :/detached -- " should find HEAD only of own worktree' ' + git worktree add other-tree HEAD && + git -C other-tree checkout --detach && + test_tick && + git -C other-tree commit --allow-empty -m other-detached && + git -C other-tree log :/other-detached -- && + test_must_fail git log :/other-detached -- +' + test_expect_success '"git log -- :/a" should not be ambiguous' ' git log -- :/a ' @@ -45,8 +71,9 @@ test_expect_success 'git log -- :' ' ' test_expect_success 'git log HEAD -- :/' ' + initial=$(git rev-parse --short HEAD^) && cat >expected <<-EOF && - 24b24cf initial + $initial initial EOF (cd sub && git log --oneline HEAD -- :/ >../actual) && test_cmp expected actual diff --git a/t/t4210-log-i18n.sh b/t/t4210-log-i18n.sh index e585fe6129..7c519436ef 100755 --- a/t/t4210-log-i18n.sh +++ b/t/t4210-log-i18n.sh @@ -44,15 +44,13 @@ test_expect_success !MINGW 'log --grep searches in log output encoding (latin1)' ' test_expect_success !MINGW 'log --grep does not find non-reencoded values (utf8)' ' - >expect && git log --encoding=utf8 --format=%s --grep=$latin1_e >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'log --grep does not find non-reencoded values (latin1)' ' - >expect && git log --encoding=ISO-8859-1 --format=%s --grep=$utf8_e >actual && - test_cmp expect actual + test_must_be_empty actual ' test_done diff --git a/t/t4211-line-log.sh b/t/t4211-line-log.sh index d0377fae5c..ef1322148e 100755 --- a/t/t4211-line-log.sh +++ b/t/t4211-line-log.sh @@ -60,7 +60,6 @@ test_bad_opts "-L 1:nonexistent" "There is no path" test_bad_opts "-L 1:simple" "There is no path" test_bad_opts "-L '/foo:b.c'" "argument not .start,end:file" test_bad_opts "-L 1000:b.c" "has only.*lines" -test_bad_opts "-L 1,1000:b.c" "has only.*lines" test_bad_opts "-L :b.c" "argument not .start,end:file" test_bad_opts "-L :foo:b.c" "no match" @@ -86,12 +85,12 @@ test_expect_success '-L ,Y (Y == nlines)' ' test_expect_success '-L ,Y (Y == nlines + 1)' ' n=$(expr $(wc -l <b.c) + 1) && - test_must_fail git log -L ,$n:b.c + git log -L ,$n:b.c ' test_expect_success '-L ,Y (Y == nlines + 2)' ' n=$(expr $(wc -l <b.c) + 2) && - test_must_fail git log -L ,$n:b.c + git log -L ,$n:b.c ' test_expect_success '-L with --first-parent and a merge' ' @@ -102,7 +101,7 @@ test_expect_success '-L with --first-parent and a merge' ' test_expect_success '-L with --output' ' git checkout parallel-change && git log --output=log -L :main:b.c >output && - test ! -s output && + test_must_be_empty output && test_line_count = 70 log ' diff --git a/t/t4212-log-corrupt.sh b/t/t4212-log-corrupt.sh index 22aa8b7c0e..03b952c90d 100755 --- a/t/t4212-log-corrupt.sh +++ b/t/t4212-log-corrupt.sh @@ -26,22 +26,20 @@ test_expect_success 'git log with broken author email' ' echo echo " foo" } >expect.out && - : >expect.err && git log broken_email >actual.out 2>actual.err && test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_must_be_empty actual.err ' test_expect_success 'git log --format with broken author email' ' echo "A U Thor+author@example.com+Thu Apr 7 15:13:13 2005 -0700" >expect.out && - : >expect.err && git log --format="%an+%ae+%ad" broken_email >actual.out 2>actual.err && test_cmp expect.out actual.out && - test_cmp expect.err actual.err + test_must_be_empty actual.err ' munge_author_date () { diff --git a/t/t4254-am-corrupt.sh b/t/t4254-am-corrupt.sh index 168739c721..fd3bdbfe2c 100755 --- a/t/t4254-am-corrupt.sh +++ b/t/t4254-am-corrupt.sh @@ -25,7 +25,7 @@ test_expect_success setup ' # fatal: unable to write file '(null)' mode 100644: Bad address # Also, it had the unwanted side-effect of deleting f. test_expect_success 'try to apply corrupted patch' ' - test_must_fail git am bad-patch.diff 2>actual + test_must_fail git -c advice.amWorkDir=false am bad-patch.diff 2>actual ' test_expect_success 'compare diagnostic; ensure file is still here' ' diff --git a/t/t4256-am-format-flowed.sh b/t/t4256-am-format-flowed.sh new file mode 100755 index 0000000000..6340310e9a --- /dev/null +++ b/t/t4256-am-format-flowed.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='test format=flowed support of git am' + +. ./test-lib.sh + +test_expect_success 'setup' ' + cp "$TEST_DIRECTORY/t4256/1/mailinfo.c.orig" mailinfo.c && + git add mailinfo.c && + git commit -m initial +' + +test_expect_success 'am with format=flowed' ' + git am <"$TEST_DIRECTORY/t4256/1/patch" >stdout 2>stderr && + test_i18ngrep "warning: Patch sent with format=flowed" stderr && + test_cmp "$TEST_DIRECTORY/t4256/1/mailinfo.c" mailinfo.c +' + +test_done diff --git a/t/t4256/1/mailinfo.c b/t/t4256/1/mailinfo.c new file mode 100644 index 0000000000..b395adbdf2 --- /dev/null +++ b/t/t4256/1/mailinfo.c @@ -0,0 +1,1245 @@ +#include "cache.h" +#include "config.h" +#include "utf8.h" +#include "strbuf.h" +#include "mailinfo.h" + +static void cleanup_space(struct strbuf *sb) +{ + size_t pos, cnt; + for (pos = 0; pos < sb->len; pos++) { + if (isspace(sb->buf[pos])) { + sb->buf[pos] = ' '; + for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++); + strbuf_remove(sb, pos + 1, cnt); + } + } +} + +static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email) +{ + struct strbuf *src = name; + if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') || + strchr(name->buf, '<') || strchr(name->buf, '>')) + src = email; + else if (name == out) + return; + strbuf_reset(out); + strbuf_addbuf(out, src); +} + +static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line) +{ + /* John Doe <johndoe> */ + + char *bra, *ket; + /* This is fallback, so do not bother if we already have an + * e-mail address. + */ + if (mi->email.len) + return; + + bra = strchr(line->buf, '<'); + if (!bra) + return; + ket = strchr(bra, '>'); + if (!ket) + return; + + strbuf_reset(&mi->email); + strbuf_add(&mi->email, bra + 1, ket - bra - 1); + + strbuf_reset(&mi->name); + strbuf_add(&mi->name, line->buf, bra - line->buf); + strbuf_trim(&mi->name); + get_sane_name(&mi->name, &mi->name, &mi->email); +} + +static const char *unquote_comment(struct strbuf *outbuf, const char *in) +{ + int c; + int take_next_literally = 0; + + strbuf_addch(outbuf, '('); + + while ((c = *in++) != 0) { + if (take_next_literally == 1) { + take_next_literally = 0; + } else { + switch (c) { + case '\\': + take_next_literally = 1; + continue; + case '(': + in = unquote_comment(outbuf, in); + continue; + case ')': + strbuf_addch(outbuf, ')'); + return in; + } + } + + strbuf_addch(outbuf, c); + } + + return in; +} + +static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in) +{ + int c; + int take_next_literally = 0; + + while ((c = *in++) != 0) { + if (take_next_literally == 1) { + take_next_literally = 0; + } else { + switch (c) { + case '\\': + take_next_literally = 1; + continue; + case '"': + return in; + } + } + + strbuf_addch(outbuf, c); + } + + return in; +} + +static void unquote_quoted_pair(struct strbuf *line) +{ + struct strbuf outbuf; + const char *in = line->buf; + int c; + + strbuf_init(&outbuf, line->len); + + while ((c = *in++) != 0) { + switch (c) { + case '"': + in = unquote_quoted_string(&outbuf, in); + continue; + case '(': + in = unquote_comment(&outbuf, in); + continue; + } + + strbuf_addch(&outbuf, c); + } + + strbuf_swap(&outbuf, line); + strbuf_release(&outbuf); + +} + +static void handle_from(struct mailinfo *mi, const struct strbuf *from) +{ + char *at; + size_t el; + struct strbuf f; + + strbuf_init(&f, from->len); + strbuf_addbuf(&f, from); + + unquote_quoted_pair(&f); + + at = strchr(f.buf, '@'); + if (!at) { + parse_bogus_from(mi, from); + goto out; + } + + /* + * If we already have one email, don't take any confusing lines + */ + if (mi->email.len && strchr(at + 1, '@')) + goto out; + + /* Pick up the string around '@', possibly delimited with <> + * pair; that is the email part. + */ + while (at > f.buf) { + char c = at[-1]; + if (isspace(c)) + break; + if (c == '<') { + at[-1] = ' '; + break; + } + at--; + } + el = strcspn(at, " \n\t\r\v\f>"); + strbuf_reset(&mi->email); + strbuf_add(&mi->email, at, el); + strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); + + /* The remainder is name. It could be + * + * - "John Doe <john.doe@xz>" (a), or + * - "john.doe@xz (John Doe)" (b), or + * - "John (zzz) Doe <john.doe@xz> (Comment)" (c) + * + * but we have removed the email part, so + * + * - remove extra spaces which could stay after email (case 'c'), and + * - trim from both ends, possibly removing the () pair at the end + * (cases 'a' and 'b'). + */ + cleanup_space(&f); + strbuf_trim(&f); + if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') { + strbuf_remove(&f, 0, 1); + strbuf_setlen(&f, f.len - 1); + } + + get_sane_name(&mi->name, &f, &mi->email); +out: + strbuf_release(&f); +} + +static void handle_header(struct strbuf **out, const struct strbuf *line) +{ + if (!*out) { + *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(*out, line->len); + } else + strbuf_reset(*out); + + strbuf_addbuf(*out, line); +} + +/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt + * to have enough heuristics to grok MIME encoded patches often found + * on our mailing lists. For example, we do not even treat header lines + * case insensitively. + */ + +static int slurp_attr(const char *line, const char *name, struct strbuf *attr) +{ + const char *ends, *ap = strcasestr(line, name); + size_t sz; + + strbuf_setlen(attr, 0); + if (!ap) + return 0; + ap += strlen(name); + if (*ap == '"') { + ap++; + ends = "\""; + } + else + ends = "; \t"; + sz = strcspn(ap, ends); + strbuf_add(attr, ap, sz); + return 1; +} + +static int has_attr_value(const char *line, const char *name, const char *value) +{ + struct strbuf sb = STRBUF_INIT; + int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value); + strbuf_release(&sb); + return rc; +} + +static void handle_content_type(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); + strbuf_init(boundary, line->len); + + mi->format_flowed = has_attr_value(line->buf, "format=", "flowed"); + mi->delsp = has_attr_value(line->buf, "delsp=", "yes"); + + if (slurp_attr(line->buf, "boundary=", boundary)) { + strbuf_insert(boundary, 0, "--", 2); + if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) { + error("Too many boundaries to handle"); + mi->input_error = -1; + mi->content_top = &mi->content[MAX_BOUNDARIES] - 1; + return; + } + *(mi->content_top) = boundary; + boundary = NULL; + } + slurp_attr(line->buf, "charset=", &mi->charset); + + if (boundary) { + strbuf_release(boundary); + free(boundary); + } +} + +static void handle_content_transfer_encoding(struct mailinfo *mi, + const struct strbuf *line) +{ + if (strcasestr(line->buf, "base64")) + mi->transfer_encoding = TE_BASE64; + else if (strcasestr(line->buf, "quoted-printable")) + mi->transfer_encoding = TE_QP; + else + mi->transfer_encoding = TE_DONTCARE; +} + +static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line) +{ + struct strbuf *content_top = *(mi->content_top); + + return ((content_top->len <= line->len) && + !memcmp(line->buf, content_top->buf, content_top->len)); +} + +static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject) +{ + size_t at = 0; + + while (at < subject->len) { + char *pos; + size_t remove; + + switch (subject->buf[at]) { + case 'r': case 'R': + if (subject->len <= at + 3) + break; + if ((subject->buf[at + 1] == 'e' || + subject->buf[at + 1] == 'E') && + subject->buf[at + 2] == ':') { + strbuf_remove(subject, at, 3); + continue; + } + at++; + break; + case ' ': case '\t': case ':': + strbuf_remove(subject, at, 1); + continue; + case '[': + pos = strchr(subject->buf + at, ']'); + if (!pos) + break; + remove = pos - subject->buf + at + 1; + if (!mi->keep_non_patch_brackets_in_subject || + (7 <= remove && + memmem(subject->buf + at, remove, "PATCH", 5))) + strbuf_remove(subject, at, remove); + else { + at += remove; + /* + * If the input had a space after the ], keep + * it. We don't bother with finding the end of + * the space, since we later normalize it + * anyway. + */ + if (isspace(subject->buf[at])) + at += 1; + } + continue; + } + break; + } + strbuf_trim(subject); +} + +#define MAX_HDR_PARSED 10 +static const char *header[MAX_HDR_PARSED] = { + "From","Subject","Date", +}; + +static inline int cmp_header(const struct strbuf *line, const char *hdr) +{ + int len = strlen(hdr); + return !strncasecmp(line->buf, hdr, len) && line->len > len && + line->buf[len] == ':' && isspace(line->buf[len + 1]); +} + +static int is_format_patch_separator(const char *line, int len) +{ + static const char SAMPLE[] = + "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n"; + const char *cp; + + if (len != strlen(SAMPLE)) + return 0; + if (!skip_prefix(line, "From ", &cp)) + return 0; + if (strspn(cp, "0123456789abcdef") != 40) + return 0; + cp += 40; + return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line)); +} + +static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047) +{ + const char *in = q_seg->buf; + int c; + struct strbuf *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(out, q_seg->len); + + while ((c = *in++) != 0) { + if (c == '=') { + int ch, d = *in; + if (d == '\n' || !d) + break; /* drop trailing newline */ + ch = hex2chr(in); + if (ch >= 0) { + strbuf_addch(out, ch); + in += 2; + continue; + } + /* garbage -- fall through */ + } + if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */ + c = 0x20; + strbuf_addch(out, c); + } + return out; +} + +static struct strbuf *decode_b_segment(const struct strbuf *b_seg) +{ + /* Decode in..ep, possibly in-place to ot */ + int c, pos = 0, acc = 0; + const char *in = b_seg->buf; + struct strbuf *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(out, b_seg->len); + + while ((c = *in++) != 0) { + if (c == '+') + c = 62; + else if (c == '/') + c = 63; + else if ('A' <= c && c <= 'Z') + c -= 'A'; + else if ('a' <= c && c <= 'z') + c -= 'a' - 26; + else if ('0' <= c && c <= '9') + c -= '0' - 52; + else + continue; /* garbage */ + switch (pos++) { + case 0: + acc = (c << 2); + break; + case 1: + strbuf_addch(out, (acc | (c >> 4))); + acc = (c & 15) << 4; + break; + case 2: + strbuf_addch(out, (acc | (c >> 2))); + acc = (c & 3) << 6; + break; + case 3: + strbuf_addch(out, (acc | c)); + acc = pos = 0; + break; + } + } + return out; +} + +static int convert_to_utf8(struct mailinfo *mi, + struct strbuf *line, const char *charset) +{ + char *out; + + if (!mi->metainfo_charset || !charset || !*charset) + return 0; + + if (same_encoding(mi->metainfo_charset, charset)) + return 0; + out = reencode_string(line->buf, mi->metainfo_charset, charset); + if (!out) { + mi->input_error = -1; + return error("cannot convert from %s to %s", + charset, mi->metainfo_charset); + } + strbuf_attach(line, out, strlen(out), strlen(out)); + return 0; +} + +static void decode_header(struct mailinfo *mi, struct strbuf *it) +{ + char *in, *ep, *cp; + struct strbuf outbuf = STRBUF_INIT, *dec; + struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT; + int found_error = 1; /* pessimism */ + + in = it->buf; + while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) { + int encoding; + strbuf_reset(&charset_q); + strbuf_reset(&piecebuf); + + if (in != ep) { + /* + * We are about to process an encoded-word + * that begins at ep, but there is something + * before the encoded word. + */ + char *scan; + for (scan = in; scan < ep; scan++) + if (!isspace(*scan)) + break; + + if (scan != ep || in == it->buf) { + /* + * We should not lose that "something", + * unless we have just processed an + * encoded-word, and there is only LWS + * before the one we are about to process. + */ + strbuf_add(&outbuf, in, ep - in); + } + } + /* E.g. + * ep : "=?iso-2022-jp?B?GyR...?= foo" + * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz" + */ + ep += 2; + + if (ep - it->buf >= it->len || !(cp = strchr(ep, '?'))) + goto release_return; + + if (cp + 3 - it->buf > it->len) + goto release_return; + strbuf_add(&charset_q, ep, cp - ep); + + encoding = cp[1]; + if (!encoding || cp[2] != '?') + goto release_return; + ep = strstr(cp + 3, "?="); + if (!ep) + goto release_return; + strbuf_add(&piecebuf, cp + 3, ep - cp - 3); + switch (tolower(encoding)) { + default: + goto release_return; + case 'b': + dec = decode_b_segment(&piecebuf); + break; + case 'q': + dec = decode_q_segment(&piecebuf, 1); + break; + } + if (convert_to_utf8(mi, dec, charset_q.buf)) + goto release_return; + + strbuf_addbuf(&outbuf, dec); + strbuf_release(dec); + free(dec); + in = ep + 2; + } + strbuf_addstr(&outbuf, in); + strbuf_reset(it); + strbuf_addbuf(it, &outbuf); + found_error = 0; +release_return: + strbuf_release(&outbuf); + strbuf_release(&charset_q); + strbuf_release(&piecebuf); + + if (found_error) + mi->input_error = -1; +} + +static int check_header(struct mailinfo *mi, + const struct strbuf *line, + struct strbuf *hdr_data[], int overwrite) +{ + int i, ret = 0, len; + struct strbuf sb = STRBUF_INIT; + + /* search for the interesting parts */ + for (i = 0; header[i]; i++) { + int len = strlen(header[i]); + if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) { + /* Unwrap inline B and Q encoding, and optionally + * normalize the meta information to utf8. + */ + strbuf_add(&sb, line->buf + len + 2, line->len - len - 2); + decode_header(mi, &sb); + handle_header(&hdr_data[i], &sb); + ret = 1; + goto check_header_out; + } + } + + /* Content stuff */ + if (cmp_header(line, "Content-Type")) { + len = strlen("Content-Type: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + strbuf_insert(&sb, 0, "Content-Type: ", len); + handle_content_type(mi, &sb); + ret = 1; + goto check_header_out; + } + if (cmp_header(line, "Content-Transfer-Encoding")) { + len = strlen("Content-Transfer-Encoding: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + handle_content_transfer_encoding(mi, &sb); + ret = 1; + goto check_header_out; + } + if (cmp_header(line, "Message-Id")) { + len = strlen("Message-Id: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + if (mi->add_message_id) + mi->message_id = strbuf_detach(&sb, NULL); + ret = 1; + goto check_header_out; + } + +check_header_out: + strbuf_release(&sb); + return ret; +} + +/* + * Returns 1 if the given line or any line beginning with the given line is an + * in-body header (that is, check_header will succeed when passed + * mi->s_hdr_data). + */ +static int is_inbody_header(const struct mailinfo *mi, + const struct strbuf *line) +{ + int i; + for (i = 0; header[i]; i++) + if (!mi->s_hdr_data[i] && cmp_header(line, header[i])) + return 1; + return 0; +} + +static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf *ret; + + switch (mi->transfer_encoding) { + case TE_QP: + ret = decode_q_segment(line, 0); + break; + case TE_BASE64: + ret = decode_b_segment(line); + break; + case TE_DONTCARE: + default: + return; + } + strbuf_reset(line); + strbuf_addbuf(line, ret); + strbuf_release(ret); + free(ret); +} + +static inline int patchbreak(const struct strbuf *line) +{ + size_t i; + + /* Beginning of a "diff -" header? */ + if (starts_with(line->buf, "diff -")) + return 1; + + /* CVS "Index: " line? */ + if (starts_with(line->buf, "Index: ")) + return 1; + + /* + * "--- <filename>" starts patches without headers + * "---<sp>*" is a manual separator + */ + if (line->len < 4) + return 0; + + if (starts_with(line->buf, "---")) { + /* space followed by a filename? */ + if (line->buf[3] == ' ' && !isspace(line->buf[4])) + return 1; + /* Just whitespace? */ + for (i = 3; i < line->len; i++) { + unsigned char c = line->buf[i]; + if (c == '\n') + return 1; + if (!isspace(c)) + break; + } + return 0; + } + return 0; +} + +static int is_scissors_line(const char *line) +{ + const char *c; + int scissors = 0, gap = 0; + const char *first_nonblank = NULL, *last_nonblank = NULL; + int visible, perforation = 0, in_perforation = 0; + + for (c = line; *c; c++) { + if (isspace(*c)) { + if (in_perforation) { + perforation++; + gap++; + } + continue; + } + last_nonblank = c; + if (first_nonblank == NULL) + first_nonblank = c; + if (*c == '-') { + in_perforation = 1; + perforation++; + continue; + } + if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) || + !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) { + in_perforation = 1; + perforation += 2; + scissors += 2; + c++; + continue; + } + in_perforation = 0; + } + + /* + * The mark must be at least 8 bytes long (e.g. "-- >8 --"). + * Even though there can be arbitrary cruft on the same line + * (e.g. "cut here"), in order to avoid misidentification, the + * perforation must occupy more than a third of the visible + * width of the line, and dashes and scissors must occupy more + * than half of the perforation. + */ + + if (first_nonblank && last_nonblank) + visible = last_nonblank - first_nonblank + 1; + else + visible = 0; + return (scissors && 8 <= visible && + visible < perforation * 3 && + gap * 2 < perforation); +} + +static void flush_inbody_header_accum(struct mailinfo *mi) +{ + if (!mi->inbody_header_accum.len) + return; + if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0)) + BUG("inbody_header_accum, if not empty, must always contain a valid in-body header"); + strbuf_reset(&mi->inbody_header_accum); +} + +static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line) +{ + if (mi->inbody_header_accum.len && + (line->buf[0] == ' ' || line->buf[0] == '\t')) { + if (mi->use_scissors && is_scissors_line(line->buf)) { + /* + * This is a scissors line; do not consider this line + * as a header continuation line. + */ + flush_inbody_header_accum(mi); + return 0; + } + strbuf_strip_suffix(&mi->inbody_header_accum, "\n"); + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + + flush_inbody_header_accum(mi); + + if (starts_with(line->buf, ">From") && isspace(line->buf[5])) + return is_format_patch_separator(line->buf + 1, line->len - 1); + if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { + int i; + for (i = 0; header[i]; i++) + if (!strcmp("Subject", header[i])) { + handle_header(&mi->s_hdr_data[i], line); + return 1; + } + return 0; + } + if (is_inbody_header(mi, line)) { + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + return 0; +} + +static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line) +{ + assert(!mi->filter_stage); + + if (mi->header_stage) { + if (!line->len || (line->len == 1 && line->buf[0] == '\n')) { + if (mi->inbody_header_accum.len) { + flush_inbody_header_accum(mi); + mi->header_stage = 0; + } + return 0; + } + } + + if (mi->use_inbody_headers && mi->header_stage) { + mi->header_stage = check_inbody_header(mi, line); + if (mi->header_stage) + return 0; + } else + /* Only trim the first (blank) line of the commit message + * when ignoring in-body headers. + */ + mi->header_stage = 0; + + /* normalize the log message to UTF-8. */ + if (convert_to_utf8(mi, line, mi->charset.buf)) + return 0; /* mi->input_error already set */ + + if (mi->use_scissors && is_scissors_line(line->buf)) { + int i; + + strbuf_setlen(&mi->log_message, 0); + mi->header_stage = 1; + + /* + * We may have already read "secondary headers"; purge + * them to give ourselves a clean restart. + */ + for (i = 0; header[i]; i++) { + if (mi->s_hdr_data[i]) + strbuf_release(mi->s_hdr_data[i]); + mi->s_hdr_data[i] = NULL; + } + return 0; + } + + if (patchbreak(line)) { + if (mi->message_id) + strbuf_addf(&mi->log_message, + "Message-Id: %s\n", mi->message_id); + return 1; + } + + strbuf_addbuf(&mi->log_message, line); + return 0; +} + +static void handle_patch(struct mailinfo *mi, const struct strbuf *line) +{ + fwrite(line->buf, 1, line->len, mi->patchfile); + mi->patch_lines++; +} + +static void handle_filter(struct mailinfo *mi, struct strbuf *line) +{ + switch (mi->filter_stage) { + case 0: + if (!handle_commit_msg(mi, line)) + break; + mi->filter_stage++; + /* fallthrough */ + case 1: + handle_patch(mi, line); + break; + } +} + +static int is_rfc2822_header(const struct strbuf *line) +{ + /* + * The section that defines the loosest possible + * field name is "3.6.8 Optional fields". + * + * optional-field = field-name ":" unstructured CRLF + * field-name = 1*ftext + * ftext = %d33-57 / %59-126 + */ + int ch; + char *cp = line->buf; + + /* Count mbox From headers as headers */ + if (starts_with(cp, "From ") || starts_with(cp, ">From ")) + return 1; + + while ((ch = *cp++)) { + if (ch == ':') + return 1; + if ((33 <= ch && ch <= 57) || + (59 <= ch && ch <= 126)) + continue; + break; + } + return 0; +} + +static int read_one_header_line(struct strbuf *line, FILE *in) +{ + struct strbuf continuation = STRBUF_INIT; + + /* Get the first part of the line. */ + if (strbuf_getline_lf(line, in)) + return 0; + + /* + * Is it an empty line or not a valid rfc2822 header? + * If so, stop here, and return false ("not a header") + */ + strbuf_rtrim(line); + if (!line->len || !is_rfc2822_header(line)) { + /* Re-add the newline */ + strbuf_addch(line, '\n'); + return 0; + } + + /* + * Now we need to eat all the continuation lines.. + * Yuck, 2822 header "folding" + */ + for (;;) { + int peek; + + peek = fgetc(in); + if (peek == EOF) + break; + ungetc(peek, in); + if (peek != ' ' && peek != '\t') + break; + if (strbuf_getline_lf(&continuation, in)) + break; + continuation.buf[0] = ' '; + strbuf_rtrim(&continuation); + strbuf_addbuf(line, &continuation); + } + strbuf_release(&continuation); + + return 1; +} + +static int find_boundary(struct mailinfo *mi, struct strbuf *line) +{ + while (!strbuf_getline_lf(line, mi->input)) { + if (*(mi->content_top) && is_multipart_boundary(mi, line)) + return 1; + } + return 0; +} + +static int handle_boundary(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf newline = STRBUF_INIT; + + strbuf_addch(&newline, '\n'); +again: + if (line->len >= (*(mi->content_top))->len + 2 && + !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) { + /* we hit an end boundary */ + /* pop the current boundary off the stack */ + strbuf_release(*(mi->content_top)); + FREE_AND_NULL(*(mi->content_top)); + + /* technically won't happen as is_multipart_boundary() + will fail first. But just in case.. + */ + if (--mi->content_top < mi->content) { + error("Detected mismatched boundaries, can't recover"); + mi->input_error = -1; + mi->content_top = mi->content; + strbuf_release(&newline); + return 0; + } + handle_filter(mi, &newline); + strbuf_release(&newline); + if (mi->input_error) + return 0; + + /* skip to the next boundary */ + if (!find_boundary(mi, line)) + return 0; + goto again; + } + + /* set some defaults */ + mi->transfer_encoding = TE_DONTCARE; + strbuf_reset(&mi->charset); + + /* slurp in this section's info */ + while (read_one_header_line(line, mi->input)) + check_header(mi, line, mi->p_hdr_data, 0); + + strbuf_release(&newline); + /* replenish line */ + if (strbuf_getline_lf(line, mi->input)) + return 0; + strbuf_addch(line, '\n'); + return 1; +} + +static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line, + struct strbuf *prev) +{ + size_t len = line->len; + const char *rest; + + if (!mi->format_flowed) { + handle_filter(mi, line); + return; + } + + if (line->buf[len - 1] == '\n') { + len--; + if (len && line->buf[len - 1] == '\r') + len--; + } + + /* Keep signature separator as-is. */ + if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) { + if (prev->len) { + handle_filter(mi, prev); + strbuf_reset(prev); + } + handle_filter(mi, line); + return; + } + + /* Unstuff space-stuffed line. */ + if (len && line->buf[0] == ' ') { + strbuf_remove(line, 0, 1); + len--; + } + + /* Save flowed line for later, but without the soft line break. */ + if (len && line->buf[len - 1] == ' ') { + strbuf_add(prev, line->buf, len - !!mi->delsp); + return; + } + + /* Prepend any previous partial lines */ + strbuf_insert(line, 0, prev->buf, prev->len); + strbuf_reset(prev); + + handle_filter(mi, line); +} + +static void handle_body(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf prev = STRBUF_INIT; + + /* Skip up to the first boundary */ + if (*(mi->content_top)) { + if (!find_boundary(mi, line)) + goto handle_body_out; + } + + do { + /* process any boundary lines */ + if (*(mi->content_top) && is_multipart_boundary(mi, line)) { + /* flush any leftover */ + if (prev.len) { + handle_filter(mi, &prev); + strbuf_reset(&prev); + } + if (!handle_boundary(mi, line)) + goto handle_body_out; + } + + /* Unwrap transfer encoding */ + decode_transfer_encoding(mi, line); + + switch (mi->transfer_encoding) { + case TE_BASE64: + case TE_QP: + { + struct strbuf **lines, **it, *sb; + + /* Prepend any previous partial lines */ + strbuf_insert(line, 0, prev.buf, prev.len); + strbuf_reset(&prev); + + /* + * This is a decoded line that may contain + * multiple new lines. Pass only one chunk + * at a time to handle_filter() + */ + lines = strbuf_split(line, '\n'); + for (it = lines; (sb = *it); it++) { + if (*(it + 1) == NULL) /* The last line */ + if (sb->buf[sb->len - 1] != '\n') { + /* Partial line, save it for later. */ + strbuf_addbuf(&prev, sb); + break; + } + handle_filter_flowed(mi, sb, &prev); + } + /* + * The partial chunk is saved in "prev" and will be + * appended by the next iteration of read_line_with_nul(). + */ + strbuf_list_free(lines); + break; + } + default: + handle_filter_flowed(mi, line, &prev); + } + + if (mi->input_error) + break; + } while (!strbuf_getwholeline(line, mi->input, '\n')); + + if (prev.len) + handle_filter(mi, &prev); + + flush_inbody_header_accum(mi); + +handle_body_out: + strbuf_release(&prev); +} + +static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data) +{ + const char *sp = data->buf; + while (1) { + char *ep = strchr(sp, '\n'); + int len; + if (!ep) + len = strlen(sp); + else + len = ep - sp; + fprintf(fout, "%s: %.*s\n", hdr, len, sp); + if (!ep) + break; + sp = ep + 1; + } +} + +static void handle_info(struct mailinfo *mi) +{ + struct strbuf *hdr; + int i; + + for (i = 0; header[i]; i++) { + /* only print inbody headers if we output a patch file */ + if (mi->patch_lines && mi->s_hdr_data[i]) + hdr = mi->s_hdr_data[i]; + else if (mi->p_hdr_data[i]) + hdr = mi->p_hdr_data[i]; + else + continue; + + if (!strcmp(header[i], "Subject")) { + if (!mi->keep_subject) { + cleanup_subject(mi, hdr); + cleanup_space(hdr); + } + output_header_lines(mi->output, "Subject", hdr); + } else if (!strcmp(header[i], "From")) { + cleanup_space(hdr); + handle_from(mi, hdr); + fprintf(mi->output, "Author: %s\n", mi->name.buf); + fprintf(mi->output, "Email: %s\n", mi->email.buf); + } else { + cleanup_space(hdr); + fprintf(mi->output, "%s: %s\n", header[i], hdr->buf); + } + } + fprintf(mi->output, "\n"); +} + +int mailinfo(struct mailinfo *mi, const char *msg, const char *patch) +{ + FILE *cmitmsg; + int peek; + struct strbuf line = STRBUF_INIT; + + cmitmsg = fopen(msg, "w"); + if (!cmitmsg) { + perror(msg); + return -1; + } + mi->patchfile = fopen(patch, "w"); + if (!mi->patchfile) { + perror(patch); + fclose(cmitmsg); + return -1; + } + + mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data))); + mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data))); + + do { + peek = fgetc(mi->input); + if (peek == EOF) { + fclose(cmitmsg); + return error("empty patch: '%s'", patch); + } + } while (isspace(peek)); + ungetc(peek, mi->input); + + /* process the email header */ + while (read_one_header_line(&line, mi->input)) + check_header(mi, &line, mi->p_hdr_data, 1); + + handle_body(mi, &line); + fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg); + fclose(cmitmsg); + fclose(mi->patchfile); + + handle_info(mi); + strbuf_release(&line); + return mi->input_error; +} + +static int git_mailinfo_config(const char *var, const char *value, void *mi_) +{ + struct mailinfo *mi = mi_; + + if (!starts_with(var, "mailinfo.")) + return git_default_config(var, value, NULL); + if (!strcmp(var, "mailinfo.scissors")) { + mi->use_scissors = git_config_bool(var, value); + return 0; + } + /* perhaps others here */ + return 0; +} + +void setup_mailinfo(struct mailinfo *mi) +{ + memset(mi, 0, sizeof(*mi)); + strbuf_init(&mi->name, 0); + strbuf_init(&mi->email, 0); + strbuf_init(&mi->charset, 0); + strbuf_init(&mi->log_message, 0); + strbuf_init(&mi->inbody_header_accum, 0); + mi->header_stage = 1; + mi->use_inbody_headers = 1; + mi->content_top = mi->content; + git_config(git_mailinfo_config, mi); +} + +void clear_mailinfo(struct mailinfo *mi) +{ + int i; + + strbuf_release(&mi->name); + strbuf_release(&mi->email); + strbuf_release(&mi->charset); + strbuf_release(&mi->inbody_header_accum); + free(mi->message_id); + + if (mi->p_hdr_data) + for (i = 0; mi->p_hdr_data[i]; i++) + strbuf_release(mi->p_hdr_data[i]); + free(mi->p_hdr_data); + if (mi->s_hdr_data) + for (i = 0; mi->s_hdr_data[i]; i++) + strbuf_release(mi->s_hdr_data[i]); + free(mi->s_hdr_data); + + while (mi->content < mi->content_top) { + free(*(mi->content_top)); + mi->content_top--; + } + + strbuf_release(&mi->log_message); +} diff --git a/t/t4256/1/mailinfo.c.orig b/t/t4256/1/mailinfo.c.orig new file mode 100644 index 0000000000..3281a37d51 --- /dev/null +++ b/t/t4256/1/mailinfo.c.orig @@ -0,0 +1,1185 @@ +#include "cache.h" +#include "config.h" +#include "utf8.h" +#include "strbuf.h" +#include "mailinfo.h" + +static void cleanup_space(struct strbuf *sb) +{ + size_t pos, cnt; + for (pos = 0; pos < sb->len; pos++) { + if (isspace(sb->buf[pos])) { + sb->buf[pos] = ' '; + for (cnt = 0; isspace(sb->buf[pos + cnt + 1]); cnt++); + strbuf_remove(sb, pos + 1, cnt); + } + } +} + +static void get_sane_name(struct strbuf *out, struct strbuf *name, struct strbuf *email) +{ + struct strbuf *src = name; + if (name->len < 3 || 60 < name->len || strchr(name->buf, '@') || + strchr(name->buf, '<') || strchr(name->buf, '>')) + src = email; + else if (name == out) + return; + strbuf_reset(out); + strbuf_addbuf(out, src); +} + +static void parse_bogus_from(struct mailinfo *mi, const struct strbuf *line) +{ + /* John Doe <johndoe> */ + + char *bra, *ket; + /* This is fallback, so do not bother if we already have an + * e-mail address. + */ + if (mi->email.len) + return; + + bra = strchr(line->buf, '<'); + if (!bra) + return; + ket = strchr(bra, '>'); + if (!ket) + return; + + strbuf_reset(&mi->email); + strbuf_add(&mi->email, bra + 1, ket - bra - 1); + + strbuf_reset(&mi->name); + strbuf_add(&mi->name, line->buf, bra - line->buf); + strbuf_trim(&mi->name); + get_sane_name(&mi->name, &mi->name, &mi->email); +} + +static const char *unquote_comment(struct strbuf *outbuf, const char *in) +{ + int c; + int take_next_literally = 0; + + strbuf_addch(outbuf, '('); + + while ((c = *in++) != 0) { + if (take_next_literally == 1) { + take_next_literally = 0; + } else { + switch (c) { + case '\\': + take_next_literally = 1; + continue; + case '(': + in = unquote_comment(outbuf, in); + continue; + case ')': + strbuf_addch(outbuf, ')'); + return in; + } + } + + strbuf_addch(outbuf, c); + } + + return in; +} + +static const char *unquote_quoted_string(struct strbuf *outbuf, const char *in) +{ + int c; + int take_next_literally = 0; + + while ((c = *in++) != 0) { + if (take_next_literally == 1) { + take_next_literally = 0; + } else { + switch (c) { + case '\\': + take_next_literally = 1; + continue; + case '"': + return in; + } + } + + strbuf_addch(outbuf, c); + } + + return in; +} + +static void unquote_quoted_pair(struct strbuf *line) +{ + struct strbuf outbuf; + const char *in = line->buf; + int c; + + strbuf_init(&outbuf, line->len); + + while ((c = *in++) != 0) { + switch (c) { + case '"': + in = unquote_quoted_string(&outbuf, in); + continue; + case '(': + in = unquote_comment(&outbuf, in); + continue; + } + + strbuf_addch(&outbuf, c); + } + + strbuf_swap(&outbuf, line); + strbuf_release(&outbuf); + +} + +static void handle_from(struct mailinfo *mi, const struct strbuf *from) +{ + char *at; + size_t el; + struct strbuf f; + + strbuf_init(&f, from->len); + strbuf_addbuf(&f, from); + + unquote_quoted_pair(&f); + + at = strchr(f.buf, '@'); + if (!at) { + parse_bogus_from(mi, from); + goto out; + } + + /* + * If we already have one email, don't take any confusing lines + */ + if (mi->email.len && strchr(at + 1, '@')) + goto out; + + /* Pick up the string around '@', possibly delimited with <> + * pair; that is the email part. + */ + while (at > f.buf) { + char c = at[-1]; + if (isspace(c)) + break; + if (c == '<') { + at[-1] = ' '; + break; + } + at--; + } + el = strcspn(at, " \n\t\r\v\f>"); + strbuf_reset(&mi->email); + strbuf_add(&mi->email, at, el); + strbuf_remove(&f, at - f.buf, el + (at[el] ? 1 : 0)); + + /* The remainder is name. It could be + * + * - "John Doe <john.doe@xz>" (a), or + * - "john.doe@xz (John Doe)" (b), or + * - "John (zzz) Doe <john.doe@xz> (Comment)" (c) + * + * but we have removed the email part, so + * + * - remove extra spaces which could stay after email (case 'c'), and + * - trim from both ends, possibly removing the () pair at the end + * (cases 'a' and 'b'). + */ + cleanup_space(&f); + strbuf_trim(&f); + if (f.buf[0] == '(' && f.len && f.buf[f.len - 1] == ')') { + strbuf_remove(&f, 0, 1); + strbuf_setlen(&f, f.len - 1); + } + + get_sane_name(&mi->name, &f, &mi->email); +out: + strbuf_release(&f); +} + +static void handle_header(struct strbuf **out, const struct strbuf *line) +{ + if (!*out) { + *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(*out, line->len); + } else + strbuf_reset(*out); + + strbuf_addbuf(*out, line); +} + +/* NOTE NOTE NOTE. We do not claim we do full MIME. We just attempt + * to have enough heuristics to grok MIME encoded patches often found + * on our mailing lists. For example, we do not even treat header lines + * case insensitively. + */ + +static int slurp_attr(const char *line, const char *name, struct strbuf *attr) +{ + const char *ends, *ap = strcasestr(line, name); + size_t sz; + + strbuf_setlen(attr, 0); + if (!ap) + return 0; + ap += strlen(name); + if (*ap == '"') { + ap++; + ends = "\""; + } + else + ends = "; \t"; + sz = strcspn(ap, ends); + strbuf_add(attr, ap, sz); + return 1; +} + +static void handle_content_type(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); + strbuf_init(boundary, line->len); + + if (slurp_attr(line->buf, "boundary=", boundary)) { + strbuf_insert(boundary, 0, "--", 2); + if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) { + error("Too many boundaries to handle"); + mi->input_error = -1; + mi->content_top = &mi->content[MAX_BOUNDARIES] - 1; + return; + } + *(mi->content_top) = boundary; + boundary = NULL; + } + slurp_attr(line->buf, "charset=", &mi->charset); + + if (boundary) { + strbuf_release(boundary); + free(boundary); + } +} + +static void handle_content_transfer_encoding(struct mailinfo *mi, + const struct strbuf *line) +{ + if (strcasestr(line->buf, "base64")) + mi->transfer_encoding = TE_BASE64; + else if (strcasestr(line->buf, "quoted-printable")) + mi->transfer_encoding = TE_QP; + else + mi->transfer_encoding = TE_DONTCARE; +} + +static int is_multipart_boundary(struct mailinfo *mi, const struct strbuf *line) +{ + struct strbuf *content_top = *(mi->content_top); + + return ((content_top->len <= line->len) && + !memcmp(line->buf, content_top->buf, content_top->len)); +} + +static void cleanup_subject(struct mailinfo *mi, struct strbuf *subject) +{ + size_t at = 0; + + while (at < subject->len) { + char *pos; + size_t remove; + + switch (subject->buf[at]) { + case 'r': case 'R': + if (subject->len <= at + 3) + break; + if ((subject->buf[at + 1] == 'e' || + subject->buf[at + 1] == 'E') && + subject->buf[at + 2] == ':') { + strbuf_remove(subject, at, 3); + continue; + } + at++; + break; + case ' ': case '\t': case ':': + strbuf_remove(subject, at, 1); + continue; + case '[': + pos = strchr(subject->buf + at, ']'); + if (!pos) + break; + remove = pos - subject->buf + at + 1; + if (!mi->keep_non_patch_brackets_in_subject || + (7 <= remove && + memmem(subject->buf + at, remove, "PATCH", 5))) + strbuf_remove(subject, at, remove); + else { + at += remove; + /* + * If the input had a space after the ], keep + * it. We don't bother with finding the end of + * the space, since we later normalize it + * anyway. + */ + if (isspace(subject->buf[at])) + at += 1; + } + continue; + } + break; + } + strbuf_trim(subject); +} + +#define MAX_HDR_PARSED 10 +static const char *header[MAX_HDR_PARSED] = { + "From","Subject","Date", +}; + +static inline int cmp_header(const struct strbuf *line, const char *hdr) +{ + int len = strlen(hdr); + return !strncasecmp(line->buf, hdr, len) && line->len > len && + line->buf[len] == ':' && isspace(line->buf[len + 1]); +} + +static int is_format_patch_separator(const char *line, int len) +{ + static const char SAMPLE[] = + "From e6807f3efca28b30decfecb1732a56c7db1137ee Mon Sep 17 00:00:00 2001\n"; + const char *cp; + + if (len != strlen(SAMPLE)) + return 0; + if (!skip_prefix(line, "From ", &cp)) + return 0; + if (strspn(cp, "0123456789abcdef") != 40) + return 0; + cp += 40; + return !memcmp(SAMPLE + (cp - line), cp, strlen(SAMPLE) - (cp - line)); +} + +static struct strbuf *decode_q_segment(const struct strbuf *q_seg, int rfc2047) +{ + const char *in = q_seg->buf; + int c; + struct strbuf *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(out, q_seg->len); + + while ((c = *in++) != 0) { + if (c == '=') { + int ch, d = *in; + if (d == '\n' || !d) + break; /* drop trailing newline */ + ch = hex2chr(in); + if (ch >= 0) { + strbuf_addch(out, ch); + in += 2; + continue; + } + /* garbage -- fall through */ + } + if (rfc2047 && c == '_') /* rfc2047 4.2 (2) */ + c = 0x20; + strbuf_addch(out, c); + } + return out; +} + +static struct strbuf *decode_b_segment(const struct strbuf *b_seg) +{ + /* Decode in..ep, possibly in-place to ot */ + int c, pos = 0, acc = 0; + const char *in = b_seg->buf; + struct strbuf *out = xmalloc(sizeof(struct strbuf)); + strbuf_init(out, b_seg->len); + + while ((c = *in++) != 0) { + if (c == '+') + c = 62; + else if (c == '/') + c = 63; + else if ('A' <= c && c <= 'Z') + c -= 'A'; + else if ('a' <= c && c <= 'z') + c -= 'a' - 26; + else if ('0' <= c && c <= '9') + c -= '0' - 52; + else + continue; /* garbage */ + switch (pos++) { + case 0: + acc = (c << 2); + break; + case 1: + strbuf_addch(out, (acc | (c >> 4))); + acc = (c & 15) << 4; + break; + case 2: + strbuf_addch(out, (acc | (c >> 2))); + acc = (c & 3) << 6; + break; + case 3: + strbuf_addch(out, (acc | c)); + acc = pos = 0; + break; + } + } + return out; +} + +static int convert_to_utf8(struct mailinfo *mi, + struct strbuf *line, const char *charset) +{ + char *out; + + if (!mi->metainfo_charset || !charset || !*charset) + return 0; + + if (same_encoding(mi->metainfo_charset, charset)) + return 0; + out = reencode_string(line->buf, mi->metainfo_charset, charset); + if (!out) { + mi->input_error = -1; + return error("cannot convert from %s to %s", + charset, mi->metainfo_charset); + } + strbuf_attach(line, out, strlen(out), strlen(out)); + return 0; +} + +static void decode_header(struct mailinfo *mi, struct strbuf *it) +{ + char *in, *ep, *cp; + struct strbuf outbuf = STRBUF_INIT, *dec; + struct strbuf charset_q = STRBUF_INIT, piecebuf = STRBUF_INIT; + int found_error = 1; /* pessimism */ + + in = it->buf; + while (in - it->buf <= it->len && (ep = strstr(in, "=?")) != NULL) { + int encoding; + strbuf_reset(&charset_q); + strbuf_reset(&piecebuf); + + if (in != ep) { + /* + * We are about to process an encoded-word + * that begins at ep, but there is something + * before the encoded word. + */ + char *scan; + for (scan = in; scan < ep; scan++) + if (!isspace(*scan)) + break; + + if (scan != ep || in == it->buf) { + /* + * We should not lose that "something", + * unless we have just processed an + * encoded-word, and there is only LWS + * before the one we are about to process. + */ + strbuf_add(&outbuf, in, ep - in); + } + } + /* E.g. + * ep : "=?iso-2022-jp?B?GyR...?= foo" + * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz" + */ + ep += 2; + + if (ep - it->buf >= it->len || !(cp = strchr(ep, '?'))) + goto release_return; + + if (cp + 3 - it->buf > it->len) + goto release_return; + strbuf_add(&charset_q, ep, cp - ep); + + encoding = cp[1]; + if (!encoding || cp[2] != '?') + goto release_return; + ep = strstr(cp + 3, "?="); + if (!ep) + goto release_return; + strbuf_add(&piecebuf, cp + 3, ep - cp - 3); + switch (tolower(encoding)) { + default: + goto release_return; + case 'b': + dec = decode_b_segment(&piecebuf); + break; + case 'q': + dec = decode_q_segment(&piecebuf, 1); + break; + } + if (convert_to_utf8(mi, dec, charset_q.buf)) + goto release_return; + + strbuf_addbuf(&outbuf, dec); + strbuf_release(dec); + free(dec); + in = ep + 2; + } + strbuf_addstr(&outbuf, in); + strbuf_reset(it); + strbuf_addbuf(it, &outbuf); + found_error = 0; +release_return: + strbuf_release(&outbuf); + strbuf_release(&charset_q); + strbuf_release(&piecebuf); + + if (found_error) + mi->input_error = -1; +} + +static int check_header(struct mailinfo *mi, + const struct strbuf *line, + struct strbuf *hdr_data[], int overwrite) +{ + int i, ret = 0, len; + struct strbuf sb = STRBUF_INIT; + + /* search for the interesting parts */ + for (i = 0; header[i]; i++) { + int len = strlen(header[i]); + if ((!hdr_data[i] || overwrite) && cmp_header(line, header[i])) { + /* Unwrap inline B and Q encoding, and optionally + * normalize the meta information to utf8. + */ + strbuf_add(&sb, line->buf + len + 2, line->len - len - 2); + decode_header(mi, &sb); + handle_header(&hdr_data[i], &sb); + ret = 1; + goto check_header_out; + } + } + + /* Content stuff */ + if (cmp_header(line, "Content-Type")) { + len = strlen("Content-Type: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + strbuf_insert(&sb, 0, "Content-Type: ", len); + handle_content_type(mi, &sb); + ret = 1; + goto check_header_out; + } + if (cmp_header(line, "Content-Transfer-Encoding")) { + len = strlen("Content-Transfer-Encoding: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + handle_content_transfer_encoding(mi, &sb); + ret = 1; + goto check_header_out; + } + if (cmp_header(line, "Message-Id")) { + len = strlen("Message-Id: "); + strbuf_add(&sb, line->buf + len, line->len - len); + decode_header(mi, &sb); + if (mi->add_message_id) + mi->message_id = strbuf_detach(&sb, NULL); + ret = 1; + goto check_header_out; + } + +check_header_out: + strbuf_release(&sb); + return ret; +} + +/* + * Returns 1 if the given line or any line beginning with the given line is an + * in-body header (that is, check_header will succeed when passed + * mi->s_hdr_data). + */ +static int is_inbody_header(const struct mailinfo *mi, + const struct strbuf *line) +{ + int i; + for (i = 0; header[i]; i++) + if (!mi->s_hdr_data[i] && cmp_header(line, header[i])) + return 1; + return 0; +} + +static void decode_transfer_encoding(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf *ret; + + switch (mi->transfer_encoding) { + case TE_QP: + ret = decode_q_segment(line, 0); + break; + case TE_BASE64: + ret = decode_b_segment(line); + break; + case TE_DONTCARE: + default: + return; + } + strbuf_reset(line); + strbuf_addbuf(line, ret); + strbuf_release(ret); + free(ret); +} + +static inline int patchbreak(const struct strbuf *line) +{ + size_t i; + + /* Beginning of a "diff -" header? */ + if (starts_with(line->buf, "diff -")) + return 1; + + /* CVS "Index: " line? */ + if (starts_with(line->buf, "Index: ")) + return 1; + + /* + * "--- <filename>" starts patches without headers + * "---<sp>*" is a manual separator + */ + if (line->len < 4) + return 0; + + if (starts_with(line->buf, "---")) { + /* space followed by a filename? */ + if (line->buf[3] == ' ' && !isspace(line->buf[4])) + return 1; + /* Just whitespace? */ + for (i = 3; i < line->len; i++) { + unsigned char c = line->buf[i]; + if (c == '\n') + return 1; + if (!isspace(c)) + break; + } + return 0; + } + return 0; +} + +static int is_scissors_line(const char *line) +{ + const char *c; + int scissors = 0, gap = 0; + const char *first_nonblank = NULL, *last_nonblank = NULL; + int visible, perforation = 0, in_perforation = 0; + + for (c = line; *c; c++) { + if (isspace(*c)) { + if (in_perforation) { + perforation++; + gap++; + } + continue; + } + last_nonblank = c; + if (first_nonblank == NULL) + first_nonblank = c; + if (*c == '-') { + in_perforation = 1; + perforation++; + continue; + } + if ((!memcmp(c, ">8", 2) || !memcmp(c, "8<", 2) || + !memcmp(c, ">%", 2) || !memcmp(c, "%<", 2))) { + in_perforation = 1; + perforation += 2; + scissors += 2; + c++; + continue; + } + in_perforation = 0; + } + + /* + * The mark must be at least 8 bytes long (e.g. "-- >8 --"). + * Even though there can be arbitrary cruft on the same line + * (e.g. "cut here"), in order to avoid misidentification, the + * perforation must occupy more than a third of the visible + * width of the line, and dashes and scissors must occupy more + * than half of the perforation. + */ + + if (first_nonblank && last_nonblank) + visible = last_nonblank - first_nonblank + 1; + else + visible = 0; + return (scissors && 8 <= visible && + visible < perforation * 3 && + gap * 2 < perforation); +} + +static void flush_inbody_header_accum(struct mailinfo *mi) +{ + if (!mi->inbody_header_accum.len) + return; + if (!check_header(mi, &mi->inbody_header_accum, mi->s_hdr_data, 0)) + BUG("inbody_header_accum, if not empty, must always contain a valid in-body header"); + strbuf_reset(&mi->inbody_header_accum); +} + +static int check_inbody_header(struct mailinfo *mi, const struct strbuf *line) +{ + if (mi->inbody_header_accum.len && + (line->buf[0] == ' ' || line->buf[0] == '\t')) { + if (mi->use_scissors && is_scissors_line(line->buf)) { + /* + * This is a scissors line; do not consider this line + * as a header continuation line. + */ + flush_inbody_header_accum(mi); + return 0; + } + strbuf_strip_suffix(&mi->inbody_header_accum, "\n"); + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + + flush_inbody_header_accum(mi); + + if (starts_with(line->buf, ">From") && isspace(line->buf[5])) + return is_format_patch_separator(line->buf + 1, line->len - 1); + if (starts_with(line->buf, "[PATCH]") && isspace(line->buf[7])) { + int i; + for (i = 0; header[i]; i++) + if (!strcmp("Subject", header[i])) { + handle_header(&mi->s_hdr_data[i], line); + return 1; + } + return 0; + } + if (is_inbody_header(mi, line)) { + strbuf_addbuf(&mi->inbody_header_accum, line); + return 1; + } + return 0; +} + +static int handle_commit_msg(struct mailinfo *mi, struct strbuf *line) +{ + assert(!mi->filter_stage); + + if (mi->header_stage) { + if (!line->len || (line->len == 1 && line->buf[0] == '\n')) { + if (mi->inbody_header_accum.len) { + flush_inbody_header_accum(mi); + mi->header_stage = 0; + } + return 0; + } + } + + if (mi->use_inbody_headers && mi->header_stage) { + mi->header_stage = check_inbody_header(mi, line); + if (mi->header_stage) + return 0; + } else + /* Only trim the first (blank) line of the commit message + * when ignoring in-body headers. + */ + mi->header_stage = 0; + + /* normalize the log message to UTF-8. */ + if (convert_to_utf8(mi, line, mi->charset.buf)) + return 0; /* mi->input_error already set */ + + if (mi->use_scissors && is_scissors_line(line->buf)) { + int i; + + strbuf_setlen(&mi->log_message, 0); + mi->header_stage = 1; + + /* + * We may have already read "secondary headers"; purge + * them to give ourselves a clean restart. + */ + for (i = 0; header[i]; i++) { + if (mi->s_hdr_data[i]) + strbuf_release(mi->s_hdr_data[i]); + mi->s_hdr_data[i] = NULL; + } + return 0; + } + + if (patchbreak(line)) { + if (mi->message_id) + strbuf_addf(&mi->log_message, + "Message-Id: %s\n", mi->message_id); + return 1; + } + + strbuf_addbuf(&mi->log_message, line); + return 0; +} + +static void handle_patch(struct mailinfo *mi, const struct strbuf *line) +{ + fwrite(line->buf, 1, line->len, mi->patchfile); + mi->patch_lines++; +} + +static void handle_filter(struct mailinfo *mi, struct strbuf *line) +{ + switch (mi->filter_stage) { + case 0: + if (!handle_commit_msg(mi, line)) + break; + mi->filter_stage++; + /* fallthrough */ + case 1: + handle_patch(mi, line); + break; + } +} + +static int is_rfc2822_header(const struct strbuf *line) +{ + /* + * The section that defines the loosest possible + * field name is "3.6.8 Optional fields". + * + * optional-field = field-name ":" unstructured CRLF + * field-name = 1*ftext + * ftext = %d33-57 / %59-126 + */ + int ch; + char *cp = line->buf; + + /* Count mbox From headers as headers */ + if (starts_with(cp, "From ") || starts_with(cp, ">From ")) + return 1; + + while ((ch = *cp++)) { + if (ch == ':') + return 1; + if ((33 <= ch && ch <= 57) || + (59 <= ch && ch <= 126)) + continue; + break; + } + return 0; +} + +static int read_one_header_line(struct strbuf *line, FILE *in) +{ + struct strbuf continuation = STRBUF_INIT; + + /* Get the first part of the line. */ + if (strbuf_getline_lf(line, in)) + return 0; + + /* + * Is it an empty line or not a valid rfc2822 header? + * If so, stop here, and return false ("not a header") + */ + strbuf_rtrim(line); + if (!line->len || !is_rfc2822_header(line)) { + /* Re-add the newline */ + strbuf_addch(line, '\n'); + return 0; + } + + /* + * Now we need to eat all the continuation lines.. + * Yuck, 2822 header "folding" + */ + for (;;) { + int peek; + + peek = fgetc(in); + if (peek == EOF) + break; + ungetc(peek, in); + if (peek != ' ' && peek != '\t') + break; + if (strbuf_getline_lf(&continuation, in)) + break; + continuation.buf[0] = ' '; + strbuf_rtrim(&continuation); + strbuf_addbuf(line, &continuation); + } + strbuf_release(&continuation); + + return 1; +} + +static int find_boundary(struct mailinfo *mi, struct strbuf *line) +{ + while (!strbuf_getline_lf(line, mi->input)) { + if (*(mi->content_top) && is_multipart_boundary(mi, line)) + return 1; + } + return 0; +} + +static int handle_boundary(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf newline = STRBUF_INIT; + + strbuf_addch(&newline, '\n'); +again: + if (line->len >= (*(mi->content_top))->len + 2 && + !memcmp(line->buf + (*(mi->content_top))->len, "--", 2)) { + /* we hit an end boundary */ + /* pop the current boundary off the stack */ + strbuf_release(*(mi->content_top)); + FREE_AND_NULL(*(mi->content_top)); + + /* technically won't happen as is_multipart_boundary() + will fail first. But just in case.. + */ + if (--mi->content_top < mi->content) { + error("Detected mismatched boundaries, can't recover"); + mi->input_error = -1; + mi->content_top = mi->content; + strbuf_release(&newline); + return 0; + } + handle_filter(mi, &newline); + strbuf_release(&newline); + if (mi->input_error) + return 0; + + /* skip to the next boundary */ + if (!find_boundary(mi, line)) + return 0; + goto again; + } + + /* set some defaults */ + mi->transfer_encoding = TE_DONTCARE; + strbuf_reset(&mi->charset); + + /* slurp in this section's info */ + while (read_one_header_line(line, mi->input)) + check_header(mi, line, mi->p_hdr_data, 0); + + strbuf_release(&newline); + /* replenish line */ + if (strbuf_getline_lf(line, mi->input)) + return 0; + strbuf_addch(line, '\n'); + return 1; +} + +static void handle_body(struct mailinfo *mi, struct strbuf *line) +{ + struct strbuf prev = STRBUF_INIT; + + /* Skip up to the first boundary */ + if (*(mi->content_top)) { + if (!find_boundary(mi, line)) + goto handle_body_out; + } + + do { + /* process any boundary lines */ + if (*(mi->content_top) && is_multipart_boundary(mi, line)) { + /* flush any leftover */ + if (prev.len) { + handle_filter(mi, &prev); + strbuf_reset(&prev); + } + if (!handle_boundary(mi, line)) + goto handle_body_out; + } + + /* Unwrap transfer encoding */ + decode_transfer_encoding(mi, line); + + switch (mi->transfer_encoding) { + case TE_BASE64: + case TE_QP: + { + struct strbuf **lines, **it, *sb; + + /* Prepend any previous partial lines */ + strbuf_insert(line, 0, prev.buf, prev.len); + strbuf_reset(&prev); + + /* + * This is a decoded line that may contain + * multiple new lines. Pass only one chunk + * at a time to handle_filter() + */ + lines = strbuf_split(line, '\n'); + for (it = lines; (sb = *it); it++) { + if (*(it + 1) == NULL) /* The last line */ + if (sb->buf[sb->len - 1] != '\n') { + /* Partial line, save it for later. */ + strbuf_addbuf(&prev, sb); + break; + } + handle_filter(mi, sb); + } + /* + * The partial chunk is saved in "prev" and will be + * appended by the next iteration of read_line_with_nul(). + */ + strbuf_list_free(lines); + break; + } + default: + handle_filter(mi, line); + } + + if (mi->input_error) + break; + } while (!strbuf_getwholeline(line, mi->input, '\n')); + + flush_inbody_header_accum(mi); + +handle_body_out: + strbuf_release(&prev); +} + +static void output_header_lines(FILE *fout, const char *hdr, const struct strbuf *data) +{ + const char *sp = data->buf; + while (1) { + char *ep = strchr(sp, '\n'); + int len; + if (!ep) + len = strlen(sp); + else + len = ep - sp; + fprintf(fout, "%s: %.*s\n", hdr, len, sp); + if (!ep) + break; + sp = ep + 1; + } +} + +static void handle_info(struct mailinfo *mi) +{ + struct strbuf *hdr; + int i; + + for (i = 0; header[i]; i++) { + /* only print inbody headers if we output a patch file */ + if (mi->patch_lines && mi->s_hdr_data[i]) + hdr = mi->s_hdr_data[i]; + else if (mi->p_hdr_data[i]) + hdr = mi->p_hdr_data[i]; + else + continue; + + if (!strcmp(header[i], "Subject")) { + if (!mi->keep_subject) { + cleanup_subject(mi, hdr); + cleanup_space(hdr); + } + output_header_lines(mi->output, "Subject", hdr); + } else if (!strcmp(header[i], "From")) { + cleanup_space(hdr); + handle_from(mi, hdr); + fprintf(mi->output, "Author: %s\n", mi->name.buf); + fprintf(mi->output, "Email: %s\n", mi->email.buf); + } else { + cleanup_space(hdr); + fprintf(mi->output, "%s: %s\n", header[i], hdr->buf); + } + } + fprintf(mi->output, "\n"); +} + +int mailinfo(struct mailinfo *mi, const char *msg, const char *patch) +{ + FILE *cmitmsg; + int peek; + struct strbuf line = STRBUF_INIT; + + cmitmsg = fopen(msg, "w"); + if (!cmitmsg) { + perror(msg); + return -1; + } + mi->patchfile = fopen(patch, "w"); + if (!mi->patchfile) { + perror(patch); + fclose(cmitmsg); + return -1; + } + + mi->p_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->p_hdr_data))); + mi->s_hdr_data = xcalloc(MAX_HDR_PARSED, sizeof(*(mi->s_hdr_data))); + + do { + peek = fgetc(mi->input); + if (peek == EOF) { + fclose(cmitmsg); + return error("empty patch: '%s'", patch); + } + } while (isspace(peek)); + ungetc(peek, mi->input); + + /* process the email header */ + while (read_one_header_line(&line, mi->input)) + check_header(mi, &line, mi->p_hdr_data, 1); + + handle_body(mi, &line); + fwrite(mi->log_message.buf, 1, mi->log_message.len, cmitmsg); + fclose(cmitmsg); + fclose(mi->patchfile); + + handle_info(mi); + strbuf_release(&line); + return mi->input_error; +} + +static int git_mailinfo_config(const char *var, const char *value, void *mi_) +{ + struct mailinfo *mi = mi_; + + if (!starts_with(var, "mailinfo.")) + return git_default_config(var, value, NULL); + if (!strcmp(var, "mailinfo.scissors")) { + mi->use_scissors = git_config_bool(var, value); + return 0; + } + /* perhaps others here */ + return 0; +} + +void setup_mailinfo(struct mailinfo *mi) +{ + memset(mi, 0, sizeof(*mi)); + strbuf_init(&mi->name, 0); + strbuf_init(&mi->email, 0); + strbuf_init(&mi->charset, 0); + strbuf_init(&mi->log_message, 0); + strbuf_init(&mi->inbody_header_accum, 0); + mi->header_stage = 1; + mi->use_inbody_headers = 1; + mi->content_top = mi->content; + git_config(git_mailinfo_config, mi); +} + +void clear_mailinfo(struct mailinfo *mi) +{ + int i; + + strbuf_release(&mi->name); + strbuf_release(&mi->email); + strbuf_release(&mi->charset); + strbuf_release(&mi->inbody_header_accum); + free(mi->message_id); + + if (mi->p_hdr_data) + for (i = 0; mi->p_hdr_data[i]; i++) + strbuf_release(mi->p_hdr_data[i]); + free(mi->p_hdr_data); + if (mi->s_hdr_data) + for (i = 0; mi->s_hdr_data[i]; i++) + strbuf_release(mi->s_hdr_data[i]); + free(mi->s_hdr_data); + + while (mi->content < mi->content_top) { + free(*(mi->content_top)); + mi->content_top--; + } + + strbuf_release(&mi->log_message); +} diff --git a/t/t4256/1/patch b/t/t4256/1/patch new file mode 100644 index 0000000000..bd0d8b0251 --- /dev/null +++ b/t/t4256/1/patch @@ -0,0 +1,129 @@ +From: A <author@example.com> +Subject: [PATCH] mailinfo: support format=flowed +Message-ID: <aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa@example.com> +Date: Sat, 25 Aug 2018 22:04:50 +0200 +User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:60.0) Gecko/20100101 + Thunderbird/60.0 +MIME-Version: 1.0 +Content-Type: text/plain; charset=utf-8; format=flowed +Content-Language: en-US +Content-Transfer-Encoding: 7bit + +--- + mailinfo.c | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 62 insertions(+), 2 deletions(-) + +diff --git a/mailinfo.c b/mailinfo.c +index 3281a37d51..b395adbdf2 100644 +--- a/mailinfo.c ++++ b/mailinfo.c +@@ -237,11 +237,22 @@ static int slurp_attr(const char *line, const char +*name, struct strbuf *attr) + return 1; + } + ++static int has_attr_value(const char *line, const char *name, const +char *value) ++{ ++ struct strbuf sb = STRBUF_INIT; ++ int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value); ++ strbuf_release(&sb); ++ return rc; ++} ++ + static void handle_content_type(struct mailinfo *mi, struct strbuf *line) + { + struct strbuf *boundary = xmalloc(sizeof(struct strbuf)); + strbuf_init(boundary, line->len); + ++ mi->format_flowed = has_attr_value(line->buf, "format=", "flowed"); ++ mi->delsp = has_attr_value(line->buf, "delsp=", "yes"); ++ + if (slurp_attr(line->buf, "boundary=", boundary)) { + strbuf_insert(boundary, 0, "--", 2); + if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) { +@@ -964,6 +975,52 @@ static int handle_boundary(struct mailinfo *mi, +struct strbuf *line) + return 1; + } + ++static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line, ++ struct strbuf *prev) ++{ ++ size_t len = line->len; ++ const char *rest; ++ ++ if (!mi->format_flowed) { ++ handle_filter(mi, line); ++ return; ++ } ++ ++ if (line->buf[len - 1] == '\n') { ++ len--; ++ if (len && line->buf[len - 1] == '\r') ++ len--; ++ } ++ ++ /* Keep signature separator as-is. */ ++ if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) { ++ if (prev->len) { ++ handle_filter(mi, prev); ++ strbuf_reset(prev); ++ } ++ handle_filter(mi, line); ++ return; ++ } ++ ++ /* Unstuff space-stuffed line. */ ++ if (len && line->buf[0] == ' ') { ++ strbuf_remove(line, 0, 1); ++ len--; ++ } ++ ++ /* Save flowed line for later, but without the soft line break. */ ++ if (len && line->buf[len - 1] == ' ') { ++ strbuf_add(prev, line->buf, len - !!mi->delsp); ++ return; ++ } ++ ++ /* Prepend any previous partial lines */ ++ strbuf_insert(line, 0, prev->buf, prev->len); ++ strbuf_reset(prev); ++ ++ handle_filter(mi, line); ++} ++ + static void handle_body(struct mailinfo *mi, struct strbuf *line) + { + struct strbuf prev = STRBUF_INIT; +@@ -1012,7 +1069,7 @@ static void handle_body(struct mailinfo *mi, +struct strbuf *line) + strbuf_addbuf(&prev, sb); + break; + } +- handle_filter(mi, sb); ++ handle_filter_flowed(mi, sb, &prev); + } + /* + * The partial chunk is saved in "prev" and will be +@@ -1022,13 +1079,16 @@ static void handle_body(struct mailinfo *mi, +struct strbuf *line) + break; + } + default: +- handle_filter(mi, line); ++ handle_filter_flowed(mi, line, &prev); + } + + if (mi->input_error) + break; + } while (!strbuf_getwholeline(line, mi->input, '\n')); + ++ if (prev.len) ++ handle_filter(mi, &prev); ++ + flush_inbody_header_accum(mi); + + handle_body_out: +-- +2.18.0 diff --git a/t/t4300-merge-tree.sh b/t/t4300-merge-tree.sh index 9015e47654..d87cc7d9ef 100755 --- a/t/t4300-merge-tree.sh +++ b/t/t4300-merge-tree.sh @@ -25,25 +25,19 @@ EXPECTED ' test_expect_success 'file add !A, B' ' - cat >expected <<\EXPECTED && -EXPECTED - git reset --hard initial && test_commit "add-not-a-b" "ONE" "AAA" && git merge-tree initial add-not-a-b initial >actual && - test_cmp expected actual + test_must_be_empty actual ' test_expect_success 'file add A, B (same)' ' - cat >expected <<\EXPECTED && -EXPECTED - git reset --hard initial && test_commit "add-a-b-same-A" "ONE" "AAA" && git reset --hard initial && test_commit "add-a-b-same-B" "ONE" "AAA" && git merge-tree initial add-a-b-same-A add-a-b-same-B >actual && - test_cmp expected actual + test_must_be_empty actual ' test_expect_success 'file add A, B (different)' ' @@ -68,13 +62,10 @@ EXPECTED ' test_expect_success 'file change A, !B' ' - cat >expected <<\EXPECTED && -EXPECTED - git reset --hard initial && test_commit "change-a-not-b" "initial-file" "BBB" && git merge-tree initial change-a-not-b initial >actual && - test_cmp expected actual + test_must_be_empty actual ' test_expect_success 'file change !A, B' ' @@ -94,15 +85,12 @@ EXPECTED ' test_expect_success 'file change A, B (same)' ' - cat >expected <<\EXPECTED && -EXPECTED - git reset --hard initial && test_commit "change-a-b-same-A" "initial-file" "AAA" && git reset --hard initial && test_commit "change-a-b-same-B" "initial-file" "AAA" && git merge-tree initial change-a-b-same-A change-a-b-same-B >actual && - test_cmp expected actual + test_must_be_empty actual ' test_expect_success 'file change A, B (different)' ' @@ -175,16 +163,13 @@ AAA" && ' test_expect_success 'file remove A, !B' ' - cat >expected <<\EXPECTED && -EXPECTED - git reset --hard initial && test_commit "rm-a-not-b-base" "ONE" "AAA" && git rm ONE && git commit -m "rm-a-not-b" && git tag "rm-a-not-b" && git merge-tree rm-a-not-b-base rm-a-not-b rm-a-not-b-base >actual && - test_cmp expected actual + test_must_be_empty actual ' test_expect_success 'file remove !A, B' ' @@ -206,16 +191,13 @@ EXPECTED ' test_expect_success 'file remove A, B (same)' ' - cat >expected <<\EXPECTED && -EXPECTED - git reset --hard initial && test_commit "rm-a-b-base" "ONE" "AAA" && git rm ONE && git commit -m "rm-a-b" && git tag "rm-a-b" && git merge-tree rm-a-b-base rm-a-b rm-a-b >actual && - test_cmp expected actual + test_must_be_empty actual ' test_expect_success 'file change A, remove B' ' @@ -260,13 +242,11 @@ EXPECTED ' test_expect_success 'tree add A, B (same)' ' - cat >expect <<-\EOF && - EOF git reset --hard initial && mkdir sub && test_commit "add sub/file" "sub/file" "file" add-tree-A && git merge-tree initial add-tree-A add-tree-A >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'tree add A, B (different)' ' diff --git a/t/t5000-tar-tree.sh b/t/t5000-tar-tree.sh index af4d9b8876..2a97b27b0a 100755 --- a/t/t5000-tar-tree.sh +++ b/t/t5000-tar-tree.sh @@ -192,7 +192,7 @@ test_expect_success \ 'validate file modification time' \ 'mkdir extract && "$TAR" xf b.tar -C extract a/a && - test-tool chmtime -v +0 extract/a/a |cut -f 1 >b.mtime && + test-tool chmtime --get extract/a/a >b.mtime && echo "1117231200" >expected.mtime && test_cmp expected.mtime b.mtime' diff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh index 08c210f035..fca001eb9b 100755 --- a/t/t5150-request-pull.sh +++ b/t/t5150-request-pull.sh @@ -81,7 +81,7 @@ test_expect_success 'setup: two scripts for reading pull requests' ' cat <<-EOT >fuzz.sed #!/bin/sed -nf s/$downstream_url_for_sed/URL/g - s/$_x40/OBJECT_NAME/g + s/$OID_REGEX/OBJECT_NAME/g s/A U Thor/AUTHOR/g s/[-0-9]\{10\} [:0-9]\{8\} [-+][0-9]\{4\}/DATE/g s/ [^ ].*/ SUBJECT/g diff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh index 65ff60f2ee..6c620cd540 100755 --- a/t/t5300-pack-object.sh +++ b/t/t5300-pack-object.sh @@ -191,7 +191,7 @@ test_expect_success 'survive missing objects/pack directory' ' mkdir missing-pack && cd missing-pack && git init && - GOP=.git/objects/pack + GOP=.git/objects/pack && rm -fr $GOP && git index-pack --stdin --keep=test <../test-3-${packname_3}.pack && test -f $GOP/pack-${packname_3}.pack && @@ -421,6 +421,12 @@ test_expect_success 'index-pack <pack> works in non-repo' ' test_path_is_file foo.idx ' +test_expect_success 'index-pack --strict <pack> works in non-repo' ' + rm -f foo.idx && + nongit git index-pack --strict ../foo.pack && + test_path_is_file foo.idx +' + test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'index-pack --threads=N or pack.threads=N warns when no pthreads' ' test_must_fail git index-pack --threads=2 2>err && grep ^warning: err >warnings && @@ -457,6 +463,11 @@ test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'pack-objects --threads=N or pack. grep -F "no threads support, ignoring pack.threads" err ' +test_expect_success 'pack-objects in too-many-packs mode' ' + GIT_TEST_FULL_IN_PACK_ARRAY=1 git repack -ad && + git fsck +' + # # WARNING! # @@ -466,9 +477,11 @@ test_expect_success !PTHREADS,C_LOCALE_OUTPUT 'pack-objects --threads=N or pack. test_expect_success \ 'fake a SHA1 hash collision' \ - 'test -f .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67 && - cp -f .git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \ - .git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67' + 'long_a=$(git hash-object a | sed -e "s!^..!&/!") && + long_b=$(git hash-object b | sed -e "s!^..!&/!") && + test -f .git/objects/$long_b && + cp -f .git/objects/$long_a \ + .git/objects/$long_b' test_expect_success \ 'make sure index-pack detects the SHA1 collision' \ diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index bb9b8bb309..91d51b35f9 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -237,7 +237,7 @@ test_expect_success 'running index-pack in the object store' ' rm -f .git/objects/pack/* && cp test-1-${pack1}.pack .git/objects/pack/pack-${pack1}.pack && ( - cd .git/objects/pack + cd .git/objects/pack && git index-pack pack-${pack1}.pack ) && test -f .git/objects/pack/pack-${pack1}.idx diff --git a/t/t5303-pack-corruption-resilience.sh b/t/t5303-pack-corruption-resilience.sh index 3634e258f8..41e6dc4dcf 100755 --- a/t/t5303-pack-corruption-resilience.sh +++ b/t/t5303-pack-corruption-resilience.sh @@ -311,4 +311,93 @@ test_expect_success \ test_must_fail git cat-file blob $blob_2 > /dev/null && test_must_fail git cat-file blob $blob_3 > /dev/null' +# \0 - empty base +# \1 - one byte in result +# \1 - one literal byte (X) +test_expect_success \ + 'apply good minimal delta' \ + 'printf "\0\1\1X" > minimal_delta && + test-tool delta -p /dev/null minimal_delta /dev/null' + +# \0 - empty base +# \1 - 1 byte in result +# \2 - two literal bytes (one too many) +test_expect_success \ + 'apply delta with too many literal bytes' \ + 'printf "\0\1\2XX" > too_big_literal && + test_must_fail test-tool delta -p /dev/null too_big_literal /dev/null' + +# \4 - four bytes in base +# \1 - one byte in result +# \221 - copy, one byte offset, one byte size +# \0 - copy from offset 0 +# \2 - copy two bytes (one too many) +test_expect_success \ + 'apply delta with too many copied bytes' \ + 'printf "\4\1\221\0\2" > too_big_copy && + printf base >base && + test_must_fail test-tool delta -p base too_big_copy /dev/null' + +# \0 - empty base +# \2 - two bytes in result +# \2 - two literal bytes (we are short one) +test_expect_success \ + 'apply delta with too few literal bytes' \ + 'printf "\0\2\2X" > truncated_delta && + test_must_fail test-tool delta -p /dev/null truncated_delta /dev/null' + +# \0 - empty base +# \1 - one byte in result +# \221 - copy, one byte offset, one byte size +# \0 - copy from offset 0 +# \1 - copy one byte (we are short one) +test_expect_success \ + 'apply delta with too few bytes in base' \ + 'printf "\0\1\221\0\1" > truncated_base && + test_must_fail test-tool delta -p /dev/null truncated_base /dev/null' + +# \4 - four bytes in base +# \2 - two bytes in result +# \1 - one literal byte (X) +# \221 - copy, one byte offset, one byte size +# (offset/size missing) +# +# Note that the literal byte is necessary to get past the uninteresting minimum +# delta size check. +test_expect_success \ + 'apply delta with truncated copy parameters' \ + 'printf "\4\2\1X\221" > truncated_copy_delta && + printf base >base && + test_must_fail test-tool delta -p base truncated_copy_delta /dev/null' + +# \0 - empty base +# \1 - one byte in result +# \1 - one literal byte (X) +# \1 - trailing garbage command +test_expect_success \ + 'apply delta with trailing garbage literal' \ + 'printf "\0\1\1X\1" > tail_garbage_literal && + test_must_fail test-tool delta -p /dev/null tail_garbage_literal /dev/null' + +# \4 - four bytes in base +# \1 - one byte in result +# \1 - one literal byte (X) +# \221 - copy, one byte offset, one byte size +# \0 - copy from offset 0 +# \1 - copy 1 byte +test_expect_success \ + 'apply delta with trailing garbage copy' \ + 'printf "\4\1\1X\221\0\1" > tail_garbage_copy && + printf base >base && + test_must_fail test-tool delta -p /dev/null tail_garbage_copy /dev/null' + +# \0 - empty base +# \1 - one byte in result +# \1 - one literal byte (X) +# \0 - bogus opcode +test_expect_success \ + 'apply delta with trailing garbage opcode' \ + 'printf "\0\1\1X\0" > tail_garbage_opcode && + test_must_fail test-tool delta -p /dev/null tail_garbage_opcode /dev/null' + test_done diff --git a/t/t5304-prune.sh b/t/t5304-prune.sh index f0f6e2a5f3..270da21ac3 100755 --- a/t/t5304-prune.sh +++ b/t/t5304-prune.sh @@ -112,8 +112,7 @@ test_expect_success 'prune: do not prune detached HEAD with no reflog' ' # (should be removed and disabled by previous test) test_path_is_missing .git/logs && git prune -n >prune_actual && - : >prune_expected && - test_cmp prune_actual prune_expected + test_must_be_empty prune_actual ' @@ -320,4 +319,14 @@ test_expect_success 'prune: handle HEAD reflog in multiple worktrees' ' test_cmp expected actual ' +test_expect_success 'prune: handle expire option correctly' ' + test_must_fail git prune --expire 2>error && + test_i18ngrep "requires a value" error && + + test_must_fail git prune --expire=nyah 2>error && + test_i18ngrep "malformed expiration" error && + + git prune --no-expire +' + test_done diff --git a/t/t5307-pack-missing-commit.sh b/t/t5307-pack-missing-commit.sh index ae52a1882d..dacb440b27 100755 --- a/t/t5307-pack-missing-commit.sh +++ b/t/t5307-pack-missing-commit.sh @@ -24,11 +24,11 @@ test_expect_success 'check corruption' ' ' test_expect_success 'rev-list notices corruption (1)' ' - test_must_fail git rev-list HEAD + test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list HEAD ' test_expect_success 'rev-list notices corruption (2)' ' - test_must_fail git rev-list --objects HEAD + test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --objects HEAD ' test_expect_success 'pack-objects notices corruption' ' diff --git a/t/t5308-pack-detect-duplicates.sh b/t/t5308-pack-detect-duplicates.sh index 156ae9e9d3..6845c1f3c3 100755 --- a/t/t5308-pack-detect-duplicates.sh +++ b/t/t5308-pack-detect-duplicates.sh @@ -4,6 +4,12 @@ test_description='handling of duplicate objects in incoming packfiles' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pack.sh +if ! test_have_prereq SHA1 +then + skip_all='not using SHA-1 for objects' + test_done +fi + # The sha1s we have in our pack. It's important that these have the same # starting byte, so that they end up in the same fanout section of the index. # That lets us make sure we are exercising the binary search with both sets. diff --git a/t/t5309-pack-delta-cycles.sh b/t/t5309-pack-delta-cycles.sh index 3e7861b075..491556dad9 100755 --- a/t/t5309-pack-delta-cycles.sh +++ b/t/t5309-pack-delta-cycles.sh @@ -4,6 +4,12 @@ test_description='test index-pack handling of delta cycles in packfiles' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-pack.sh +if ! test_have_prereq SHA1 +then + skip_all='not using SHA-1 for objects' + test_done +fi + # Two similar-ish objects that we have computed deltas between. A=01d7713666f4de822776c7622c10f1b07de280dc B=e68fe8129b546b101aee9510c5328e7f21ca1d18 diff --git a/t/t5310-pack-bitmaps.sh b/t/t5310-pack-bitmaps.sh index f6d600fd82..1be3459c5b 100755 --- a/t/t5310-pack-bitmaps.sh +++ b/t/t5310-pack-bitmaps.sh @@ -9,7 +9,8 @@ objpath () { # show objects present in pack ($1 should be associated *.idx) list_packed_objects () { - git show-index <"$1" | cut -d' ' -f2 + git show-index <"$1" >object-list && + cut -d' ' -f2 object-list } # has_any pattern-file content-file @@ -204,8 +205,8 @@ test_expect_success 'pack-objects to file can use bitmap' ' # verify equivalent packs are generated with/without using bitmap index packasha1=$(git pack-objects --no-use-bitmap-index --all packa </dev/null) && packbsha1=$(git pack-objects --use-bitmap-index --all packb </dev/null) && - list_packed_objects <packa-$packasha1.idx >packa.objects && - list_packed_objects <packb-$packbsha1.idx >packb.objects && + list_packed_objects packa-$packasha1.idx >packa.objects && + list_packed_objects packb-$packbsha1.idx >packb.objects && test_cmp packa.objects packb.objects ' @@ -264,9 +265,9 @@ test_expect_success 'pack with missing parent' ' ' test_expect_success JGIT 'we can read jgit bitmaps' ' - git clone . compat-jgit && + git clone --bare . compat-jgit.git && ( - cd compat-jgit && + cd compat-jgit.git && rm -f .git/objects/pack/*.bitmap && jgit gc && git rev-list --test-bitmap HEAD @@ -274,9 +275,9 @@ test_expect_success JGIT 'we can read jgit bitmaps' ' ' test_expect_success JGIT 'jgit can read our bitmaps' ' - git clone . compat-us && + git clone --bare . compat-us.git && ( - cd compat-us && + cd compat-us.git && git repack -adb && # jgit gc will barf if it does not like our bitmaps jgit gc @@ -309,9 +310,8 @@ test_expect_success 'pack reuse respects --honor-pack-keep' ' done && reusable_pack --honor-pack-keep >empty.pack && git index-pack empty.pack && - >expect && git show-index <empty.idx >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'pack reuse respects --local' ' @@ -319,16 +319,120 @@ test_expect_success 'pack reuse respects --local' ' test_when_finished "mv alt.git/objects/pack/* .git/objects/pack/" && reusable_pack --local >empty.pack && git index-pack empty.pack && - >expect && git show-index <empty.idx >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'pack reuse respects --incremental' ' reusable_pack --incremental >empty.pack && git index-pack empty.pack && - >expect && git show-index <empty.idx >actual && + test_must_be_empty actual +' + +test_expect_success 'truncated bitmap fails gracefully' ' + git repack -ad && + git rev-list --use-bitmap-index --count --all >expect && + bitmap=$(ls .git/objects/pack/*.bitmap) && + test_when_finished "rm -f $bitmap" && + test_copy_bytes 512 <$bitmap >$bitmap.tmp && + mv -f $bitmap.tmp $bitmap && + git rev-list --use-bitmap-index --count --all >actual 2>stderr && + test_cmp expect actual && + test_i18ngrep corrupt stderr +' + +# have_delta <obj> <expected_base> +# +# Note that because this relies on cat-file, it might find _any_ copy of an +# object in the repository. The caller is responsible for making sure +# there's only one (e.g., via "repack -ad", or having just fetched a copy). +have_delta () { + echo $2 >expect && + echo $1 | git cat-file --batch-check="%(deltabase)" >actual && test_cmp expect actual +} + +# Create a state of history with these properties: +# +# - refs that allow a client to fetch some new history, while sharing some old +# history with the server; we use branches delta-reuse-old and +# delta-reuse-new here +# +# - the new history contains an object that is stored on the server as a delta +# against a base that is in the old history +# +# - the base object is not immediately reachable from the tip of the old +# history; finding it would involve digging down through history we know the +# other side has +# +# This should result in a state where fetching from old->new would not +# traditionally reuse the on-disk delta (because we'd have to dig to realize +# that the client has it), but we will do so if bitmaps can tell us cheaply +# that the other side has it. +test_expect_success 'set up thin delta-reuse parent' ' + # This first commit contains the buried base object. + test-tool genrandom delta 16384 >file && + git add file && + git commit -m "delta base" && + base=$(git rev-parse --verify HEAD:file) && + + # These intermediate commits bury the base back in history. + # This becomes the "old" state. + for i in 1 2 3 4 5 + do + echo $i >file && + git commit -am "intermediate $i" || return 1 + done && + git branch delta-reuse-old && + + # And now our new history has a delta against the buried base. Note + # that this must be smaller than the original file, since pack-objects + # prefers to create deltas from smaller objects to larger. + test-tool genrandom delta 16300 >file && + git commit -am "delta result" && + delta=$(git rev-parse --verify HEAD:file) && + git branch delta-reuse-new && + + # Repack with bitmaps and double check that we have the expected delta + # relationship. + git repack -adb && + have_delta $delta $base +' + +# Now we can sanity-check the non-bitmap behavior (that the server is not able +# to reuse the delta). This isn't strictly something we care about, so this +# test could be scrapped in the future. But it makes sure that the next test is +# actually triggering the feature we want. +# +# Note that our tools for working with on-the-wire "thin" packs are limited. So +# we actually perform the fetch, retain the resulting pack, and inspect the +# result. +test_expect_success 'fetch without bitmaps ignores delta against old base' ' + test_config pack.usebitmaps false && + test_when_finished "rm -rf client.git" && + git init --bare client.git && + ( + cd client.git && + git config transfer.unpackLimit 1 && + git fetch .. delta-reuse-old:delta-reuse-old && + git fetch .. delta-reuse-new:delta-reuse-new && + have_delta $delta $ZERO_OID + ) ' + +# And do the same for the bitmap case, where we do expect to find the delta. +test_expect_success 'fetch with bitmaps can reuse old base' ' + test_config pack.usebitmaps true && + test_when_finished "rm -rf client.git" && + git init --bare client.git && + ( + cd client.git && + git config transfer.unpackLimit 1 && + git fetch .. delta-reuse-old:delta-reuse-old && + git fetch .. delta-reuse-new:delta-reuse-new && + have_delta $delta $base + ) +' + test_done diff --git a/t/t5313-pack-bounds-checks.sh b/t/t5313-pack-bounds-checks.sh index 4fe4ad9d61..f1708d415e 100755 --- a/t/t5313-pack-bounds-checks.sh +++ b/t/t5313-pack-bounds-checks.sh @@ -90,9 +90,8 @@ test_expect_success 'matched bogus object count' ' # Unlike above, we should notice early that the .idx is totally # bogus, and not even enumerate its contents. - >expect && git cat-file --batch-all-objects --batch-check >actual && - test_cmp expect actual && + test_must_be_empty actual && # But as before, we can do the same object-access checks. test_must_fail git cat-file blob $object && diff --git a/t/t5314-pack-cycle-detection.sh b/t/t5314-pack-cycle-detection.sh index f31995d3d2..e525466de0 100755 --- a/t/t5314-pack-cycle-detection.sh +++ b/t/t5314-pack-cycle-detection.sh @@ -98,9 +98,8 @@ test_expect_success 'repack' ' # We first want to check that we do not have any internal errors, # and also that we do not hit the last-ditch cycle-breaking code # in write_object(), which will issue a warning to stderr. - >expect && git repack -ad 2>stderr && - test_cmp expect stderr && + test_must_be_empty stderr && # And then double-check that the resulting pack is usable (i.e., # we did not fail to notice any cycles). We know we are accessing diff --git a/t/t5317-pack-objects-filter-objects.sh b/t/t5317-pack-objects-filter-objects.sh index 1b0acc383b..6710c8bc8c 100755 --- a/t/t5317-pack-objects-filter-objects.sh +++ b/t/t5317-pack-objects-filter-objects.sh @@ -160,6 +160,22 @@ test_expect_success 'verify blob:limit=1k' ' test_cmp observed expected ' +test_expect_success 'verify explicitly specifying oversized blob in input' ' + git -C r2 ls-files -s large.1000 large.10000 \ + | awk -f print_2.awk \ + | sort >expected && + git -C r2 pack-objects --rev --stdout --filter=blob:limit=1k >filter.pack <<-EOF && + HEAD + $(git -C r2 rev-parse HEAD:large.10000) + EOF + git -C r2 index-pack ../filter.pack && + git -C r2 verify-pack -v ../filter.pack \ + | grep blob \ + | awk -f print_1.awk \ + | sort >observed && + test_cmp observed expected +' + test_expect_success 'verify blob:limit=1m' ' git -C r2 ls-files -s large.1000 large.10000 \ | awk -f print_2.awk \ diff --git a/t/t5318-commit-graph.sh b/t/t5318-commit-graph.sh new file mode 100755 index 0000000000..0c500f7ca2 --- /dev/null +++ b/t/t5318-commit-graph.sh @@ -0,0 +1,471 @@ +#!/bin/sh + +test_description='commit graph' +. ./test-lib.sh + +test_expect_success 'setup full repo' ' + mkdir full && + cd "$TRASH_DIRECTORY/full" && + git init && + git config core.commitGraph true && + objdir=".git/objects" +' + +test_expect_success 'verify graph with no graph file' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph verify +' + +test_expect_success 'write graph with no packs' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph write --object-dir . && + test_path_is_file info/commit-graph +' + +test_expect_success 'create commits and repack' ' + cd "$TRASH_DIRECTORY/full" && + for i in $(test_seq 3) + do + test_commit $i && + git branch commits/$i + done && + git repack +' + +graph_git_two_modes() { + git -c core.commitGraph=true $1 >output + git -c core.commitGraph=false $1 >expect + test_cmp output expect +} + +graph_git_behavior() { + MSG=$1 + DIR=$2 + BRANCH=$3 + COMPARE=$4 + test_expect_success "check normal git operations: $MSG" ' + cd "$TRASH_DIRECTORY/$DIR" && + graph_git_two_modes "log --oneline $BRANCH" && + graph_git_two_modes "log --topo-order $BRANCH" && + graph_git_two_modes "log --graph $COMPARE..$BRANCH" && + graph_git_two_modes "branch -vv" && + graph_git_two_modes "merge-base -a $BRANCH $COMPARE" + ' +} + +graph_git_behavior 'no graph' full commits/3 commits/1 + +graph_read_expect() { + OPTIONAL="" + NUM_CHUNKS=3 + if test ! -z $2 + then + OPTIONAL=" $2" + NUM_CHUNKS=$((3 + $(echo "$2" | wc -w))) + fi + cat >expect <<- EOF + header: 43475048 1 1 $NUM_CHUNKS 0 + num_commits: $1 + chunks: oid_fanout oid_lookup commit_metadata$OPTIONAL + EOF + git commit-graph read >output && + test_cmp expect output +} + +test_expect_success 'write graph' ' + cd "$TRASH_DIRECTORY/full" && + graph1=$(git commit-graph write) && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "3" +' + +graph_git_behavior 'graph exists' full commits/3 commits/1 + +test_expect_success 'Add more commits' ' + cd "$TRASH_DIRECTORY/full" && + git reset --hard commits/1 && + for i in $(test_seq 4 5) + do + test_commit $i && + git branch commits/$i + done && + git reset --hard commits/2 && + for i in $(test_seq 6 7) + do + test_commit $i && + git branch commits/$i + done && + git reset --hard commits/2 && + git merge commits/4 && + git branch merge/1 && + git reset --hard commits/4 && + git merge commits/6 && + git branch merge/2 && + git reset --hard commits/3 && + git merge commits/5 commits/7 && + git branch merge/3 && + git repack +' + +# Current graph structure: +# +# __M3___ +# / | \ +# 3 M1 5 M2 7 +# |/ \|/ \| +# 2 4 6 +# |___/____/ +# 1 + +test_expect_success 'write graph with merges' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph write && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "10" "large_edges" +' + +graph_git_behavior 'merge 1 vs 2' full merge/1 merge/2 +graph_git_behavior 'merge 1 vs 3' full merge/1 merge/3 +graph_git_behavior 'merge 2 vs 3' full merge/2 merge/3 + +test_expect_success 'Add one more commit' ' + cd "$TRASH_DIRECTORY/full" && + test_commit 8 && + git branch commits/8 && + ls $objdir/pack | grep idx >existing-idx && + git repack && + ls $objdir/pack| grep idx | grep -v -f existing-idx >new-idx +' + +# Current graph structure: +# +# 8 +# | +# __M3___ +# / | \ +# 3 M1 5 M2 7 +# |/ \|/ \| +# 2 4 6 +# |___/____/ +# 1 + +graph_git_behavior 'mixed mode, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'mixed mode, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'write graph with new commit' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph write && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "11" "large_edges" +' + +graph_git_behavior 'full graph, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'full graph, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'write graph with nothing new' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph write && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "11" "large_edges" +' + +graph_git_behavior 'cleared graph, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'cleared graph, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'build graph from latest pack with closure' ' + cd "$TRASH_DIRECTORY/full" && + cat new-idx | git commit-graph write --stdin-packs && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "9" "large_edges" +' + +graph_git_behavior 'graph from pack, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'graph from pack, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'build graph from commits with closure' ' + cd "$TRASH_DIRECTORY/full" && + git tag -a -m "merge" tag/merge merge/2 && + git rev-parse tag/merge >commits-in && + git rev-parse merge/1 >>commits-in && + cat commits-in | git commit-graph write --stdin-commits && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "6" +' + +graph_git_behavior 'graph from commits, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'graph from commits, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'build graph from commits with append' ' + cd "$TRASH_DIRECTORY/full" && + git rev-parse merge/3 | git commit-graph write --stdin-commits --append && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "10" "large_edges" +' + +graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'append graph, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'build graph using --reachable' ' + cd "$TRASH_DIRECTORY/full" && + git commit-graph write --reachable && + test_path_is_file $objdir/info/commit-graph && + graph_read_expect "11" "large_edges" +' + +graph_git_behavior 'append graph, commit 8 vs merge 1' full commits/8 merge/1 +graph_git_behavior 'append graph, commit 8 vs merge 2' full commits/8 merge/2 + +test_expect_success 'setup bare repo' ' + cd "$TRASH_DIRECTORY" && + git clone --bare --no-local full bare && + cd bare && + git config core.commitGraph true && + baredir="./objects" +' + +graph_git_behavior 'bare repo, commit 8 vs merge 1' bare commits/8 merge/1 +graph_git_behavior 'bare repo, commit 8 vs merge 2' bare commits/8 merge/2 + +test_expect_success 'write graph in bare repo' ' + cd "$TRASH_DIRECTORY/bare" && + git commit-graph write && + test_path_is_file $baredir/info/commit-graph && + graph_read_expect "11" "large_edges" +' + +graph_git_behavior 'bare repo with graph, commit 8 vs merge 1' bare commits/8 merge/1 +graph_git_behavior 'bare repo with graph, commit 8 vs merge 2' bare commits/8 merge/2 + +test_expect_success 'perform fast-forward merge in full repo' ' + cd "$TRASH_DIRECTORY/full" && + git checkout -b merge-5-to-8 commits/5 && + git merge commits/8 && + git show-ref -s merge-5-to-8 >output && + git show-ref -s commits/8 >expect && + test_cmp expect output +' + +test_expect_success 'check that gc computes commit-graph' ' + cd "$TRASH_DIRECTORY/full" && + git commit --allow-empty -m "blank" && + git commit-graph write --reachable && + cp $objdir/info/commit-graph commit-graph-before-gc && + git reset --hard HEAD~1 && + git config gc.writeCommitGraph true && + git gc && + cp $objdir/info/commit-graph commit-graph-after-gc && + ! test_cmp_bin commit-graph-before-gc commit-graph-after-gc && + git commit-graph write --reachable && + test_cmp_bin commit-graph-after-gc $objdir/info/commit-graph +' + +# the verify tests below expect the commit-graph to contain +# exactly the commits reachable from the commits/8 branch. +# If the file changes the set of commits in the list, then the +# offsets into the binary file will result in different edits +# and the tests will likely break. + +test_expect_success 'git commit-graph verify' ' + cd "$TRASH_DIRECTORY/full" && + git rev-parse commits/8 | git commit-graph write --stdin-commits && + git commit-graph verify >output +' + +NUM_COMMITS=9 +NUM_OCTOPUS_EDGES=2 +HASH_LEN=20 +GRAPH_BYTE_VERSION=4 +GRAPH_BYTE_HASH=5 +GRAPH_BYTE_CHUNK_COUNT=6 +GRAPH_CHUNK_LOOKUP_OFFSET=8 +GRAPH_CHUNK_LOOKUP_WIDTH=12 +GRAPH_CHUNK_LOOKUP_ROWS=5 +GRAPH_BYTE_OID_FANOUT_ID=$GRAPH_CHUNK_LOOKUP_OFFSET +GRAPH_BYTE_OID_LOOKUP_ID=$(($GRAPH_CHUNK_LOOKUP_OFFSET + \ + 1 * $GRAPH_CHUNK_LOOKUP_WIDTH)) +GRAPH_BYTE_COMMIT_DATA_ID=$(($GRAPH_CHUNK_LOOKUP_OFFSET + \ + 2 * $GRAPH_CHUNK_LOOKUP_WIDTH)) +GRAPH_FANOUT_OFFSET=$(($GRAPH_CHUNK_LOOKUP_OFFSET + \ + $GRAPH_CHUNK_LOOKUP_WIDTH * $GRAPH_CHUNK_LOOKUP_ROWS)) +GRAPH_BYTE_FANOUT1=$(($GRAPH_FANOUT_OFFSET + 4 * 4)) +GRAPH_BYTE_FANOUT2=$(($GRAPH_FANOUT_OFFSET + 4 * 255)) +GRAPH_OID_LOOKUP_OFFSET=$(($GRAPH_FANOUT_OFFSET + 4 * 256)) +GRAPH_BYTE_OID_LOOKUP_ORDER=$(($GRAPH_OID_LOOKUP_OFFSET + $HASH_LEN * 8)) +GRAPH_BYTE_OID_LOOKUP_MISSING=$(($GRAPH_OID_LOOKUP_OFFSET + $HASH_LEN * 4 + 10)) +GRAPH_COMMIT_DATA_OFFSET=$(($GRAPH_OID_LOOKUP_OFFSET + $HASH_LEN * $NUM_COMMITS)) +GRAPH_BYTE_COMMIT_TREE=$GRAPH_COMMIT_DATA_OFFSET +GRAPH_BYTE_COMMIT_PARENT=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN)) +GRAPH_BYTE_COMMIT_EXTRA_PARENT=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 4)) +GRAPH_BYTE_COMMIT_WRONG_PARENT=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 3)) +GRAPH_BYTE_COMMIT_GENERATION=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 11)) +GRAPH_BYTE_COMMIT_DATE=$(($GRAPH_COMMIT_DATA_OFFSET + $HASH_LEN + 12)) +GRAPH_COMMIT_DATA_WIDTH=$(($HASH_LEN + 16)) +GRAPH_OCTOPUS_DATA_OFFSET=$(($GRAPH_COMMIT_DATA_OFFSET + \ + $GRAPH_COMMIT_DATA_WIDTH * $NUM_COMMITS)) +GRAPH_BYTE_OCTOPUS=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4)) +GRAPH_BYTE_FOOTER=$(($GRAPH_OCTOPUS_DATA_OFFSET + 4 * $NUM_OCTOPUS_EDGES)) + +# usage: corrupt_graph_and_verify <position> <data> <string> +# Manipulates the commit-graph file at the position +# by inserting the data, then runs 'git commit-graph verify' +# and places the output in the file 'err'. Test 'err' for +# the given string. +corrupt_graph_and_verify() { + pos=$1 + data="${2:-\0}" + grepstr=$3 + cd "$TRASH_DIRECTORY/full" && + test_when_finished mv commit-graph-backup $objdir/info/commit-graph && + cp $objdir/info/commit-graph commit-graph-backup && + printf "$data" | dd of="$objdir/info/commit-graph" bs=1 seek="$pos" conv=notrunc && + test_must_fail git commit-graph verify 2>test_err && + grep -v "^+" test_err >err + test_i18ngrep "$grepstr" err +} + +test_expect_success 'detect bad signature' ' + corrupt_graph_and_verify 0 "\0" \ + "graph signature" +' + +test_expect_success 'detect bad version' ' + corrupt_graph_and_verify $GRAPH_BYTE_VERSION "\02" \ + "graph version" +' + +test_expect_success 'detect bad hash version' ' + corrupt_graph_and_verify $GRAPH_BYTE_HASH "\02" \ + "hash version" +' + +test_expect_success 'detect low chunk count' ' + corrupt_graph_and_verify $GRAPH_BYTE_CHUNK_COUNT "\02" \ + "missing the .* chunk" +' + +test_expect_success 'detect missing OID fanout chunk' ' + corrupt_graph_and_verify $GRAPH_BYTE_OID_FANOUT_ID "\0" \ + "missing the OID Fanout chunk" +' + +test_expect_success 'detect missing OID lookup chunk' ' + corrupt_graph_and_verify $GRAPH_BYTE_OID_LOOKUP_ID "\0" \ + "missing the OID Lookup chunk" +' + +test_expect_success 'detect missing commit data chunk' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_DATA_ID "\0" \ + "missing the Commit Data chunk" +' + +test_expect_success 'detect incorrect fanout' ' + corrupt_graph_and_verify $GRAPH_BYTE_FANOUT1 "\01" \ + "fanout value" +' + +test_expect_success 'detect incorrect fanout final value' ' + corrupt_graph_and_verify $GRAPH_BYTE_FANOUT2 "\01" \ + "fanout value" +' + +test_expect_success 'detect incorrect OID order' ' + corrupt_graph_and_verify $GRAPH_BYTE_OID_LOOKUP_ORDER "\01" \ + "incorrect OID order" +' + +test_expect_success 'detect OID not in object database' ' + corrupt_graph_and_verify $GRAPH_BYTE_OID_LOOKUP_MISSING "\01" \ + "from object database" +' + +test_expect_success 'detect incorrect tree OID' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_TREE "\01" \ + "root tree OID for commit" +' + +test_expect_success 'detect incorrect parent int-id' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_PARENT "\01" \ + "invalid parent" +' + +test_expect_success 'detect extra parent int-id' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_EXTRA_PARENT "\00" \ + "is too long" +' + +test_expect_success 'detect wrong parent' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_WRONG_PARENT "\01" \ + "commit-graph parent for" +' + +test_expect_success 'detect incorrect generation number' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_GENERATION "\070" \ + "generation for commit" +' + +test_expect_success 'detect incorrect generation number' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_GENERATION "\01" \ + "non-zero generation number" +' + +test_expect_success 'detect incorrect commit date' ' + corrupt_graph_and_verify $GRAPH_BYTE_COMMIT_DATE "\01" \ + "commit date" +' + +test_expect_success 'detect incorrect parent for octopus merge' ' + corrupt_graph_and_verify $GRAPH_BYTE_OCTOPUS "\01" \ + "invalid parent" +' + +test_expect_success 'detect invalid checksum hash' ' + corrupt_graph_and_verify $GRAPH_BYTE_FOOTER "\00" \ + "incorrect checksum" +' + +test_expect_success 'git fsck (checks commit-graph)' ' + cd "$TRASH_DIRECTORY/full" && + git fsck && + corrupt_graph_and_verify $GRAPH_BYTE_FOOTER "\00" \ + "incorrect checksum" && + test_must_fail git fsck +' + +test_expect_success 'setup non-the_repository tests' ' + rm -rf repo && + git init repo && + test_commit -C repo one && + test_commit -C repo two && + git -C repo config core.commitGraph true && + git -C repo rev-parse two | \ + git -C repo commit-graph write --stdin-commits +' + +test_expect_success 'parse_commit_in_graph works for non-the_repository' ' + test-tool repository parse_commit_in_graph \ + repo/.git repo "$(git -C repo rev-parse two)" >actual && + { + git -C repo log --pretty=format:"%ct " -1 && + git -C repo rev-parse one + } >expect && + test_cmp expect actual && + + test-tool repository parse_commit_in_graph \ + repo/.git repo "$(git -C repo rev-parse one)" >actual && + git -C repo log --pretty="%ct" -1 one >expect && + test_cmp expect actual +' + +test_expect_success 'get_commit_tree_in_graph works for non-the_repository' ' + test-tool repository get_commit_tree_in_graph \ + repo/.git repo "$(git -C repo rev-parse two)" >actual && + git -C repo rev-parse two^{tree} >expect && + test_cmp expect actual && + + test-tool repository get_commit_tree_in_graph \ + repo/.git repo "$(git -C repo rev-parse one)" >actual && + git -C repo rev-parse one^{tree} >expect && + test_cmp expect actual +' + +test_done diff --git a/t/t5319-multi-pack-index.sh b/t/t5319-multi-pack-index.sh new file mode 100755 index 0000000000..6f56b38674 --- /dev/null +++ b/t/t5319-multi-pack-index.sh @@ -0,0 +1,217 @@ +#!/bin/sh + +test_description='multi-pack-indexes' +. ./test-lib.sh + +objdir=.git/objects + +midx_read_expect () { + NUM_PACKS=$1 + NUM_OBJECTS=$2 + NUM_CHUNKS=$3 + OBJECT_DIR=$4 + EXTRA_CHUNKS="$5" + { + cat <<-EOF && + header: 4d494458 1 $NUM_CHUNKS $NUM_PACKS + chunks: pack-names oid-fanout oid-lookup object-offsets$EXTRA_CHUNKS + num_objects: $NUM_OBJECTS + packs: + EOF + if test $NUM_PACKS -ge 1 + then + ls $OBJECT_DIR/pack/ | grep idx | sort + fi && + printf "object-dir: $OBJECT_DIR\n" + } >expect && + test-tool read-midx $OBJECT_DIR >actual && + test_cmp expect actual +} + +test_expect_success 'write midx with no packs' ' + test_when_finished rm -f pack/multi-pack-index && + git multi-pack-index --object-dir=. write && + midx_read_expect 0 0 4 . +' + +generate_objects () { + i=$1 + iii=$(printf '%03i' $i) + { + test-tool genrandom "bar" 200 && + test-tool genrandom "baz $iii" 50 + } >wide_delta_$iii && + { + test-tool genrandom "foo"$i 100 && + test-tool genrandom "foo"$(( $i + 1 )) 100 && + test-tool genrandom "foo"$(( $i + 2 )) 100 + } >deep_delta_$iii && + { + echo $iii && + test-tool genrandom "$iii" 8192 + } >file_$iii && + git update-index --add file_$iii deep_delta_$iii wide_delta_$iii +} + +commit_and_list_objects () { + { + echo 101 && + test-tool genrandom 100 8192; + } >file_101 && + git update-index --add file_101 && + tree=$(git write-tree) && + commit=$(git commit-tree $tree -p HEAD</dev/null) && + { + echo $tree && + git ls-tree $tree | sed -e "s/.* \\([0-9a-f]*\\) .*/\\1/" + } >obj-list && + git reset --hard $commit +} + +test_expect_success 'create objects' ' + test_commit initial && + for i in $(test_seq 1 5) + do + generate_objects $i + done && + commit_and_list_objects +' + +test_expect_success 'write midx with one v1 pack' ' + pack=$(git pack-objects --index-version=1 $objdir/pack/test <obj-list) && + test_when_finished rm $objdir/pack/test-$pack.pack \ + $objdir/pack/test-$pack.idx $objdir/pack/multi-pack-index && + git multi-pack-index --object-dir=$objdir write && + midx_read_expect 1 18 4 $objdir +' + +midx_git_two_modes () { + if [ "$2" = "sorted" ] + then + git -c core.multiPackIndex=false $1 | sort >expect && + git -c core.multiPackIndex=true $1 | sort >actual + else + git -c core.multiPackIndex=false $1 >expect && + git -c core.multiPackIndex=true $1 >actual + fi && + test_cmp expect actual +} + +compare_results_with_midx () { + MSG=$1 + test_expect_success "check normal git operations: $MSG" ' + midx_git_two_modes "rev-list --objects --all" && + midx_git_two_modes "log --raw" && + midx_git_two_modes "count-objects --verbose" && + midx_git_two_modes "cat-file --batch-all-objects --buffer --batch-check" && + midx_git_two_modes "cat-file --batch-all-objects --buffer --batch-check --unsorted" sorted + ' +} + +test_expect_success 'write midx with one v2 pack' ' + git pack-objects --index-version=2,0x40 $objdir/pack/test <obj-list && + git multi-pack-index --object-dir=$objdir write && + midx_read_expect 1 18 4 $objdir +' + +compare_results_with_midx "one v2 pack" + +test_expect_success 'add more objects' ' + for i in $(test_seq 6 10) + do + generate_objects $i + done && + commit_and_list_objects +' + +test_expect_success 'write midx with two packs' ' + git pack-objects --index-version=1 $objdir/pack/test-2 <obj-list && + git multi-pack-index --object-dir=$objdir write && + midx_read_expect 2 34 4 $objdir +' + +compare_results_with_midx "two packs" + +test_expect_success 'add more packs' ' + for j in $(test_seq 11 20) + do + generate_objects $j && + commit_and_list_objects && + git pack-objects --index-version=2 $objdir/pack/test-pack <obj-list + done +' + +compare_results_with_midx "mixed mode (two packs + extra)" + +test_expect_success 'write midx with twelve packs' ' + git multi-pack-index --object-dir=$objdir write && + midx_read_expect 12 74 4 $objdir +' + +compare_results_with_midx "twelve packs" + +test_expect_success 'repack removes multi-pack-index' ' + test_path_is_file $objdir/pack/multi-pack-index && + git repack -adf && + test_path_is_missing $objdir/pack/multi-pack-index +' + +compare_results_with_midx "after repack" + +test_expect_success 'multi-pack-index and pack-bitmap' ' + git -c repack.writeBitmaps=true repack -ad && + git multi-pack-index write && + git rev-list --test-bitmap HEAD +' + +test_expect_success 'multi-pack-index and alternates' ' + git init --bare alt.git && + echo $(pwd)/alt.git/objects >.git/objects/info/alternates && + echo content1 >file1 && + altblob=$(GIT_DIR=alt.git git hash-object -w file1) && + git cat-file blob $altblob && + git rev-list --all +' + +compare_results_with_midx "with alternate (local midx)" + +test_expect_success 'multi-pack-index in an alternate' ' + mv .git/objects/pack/* alt.git/objects/pack && + test_commit add_local_objects && + git repack --local && + git multi-pack-index write && + midx_read_expect 1 3 4 $objdir && + git reset --hard HEAD~1 && + rm -f .git/objects/pack/* +' + +compare_results_with_midx "with alternate (remote midx)" + + +# usage: corrupt_data <file> <pos> [<data>] +corrupt_data () { + file=$1 + pos=$2 + data="${3:-\0}" + printf "$data" | dd of="$file" bs=1 seek="$pos" conv=notrunc +} + +# Force 64-bit offsets by manipulating the idx file. +# This makes the IDX file _incorrect_ so be careful to clean up after! +test_expect_success 'force some 64-bit offsets with pack-objects' ' + mkdir objects64 && + mkdir objects64/pack && + for i in $(test_seq 1 11) + do + generate_objects 11 + done && + commit_and_list_objects && + pack64=$(git pack-objects --index-version=2,0x40 objects64/pack/test-64 <obj-list) && + idx64=objects64/pack/test-64-$pack64.idx && + chmod u+w $idx64 && + corrupt_data $idx64 2999 "\02" && + midx64=$(git multi-pack-index --object-dir=objects64 write) && + midx_read_expect 1 63 5 objects64 " large-offsets" +' + +test_done diff --git a/t/t5320-delta-islands.sh b/t/t5320-delta-islands.sh new file mode 100755 index 0000000000..fea92a5777 --- /dev/null +++ b/t/t5320-delta-islands.sh @@ -0,0 +1,143 @@ +#!/bin/sh + +test_description='exercise delta islands' +. ./test-lib.sh + +# returns true iff $1 is a delta based on $2 +is_delta_base () { + delta_base=$(echo "$1" | git cat-file --batch-check='%(deltabase)') && + echo >&2 "$1 has base $delta_base" && + test "$delta_base" = "$2" +} + +# generate a commit on branch $1 with a single file, "file", whose +# content is mostly based on the seed $2, but with a unique bit +# of content $3 appended. This should allow us to see whether +# blobs of different refs delta against each other. +commit() { + blob=$({ test-tool genrandom "$2" 10240 && echo "$3"; } | + git hash-object -w --stdin) && + tree=$(printf '100644 blob %s\tfile\n' "$blob" | git mktree) && + commit=$(echo "$2-$3" | git commit-tree "$tree" ${4:+-p "$4"}) && + git update-ref "refs/heads/$1" "$commit" && + eval "$1"'=$(git rev-parse $1:file)' && + eval "echo >&2 $1=\$$1" +} + +test_expect_success 'setup commits' ' + commit one seed 1 && + commit two seed 12 +' + +# Note: This is heavily dependent on the "prefer larger objects as base" +# heuristic. +test_expect_success 'vanilla repack deltas one against two' ' + git repack -adf && + is_delta_base $one $two +' + +test_expect_success 'island repack with no island definition is vanilla' ' + git repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'island repack with no matches is vanilla' ' + git -c "pack.island=refs/foo" repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'separate islands disallows delta' ' + git -c "pack.island=refs/heads/(.*)" repack -adfi && + ! is_delta_base $one $two && + ! is_delta_base $two $one +' + +test_expect_success 'same island allows delta' ' + git -c "pack.island=refs/heads" repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'coalesce same-named islands' ' + git \ + -c "pack.island=refs/(.*)/one" \ + -c "pack.island=refs/(.*)/two" \ + repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'island restrictions drop reused deltas' ' + git repack -adfi && + is_delta_base $one $two && + git -c "pack.island=refs/heads/(.*)" repack -adi && + ! is_delta_base $one $two && + ! is_delta_base $two $one +' + +test_expect_success 'island regexes are left-anchored' ' + git -c "pack.island=heads/(.*)" repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'island regexes follow last-one-wins scheme' ' + git \ + -c "pack.island=refs/heads/(.*)" \ + -c "pack.island=refs/heads/" \ + repack -adfi && + is_delta_base $one $two +' + +test_expect_success 'setup shared history' ' + commit root shared root && + commit one shared 1 root && + commit two shared 12-long root +' + +# We know that $two will be preferred as a base from $one, +# because we can transform it with a pure deletion. +# +# We also expect $root as a delta against $two by the "longest is base" rule. +test_expect_success 'vanilla delta goes between branches' ' + git repack -adf && + is_delta_base $one $two && + is_delta_base $root $two +' + +# Here we should allow $one to base itself on $root; even though +# they are in different islands, the objects in $root are in a superset +# of islands compared to those in $one. +# +# Similarly, $two can delta against $root by our rules. And unlike $one, +# in which we are just allowing it, the island rules actually put $root +# as a possible base for $two, which it would not otherwise be (due to the size +# sorting). +test_expect_success 'deltas allowed against superset islands' ' + git -c "pack.island=refs/heads/(.*)" repack -adfi && + is_delta_base $one $root && + is_delta_base $two $root +' + +# We are going to test the packfile order here, so we again have to make some +# assumptions. We assume that "$root", as part of our core "one", must come +# before "$two". This should be guaranteed by the island code. However, for +# this test to fail without islands, we are also assuming that it would not +# otherwise do so. This is true by the current write order, which will put +# commits (and their contents) before their parents. +test_expect_success 'island core places core objects first' ' + cat >expect <<-EOF && + $root + $two + EOF + git -c "pack.island=refs/heads/(.*)" \ + -c "pack.islandcore=one" \ + repack -adfi && + git verify-pack -v .git/objects/pack/*.pack | + cut -d" " -f1 | + egrep "$root|$two" >actual && + test_cmp expect actual +' + +test_expect_success 'unmatched island core is not fatal' ' + git -c "pack.islandcore=one" repack -adfi +' + +test_done diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 911eae1bf7..f1932ea431 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -86,7 +86,7 @@ test_expect_success 'push can be used to delete a ref' ' test_expect_success 'refuse deleting push with denyDeletes' ' ( cd victim && - ( git branch -D extra || : ) && + test_might_fail git branch -D extra && git config receive.denyDeletes true && git branch extra master ) && @@ -119,7 +119,7 @@ test_expect_success 'override denyDeletes with git -c receive-pack' ' test_expect_success 'denyNonFastforwards trumps --force' ' ( cd victim && - ( git branch -D extra || : ) && + test_might_fail git branch -D extra && git config receive.denyNonFastforwards true ) && victim_orig=$(cd victim && git rev-parse --verify master) && diff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh index 7f278d8ce9..956d69f5b1 100755 --- a/t/t5401-update-hooks.sh +++ b/t/t5401-update-hooks.sh @@ -82,13 +82,13 @@ test_expect_success 'hooks ran' ' ' test_expect_success 'pre-receive hook input' ' - (echo $commit0 $commit1 refs/heads/master; + (echo $commit0 $commit1 refs/heads/master && echo $commit1 $commit0 refs/heads/tofail ) | test_cmp - victim.git/pre-receive.stdin ' test_expect_success 'update hook arguments' ' - (echo refs/heads/master $commit0 $commit1; + (echo refs/heads/master $commit0 $commit1 && echo refs/heads/tofail $commit1 $commit0 ) | test_cmp - victim.git/update.args ' @@ -104,17 +104,17 @@ test_expect_success 'post-update hook arguments' ' ' test_expect_success 'all hook stdin is /dev/null' ' - ! test -s victim.git/update.stdin && - ! test -s victim.git/post-update.stdin + test_must_be_empty victim.git/update.stdin && + test_must_be_empty victim.git/post-update.stdin ' test_expect_success 'all *-receive hook args are empty' ' - ! test -s victim.git/pre-receive.args && - ! test -s victim.git/post-receive.args + test_must_be_empty victim.git/pre-receive.args && + test_must_be_empty victim.git/post-receive.args ' test_expect_success 'send-pack produced no output' ' - ! test -s send.out + test_must_be_empty send.out ' cat <<EOF >expect diff --git a/t/t5404-tracking-branches.sh b/t/t5404-tracking-branches.sh index 2b8c0bac7d..2762f420bc 100755 --- a/t/t5404-tracking-branches.sh +++ b/t/t5404-tracking-branches.sh @@ -56,7 +56,7 @@ test_expect_success 'deleted branches have their tracking branches removed' ' test_expect_success 'already deleted tracking branches ignored' ' git branch -d -r origin/b3 && git push origin :b3 >output 2>&1 && - ! grep error output + ! grep "^error: " output ' test_done diff --git a/t/t5405-send-pack-rewind.sh b/t/t5405-send-pack-rewind.sh index 4bda18a662..235fb7686a 100755 --- a/t/t5405-send-pack-rewind.sh +++ b/t/t5405-send-pack-rewind.sh @@ -25,8 +25,7 @@ test_expect_success 'non forced push should die not segfault' ' ( cd another && - git push .. master:master - test $? = 1 + test_must_fail git push .. master:master ) ' diff --git a/t/t5406-remote-rejects.sh b/t/t5406-remote-rejects.sh index 59e80a5ea2..ff06f99649 100755 --- a/t/t5406-remote-rejects.sh +++ b/t/t5406-remote-rejects.sh @@ -6,8 +6,9 @@ test_description='remote push rejects are reported by client' test_expect_success 'setup' ' mkdir .git/hooks && - (echo "#!/bin/sh" ; echo "exit 1") >.git/hooks/update && - chmod +x .git/hooks/update && + write_script .git/hooks/update <<-\EOF && + exit 1 + EOF echo 1 >file && git add file && git commit -m 1 && diff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh index 7a48236e87..9b2a274c71 100755 --- a/t/t5407-post-rewrite-hook.sh +++ b/t/t5407-post-rewrite-hook.sh @@ -113,7 +113,7 @@ test_expect_success 'git rebase -m' ' test_expect_success 'git rebase -m --skip' ' git reset --hard D && clear_hook_input && - test_must_fail git rebase --onto A B && + test_must_fail git rebase -m --onto A B && test_must_fail git rebase --skip && echo D > foo && git add foo && diff --git a/t/t5409-colorize-remote-messages.sh b/t/t5409-colorize-remote-messages.sh new file mode 100755 index 0000000000..f81b6813c0 --- /dev/null +++ b/t/t5409-colorize-remote-messages.sh @@ -0,0 +1,101 @@ +#!/bin/sh + +test_description='remote messages are colorized on the client' + +. ./test-lib.sh + +test_expect_success 'setup' ' + mkdir .git/hooks && + write_script .git/hooks/update <<-\EOF && + echo error: error + echo ERROR: also highlighted + echo hint: hint + echo hinting: not highlighted + echo success: success + echo warning: warning + echo prefixerror: error + echo " " "error: leading space" + echo " " + echo Err + exit 0 + EOF + echo 1 >file && + git add file && + git commit -m 1 && + git clone . child && + ( + cd child && + test_commit message2 file content2 + ) +' + +test_expect_success 'keywords' ' + git --git-dir child/.git -c color.remote=always push -f origin HEAD:refs/heads/keywords 2>output && + test_decode_color <output >decoded && + grep "<BOLD;RED>error<RESET>: error" decoded && + grep "<YELLOW>hint<RESET>:" decoded && + grep "<BOLD;GREEN>success<RESET>:" decoded && + grep "<BOLD;YELLOW>warning<RESET>:" decoded +' + +test_expect_success 'whole words at line start' ' + git --git-dir child/.git -c color.remote=always push -f origin HEAD:refs/heads/whole-words 2>output && + test_decode_color <output >decoded && + grep "<YELLOW>hint<RESET>:" decoded && + grep "hinting: not highlighted" decoded && + grep "prefixerror: error" decoded +' + +test_expect_success 'short line' ' + git -C child -c color.remote=always push -f origin HEAD:short-line 2>output && + test_decode_color <output >decoded && + grep "remote: Err" decoded +' + +test_expect_success 'case-insensitive' ' + git --git-dir child/.git -c color.remote=always push -f origin HEAD:refs/heads/case-insensitive 2>output && + cat output && + test_decode_color <output >decoded && + grep "<BOLD;RED>error<RESET>: error" decoded && + grep "<BOLD;RED>ERROR<RESET>: also highlighted" decoded +' + +test_expect_success 'leading space' ' + git --git-dir child/.git -c color.remote=always push -f origin HEAD:refs/heads/leading-space 2>output && cat output && + test_decode_color <output >decoded && + grep " <BOLD;RED>error<RESET>: leading space" decoded +' + +test_expect_success 'spaces only' ' + git -C child -c color.remote=always push -f origin HEAD:only-space 2>output && + test_decode_color <output >decoded && + grep "remote: " decoded +' + +test_expect_success 'no coloring for redirected output' ' + git --git-dir child/.git push -f origin HEAD:refs/heads/redirected-output 2>output && + test_decode_color <output >decoded && + grep "error: error" decoded +' + +test_expect_success 'push with customized color' ' + git --git-dir child/.git -c color.remote=always -c color.remote.error=blue push -f origin HEAD:refs/heads/customized-color 2>output && + test_decode_color <output >decoded && + grep "<BLUE>error<RESET>:" decoded && + grep "<BOLD;GREEN>success<RESET>:" decoded +' + + +test_expect_success 'error in customized color' ' + git --git-dir child/.git -c color.remote=always -c color.remote.error=i-am-not-a-color push -f origin HEAD:refs/heads/error-customized-color 2>output && + test_decode_color <output >decoded && + grep "<BOLD;GREEN>success<RESET>:" decoded +' + +test_expect_success 'fallback to color.ui' ' + git --git-dir child/.git -c color.ui=always push -f origin HEAD:refs/heads/fallback-color-ui 2>output && + test_decode_color <output >decoded && + grep "<BOLD;RED>error<RESET>: error" decoded +' + +test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index 0680dec808..1b5a4a6d38 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -30,7 +30,7 @@ add () { test_tick && commit=$(echo "$text" | git commit-tree $tree $parents) && eval "$name=$commit; export $name" && - echo $commit > .git/refs/heads/$branch && + git update-ref "refs/heads/$branch" "$commit" && eval ${branch}TIP=$commit } @@ -45,10 +45,10 @@ pull_to_client () { case "$heads" in *A*) - echo $ATIP > .git/refs/heads/A;; + git update-ref refs/heads/A "$ATIP";; esac && case "$heads" in *B*) - echo $BTIP > .git/refs/heads/B;; + git update-ref refs/heads/B "$BTIP";; esac && git symbolic-ref HEAD refs/heads/$(echo $heads \ | sed -e "s/^\(.\).*$/\1/") && @@ -92,8 +92,8 @@ test_expect_success 'setup' ' cur=$(($cur+1)) done && add B1 $A1 && - echo $ATIP > .git/refs/heads/A && - echo $BTIP > .git/refs/heads/B && + git update-ref refs/heads/A "$ATIP" && + git update-ref refs/heads/B "$BTIP" && git symbolic-ref HEAD refs/heads/B ' @@ -161,7 +161,7 @@ test_expect_success 'clone shallow object count' ' test_expect_success 'clone shallow object count (part 2)' ' sed -e "/^in-pack:/d" -e "/^packs:/d" -e "/^size-pack:/d" \ -e "/: 0$/d" count.shallow > count_output && - ! test -s count_output + test_must_be_empty count_output ' test_expect_success 'fsck in shallow repo' ' @@ -259,7 +259,7 @@ test_expect_success 'clone shallow object count' ' test_expect_success 'pull in shallow repo with missing merge base' ' ( cd shallow && - git fetch --depth 4 .. A + git fetch --depth 4 .. A && test_must_fail git merge --allow-unrelated-histories FETCH_HEAD ) ' @@ -403,7 +403,7 @@ test_expect_success 'fetch creating new shallow root' ' git fetch --depth=1 --progress 2>actual && # This should fetch only the empty commit, no tree or # blob objects - grep "remote: Total 1" actual + test_i18ngrep "remote: Total 1" actual ) ' @@ -518,6 +518,54 @@ test_expect_success 'test --all, --depth, and explicit tag' ' ) >out-adt 2>error-adt ' +test_expect_success 'test --all with tag to non-tip' ' + git commit --allow-empty -m non-tip && + git commit --allow-empty -m tip && + git tag -m "annotated" non-tip HEAD^ && + ( + cd client && + git fetch-pack --all .. + ) +' + +test_expect_success 'test --all wrt tag to non-commits' ' + # create tag-to-{blob,tree,commit,tag}, making sure all tagged objects + # are reachable only via created tag references. + blob=$(echo "hello blob" | git hash-object -t blob -w --stdin) && + git tag -a -m "tag -> blob" tag-to-blob $blob && + + tree=$(printf "100644 blob $blob\tfile" | git mktree) && + git tag -a -m "tag -> tree" tag-to-tree $tree && + + tree2=$(printf "100644 blob $blob\tfile2" | git mktree) && + commit=$(git commit-tree -m "hello commit" $tree) && + git tag -a -m "tag -> commit" tag-to-commit $commit && + + blob2=$(echo "hello blob2" | git hash-object -t blob -w --stdin) && + tag=$(git mktag <<-EOF + object $blob2 + type blob + tag tag-to-blob2 + tagger author A U Thor <author@example.com> 0 +0000 + + hello tag + EOF + ) && + git tag -a -m "tag -> tag" tag-to-tag $tag && + + # `fetch-pack --all` should succeed fetching all those objects. + mkdir fetchall && + ( + cd fetchall && + git init && + git fetch-pack --all .. && + git cat-file blob $blob >/dev/null && + git cat-file tree $tree >/dev/null && + git cat-file commit $commit >/dev/null && + git cat-file tag $tag >/dev/null + ) +' + test_expect_success 'shallow fetch with tags does not break the repository' ' mkdir repo1 && ( @@ -711,6 +759,17 @@ test_expect_success 'fetch shallow since ...' ' test_cmp expected actual ' +test_expect_success 'clone shallow since selects no commits' ' + test_create_repo shallow-since-the-future && + ( + cd shallow-since-the-future && + GIT_COMMITTER_DATE="100000000 +0700" git commit --allow-empty -m one && + GIT_COMMITTER_DATE="200000000 +0700" git commit --allow-empty -m two && + GIT_COMMITTER_DATE="300000000 +0700" git commit --allow-empty -m three && + test_must_fail git clone --shallow-since "900000000 +0700" "file://$(pwd)/." ../shallow111 + ) +' + test_expect_success 'shallow clone exclude tag two' ' test_create_repo shallow-exclude && ( @@ -755,6 +814,39 @@ test_expect_success 'fetching deepen' ' ) ' +test_expect_success 'use ref advertisement to prune "have" lines sent' ' + rm -rf server client && + git init server && + test_commit -C server both_have_1 && + git -C server tag -d both_have_1 && + test_commit -C server both_have_2 && + + git clone server client && + test_commit -C server server_has && + test_commit -C client client_has && + + # In both protocol v0 and v2, ensure that the parent of both_have_2 is + # not sent as a "have" line. The client should know that the server has + # both_have_2, so it only needs to inform the server that it has + # both_have_2, and the server can infer the rest. + + rm -f trace && + cp -r client clientv0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv0 \ + fetch origin server_has both_have_2 && + grep "have $(git -C client rev-parse client_has)" trace && + grep "have $(git -C client rev-parse both_have_2)" trace && + ! grep "have $(git -C client rev-parse both_have_2^)" trace && + + rm -f trace && + cp -r client clientv2 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C clientv2 -c protocol.version=2 \ + fetch origin server_has both_have_2 && + grep "have $(git -C client rev-parse client_has)" trace && + grep "have $(git -C client rev-parse both_have_2)" trace && + ! grep "have $(git -C client rev-parse both_have_2^)" trace +' + test_expect_success 'filtering by size' ' rm -rf server client && test_create_repo server && diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index 49d3621a92..62f3569891 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -3,13 +3,16 @@ test_description='fetch/receive strict mode' . ./test-lib.sh -test_expect_success setup ' +test_expect_success 'setup and inject "corrupt or missing" object' ' echo hello >greetings && git add greetings && git commit -m greetings && S=$(git rev-parse :greetings | sed -e "s|^..|&/|") && X=$(echo bye | git hash-object -w --stdin | sed -e "s|^..|&/|") && + echo $S >S && + echo $X >X && + cp .git/objects/$S .git/objects/$S.back && mv -f .git/objects/$X .git/objects/$S && test_must_fail git fsck @@ -115,6 +118,13 @@ test_expect_success 'push with transfer.fsckobjects' ' test_cmp exp act ' +test_expect_success 'repair the "corrupt or missing" object' ' + mv -f .git/objects/$(cat S) .git/objects/$(cat X) && + mv .git/objects/$(cat S).back .git/objects/$(cat S) && + rm -rf .git/objects/$(cat X) && + git fsck +' + cat >bogus-commit <<EOF tree $EMPTY_TREE author Bugs Bunny 1234567890 +0000 @@ -123,6 +133,14 @@ committer Bugs Bunny <bugs@bun.ni> 1234567890 +0000 This commit object intentionally broken EOF +test_expect_success 'fsck with invalid or bogus skipList input' ' + git -c fsck.skipList=/dev/null -c fsck.missingEmail=ignore fsck && + test_must_fail git -c fsck.skipList=does-not-exist -c fsck.missingEmail=ignore fsck 2>err && + test_i18ngrep "Could not open skip list: does-not-exist" err && + test_must_fail git -c fsck.skipList=.git/config -c fsck.missingEmail=ignore fsck 2>err && + test_i18ngrep "Invalid SHA-1: \[core\]" err +' + test_expect_success 'push with receive.fsck.skipList' ' commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && git push . $commit:refs/heads/bogus && @@ -130,11 +148,61 @@ test_expect_success 'push with receive.fsck.skipList' ' git init dst && git --git-dir=dst/.git config receive.fsckObjects true && test_must_fail git push --porcelain dst bogus && - git --git-dir=dst/.git config receive.fsck.skipList SKIP && echo $commit >dst/.git/SKIP && + + # receive.fsck.* does not fall back on fsck.* + git --git-dir=dst/.git config fsck.skipList SKIP && + test_must_fail git push --porcelain dst bogus && + + # Invalid and/or bogus skipList input + git --git-dir=dst/.git config receive.fsck.skipList /dev/null && + test_must_fail git push --porcelain dst bogus && + git --git-dir=dst/.git config receive.fsck.skipList does-not-exist && + test_must_fail git push --porcelain dst bogus 2>err && + test_i18ngrep "Could not open skip list: does-not-exist" err && + git --git-dir=dst/.git config receive.fsck.skipList config && + test_must_fail git push --porcelain dst bogus 2>err && + test_i18ngrep "Invalid SHA-1: \[core\]" err && + + git --git-dir=dst/.git config receive.fsck.skipList SKIP && git push --porcelain dst bogus ' +test_expect_success 'fetch with fetch.fsck.skipList' ' + commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && + refspec=refs/heads/bogus:refs/heads/bogus && + git push . $commit:refs/heads/bogus && + rm -rf dst && + git init dst && + git --git-dir=dst/.git config fetch.fsckObjects true && + test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec && + git --git-dir=dst/.git config fetch.fsck.skipList /dev/null && + test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec && + echo $commit >dst/.git/SKIP && + + # fetch.fsck.* does not fall back on fsck.* + git --git-dir=dst/.git config fsck.skipList dst/.git/SKIP && + test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec && + + # Invalid and/or bogus skipList input + git --git-dir=dst/.git config fetch.fsck.skipList /dev/null && + test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec && + git --git-dir=dst/.git config fetch.fsck.skipList does-not-exist && + test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err && + test_i18ngrep "Could not open skip list: does-not-exist" err && + git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/config && + test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err && + test_i18ngrep "Invalid SHA-1: \[core\]" err && + + git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/SKIP && + git --git-dir=dst/.git fetch "file://$(pwd)" $refspec +' + +test_expect_success 'fsck.<unknownmsg-id> dies' ' + test_must_fail git -c fsck.whatEver=ignore fsck 2>err && + test_i18ngrep "Unhandled message id: whatever" err +' + test_expect_success 'push with receive.fsck.missingEmail=warn' ' commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && git push . $commit:refs/heads/bogus && @@ -142,19 +210,58 @@ test_expect_success 'push with receive.fsck.missingEmail=warn' ' git init dst && git --git-dir=dst/.git config receive.fsckobjects true && test_must_fail git push --porcelain dst bogus && + + # receive.fsck.<msg-id> does not fall back on fsck.<msg-id> + git --git-dir=dst/.git config fsck.missingEmail warn && + test_must_fail git push --porcelain dst bogus && + + # receive.fsck.<unknownmsg-id> warns + git --git-dir=dst/.git config \ + receive.fsck.whatEver error && + git --git-dir=dst/.git config \ receive.fsck.missingEmail warn && git push --porcelain dst bogus >act 2>&1 && grep "missingEmail" act && + test_i18ngrep "Skipping unknown msg id.*whatever" act && git --git-dir=dst/.git branch -D bogus && git --git-dir=dst/.git config --add \ receive.fsck.missingEmail ignore && - git --git-dir=dst/.git config --add \ - receive.fsck.badDate warn && git push --porcelain dst bogus >act 2>&1 && ! grep "missingEmail" act ' +test_expect_success 'fetch with fetch.fsck.missingEmail=warn' ' + commit="$(git hash-object -t commit -w --stdin <bogus-commit)" && + refspec=refs/heads/bogus:refs/heads/bogus && + git push . $commit:refs/heads/bogus && + rm -rf dst && + git init dst && + git --git-dir=dst/.git config fetch.fsckobjects true && + test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec && + + # fetch.fsck.<msg-id> does not fall back on fsck.<msg-id> + git --git-dir=dst/.git config fsck.missingEmail warn && + test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec && + + # receive.fsck.<unknownmsg-id> warns + git --git-dir=dst/.git config \ + fetch.fsck.whatEver error && + + git --git-dir=dst/.git config \ + fetch.fsck.missingEmail warn && + git --git-dir=dst/.git fetch "file://$(pwd)" $refspec >act 2>&1 && + grep "missingEmail" act && + test_i18ngrep "Skipping unknown msg id.*whatever" act && + rm -rf dst && + git init dst && + git --git-dir=dst/.git config fetch.fsckobjects true && + git --git-dir=dst/.git config \ + fetch.fsck.missingEmail ignore && + git --git-dir=dst/.git fetch "file://$(pwd)" $refspec >act 2>&1 && + ! grep "missingEmail" act +' + test_expect_success \ 'receive.fsck.unterminatedHeader=warn triggers error' ' rm -rf dst && @@ -166,4 +273,15 @@ test_expect_success \ grep "Cannot demote unterminatedheader" act ' +test_expect_success \ + 'fetch.fsck.unterminatedHeader=warn triggers error' ' + rm -rf dst && + git init dst && + git --git-dir=dst/.git config fetch.fsckobjects true && + git --git-dir=dst/.git config \ + fetch.fsck.unterminatedheader warn && + test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" HEAD && + grep "Cannot demote unterminatedheader" act +' + test_done diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh index a6c0178f3a..241e6a319d 100755 --- a/t/t5505-remote.sh +++ b/t/t5505-remote.sh @@ -74,8 +74,7 @@ test_expect_success 'add another remote' ' git for-each-ref "--format=%(refname)" refs/remotes | sed -e "/^refs\/remotes\/origin\//d" \ -e "/^refs\/remotes\/second\//d" >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ) ' @@ -112,8 +111,7 @@ test_expect_success C_LOCALE_OUTPUT 'remove remote' ' check_remote_track origin master side && git for-each-ref "--format=%(refname)" refs/remotes | sed -e "/^refs\/remotes\/origin\//d" >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ) ' @@ -348,17 +346,13 @@ URL: $(pwd)/one EOF test_expect_success 'prune --dry-run' ' - ( - cd one && - git branch -m side2 side) && + git -C one branch -m side2 side && + test_when_finished "git -C one branch -m side side2" && ( cd test && git remote prune --dry-run origin >output && git rev-parse refs/remotes/origin/side2 && test_must_fail git rev-parse refs/remotes/origin/side && - ( - cd ../one && - git branch -m side side2) && test_i18ncmp expect output ) ' @@ -848,7 +842,7 @@ test_expect_success 'migrate a remote from named file in $GIT_DIR/branches (2)' git remote rename origin origin && test_path_is_missing .git/branches/origin && test "$(git config remote.origin.url)" = "quux" && - test "$(git config remote.origin.fetch)" = "refs/heads/foom:refs/heads/origin" + test "$(git config remote.origin.fetch)" = "refs/heads/foom:refs/heads/origin" && test "$(git config remote.origin.push)" = "HEAD:refs/heads/foom" ) ' @@ -876,7 +870,7 @@ test_expect_success 'remote prune to cause a dangling symref' ' cd eight && test_must_fail git branch nomore origin ) 2>err && - grep "dangling symref" err + test_i18ngrep "dangling symref" err ' test_expect_success 'show empty remote' ' diff --git a/t/t5509-fetch-push-namespaces.sh b/t/t5509-fetch-push-namespaces.sh index 75c570adca..c88df78c0b 100755 --- a/t/t5509-fetch-push-namespaces.sh +++ b/t/t5509-fetch-push-namespaces.sh @@ -44,7 +44,7 @@ test_expect_success 'pushing into a repository using a ref namespace' ' test_cmp expected actual && # Try a namespace with no content git ls-remote "ext::git --namespace=garbage %s ../pushee" >actual && - test_cmp /dev/null actual && + test_must_be_empty actual && git ls-remote pushee-unnamespaced >actual && sed -e "s|refs/|refs/namespaces/namespace/refs/|" expected >expected.unnamespaced && test_cmp expected.unnamespaced actual diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index ae5a530a2d..3b7b30568c 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -63,7 +63,7 @@ test_expect_success "fetch test" ' git commit -a -m "updated by origin" && cd two && git fetch && - test -f .git/refs/heads/one && + git rev-parse --verify refs/heads/one && mine=$(git rev-parse refs/heads/one) && his=$(cd ../one && git rev-parse refs/heads/master) && test "z$mine" = "z$his" @@ -73,8 +73,8 @@ test_expect_success "fetch test for-merge" ' cd "$D" && cd three && git fetch && - test -f .git/refs/heads/two && - test -f .git/refs/heads/one && + git rev-parse --verify refs/heads/two && + git rev-parse --verify refs/heads/one && master_in_two=$(cd ../two && git rev-parse master) && one_in_two=$(cd ../two && git rev-parse one) && { @@ -535,6 +535,41 @@ test_expect_success "should be able to fetch with duplicate refspecs" ' ) ' +test_expect_success 'LHS of refspec follows ref disambiguation rules' ' + mkdir lhs-ambiguous && + ( + cd lhs-ambiguous && + git init server && + test_commit -C server unwanted && + test_commit -C server wanted && + + git init client && + + # Check a name coming after "refs" alphabetically ... + git -C server update-ref refs/heads/s wanted && + git -C server update-ref refs/heads/refs/heads/s unwanted && + git -C client fetch ../server +refs/heads/s:refs/heads/checkthis && + git -C server rev-parse wanted >expect && + git -C client rev-parse checkthis >actual && + test_cmp expect actual && + + # ... and one before. + git -C server update-ref refs/heads/q wanted && + git -C server update-ref refs/heads/refs/heads/q unwanted && + git -C client fetch ../server +refs/heads/q:refs/heads/checkthis && + git -C server rev-parse wanted >expect && + git -C client rev-parse checkthis >actual && + test_cmp expect actual && + + # Tags are preferred over branches like refs/{heads,tags}/* + git -C server update-ref refs/tags/t wanted && + git -C server update-ref refs/heads/t unwanted && + git -C client fetch ../server +t:refs/heads/checkthis && + git -C server rev-parse wanted >expect && + git -C client rev-parse checkthis >actual + ) +' + # configured prune tests set_config_tristate () { @@ -613,7 +648,7 @@ test_configured_prune_type () { git rev-parse --verify refs/tags/newtag ) && - # now remove it + # now remove them git branch -d newbranch && git tag -d newtag && @@ -828,9 +863,11 @@ test_expect_success 'fetching with auto-gc does not lock up' ' test_commit test2 && ( cd auto-gc && + git config fetch.unpackLimit 1 && git config gc.autoPackLimit 1 && git config gc.autoDetach false && GIT_ASK_YESNO="$D/askyesno" git fetch >fetch.out 2>&1 && + test_i18ngrep "Auto packing the repository" fetch.out && ! grep "Should I try again" fetch.out ) ' @@ -865,4 +902,82 @@ test_expect_success C_LOCALE_OUTPUT 'fetch compact output' ' test_cmp expect actual ' +setup_negotiation_tip () { + SERVER="$1" + URL="$2" + USE_PROTOCOL_V2="$3" + + rm -rf "$SERVER" client trace && + git init "$SERVER" && + test_commit -C "$SERVER" alpha_1 && + test_commit -C "$SERVER" alpha_2 && + git -C "$SERVER" checkout --orphan beta && + test_commit -C "$SERVER" beta_1 && + test_commit -C "$SERVER" beta_2 && + + git clone "$URL" client && + + if test "$USE_PROTOCOL_V2" -eq 1 + then + git -C "$SERVER" config protocol.version 2 && + git -C client config protocol.version 2 + fi && + + test_commit -C "$SERVER" beta_s && + git -C "$SERVER" checkout master && + test_commit -C "$SERVER" alpha_s && + git -C "$SERVER" tag -d alpha_1 alpha_2 beta_1 beta_2 +} + +check_negotiation_tip () { + # Ensure that {alpha,beta}_1 are sent as "have", but not {alpha_beta}_2 + ALPHA_1=$(git -C client rev-parse alpha_1) && + grep "fetch> have $ALPHA_1" trace && + BETA_1=$(git -C client rev-parse beta_1) && + grep "fetch> have $BETA_1" trace && + ALPHA_2=$(git -C client rev-parse alpha_2) && + ! grep "fetch> have $ALPHA_2" trace && + BETA_2=$(git -C client rev-parse beta_2) && + ! grep "fetch> have $BETA_2" trace +} + +test_expect_success '--negotiation-tip limits "have" lines sent' ' + setup_negotiation_tip server server 0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-tip=alpha_1 --negotiation-tip=beta_1 \ + origin alpha_s beta_s && + check_negotiation_tip +' + +test_expect_success '--negotiation-tip understands globs' ' + setup_negotiation_tip server server 0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-tip=*_1 \ + origin alpha_s beta_s && + check_negotiation_tip +' + +test_expect_success '--negotiation-tip understands abbreviated SHA-1' ' + setup_negotiation_tip server server 0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-tip=$(git -C client rev-parse --short alpha_1) \ + --negotiation-tip=$(git -C client rev-parse --short beta_1) \ + origin alpha_s beta_s && + check_negotiation_tip +' + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success '--negotiation-tip limits "have" lines sent with HTTP protocol v2' ' + setup_negotiation_tip "$HTTPD_DOCUMENT_ROOT_PATH/server" \ + "$HTTPD_URL/smart/server" 1 && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client fetch \ + --negotiation-tip=alpha_1 --negotiation-tip=beta_1 \ + origin alpha_s beta_s && + check_negotiation_tip +' + +stop_httpd + test_done diff --git a/t/t5512-ls-remote.sh b/t/t5512-ls-remote.sh index 02106c9226..bc5703ff9b 100755 --- a/t/t5512-ls-remote.sh +++ b/t/t5512-ls-remote.sh @@ -10,9 +10,12 @@ test_expect_success setup ' test_tick && git commit -m initial && git tag mark && + git tag mark1.1 && + git tag mark1.2 && + git tag mark1.10 && git show-ref --tags -d | sed -e "s/ / /" >expected.tag && ( - echo "$(git rev-parse HEAD) HEAD" + echo "$(git rev-parse HEAD) HEAD" && git show-ref -d | sed -e "s/ / /" ) >expected.all && @@ -39,6 +42,39 @@ test_expect_success 'ls-remote self' ' test_cmp expected.all actual ' +test_expect_success 'ls-remote --sort="version:refname" --tags self' ' + cat >expect <<-EOF && + $(git rev-parse mark) refs/tags/mark + $(git rev-parse mark1.1) refs/tags/mark1.1 + $(git rev-parse mark1.2) refs/tags/mark1.2 + $(git rev-parse mark1.10) refs/tags/mark1.10 + EOF + git ls-remote --sort="version:refname" --tags self >actual && + test_cmp expect actual +' + +test_expect_success 'ls-remote --sort="-version:refname" --tags self' ' + cat >expect <<-EOF && + $(git rev-parse mark1.10) refs/tags/mark1.10 + $(git rev-parse mark1.2) refs/tags/mark1.2 + $(git rev-parse mark1.1) refs/tags/mark1.1 + $(git rev-parse mark) refs/tags/mark + EOF + git ls-remote --sort="-version:refname" --tags self >actual && + test_cmp expect actual +' + +test_expect_success 'ls-remote --sort="-refname" --tags self' ' + cat >expect <<-EOF && + $(git rev-parse mark1.2) refs/tags/mark1.2 + $(git rev-parse mark1.10) refs/tags/mark1.10 + $(git rev-parse mark1.1) refs/tags/mark1.1 + $(git rev-parse mark) refs/tags/mark + EOF + git ls-remote --sort="-refname" --tags self >actual && + test_cmp expect actual +' + test_expect_success 'dies when no remote specified and no default remotes found' ' test_must_fail git ls-remote ' @@ -69,7 +105,7 @@ test_expect_success 'use branch.<name>.remote if possible' ' git clone . other.git && ( cd other.git && - echo "$(git rev-parse HEAD) HEAD" + echo "$(git rev-parse HEAD) HEAD" && git show-ref | sed -e "s/ / /" ) >exp && @@ -119,19 +155,17 @@ test_expect_success 'die with non-2 for wrong repository even with --exit-code' test_expect_success 'Report success even when nothing matches' ' git ls-remote other.git "refs/nsn/*" >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'Report no-match with --exit-code' ' test_expect_code 2 git ls-remote --exit-code other.git "refs/nsn/*" >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'Report match with --exit-code' ' git ls-remote --exit-code other.git "refs/tags/*" >actual && - git ls-remote . tags/mark >expect && + git ls-remote . tags/mark* >expect && test_cmp expect actual ' @@ -171,13 +205,17 @@ test_expect_success 'overrides work between mixed transfer/upload-pack hideRefs' ' test_expect_success 'ls-remote --symref' ' - cat >expect <<-\EOF && + git fetch origin && + cat >expect <<-EOF && ref: refs/heads/master HEAD - 1bd44cb9d13204b0fe1958db0082f5028a16eb3a HEAD - 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/heads/master - 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/remotes/origin/HEAD - 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/remotes/origin/master - 1bd44cb9d13204b0fe1958db0082f5028a16eb3a refs/tags/mark + $(git rev-parse HEAD) HEAD + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse HEAD) refs/remotes/origin/HEAD + $(git rev-parse refs/remotes/origin/master) refs/remotes/origin/master + $(git rev-parse refs/tags/mark) refs/tags/mark + $(git rev-parse refs/tags/mark1.1) refs/tags/mark1.1 + $(git rev-parse refs/tags/mark1.10) refs/tags/mark1.10 + $(git rev-parse refs/tags/mark1.2) refs/tags/mark1.2 EOF git ls-remote --symref >actual && test_cmp expect actual diff --git a/t/t5514-fetch-multiple.sh b/t/t5514-fetch-multiple.sh index 4b4b6673b8..0030c92e1a 100755 --- a/t/t5514-fetch-multiple.sh +++ b/t/t5514-fetch-multiple.sh @@ -152,7 +152,6 @@ test_expect_success 'git fetch --multiple (ignoring skipFetchAll)' ' ' test_expect_success 'git fetch --all --no-tags' ' - >expect && git clone one test5 && git clone test5 test6 && (cd test5 && git tag test-tag) && @@ -161,7 +160,7 @@ test_expect_success 'git fetch --all --no-tags' ' git fetch --all --no-tags && git tag >output ) && - test_cmp expect test6/output + test_must_be_empty test6/output ' test_expect_success 'git fetch --all --tags' ' diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh index 82239138d5..7a8f56db53 100755 --- a/t/t5516-fetch-push.sh +++ b/t/t5516-fetch-push.sh @@ -94,6 +94,9 @@ mk_child() { } check_push_result () { + test $# -ge 3 || + error "bug in the test script: check_push_result requires at least 3 parameters" + repo_name="$1" shift @@ -553,10 +556,7 @@ test_expect_success 'branch.*.pushremote config order is irrelevant' ' test_expect_success 'push with dry-run' ' mk_test testrepo heads/master && - ( - cd testrepo && - old_commit=$(git show-ref -s --verify refs/heads/master) - ) && + old_commit=$(git -C testrepo show-ref -s --verify refs/heads/master) && git push --dry-run testrepo : && check_push_result testrepo $old_commit heads/master ' @@ -612,7 +612,7 @@ test_expect_success 'push does not update local refs on failure' ' chmod +x testrepo/.git/hooks/pre-receive && ( cd child && - git pull .. master + git pull .. master && test_must_fail git push && test $(git rev-parse master) != \ $(git rev-parse remotes/origin/master) @@ -634,7 +634,7 @@ test_expect_success 'pushing valid refs triggers post-receive and post-update ho orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && newmaster=$(git show-ref -s --verify refs/heads/master) && orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) && - newnext=$_z40 && + newnext=$ZERO_OID && git push testrepo refs/heads/master:refs/heads/master :refs/heads/next && ( cd testrepo/.git && @@ -672,15 +672,15 @@ test_expect_success 'deleting dangling ref triggers hooks with correct args' ' ( cd testrepo/.git && cat >pre-receive.expect <<-EOF && - $_z40 $_z40 refs/heads/master + $ZERO_OID $ZERO_OID refs/heads/master EOF cat >update.expect <<-EOF && - refs/heads/master $_z40 $_z40 + refs/heads/master $ZERO_OID $ZERO_OID EOF cat >post-receive.expect <<-EOF && - $_z40 $_z40 refs/heads/master + $ZERO_OID $ZERO_OID refs/heads/master EOF cat >post-update.expect <<-EOF && @@ -703,12 +703,12 @@ test_expect_success 'deletion of a non-existent ref is not fed to post-receive a cd testrepo/.git && cat >pre-receive.expect <<-EOF && $orgmaster $newmaster refs/heads/master - $_z40 $_z40 refs/heads/nonexistent + $ZERO_OID $ZERO_OID refs/heads/nonexistent EOF cat >update.expect <<-EOF && refs/heads/master $orgmaster $newmaster - refs/heads/nonexistent $_z40 $_z40 + refs/heads/nonexistent $ZERO_OID $ZERO_OID EOF cat >post-receive.expect <<-EOF && @@ -732,11 +732,11 @@ test_expect_success 'deletion of a non-existent ref alone does trigger post-rece ( cd testrepo/.git && cat >pre-receive.expect <<-EOF && - $_z40 $_z40 refs/heads/nonexistent + $ZERO_OID $ZERO_OID refs/heads/nonexistent EOF cat >update.expect <<-EOF && - refs/heads/nonexistent $_z40 $_z40 + refs/heads/nonexistent $ZERO_OID $ZERO_OID EOF test_cmp pre-receive.expect pre-receive.actual && @@ -751,7 +751,7 @@ test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks w orgmaster=$(cd testrepo && git show-ref -s --verify refs/heads/master) && newmaster=$(git show-ref -s --verify refs/heads/master) && orgnext=$(cd testrepo && git show-ref -s --verify refs/heads/next) && - newnext=$_z40 && + newnext=$ZERO_OID && orgpu=$(cd testrepo && git show-ref -s --verify refs/heads/pu) && newpu=$(git show-ref -s --verify refs/heads/master) && git push testrepo refs/heads/master:refs/heads/master \ @@ -763,14 +763,14 @@ test_expect_success 'mixed ref updates, deletes, invalid deletes trigger hooks w $orgmaster $newmaster refs/heads/master $orgnext $newnext refs/heads/next $orgpu $newpu refs/heads/pu - $_z40 $_z40 refs/heads/nonexistent + $ZERO_OID $ZERO_OID refs/heads/nonexistent EOF cat >update.expect <<-EOF && refs/heads/master $orgmaster $newmaster refs/heads/next $orgnext $newnext refs/heads/pu $orgpu $newpu - refs/heads/nonexistent $_z40 $_z40 + refs/heads/nonexistent $ZERO_OID $ZERO_OID EOF cat >post-receive.expect <<-EOF && @@ -923,7 +923,7 @@ test_expect_success 'push into aliased refs (consistent)' ' ( cd child1 && git branch foo && - git symbolic-ref refs/heads/bar refs/heads/foo + git symbolic-ref refs/heads/bar refs/heads/foo && git config receive.denyCurrentBranch false ) && ( @@ -945,7 +945,7 @@ test_expect_success 'push into aliased refs (inconsistent)' ' ( cd child1 && git branch foo && - git symbolic-ref refs/heads/bar refs/heads/foo + git symbolic-ref refs/heads/bar refs/heads/foo && git config receive.denyCurrentBranch false ) && ( @@ -965,26 +965,76 @@ test_expect_success 'push into aliased refs (inconsistent)' ' ) ' -test_expect_success 'push requires --force to update lightweight tag' ' - mk_test testrepo heads/master && - mk_child testrepo child1 && - mk_child testrepo child2 && - ( - cd child1 && - git tag Tag && - git push ../child2 Tag && - git push ../child2 Tag && - >file1 && - git add file1 && - git commit -m "file1" && - git tag -f Tag && - test_must_fail git push ../child2 Tag && - git push --force ../child2 Tag && - git tag -f Tag && - test_must_fail git push ../child2 Tag HEAD~ && - git push --force ../child2 Tag - ) -' +test_force_push_tag () { + tag_type_description=$1 + tag_args=$2 + + test_expect_success "force pushing required to update $tag_type_description" " + mk_test testrepo heads/master && + mk_child testrepo child1 && + mk_child testrepo child2 && + ( + cd child1 && + git tag testTag && + git push ../child2 testTag && + >file1 && + git add file1 && + git commit -m 'file1' && + git tag $tag_args testTag && + test_must_fail git push ../child2 testTag && + git push --force ../child2 testTag && + git tag $tag_args testTag HEAD~ && + test_must_fail git push ../child2 testTag && + git push --force ../child2 testTag && + + # Clobbering without + in refspec needs --force + git tag -f testTag && + test_must_fail git push ../child2 'refs/tags/*:refs/tags/*' && + git push --force ../child2 'refs/tags/*:refs/tags/*' && + + # Clobbering with + in refspec does not need --force + git tag -f testTag HEAD~ && + git push ../child2 '+refs/tags/*:refs/tags/*' && + + # Clobbering with --no-force still obeys + in refspec + git tag -f testTag && + git push --no-force ../child2 '+refs/tags/*:refs/tags/*' && + + # Clobbering with/without --force and 'tag <name>' format + git tag -f testTag HEAD~ && + test_must_fail git push ../child2 tag testTag && + git push --force ../child2 tag testTag + ) + " +} + +test_force_push_tag "lightweight tag" "-f" +test_force_push_tag "annotated tag" "-f -a -m'tag message'" + +test_force_fetch_tag () { + tag_type_description=$1 + tag_args=$2 + + test_expect_success "fetch will not clobber an existing $tag_type_description without --force" " + mk_test testrepo heads/master && + mk_child testrepo child1 && + mk_child testrepo child2 && + ( + cd testrepo && + git tag testTag && + git -C ../child1 fetch origin tag testTag && + >file1 && + git add file1 && + git commit -m 'file1' && + git tag $tag_args testTag && + test_must_fail git -C ../child1 fetch origin tag testTag && + git -C ../child1 fetch origin '+refs/tags/*:refs/tags/*' + ) + " +} + +test_force_fetch_tag "lightweight tag" "-f" +test_force_fetch_tag "annotated tag" "-f -a -m'tag message'" test_expect_success 'push --porcelain' ' mk_empty testrepo && @@ -1011,7 +1061,7 @@ test_expect_success 'push --porcelain rejected' ' mk_empty testrepo && git push testrepo refs/heads/master:refs/remotes/origin/master && (cd testrepo && - git reset --hard origin/master^ + git reset --hard origin/master^ && git config receive.denyCurrentBranch true) && echo >.git/foo "To testrepo" && @@ -1025,7 +1075,7 @@ test_expect_success 'push --porcelain --dry-run rejected' ' mk_empty testrepo && git push testrepo refs/heads/master:refs/remotes/origin/master && (cd testrepo && - git reset --hard origin/master + git reset --hard origin/master && git config receive.denyCurrentBranch true) && echo >.git/foo "To testrepo" && @@ -1121,6 +1171,25 @@ test_expect_success 'fetch exact SHA1' ' ) ' +test_expect_success 'fetch exact SHA1 in protocol v2' ' + mk_test testrepo heads/master hidden/one && + git push testrepo master:refs/hidden/one && + git -C testrepo config transfer.hiderefs refs/hidden && + check_push_result testrepo $the_commit hidden/one && + + mk_child testrepo child && + git -C child config protocol.version 2 && + + # make sure $the_commit does not exist here + git -C child repack -a -d && + git -C child prune && + test_must_fail git -C child cat-file -t $the_commit && + + # fetching the hidden object succeeds by default + # NEEDSWORK: should this match the v0 behavior instead? + git -C child fetch -v ../testrepo $the_commit:refs/heads/copy +' + for configallowtipsha1inwant in true false do test_expect_success "shallow fetch reachable SHA1 (but not a ref), allowtipsha1inwant=$configallowtipsha1inwant" ' @@ -1314,7 +1383,7 @@ test_expect_success 'push --follow-tag only pushes relevant tags' ' git commit --allow-empty -m "future commit" && git tag -m "future" future && git checkout master && - git for-each-ref refs/heads/master refs/tags/tag >../expect + git for-each-ref refs/heads/master refs/tags/tag >../expect && git push --follow-tag ../dst master ) && ( diff --git a/t/t5517-push-mirror.sh b/t/t5517-push-mirror.sh index 02f160aae0..c05a661400 100755 --- a/t/t5517-push-mirror.sh +++ b/t/t5517-push-mirror.sh @@ -71,7 +71,7 @@ test_expect_success 'push mirror force updates existing branches' ' git push --mirror up && echo two >foo && git add foo && git commit -m two && git push --mirror up && - git reset --hard HEAD^ + git reset --hard HEAD^ && git push --mirror up ) && master_master=$(cd master && git show-ref -s --verify refs/heads/master) && @@ -88,7 +88,7 @@ test_expect_success 'push mirror removes branches' ' echo one >foo && git add foo && git commit -m one && git branch remove master && git push --mirror up && - git branch -D remove + git branch -D remove && git push --mirror up ) && ( @@ -170,7 +170,7 @@ test_expect_success 'push mirror force updates existing tags' ' echo two >foo && git add foo && git commit -m two && git tag -f tmaster master && git push --mirror up && - git reset --hard HEAD^ + git reset --hard HEAD^ && git tag -f tmaster master && git push --mirror up ) && @@ -188,7 +188,7 @@ test_expect_success 'push mirror removes tags' ' echo one >foo && git add foo && git commit -m one && git tag -f tremove master && git push --mirror up && - git tag -d tremove + git tag -d tremove && git push --mirror up ) && ( @@ -235,7 +235,7 @@ test_expect_success 'remote.foo.mirror adds and removes branches' ' git branch keep master && git branch remove master && git push up && - git branch -D remove + git branch -D remove && git push up ) && ( diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh index 59c4b778d3..5e501c8b08 100755 --- a/t/t5520-pull.sh +++ b/t/t5520-pull.sh @@ -475,10 +475,22 @@ test_expect_success 'pull.rebase=interactive' ' false EOF test_set_editor "$TRASH_DIRECTORY/fake-editor" && + test_when_finished "test_might_fail git rebase --abort" && test_must_fail git pull --rebase=interactive . copy && test "I was here" = "$(cat fake.out)" ' +test_expect_success 'pull --rebase=i' ' + write_script "$TRASH_DIRECTORY/fake-editor" <<-\EOF && + echo I was here, too >fake.out && + false + EOF + test_set_editor "$TRASH_DIRECTORY/fake-editor" && + test_when_finished "test_might_fail git rebase --abort" && + test_must_fail git pull --rebase=i . copy && + test "I was here, too" = "$(cat fake.out)" +' + test_expect_success 'pull.rebase=invalid fails' ' git reset --hard before-preserve-rebase && test_config pull.rebase invalid && @@ -618,6 +630,18 @@ test_expect_success 'pull --rebase fails on unborn branch with staged changes' ' ) ' +test_expect_success 'pull --rebase fails on corrupt HEAD' ' + test_when_finished "rm -rf corrupt" && + git init corrupt && + ( + cd corrupt && + test_commit one && + obj=$(git rev-parse --verify HEAD | sed "s#^..#&/#") && + rm -f .git/objects/$obj && + test_must_fail git pull --rebase + ) +' + test_expect_success 'setup for detecting upstreamed changes' ' mkdir src && (cd src && diff --git a/t/t5523-push-upstream.sh b/t/t5523-push-upstream.sh index d6981ba304..c0df81a014 100755 --- a/t/t5523-push-upstream.sh +++ b/t/t5523-push-upstream.sh @@ -113,7 +113,7 @@ test_expect_success TTY 'quiet push' ' ensure_fresh_upstream && test_terminal git push --quiet --no-progress upstream master 2>&1 | tee output && - test_cmp /dev/null output + test_must_be_empty output ' test_done diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 9cc4b569c0..6c2f9b2ba2 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -98,8 +98,8 @@ test_expect_success "fetch alone only fetches superproject" ' cd downstream && git fetch >../actual.out 2>../actual.err ) && - ! test -s actual.out && - ! test -s actual.err + test_must_be_empty actual.out && + test_must_be_empty actual.err ' test_expect_success "fetch --no-recurse-submodules only fetches superproject" ' @@ -107,8 +107,8 @@ test_expect_success "fetch --no-recurse-submodules only fetches superproject" ' cd downstream && git fetch --no-recurse-submodules >../actual.out 2>../actual.err ) && - ! test -s actual.out && - ! test -s actual.err + test_must_be_empty actual.out && + test_must_be_empty actual.err ' test_expect_success "using fetchRecurseSubmodules=true in .gitmodules recurses into submodules" ' @@ -127,8 +127,8 @@ test_expect_success "--no-recurse-submodules overrides .gitmodules config" ' cd downstream && git fetch --no-recurse-submodules >../actual.out 2>../actual.err ) && - ! test -s actual.out && - ! test -s actual.err + test_must_be_empty actual.out && + test_must_be_empty actual.err ' test_expect_success "using fetchRecurseSubmodules=false in .git/config overrides setting in .gitmodules" ' @@ -137,8 +137,8 @@ test_expect_success "using fetchRecurseSubmodules=false in .git/config overrides git config submodule.submodule.fetchRecurseSubmodules false && git fetch >../actual.out 2>../actual.err ) && - ! test -s actual.out && - ! test -s actual.err + test_must_be_empty actual.out && + test_must_be_empty actual.err ' test_expect_success "--recurse-submodules overrides fetchRecurseSubmodules setting from .git/config" ' @@ -157,8 +157,8 @@ test_expect_success "--quiet propagates to submodules" ' cd downstream && git fetch --recurse-submodules --quiet >../actual.out 2>../actual.err ) && - ! test -s actual.out && - ! test -s actual.err + test_must_be_empty actual.out && + test_must_be_empty actual.err ' test_expect_success "--quiet propagates to parallel submodules" ' @@ -166,8 +166,8 @@ test_expect_success "--quiet propagates to parallel submodules" ' cd downstream && git fetch --recurse-submodules -j 2 --quiet >../actual.out 2>../actual.err ) && - ! test -s actual.out && - ! test -s actual.err + test_must_be_empty actual.out && + test_must_be_empty actual.err ' test_expect_success "--dry-run propagates to submodules" ' @@ -221,8 +221,8 @@ test_expect_success "--no-recurse-submodules overrides config setting" ' git config fetch.recurseSubmodules true && git fetch --no-recurse-submodules >../actual.out 2>../actual.err ) && - ! test -s actual.out && - ! test -s actual.err + test_must_be_empty actual.out && + test_must_be_empty actual.err ' test_expect_success "Recursion doesn't happen when no new commits are fetched in the superproject" ' @@ -235,8 +235,8 @@ test_expect_success "Recursion doesn't happen when no new commits are fetched in git config --unset fetch.recurseSubmodules && git fetch >../actual.out 2>../actual.err ) && - ! test -s actual.out && - ! test -s actual.err + test_must_be_empty actual.out && + test_must_be_empty actual.err ' test_expect_success "Recursion stops when no new submodule commits are fetched" ' @@ -268,7 +268,7 @@ test_expect_success "Recursion doesn't happen when new superproject commits don' cd downstream && git fetch >../actual.out 2>../actual.err ) && - ! test -s actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err.file actual.err ' @@ -357,8 +357,8 @@ test_expect_success "'--recurse-submodules=on-demand' doesn't recurse when no ne git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err && git config --unset fetch.recurseSubmodules ) && - ! test -s actual.out && - ! test -s actual.err + test_must_be_empty actual.out && + test_must_be_empty actual.err ' test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necessary (and ignores config)" ' @@ -379,7 +379,7 @@ test_expect_success "'--recurse-submodules=on-demand' recurses as deep as necess git config -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive false ) && git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err && - git config --unset fetch.recurseSubmodules + git config --unset fetch.recurseSubmodules && ( cd submodule && git config --unset -f .gitmodules submodule.subdir/deepsubmodule.fetchRecursive @@ -402,7 +402,7 @@ test_expect_success "'--recurse-submodules=on-demand' stops when no new submodul cd downstream && git fetch --recurse-submodules=on-demand >../actual.out 2>../actual.err ) && - ! test -s actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err.file actual.err ' @@ -477,7 +477,7 @@ test_expect_success "don't fetch submodule when newly recorded commits are alrea cd downstream && git fetch >../actual.out 2>../actual.err ) && - ! test -s actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err actual.err && ( cd submodule && @@ -495,7 +495,6 @@ test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .git git add submodule && git rm .gitmodules && git commit -m "new submodule without .gitmodules" && - printf "" >expect.out && head2=$(git rev-parse --short HEAD) && echo "From $pwd/." >expect.err.2 && echo " $head1..$head2 master -> origin/master" >>expect.err.2 && @@ -514,7 +513,7 @@ test_expect_success "'fetch.recurseSubmodules=on-demand' works also without .git git config --unset fetch.recurseSubmodules && git reset --hard ) && - test_i18ncmp expect.out actual.out && + test_must_be_empty actual.out && test_i18ncmp expect.err.2 actual.err && git checkout HEAD^ -- .gitmodules && git add .gitmodules && @@ -574,11 +573,7 @@ test_expect_success "fetch new commits when submodule got renamed" ' git clone . downstream_rename && ( cd downstream_rename && - git submodule update --init && -# NEEDSWORK: we omitted --recursive for the submodule update here since -# that does not work. See test 7001 for mv "moving nested submodules" -# for details. Once that is fixed we should add the --recursive option -# here. + git submodule update --init --recursive && git checkout -b rename && git mv submodule submodule_renamed && ( diff --git a/t/t5527-fetch-odd-refs.sh b/t/t5527-fetch-odd-refs.sh index 207899a99f..3b0cb98422 100755 --- a/t/t5527-fetch-odd-refs.sh +++ b/t/t5527-fetch-odd-refs.sh @@ -27,7 +27,7 @@ test_expect_success 'suffix ref is ignored during fetch' ' ' test_expect_success 'try to create repo with absurdly long refname' ' - ref240=$_z40/$_z40/$_z40/$_z40/$_z40/$_z40 && + ref240=$ZERO_OID/$ZERO_OID/$ZERO_OID/$ZERO_OID/$ZERO_OID/$ZERO_OID && ref1440=$ref240/$ref240/$ref240/$ref240/$ref240/$ref240 && git init long && ( diff --git a/t/t5531-deep-submodule-push.sh b/t/t5531-deep-submodule-push.sh index 39cb2c1c34..e2c37fd978 100755 --- a/t/t5531-deep-submodule-push.sh +++ b/t/t5531-deep-submodule-push.sh @@ -354,7 +354,7 @@ test_expect_success 'push succeeds if submodule has no remote and is on the firs git clone a a1 && ( cd a1 && - git init b + git init b && ( cd b && >junk && diff --git a/t/t5533-push-cas.sh b/t/t5533-push-cas.sh index d38ecee217..0b0eb1d025 100755 --- a/t/t5533-push-cas.sh +++ b/t/t5533-push-cas.sh @@ -142,9 +142,8 @@ test_expect_success 'push to delete (protected, forced)' ' cd dst && git push --force --force-with-lease=master:master^ origin :master ) && - >expect && git ls-remote src refs/heads/master >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'push to delete (allowed)' ' @@ -154,9 +153,8 @@ test_expect_success 'push to delete (allowed)' ' git push --force-with-lease=master origin :master 2>err && grep deleted err ) && - >expect && git ls-remote src refs/heads/master >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'cover everything with default force-with-lease (protected)' ' diff --git a/t/t5534-push-signed.sh b/t/t5534-push-signed.sh index 1cea758f78..030331f1c5 100755 --- a/t/t5534-push-signed.sh +++ b/t/t5534-push-signed.sh @@ -194,10 +194,12 @@ test_expect_success GPG 'fail without key and heed user.signingkey' ' EOF - unset GIT_COMMITTER_EMAIL && - git config user.email hasnokey@nowhere.com && - test_must_fail git push --signed dst noop ff +noff && - git config user.signingkey committer@example.com && + test_config user.email hasnokey@nowhere.com && + ( + sane_unset GIT_COMMITTER_EMAIL && + test_must_fail git push --signed dst noop ff +noff + ) && + test_config user.signingkey $GIT_COMMITTER_EMAIL && git push --signed dst noop ff +noff && ( @@ -218,4 +220,57 @@ test_expect_success GPG 'fail without key and heed user.signingkey' ' test_cmp expect dst/push-cert-status ' +test_expect_success GPGSM 'fail without key and heed user.signingkey x509' ' + test_config gpg.format x509 && + prepare_dst && + mkdir -p dst/.git/hooks && + git -C dst config receive.certnonceseed sekrit && + write_script dst/.git/hooks/post-receive <<-\EOF && + # discard the update list + cat >/dev/null + # record the push certificate + if test -n "${GIT_PUSH_CERT-}" + then + git cat-file blob $GIT_PUSH_CERT >../push-cert + fi && + + cat >../push-cert-status <<E_O_F + SIGNER=${GIT_PUSH_CERT_SIGNER-nobody} + KEY=${GIT_PUSH_CERT_KEY-nokey} + STATUS=${GIT_PUSH_CERT_STATUS-nostatus} + NONCE_STATUS=${GIT_PUSH_CERT_NONCE_STATUS-nononcestatus} + NONCE=${GIT_PUSH_CERT_NONCE-nononce} + E_O_F + + EOF + + test_config user.email hasnokey@nowhere.com && + test_config user.signingkey "" && + ( + sane_unset GIT_COMMITTER_EMAIL && + test_must_fail git push --signed dst noop ff +noff + ) && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + git push --signed dst noop ff +noff && + + ( + cat <<-\EOF && + SIGNER=/CN=C O Mitter/O=Example/SN=C O/GN=Mitter + KEY= + STATUS=G + NONCE_STATUS=OK + 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 ":") && + sed -e "s/^KEY=/KEY=${key}/" expect.in >expect && + + noop=$(git rev-parse noop) && + ff=$(git rev-parse ff) && + noff=$(git rev-parse noff) && + grep "$noop $ff refs/heads/ff" dst/push-cert && + grep "$noop $noff refs/heads/noff" dst/push-cert && + test_cmp expect dst/push-cert-status +' + test_done diff --git a/t/t5537-fetch-shallow.sh b/t/t5537-fetch-shallow.sh index df8d2f095a..7045685e2d 100755 --- a/t/t5537-fetch-shallow.sh +++ b/t/t5537-fetch-shallow.sh @@ -175,8 +175,8 @@ EOF test_expect_success POSIXPERM,SANITY 'shallow fetch from a read-only repo' ' cp -R .git read-only.git && - find read-only.git -print | xargs chmod -w && test_when_finished "find read-only.git -type d -print | xargs chmod +w" && + find read-only.git -print | xargs chmod -w && git clone --no-local --depth=2 read-only.git from-read-only && git --git-dir=from-read-only/.git log --format=%s >actual && cat >expect <<EOF && @@ -186,4 +186,47 @@ EOF test_cmp expect actual ' +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo" + +test_expect_success 'shallow fetches check connectivity before writing shallow file' ' + rm -rf "$REPO" client && + + git init "$REPO" && + test_commit -C "$REPO" one && + test_commit -C "$REPO" two && + test_commit -C "$REPO" three && + + git init client && + + # Use protocol v2 to ensure that shallow information is sent exactly + # once by the server, since we are planning to manipulate it. + git -C "$REPO" config protocol.version 2 && + git -C client config protocol.version 2 && + + git -C client fetch --depth=2 "$HTTPD_URL/one_time_sed/repo" master:a_branch && + + # Craft a situation in which the server sends back an unshallow request + # with an empty packfile. This is done by refetching with a shorter + # depth (to ensure that the packfile is empty), and overwriting the + # shallow line in the response with the unshallow line we want. + printf "s/0034shallow %s/0036unshallow %s/" \ + "$(git -C "$REPO" rev-parse HEAD)" \ + "$(git -C "$REPO" rev-parse HEAD^)" \ + >"$HTTPD_ROOT_PATH/one-time-sed" && + test_must_fail git -C client fetch --depth=1 "$HTTPD_URL/one_time_sed/repo" \ + master:a_branch && + + # Ensure that the one-time-sed script was used. + ! test -e "$HTTPD_ROOT_PATH/one-time-sed" && + + # Ensure that the resulting repo is consistent, despite our failure to + # fetch. + git -C client fsck +' + +stop_httpd + test_done diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh index 21340e89c9..5475afc052 100755 --- a/t/t5541-http-push-smart.sh +++ b/t/t5541-http-push-smart.sh @@ -38,25 +38,16 @@ GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200 EOF test_expect_success 'no empty path components' ' + # Clear the log, so that it does not affect the "used receive-pack + # service" test which reads the log too. + test_when_finished ">\"\$HTTPD_ROOT_PATH\"/access.log" && + # In the URL, add a trailing slash, and see if git appends yet another # slash. cd "$ROOT_PATH" && git clone $HTTPD_URL/smart/test_repo.git/ test_repo_clone && - sed -e " - s/^.* \"// - s/\"// - s/ [1-9][0-9]*\$// - s/^GET /GET / - " >act <"$HTTPD_ROOT_PATH"/access.log && - - # Clear the log, so that it does not affect the "used receive-pack - # service" test which reads the log too. - # - # We do this before the actual comparison to ensure the log is cleared. - echo > "$HTTPD_ROOT_PATH"/access.log && - - test_cmp exp act + check_access_log exp ' test_expect_success 'clone remote repository' ' @@ -124,7 +115,6 @@ test_expect_success 'rejected update prints status' ' rm -f "$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git/hooks/update" cat >exp <<EOF - GET /smart/test_repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 POST /smart/test_repo.git/git-upload-pack HTTP/1.1 200 GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 @@ -138,13 +128,7 @@ GET /smart/test_repo.git/info/refs?service=git-receive-pack HTTP/1.1 200 POST /smart/test_repo.git/git-receive-pack HTTP/1.1 200 EOF test_expect_success 'used receive-pack service' ' - sed -e " - s/^.* \"// - s/\"// - s/ [1-9][0-9]*\$// - s/^GET /GET / - " >act <"$HTTPD_ROOT_PATH"/access.log && - test_cmp exp act + check_access_log exp ' test_http_push_nonff "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git \ @@ -226,7 +210,7 @@ test_expect_success TTY 'push --quiet silences status and progress' ' cd "$ROOT_PATH"/test_repo_clone && test_commit quiet && test_terminal git push --quiet >output 2>&1 && - test_cmp /dev/null output + test_must_be_empty output ' test_expect_success TTY 'push --no-progress silences progress but not status' ' @@ -377,5 +361,17 @@ test_expect_success 'push status output scrubs password' ' grep "^To $HTTPD_URL/smart/test_repo.git" status ' +test_expect_success 'colorize errors/hints' ' + cd "$ROOT_PATH"/test_repo_clone && + test_must_fail git -c color.transport=always -c color.advice=always \ + -c color.push=always \ + push origin origin/master^:master 2>act && + test_decode_color <act >decoded && + test_i18ngrep "<RED>.*rejected.*<RESET>" decoded && + test_i18ngrep "<RED>error: failed to push some refs" decoded && + test_i18ngrep "<YELLOW>hint: " decoded && + test_i18ngrep ! "^hint: " decoded +' + stop_httpd test_done diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh index 3480b33007..7079bcf9a0 100755 --- a/t/t5543-atomic-push.sh +++ b/t/t5543-atomic-push.sh @@ -178,7 +178,7 @@ test_expect_success 'atomic push obeys update hook preventing a branch to be pus test_expect_success 'atomic push is not advertised if configured' ' mk_repo_pair && ( - cd upstream + cd upstream && git config receive.advertiseatomic 0 ) && ( diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index 8552184e74..6d7d88ccc9 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -169,6 +169,17 @@ test_expect_success 'fetch changes via manual http-fetch' ' test_cmp file clone2/file ' +test_expect_success 'manual http-fetch without -a works just as well' ' + cp -R clone-tmpl clone3 && + + HEAD=$(git rev-parse --verify HEAD) && + (cd clone3 && + git http-fetch -w heads/master-new $HEAD $(git config remote.origin.url) && + git checkout master-new && + test $HEAD = $(git rev-parse --verify HEAD)) && + test_cmp file clone3/file +' + test_expect_success 'http remote detects correct HEAD' ' git push public master:other && (cd clone && diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh index f5721b4a59..771f36f9ff 100755 --- a/t/t5551-http-fetch-smart.sh +++ b/t/t5551-http-fetch-smart.sh @@ -26,14 +26,14 @@ setup_askpass_helper cat >exp <<EOF > GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 > Accept: */* -> Accept-Encoding: gzip +> Accept-Encoding: ENCODINGS > Pragma: no-cache < HTTP/1.1 200 OK < Pragma: no-cache < Cache-Control: no-cache, max-age=0, must-revalidate < Content-Type: application/x-git-upload-pack-advertisement > POST /smart/repo.git/git-upload-pack HTTP/1.1 -> Accept-Encoding: gzip +> Accept-Encoding: ENCODINGS > Content-Type: application/x-git-upload-pack-request > Accept: application/x-git-upload-pack-result > Content-Length: xxx @@ -79,8 +79,13 @@ test_expect_success 'clone http repository' ' /^< Date: /d /^< Content-Length: /d /^< Transfer-Encoding: /d - " >act && - test_cmp exp act + " >actual && + sed -e "s/^> Accept-Encoding: .*/> Accept-Encoding: ENCODINGS/" \ + actual >actual.smudged && + test_cmp exp actual.smudged && + + grep "Accept-Encoding:.*gzip" actual >actual.gzip && + test_line_count = 2 actual.gzip ' test_expect_success 'fetch changes via http' ' @@ -98,13 +103,7 @@ GET /smart/repo.git/info/refs?service=git-upload-pack HTTP/1.1 200 POST /smart/repo.git/git-upload-pack HTTP/1.1 200 EOF test_expect_success 'used upload-pack service' ' - sed -e " - s/^.* \"// - s/\"// - s/ [1-9][0-9]*\$// - s/^GET /GET / - " >act <"$HTTPD_ROOT_PATH"/access.log && - test_cmp exp act + check_access_log exp ' test_expect_success 'follow redirects (301)' ' @@ -364,6 +363,24 @@ test_expect_success 'custom http headers' ' submodule update sub ' +test_expect_success 'using fetch command in remote-curl updates refs' ' + SERVER="$HTTPD_DOCUMENT_ROOT_PATH/twobranch" && + rm -rf "$SERVER" client && + + git init "$SERVER" && + test_commit -C "$SERVER" foo && + git -C "$SERVER" update-ref refs/heads/anotherbranch foo && + + git clone $HTTPD_URL/smart/twobranch client && + + test_commit -C "$SERVER" bar && + git -C client -c protocol.version=0 fetch && + + git -C "$SERVER" rev-parse master >expect && + git -C client rev-parse origin/master >actual && + test_cmp expect actual +' + test_expect_success 'GIT_REDACT_COOKIES redacts cookies' ' rm -rf clone && echo "Set-Cookie: Foo=1" >cookies && diff --git a/t/t5552-skipping-fetch-negotiator.sh b/t/t5552-skipping-fetch-negotiator.sh new file mode 100755 index 0000000000..30857b84a8 --- /dev/null +++ b/t/t5552-skipping-fetch-negotiator.sh @@ -0,0 +1,215 @@ +#!/bin/sh + +test_description='test skipping fetch negotiator' +. ./test-lib.sh + +have_sent () { + while test "$#" -ne 0 + do + grep "fetch> have $(git -C client rev-parse $1)" trace + if test $? -ne 0 + then + echo "No have $(git -C client rev-parse $1) ($1)" + return 1 + fi + shift + done +} + +have_not_sent () { + while test "$#" -ne 0 + do + grep "fetch> have $(git -C client rev-parse $1)" trace + if test $? -eq 0 + then + return 1 + fi + shift + done +} + +# trace_fetch <client_dir> <server_dir> [args] +# +# Trace the packet output of fetch, but make sure we disable the variable +# in the child upload-pack, so we don't combine the results in the same file. +trace_fetch () { + client=$1; shift + server=$1; shift + GIT_TRACE_PACKET="$(pwd)/trace" \ + git -C "$client" fetch \ + --upload-pack 'unset GIT_TRACE_PACKET; git-upload-pack' \ + "$server" "$@" +} + +test_expect_success 'commits with no parents are sent regardless of skip distance' ' + git init server && + test_commit -C server to_fetch && + + git init client && + for i in $(test_seq 7) + do + test_commit -C client c$i + done && + + # We send: "c7" (skip 1) "c5" (skip 2) "c2" (skip 4). After that, since + # "c1" has no parent, it is still sent as "have" even though it would + # normally be skipped. + test_config -C client fetch.negotiationalgorithm skipping && + trace_fetch client "$(pwd)/server" && + have_sent c7 c5 c2 c1 && + have_not_sent c6 c4 c3 +' + +test_expect_success 'unknown fetch.negotiationAlgorithm values error out' ' + rm -rf server client trace && + git init server && + test_commit -C server to_fetch && + + git init client && + test_commit -C client on_client && + git -C client checkout on_client && + + test_config -C client fetch.negotiationAlgorithm invalid && + test_must_fail git -C client fetch "$(pwd)/server" 2>err && + test_i18ngrep "unknown fetch negotiation algorithm" err && + + # Explicit "default" value + test_config -C client fetch.negotiationAlgorithm default && + git -C client -c fetch.negotiationAlgorithm=default fetch "$(pwd)/server" && + + # Implementation detail: If there is nothing to fetch, we will not error out + test_config -C client fetch.negotiationAlgorithm invalid && + git -C client fetch "$(pwd)/server" 2>err && + test_i18ngrep ! "unknown fetch negotiation algorithm" err +' + +test_expect_success 'when two skips collide, favor the larger one' ' + rm -rf server client trace && + git init server && + test_commit -C server to_fetch && + + git init client && + for i in $(test_seq 11) + do + test_commit -C client c$i + done && + git -C client checkout c5 && + test_commit -C client c5side && + + # Before reaching c5, we send "c5side" (skip 1) and "c11" (skip 1) "c9" + # (skip 2) "c6" (skip 4). The larger skip (skip 4) takes precedence, so + # the next "have" sent will be "c1" (from "c6" skip 4) and not "c4" + # (from "c5side" skip 1). + test_config -C client fetch.negotiationalgorithm skipping && + trace_fetch client "$(pwd)/server" && + have_sent c5side c11 c9 c6 c1 && + have_not_sent c10 c8 c7 c5 c4 c3 c2 +' + +test_expect_success 'use ref advertisement to filter out commits' ' + rm -rf server client trace && + git init server && + test_commit -C server c1 && + test_commit -C server c2 && + test_commit -C server c3 && + git -C server tag -d c1 c2 c3 && + + git clone server client && + test_commit -C client c4 && + test_commit -C client c5 && + git -C client checkout c4^^ && + test_commit -C client c2side && + + git -C server checkout --orphan anotherbranch && + test_commit -C server to_fetch && + + # The server advertising "c3" (as "refs/heads/master") means that we do + # not need to send any ancestors of "c3", but we still need to send "c3" + # itself. + test_config -C client fetch.negotiationalgorithm skipping && + trace_fetch client origin to_fetch && + have_sent c5 c4^ c2side && + have_not_sent c4 c4^^ c4^^^ +' + +test_expect_success 'handle clock skew' ' + rm -rf server client trace && + git init server && + test_commit -C server to_fetch && + + git init client && + + # 2 regular commits + test_tick=2000000000 && + test_commit -C client c1 && + test_commit -C client c2 && + + # 4 old commits + test_tick=1000000000 && + git -C client checkout c1 && + test_commit -C client old1 && + test_commit -C client old2 && + test_commit -C client old3 && + test_commit -C client old4 && + + # "c2" and "c1" are popped first, then "old4" to "old1". "old1" would + # normally be skipped, but is treated as a commit without a parent here + # and sent, because (due to clock skew) its only parent has already been + # popped off the priority queue. + test_config -C client fetch.negotiationalgorithm skipping && + trace_fetch client "$(pwd)/server" && + have_sent c2 c1 old4 old2 old1 && + have_not_sent old3 +' + +test_expect_success 'do not send "have" with ancestors of commits that server ACKed' ' + rm -rf server client trace && + git init server && + test_commit -C server to_fetch && + + git init client && + for i in $(test_seq 8) + do + git -C client checkout --orphan b$i && + test_commit -C client b$i.c0 + done && + for j in $(test_seq 19) + do + for i in $(test_seq 8) + do + git -C client checkout b$i && + test_commit -C client b$i.c$j + done + done && + + # Copy this branch over to the server and add a commit on it so that it + # is reachable but not advertised. + git -C server fetch --no-tags "$(pwd)/client" b1:refs/heads/b1 && + git -C server checkout b1 && + test_commit -C server commit-on-b1 && + + test_config -C client fetch.negotiationalgorithm skipping && + trace_fetch client "$(pwd)/server" to_fetch && + grep " fetch" trace && + + # fetch-pack sends 2 requests each containing 16 "have" lines before + # processing the first response. In these 2 requests, 4 commits from + # each branch are sent. Just check the first branch. + have_sent b1.c19 b1.c17 b1.c14 b1.c9 && + have_not_sent b1.c18 b1.c16 b1.c15 b1.c13 b1.c12 b1.c11 b1.c10 && + + # While fetch-pack is processing the first response, it should read that + # the server ACKs b1.c19 and b1.c17. + grep "fetch< ACK $(git -C client rev-parse b1.c19) common" trace && + grep "fetch< ACK $(git -C client rev-parse b1.c17) common" trace && + + # fetch-pack should thus not send any more commits in the b1 branch, but + # should still send the others (in this test, just check b2). + for i in $(test_seq 0 8) + do + have_not_sent b1.c$i + done && + have_sent b2.c1 b2.c0 +' + +test_done diff --git a/t/t5561-http-backend.sh b/t/t5561-http-backend.sh index 90e0d6f0fe..1c49054595 100755 --- a/t/t5561-http-backend.sh +++ b/t/t5561-http-backend.sh @@ -3,10 +3,16 @@ test_description='test git-http-backend' . ./test-lib.sh . "$TEST_DIRECTORY"/lib-httpd.sh + +if ! test_have_prereq CURL; then + skip_all='skipping raw http-backend tests, curl not available' + test_done +fi + start_httpd GET() { - curl --include "$HTTPD_URL/$SMART/repo.git/$1" >out 2>/dev/null && + curl --include "$HTTPD_URL/$SMART/repo.git/$1" >out && tr '\015' Q <out | sed ' s/Q$// @@ -19,7 +25,7 @@ GET() { POST() { curl --include --data "$2" \ --header "Content-Type: application/x-$1-request" \ - "$HTTPD_URL/smart/repo.git/$1" >out 2>/dev/null && + "$HTTPD_URL/smart/repo.git/$1" >out && tr '\015' Q <out | sed ' s/Q$// @@ -123,13 +129,7 @@ GET /smart/repo.git/info/refs?service=git-receive-pack HTTP/1.1 403 - POST /smart/repo.git/git-receive-pack HTTP/1.1 403 - EOF test_expect_success 'server request log matches test results' ' - sed -e " - s/^.* \"// - s/\"// - s/ [1-9][0-9]*\$// - s/^GET /GET / - " >act <"$HTTPD_ROOT_PATH"/access.log && - test_cmp exp act + check_access_log exp ' stop_httpd diff --git a/t/t5562-http-backend-content-length.sh b/t/t5562-http-backend-content-length.sh new file mode 100755 index 0000000000..b24d8b05a4 --- /dev/null +++ b/t/t5562-http-backend-content-length.sh @@ -0,0 +1,167 @@ +#!/bin/sh + +test_description='test git-http-backend respects CONTENT_LENGTH' +. ./test-lib.sh + +test_lazy_prereq GZIP 'gzip --version' + +verify_http_result() { + # some fatal errors still produce status 200 + # so check if there is the error message + if grep 'fatal:' act.err + then + return 1 + fi + + if ! grep "Status" act.out >act + then + printf "Status: 200 OK\r\n" >act + fi + printf "Status: $1\r\n" >exp && + test_cmp exp act +} + +test_http_env() { + handler_type="$1" + request_body="$2" + shift + env \ + CONTENT_TYPE="application/x-git-$handler_type-pack-request" \ + QUERY_STRING="/repo.git/git-$handler_type-pack" \ + PATH_TRANSLATED="$PWD/.git/git-$handler_type-pack" \ + GIT_HTTP_EXPORT_ALL=TRUE \ + REQUEST_METHOD=POST \ + "$TEST_DIRECTORY"/t5562/invoke-with-content-length.pl \ + "$request_body" git http-backend >act.out 2>act.err +} + +ssize_b100dots() { + # hardcoded ((size_t) SSIZE_MAX) + 1 + case "$(build_option sizeof-size_t)" in + 8) echo 9223372036854775808;; + 4) echo 2147483648;; + *) die "Unexpected ssize_t size: $(build_option sizeof-size_t)";; + esac +} + +test_expect_success 'setup' ' + HTTP_CONTENT_ENCODING="identity" && + export HTTP_CONTENT_ENCODING && + git config http.receivepack true && + test_commit c0 && + test_commit c1 && + hash_head=$(git rev-parse HEAD) && + hash_prev=$(git rev-parse HEAD~1) && + printf "want %s" "$hash_head" | packetize >fetch_body && + printf 0000 >>fetch_body && + printf "have %s" "$hash_prev" | packetize >>fetch_body && + printf done | packetize >>fetch_body && + test_copy_bytes 10 <fetch_body >fetch_body.trunc && + hash_next=$(git commit-tree -p HEAD -m next HEAD^{tree}) && + printf "%s %s refs/heads/newbranch\\0report-status\\n" "$_z40" "$hash_next" | packetize >push_body && + printf 0000 >>push_body && + echo "$hash_next" | git pack-objects --stdout >>push_body && + test_copy_bytes 10 <push_body >push_body.trunc && + : >empty_body +' + +test_expect_success GZIP 'setup, compression related' ' + gzip -c fetch_body >fetch_body.gz && + test_copy_bytes 10 <fetch_body.gz >fetch_body.gz.trunc && + gzip -c push_body >push_body.gz && + test_copy_bytes 10 <push_body.gz >push_body.gz.trunc +' + +test_expect_success 'fetch plain' ' + test_http_env upload fetch_body && + verify_http_result "200 OK" +' + +test_expect_success 'fetch plain truncated' ' + test_http_env upload fetch_body.trunc && + ! verify_http_result "200 OK" +' + +test_expect_success 'fetch plain empty' ' + test_http_env upload empty_body && + ! verify_http_result "200 OK" +' + +test_expect_success GZIP 'fetch gzipped' ' + test_env HTTP_CONTENT_ENCODING="gzip" test_http_env upload fetch_body.gz && + verify_http_result "200 OK" +' + +test_expect_success GZIP 'fetch gzipped truncated' ' + test_env HTTP_CONTENT_ENCODING="gzip" test_http_env upload fetch_body.gz.trunc && + ! verify_http_result "200 OK" +' + +test_expect_success GZIP 'fetch gzipped empty' ' + test_env HTTP_CONTENT_ENCODING="gzip" test_http_env upload empty_body && + ! verify_http_result "200 OK" +' + +test_expect_success GZIP 'push plain' ' + test_when_finished "git branch -D newbranch" && + test_http_env receive push_body && + verify_http_result "200 OK" && + git rev-parse newbranch >act.head && + echo "$hash_next" >exp.head && + test_cmp act.head exp.head +' + +test_expect_success 'push plain truncated' ' + test_http_env receive push_body.trunc && + ! verify_http_result "200 OK" +' + +test_expect_success 'push plain empty' ' + test_http_env receive empty_body && + ! verify_http_result "200 OK" +' + +test_expect_success GZIP 'push gzipped' ' + test_when_finished "git branch -D newbranch" && + test_env HTTP_CONTENT_ENCODING="gzip" test_http_env receive push_body.gz && + verify_http_result "200 OK" && + git rev-parse newbranch >act.head && + echo "$hash_next" >exp.head && + test_cmp act.head exp.head +' + +test_expect_success GZIP 'push gzipped truncated' ' + test_env HTTP_CONTENT_ENCODING="gzip" test_http_env receive push_body.gz.trunc && + ! verify_http_result "200 OK" +' + +test_expect_success GZIP 'push gzipped empty' ' + test_env HTTP_CONTENT_ENCODING="gzip" test_http_env receive empty_body && + ! verify_http_result "200 OK" +' + +test_expect_success 'CONTENT_LENGTH overflow ssite_t' ' + NOT_FIT_IN_SSIZE=$(ssize_b100dots) && + env \ + CONTENT_TYPE=application/x-git-upload-pack-request \ + QUERY_STRING=/repo.git/git-upload-pack \ + PATH_TRANSLATED="$PWD"/.git/git-upload-pack \ + GIT_HTTP_EXPORT_ALL=TRUE \ + REQUEST_METHOD=POST \ + CONTENT_LENGTH="$NOT_FIT_IN_SSIZE" \ + git http-backend </dev/zero >/dev/null 2>err && + grep "fatal:.*CONTENT_LENGTH" err +' + +test_expect_success 'empty CONTENT_LENGTH' ' + env \ + QUERY_STRING="service=git-receive-pack" \ + PATH_TRANSLATED="$PWD"/.git/info/refs \ + GIT_HTTP_EXPORT_ALL=TRUE \ + REQUEST_METHOD=GET \ + CONTENT_LENGTH="" \ + git http-backend <empty_body >act.out 2>act.err && + verify_http_result "200 OK" +' + +test_done diff --git a/t/t5562/invoke-with-content-length.pl b/t/t5562/invoke-with-content-length.pl new file mode 100755 index 0000000000..6c2aae7692 --- /dev/null +++ b/t/t5562/invoke-with-content-length.pl @@ -0,0 +1,37 @@ +#!/usr/bin/perl +use 5.008; +use strict; +use warnings; + +my $body_filename = $ARGV[0]; +my @command = @ARGV[1 .. $#ARGV]; + +# read data +my $body_size = -s $body_filename; +$ENV{"CONTENT_LENGTH"} = $body_size; +open(my $body_fh, "<", $body_filename) or die "Cannot open $body_filename: $!"; +my $body_data; +defined read($body_fh, $body_data, $body_size) or die "Cannot read $body_filename: $!"; +close($body_fh); + +my $exited = 0; +$SIG{"CHLD"} = sub { + $exited = 1; +}; + +# write data +my $pid = open(my $out, "|-", @command); +{ + # disable buffering at $out + my $old_selected = select; + select $out; + $| = 1; + select $old_selected; +} +print $out $body_data or die "Cannot write data: $!"; + +sleep 60; # is interrupted by SIGCHLD +if (!$exited) { + close($out); + die "Command did not exit after reading whole body"; +} diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 0d4c52016b..7466aad111 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -7,9 +7,9 @@ test_description='test fetching over git protocol' start_git_daemon check_verbose_connect () { - grep -F "Looking up 127.0.0.1 ..." stderr && - grep -F "Connecting to 127.0.0.1 (port " stderr && - grep -F "done." stderr + test_i18ngrep -F "Looking up 127.0.0.1 ..." stderr && + test_i18ngrep -F "Connecting to 127.0.0.1 (port " stderr && + test_i18ngrep -F "done." stderr } test_expect_success 'setup repository' ' @@ -51,7 +51,7 @@ test_expect_success 'no-op fetch -v stderr is as expected' ' test_expect_success 'no-op fetch without "-v" is quiet' ' (cd clone && git fetch 2>../stderr) && - ! test -s stderr + test_must_be_empty stderr ' test_expect_success 'remote detects correct HEAD' ' diff --git a/t/t5571-pre-push-hook.sh b/t/t5571-pre-push-hook.sh index ba975bb355..ac53d63869 100755 --- a/t/t5571-pre-push-hook.sh +++ b/t/t5571-pre-push-hook.sh @@ -78,8 +78,8 @@ test_expect_success 'push to default' ' cat >expected <<EOF parent1 repo1 -refs/tags/one $COMMIT1 refs/tags/tag1 $_z40 -HEAD~ $COMMIT2 refs/heads/prev $_z40 +refs/tags/one $COMMIT1 refs/tags/tag1 $ZERO_OID +HEAD~ $COMMIT2 refs/heads/prev $ZERO_OID EOF test_expect_success 'push non-branches' ' @@ -90,7 +90,7 @@ test_expect_success 'push non-branches' ' cat >expected <<EOF parent1 repo1 -(delete) $_z40 refs/heads/prev $COMMIT2 +(delete) $ZERO_OID refs/heads/prev $COMMIT2 EOF test_expect_success 'push delete' ' @@ -101,7 +101,7 @@ test_expect_success 'push delete' ' cat >expected <<EOF repo1 repo1 -HEAD $COMMIT3 refs/heads/other $_z40 +HEAD $COMMIT3 refs/heads/other $ZERO_OID EOF test_expect_success 'push to URL' ' diff --git a/t/t5572-pull-submodule.sh b/t/t5572-pull-submodule.sh index 321bd37deb..f916729a12 100755 --- a/t/t5572-pull-submodule.sh +++ b/t/t5572-pull-submodule.sh @@ -132,4 +132,25 @@ test_expect_success 'pull rebase recursing fails with conflicts' ' test_i18ngrep "locally recorded submodule modifications" err ' +test_expect_success 'branch has no merge base with remote-tracking counterpart' ' + rm -rf parent child && + + test_create_repo a-submodule && + test_commit -C a-submodule foo && + + test_create_repo parent && + git -C parent submodule add "$(pwd)/a-submodule" && + git -C parent commit -m foo && + + git clone parent child && + + # Reset master so that it has no merge base with + # refs/remotes/origin/master. + OTHER=$(git -C child commit-tree -m bar \ + $(git -C child rev-parse HEAD^{tree})) && + git -C child reset --hard "$OTHER" && + + git -C child pull --recurse-submodules --rebase +' + test_done diff --git a/t/t5573-pull-verify-signatures.sh b/t/t5573-pull-verify-signatures.sh index 9594e891f4..747775c147 100755 --- a/t/t5573-pull-verify-signatures.sh +++ b/t/t5573-pull-verify-signatures.sh @@ -29,7 +29,7 @@ test_expect_success GPG 'create repositories with signed commits' ' echo 4 >d && git add d && test_tick && git commit -S -m "bad" && git cat-file commit HEAD >raw && - sed -e "s/bad/forged bad/" raw >forged && + sed -e "s/^bad/forged bad/" raw >forged && git hash-object -w -t commit forged >forged.commit && git checkout $(cat forged.commit) ) && diff --git a/t/t5601-clone.sh b/t/t5601-clone.sh index 0b62037744..f1a49e94f5 100755 --- a/t/t5601-clone.sh +++ b/t/t5601-clone.sh @@ -618,16 +618,22 @@ hex2oct () { test_expect_success 'clone on case-insensitive fs' ' git init icasefs && ( - cd icasefs + cd icasefs && o=$(git hash-object -w --stdin </dev/null | hex2oct) && t=$(printf "100644 X\0${o}100644 x\0${o}" | git hash-object -w -t tree --stdin) && c=$(git commit-tree -m bogus $t) && git update-ref refs/heads/bogus $c && - git clone -b bogus . bogus + git clone -b bogus . bogus 2>warning ) ' +test_expect_success !MINGW,!CYGWIN,CASE_INSENSITIVE_FS 'colliding file detection' ' + grep X icasefs/warning && + grep x icasefs/warning && + test_i18ngrep "the following paths have collided" icasefs/warning +' + partial_clone () { SERVER="$1" && URL="$2" && diff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh index 3c087e907c..af23419ebf 100755 --- a/t/t5605-clone-local.sh +++ b/t/t5605-clone-local.sh @@ -94,7 +94,7 @@ test_expect_success 'clone empty repository' ' git config receive.denyCurrentBranch warn) && git clone empty empty-clone && test_tick && - (cd empty-clone + (cd empty-clone && echo "content" >> foo && git add foo && git commit -m "Initial commit" && diff --git a/t/t5608-clone-2gb.sh b/t/t5608-clone-2gb.sh index df822d9a3e..2c6bc07344 100755 --- a/t/t5608-clone-2gb.sh +++ b/t/t5608-clone-2gb.sh @@ -23,7 +23,7 @@ test_expect_success CLONE_2GB 'setup' ' printf "blob\nmark :$i\ndata $blobsize\n" && #test-tool genrandom $i $blobsize && printf "%-${blobsize}s" $i && - echo "M 100644 :$i $i" >> commit + echo "M 100644 :$i $i" >> commit && i=$(($i+1)) || echo $? > exit-status done && diff --git a/t/t5612-clone-refspec.sh b/t/t5612-clone-refspec.sh index fac5a73851..e36ac01661 100755 --- a/t/t5612-clone-refspec.sh +++ b/t/t5612-clone-refspec.sh @@ -97,14 +97,13 @@ test_expect_success 'clone with --no-tags' ' git fetch && git for-each-ref refs/tags >../actual ) && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success '--single-branch while HEAD pointing at master' ' ( cd dir_master && - git fetch && + git fetch --force && git for-each-ref refs/remotes/origin | sed -e "/HEAD$/d" \ -e "s|/remotes/origin/|/heads/|" >../actual @@ -115,7 +114,7 @@ test_expect_success '--single-branch while HEAD pointing at master' ' test_cmp expect actual && ( cd dir_master && - git fetch --tags && + git fetch --tags --force && git for-each-ref refs/tags >../actual ) && git for-each-ref refs/tags >expect && @@ -140,8 +139,7 @@ test_expect_success '--single-branch while HEAD pointing at master and --no-tags git fetch && git for-each-ref refs/tags >../actual ) && - >expect && - test_cmp expect actual && + test_must_be_empty actual && test_line_count = 0 actual && # get tags with --tags overrides tagOpt ( @@ -230,8 +228,7 @@ test_expect_success '--single-branch with detached' ' -e "s|/remotes/origin/|/heads/|" >../actual ) && # nothing - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_done diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index cee5565367..bbbe7537df 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -154,4 +154,112 @@ test_expect_success 'partial clone with transfer.fsckobjects=1 uses index-pack - grep "git index-pack.*--fsck-objects" trace ' +test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' ' + rm -rf src dst && + git init src && + test_commit -C src x && + test_config -C src uploadpack.allowfilter 1 && + test_config -C src uploadpack.allowanysha1inwant 1 && + + # Create a tag pointing to a blob. + BLOB=$(echo blob-contents | git -C src hash-object --stdin -w) && + git -C src tag myblob "$BLOB" && + + git clone --filter="blob:none" "file://$(pwd)/src" dst 2>err && + ! grep "does not point to a valid object" err && + git -C dst fsck +' + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +# Converts bytes into a form suitable for inclusion in a sed command. For +# example, "printf 'ab\r\n' | hex_unpack" results in '\x61\x62\x0d\x0a'. +sed_escape () { + perl -e '$/ = undef; $input = <>; print unpack("H2" x length($input), $input)' | + sed 's/\(..\)/\\x\1/g' +} + +test_expect_success 'upon cloning, check that all refs point to objects' ' + SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" && + rm -rf "$SERVER" repo && + test_create_repo "$SERVER" && + test_commit -C "$SERVER" foo && + test_config -C "$SERVER" uploadpack.allowfilter 1 && + test_config -C "$SERVER" uploadpack.allowanysha1inwant 1 && + + # Create a tag pointing to a blob. + BLOB=$(echo blob-contents | git -C "$SERVER" hash-object --stdin -w) && + git -C "$SERVER" tag myblob "$BLOB" && + + # Craft a packfile not including that blob. + git -C "$SERVER" rev-parse HEAD | + git -C "$SERVER" pack-objects --stdout >incomplete.pack && + + # Replace the existing packfile with the crafted one. The protocol + # requires that the packfile be sent in sideband 1, hence the extra + # \x01 byte at the beginning. + printf "1,/packfile/!c %04x\\\\x01%s0000" \ + "$(($(wc -c <incomplete.pack) + 5))" \ + "$(sed_escape <incomplete.pack)" \ + >"$HTTPD_ROOT_PATH/one-time-sed" && + + # Use protocol v2 because the sed command looks for the "packfile" + # section header. + test_config -C "$SERVER" protocol.version 2 && + test_must_fail git -c protocol.version=2 clone \ + --filter=blob:none $HTTPD_URL/one_time_sed/server repo 2>err && + + grep "did not send all necessary objects" err && + + # Ensure that the one-time-sed script was used. + ! test -e "$HTTPD_ROOT_PATH/one-time-sed" +' + +test_expect_success 'when partial cloning, tolerate server not sending target of tag' ' + SERVER="$HTTPD_DOCUMENT_ROOT_PATH/server" && + rm -rf "$SERVER" repo && + test_create_repo "$SERVER" && + test_commit -C "$SERVER" foo && + test_config -C "$SERVER" uploadpack.allowfilter 1 && + test_config -C "$SERVER" uploadpack.allowanysha1inwant 1 && + + # Create an annotated tag pointing to a blob. + BLOB=$(echo blob-contents | git -C "$SERVER" hash-object --stdin -w) && + git -C "$SERVER" tag -m message -a myblob "$BLOB" && + + # Craft a packfile including the tag, but not the blob it points to. + # Also, omit objects referenced from HEAD in order to force a second + # fetch (to fetch missing objects) upon the automatic checkout that + # happens after a clone. + printf "%s\n%s\n--not\n%s\n%s\n" \ + $(git -C "$SERVER" rev-parse HEAD) \ + $(git -C "$SERVER" rev-parse myblob) \ + $(git -C "$SERVER" rev-parse HEAD^{tree}) \ + $(git -C "$SERVER" rev-parse myblob^{blob}) | + git -C "$SERVER" pack-objects --thin --stdout >incomplete.pack && + + # Replace the existing packfile with the crafted one. The protocol + # requires that the packfile be sent in sideband 1, hence the extra + # \x01 byte at the beginning. + printf "1,/packfile/!c %04x\\\\x01%s0000" \ + "$(($(wc -c <incomplete.pack) + 5))" \ + "$(sed_escape <incomplete.pack)" \ + >"$HTTPD_ROOT_PATH/one-time-sed" && + + # Use protocol v2 because the sed command looks for the "packfile" + # section header. + test_config -C "$SERVER" protocol.version 2 && + + # Exercise to make sure it works. + git -c protocol.version=2 clone \ + --filter=blob:none $HTTPD_URL/one_time_sed/server repo 2> err && + ! grep "missing object referenced by" err && + + # Ensure that the one-time-sed script was used. + ! test -e "$HTTPD_ROOT_PATH/one-time-sed" +' + +stop_httpd + test_done diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh new file mode 100755 index 0000000000..75ec79e6cb --- /dev/null +++ b/t/t5701-git-serve.sh @@ -0,0 +1,211 @@ +#!/bin/sh + +test_description='test git-serve and server commands' + +. ./test-lib.sh + +test_expect_success 'test capability advertisement' ' + cat >expect <<-EOF && + version 2 + agent=git/$(git version | cut -d" " -f3) + ls-refs + fetch=shallow + server-option + 0000 + EOF + + git serve --advertise-capabilities >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'stateless-rpc flag does not list capabilities' ' + # Empty request + test-pkt-line pack >in <<-EOF && + 0000 + EOF + git serve --stateless-rpc >out <in && + test_must_be_empty out && + + # EOF + git serve --stateless-rpc >out && + test_must_be_empty out +' + +test_expect_success 'request invalid capability' ' + test-pkt-line pack >in <<-EOF && + foobar + 0000 + EOF + test_must_fail git serve --stateless-rpc 2>err <in && + test_i18ngrep "unknown capability" err +' + +test_expect_success 'request with no command' ' + test-pkt-line pack >in <<-EOF && + agent=git/test + 0000 + EOF + test_must_fail git serve --stateless-rpc 2>err <in && + test_i18ngrep "no command requested" err +' + +test_expect_success 'request invalid command' ' + test-pkt-line pack >in <<-EOF && + command=foo + agent=git/test + 0000 + EOF + test_must_fail git serve --stateless-rpc 2>err <in && + test_i18ngrep "invalid command" err +' + +# Test the basics of ls-refs +# +test_expect_success 'setup some refs and tags' ' + test_commit one && + git branch dev master && + test_commit two && + git symbolic-ref refs/heads/release refs/heads/master && + git tag -a -m "annotated tag" annotated-tag +' + +test_expect_success 'basics of ls-refs' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse HEAD) HEAD + $(git rev-parse refs/heads/dev) refs/heads/dev + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse refs/heads/release) refs/heads/release + $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag + $(git rev-parse refs/tags/one) refs/tags/one + $(git rev-parse refs/tags/two) refs/tags/two + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'basic ref-prefixes' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0001 + ref-prefix refs/heads/master + ref-prefix refs/tags/one + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse refs/tags/one) refs/tags/one + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'refs/heads prefix' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0001 + ref-prefix refs/heads/ + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/dev) refs/heads/dev + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse refs/heads/release) refs/heads/release + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'peel parameter' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0001 + peel + ref-prefix refs/tags/ + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag peeled:$(git rev-parse refs/tags/annotated-tag^{}) + $(git rev-parse refs/tags/one) refs/tags/one + $(git rev-parse refs/tags/two) refs/tags/two + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'symrefs parameter' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + 0001 + symrefs + ref-prefix refs/heads/ + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse refs/heads/dev) refs/heads/dev + $(git rev-parse refs/heads/master) refs/heads/master + $(git rev-parse refs/heads/release) refs/heads/release symref-target:refs/heads/master + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'sending server-options' ' + test-pkt-line pack >in <<-EOF && + command=ls-refs + server-option=hello + server-option=world + 0001 + ref-prefix HEAD + 0000 + EOF + + cat >expect <<-EOF && + $(git rev-parse HEAD) HEAD + 0000 + EOF + + git serve --stateless-rpc <in >out && + test-pkt-line unpack <out >actual && + test_cmp actual expect +' + +test_expect_success 'unexpected lines are not allowed in fetch request' ' + git init server && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + this-is-not-a-command + 0000 + EOF + + test_must_fail git -C server serve --stateless-rpc <in >/dev/null 2>err && + grep "unexpected line: .this-is-not-a-command." err +' + +test_done diff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh new file mode 100755 index 0000000000..3beeed4546 --- /dev/null +++ b/t/t5702-protocol-v2.sh @@ -0,0 +1,501 @@ +#!/bin/sh + +test_description='test git wire-protocol version 2' + +TEST_NO_CREATE_REPO=1 + +. ./test-lib.sh + +# Test protocol v2 with 'git://' transport +# +. "$TEST_DIRECTORY"/lib-git-daemon.sh +start_git_daemon --export-all --enable=receive-pack +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent + +test_expect_success 'create repo to be served by git-daemon' ' + git init "$daemon_parent" && + test_commit -C "$daemon_parent" one +' + +test_expect_success 'list refs with git:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote --symref "$GIT_DAEMON_URL/parent" >actual && + + # Client requested to use protocol v2 + grep "git> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + grep "git< version 2" log && + + git ls-remote --symref "$GIT_DAEMON_URL/parent" >expect && + test_cmp actual expect +' + +test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote "$GIT_DAEMON_URL/parent" master >actual && + + cat >expect <<-EOF && + $(git -C "$daemon_parent" rev-parse refs/heads/master)$(printf "\t")refs/heads/master + EOF + + test_cmp actual expect +' + +test_expect_success 'clone with git:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + clone "$GIT_DAEMON_URL/parent" daemon_child && + + git -C daemon_child log -1 --format=%s >actual && + git -C "$daemon_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v2 + grep "clone> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + grep "clone< version 2" log +' + +test_expect_success 'fetch with git:// using protocol v2' ' + test_when_finished "rm -f log" && + + test_commit -C "$daemon_parent" two && + + GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \ + fetch && + + git -C daemon_child log -1 --format=%s origin/master >actual && + git -C "$daemon_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v2 + grep "fetch> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + grep "fetch< version 2" log +' + +test_expect_success 'pull with git:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \ + pull && + + git -C daemon_child log -1 --format=%s >actual && + git -C "$daemon_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v2 + grep "fetch> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + grep "fetch< version 2" log +' + +test_expect_success 'push with git:// and a config of v2 does not request v2' ' + test_when_finished "rm -f log" && + + # Till v2 for push is designed, make sure that if a client has + # protocol.version configured to use v2, that the client instead falls + # back and uses v0. + + test_commit -C daemon_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET="$(pwd)/log" git -C daemon_child -c protocol.version=2 \ + push origin HEAD:client_branch && + + git -C daemon_child log -1 --format=%s >actual && + git -C "$daemon_parent" log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Client requested to use protocol v2 + ! grep "push> .*\\\0\\\0version=2\\\0$" log && + # Server responded using protocol v2 + ! grep "push< version 2" log +' + +stop_git_daemon + +# Test protocol v2 with 'file://' transport +# +test_expect_success 'create repo to be served by file:// transport' ' + git init file_parent && + test_commit -C file_parent one +' + +test_expect_success 'list refs with file:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote --symref "file://$(pwd)/file_parent" >actual && + + # Server responded using protocol v2 + grep "git< version 2" log && + + git ls-remote --symref "file://$(pwd)/file_parent" >expect && + test_cmp actual expect +' + +test_expect_success 'ref advertisment is filtered with ls-remote using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote "file://$(pwd)/file_parent" master >actual && + + cat >expect <<-EOF && + $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master + EOF + + test_cmp actual expect +' + +test_expect_success 'server-options are sent when using ls-remote' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + ls-remote -o hello -o world "file://$(pwd)/file_parent" master >actual && + + cat >expect <<-EOF && + $(git -C file_parent rev-parse refs/heads/master)$(printf "\t")refs/heads/master + EOF + + test_cmp actual expect && + grep "server-option=hello" log && + grep "server-option=world" log +' + + +test_expect_success 'clone with file:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -c protocol.version=2 \ + clone "file://$(pwd)/file_parent" file_child && + + git -C file_child log -1 --format=%s >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v2 + grep "clone< version 2" log && + + # Client sent ref-prefixes to filter the ref-advertisement + grep "ref-prefix HEAD" log && + grep "ref-prefix refs/heads/" log && + grep "ref-prefix refs/tags/" log +' + +test_expect_success 'fetch with file:// using protocol v2' ' + test_when_finished "rm -f log" && + + test_commit -C file_parent two && + + GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ + fetch origin && + + git -C file_child log -1 --format=%s origin/master >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v2 + grep "fetch< version 2" log +' + +test_expect_success 'ref advertisment is filtered during fetch using protocol v2' ' + test_when_finished "rm -f log" && + + test_commit -C file_parent three && + git -C file_parent branch unwanted-branch three && + + GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ + fetch origin master && + + git -C file_child log -1 --format=%s origin/master >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + grep "refs/heads/master" log && + ! grep "refs/heads/unwanted-branch" log +' + +test_expect_success 'server-options are sent when fetching' ' + test_when_finished "rm -f log" && + + test_commit -C file_parent four && + + GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ + fetch -o hello -o world origin master && + + git -C file_child log -1 --format=%s origin/master >actual && + git -C file_parent log -1 --format=%s >expect && + test_cmp expect actual && + + grep "server-option=hello" log && + grep "server-option=world" log +' + +test_expect_success 'upload-pack respects config using protocol v2' ' + git init server && + write_script server/.git/hook <<-\EOF && + touch hookout + "$@" + EOF + test_commit -C server one && + + test_config_global uploadpack.packobjectshook ./hook && + test_path_is_missing server/.git/hookout && + git -c protocol.version=2 clone "file://$(pwd)/server" client && + test_path_is_file server/.git/hookout +' + +test_expect_success 'setup filter tests' ' + rm -rf server client && + git init server && + + # 1 commit to create a file, and 1 commit to modify it + test_commit -C server message1 a.txt && + test_commit -C server message2 a.txt && + git -C server config protocol.version 2 && + git -C server config uploadpack.allowfilter 1 && + git -C server config uploadpack.allowanysha1inwant 1 && + git -C server config protocol.version 2 +' + +test_expect_success 'partial clone' ' + GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \ + clone --filter=blob:none "file://$(pwd)/server" client && + grep "version 2" trace && + + # Ensure that the old version of the file is missing + git -C client rev-list master --quiet --objects --missing=print \ + >observed.oids && + grep "$(git -C server rev-parse message1:a.txt)" observed.oids && + + # Ensure that client passes fsck + git -C client fsck +' + +test_expect_success 'dynamically fetch missing object' ' + rm "$(pwd)/trace" && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + cat-file -p $(git -C server rev-parse message1:a.txt) && + grep "version 2" trace +' + +test_expect_success 'partial fetch' ' + rm -rf client "$(pwd)/trace" && + git init client && + SERVER="file://$(pwd)/server" && + test_config -C client extensions.partialClone "$SERVER" && + + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + fetch --filter=blob:none "$SERVER" master:refs/heads/other && + grep "version 2" trace && + + # Ensure that the old version of the file is missing + git -C client rev-list other --quiet --objects --missing=print \ + >observed.oids && + grep "$(git -C server rev-parse message1:a.txt)" observed.oids && + + # Ensure that client passes fsck + git -C client fsck +' + +test_expect_success 'do not advertise filter if not configured to do so' ' + SERVER="file://$(pwd)/server" && + + rm "$(pwd)/trace" && + git -C server config uploadpack.allowfilter 1 && + GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \ + ls-remote "$SERVER" && + grep "fetch=.*filter" trace && + + rm "$(pwd)/trace" && + git -C server config uploadpack.allowfilter 0 && + GIT_TRACE_PACKET="$(pwd)/trace" git -c protocol.version=2 \ + ls-remote "$SERVER" && + grep "fetch=" trace >fetch_capabilities && + ! grep filter fetch_capabilities +' + +test_expect_success 'partial clone warns if filter is not advertised' ' + rm -rf client && + git -C server config uploadpack.allowfilter 0 && + git -c protocol.version=2 \ + clone --filter=blob:none "file://$(pwd)/server" client 2>err && + test_i18ngrep "filtering not recognized by server, ignoring" err +' + +test_expect_success 'even with handcrafted request, filter does not work if not advertised' ' + git -C server config uploadpack.allowfilter 0 && + + # Custom request that tries to filter even though it is not advertised. + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + want $(git -C server rev-parse master) + filter blob:none + 0000 + EOF + + test_must_fail git -C server serve --stateless-rpc <in >/dev/null 2>err && + grep "unexpected line: .filter blob:none." err && + + # Exercise to ensure that if advertised, filter works + git -C server config uploadpack.allowfilter 1 && + git -C server serve --stateless-rpc <in >/dev/null +' + +test_expect_success 'default refspec is used to filter ref when fetchcing' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" git -C file_child -c protocol.version=2 \ + fetch origin && + + git -C file_child log -1 --format=%s three >actual && + git -C file_parent log -1 --format=%s three >expect && + test_cmp expect actual && + + grep "ref-prefix refs/heads/" log && + grep "ref-prefix refs/tags/" log +' + +test_expect_success 'fetch supports various ways of have lines' ' + rm -rf server client trace && + git init server && + test_commit -C server dwim && + TREE=$(git -C server rev-parse HEAD^{tree}) && + git -C server tag exact \ + $(git -C server commit-tree -m a "$TREE") && + git -C server tag dwim-unwanted \ + $(git -C server commit-tree -m b "$TREE") && + git -C server tag exact-unwanted \ + $(git -C server commit-tree -m c "$TREE") && + git -C server tag prefix1 \ + $(git -C server commit-tree -m d "$TREE") && + git -C server tag prefix2 \ + $(git -C server commit-tree -m e "$TREE") && + git -C server tag fetch-by-sha1 \ + $(git -C server commit-tree -m f "$TREE") && + git -C server tag completely-unrelated \ + $(git -C server commit-tree -m g "$TREE") && + + git init client && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + fetch "file://$(pwd)/server" \ + dwim \ + refs/tags/exact \ + refs/tags/prefix*:refs/tags/prefix* \ + "$(git -C server rev-parse fetch-by-sha1)" && + + # Ensure that the appropriate prefixes are sent (using a sample) + grep "fetch> ref-prefix dwim" trace && + grep "fetch> ref-prefix refs/heads/dwim" trace && + grep "fetch> ref-prefix refs/tags/prefix" trace && + + # Ensure that the correct objects are returned + git -C client cat-file -e $(git -C server rev-parse dwim) && + git -C client cat-file -e $(git -C server rev-parse exact) && + git -C client cat-file -e $(git -C server rev-parse prefix1) && + git -C client cat-file -e $(git -C server rev-parse prefix2) && + git -C client cat-file -e $(git -C server rev-parse fetch-by-sha1) && + test_must_fail git -C client cat-file -e \ + $(git -C server rev-parse dwim-unwanted) && + test_must_fail git -C client cat-file -e \ + $(git -C server rev-parse exact-unwanted) && + test_must_fail git -C client cat-file -e \ + $(git -C server rev-parse completely-unrelated) +' + +test_expect_success 'fetch supports include-tag and tag following' ' + rm -rf server client trace && + git init server && + + test_commit -C server to_fetch && + git -C server tag -a annotated_tag -m message && + + git init client && + GIT_TRACE_PACKET="$(pwd)/trace" git -C client -c protocol.version=2 \ + fetch "$(pwd)/server" to_fetch:to_fetch && + + grep "fetch> ref-prefix to_fetch" trace && + grep "fetch> ref-prefix refs/tags/" trace && + grep "fetch> include-tag" trace && + + git -C client cat-file -e $(git -C client rev-parse annotated_tag) +' + +# Test protocol v2 with 'http://' transport +# +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +test_expect_success 'create repo to be served by http:// transport' ' + git init "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" config http.receivepack true && + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" one +' + +test_expect_success 'clone with http:// using protocol v2' ' + test_when_finished "rm -f log" && + + GIT_TRACE_PACKET="$(pwd)/log" GIT_TRACE_CURL="$(pwd)/log" git -c protocol.version=2 \ + clone "$HTTPD_URL/smart/http_parent" http_child && + + git -C http_child log -1 --format=%s >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Client requested to use protocol v2 + grep "Git-Protocol: version=2" log && + # Server responded using protocol v2 + grep "git< version 2" log +' + +test_expect_success 'fetch with http:// using protocol v2' ' + test_when_finished "rm -f log" && + + test_commit -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" two && + + GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \ + fetch && + + git -C http_child log -1 --format=%s origin/master >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s >expect && + test_cmp expect actual && + + # Server responded using protocol v2 + grep "git< version 2" log +' + +test_expect_success 'push with http:// and a config of v2 does not request v2' ' + test_when_finished "rm -f log" && + # Till v2 for push is designed, make sure that if a client has + # protocol.version configured to use v2, that the client instead falls + # back and uses v0. + + test_commit -C http_child three && + + # Push to another branch, as the target repository has the + # master branch checked out and we cannot push into it. + GIT_TRACE_PACKET="$(pwd)/log" git -C http_child -c protocol.version=2 \ + push origin HEAD:client_branch && + + git -C http_child log -1 --format=%s >actual && + git -C "$HTTPD_DOCUMENT_ROOT_PATH/http_parent" log -1 --format=%s client_branch >expect && + test_cmp expect actual && + + # Client didnt request to use protocol v2 + ! grep "Git-Protocol: version=2" log && + # Server didnt respond using protocol v2 + ! grep "git< version 2" log +' + + +stop_httpd + +test_done diff --git a/t/t5703-upload-pack-ref-in-want.sh b/t/t5703-upload-pack-ref-in-want.sh new file mode 100755 index 0000000000..d1ccc22331 --- /dev/null +++ b/t/t5703-upload-pack-ref-in-want.sh @@ -0,0 +1,377 @@ +#!/bin/sh + +test_description='upload-pack ref-in-want' + +. ./test-lib.sh + +get_actual_refs () { + sed -n -e '/wanted-refs/,/0001/{ + /wanted-refs/d + /0001/d + p + }' <out | test-pkt-line unpack >actual_refs +} + +get_actual_commits () { + sed -n -e '/packfile/,/0000/{ + /packfile/d + p + }' <out | test-pkt-line unpack-sideband >o.pack && + git index-pack o.pack && + git verify-pack -v o.idx | grep commit | cut -c-40 | sort >actual_commits +} + +check_output () { + get_actual_refs && + test_cmp expected_refs actual_refs && + get_actual_commits && + test_cmp expected_commits actual_commits +} + +# c(o/foo) d(o/bar) +# \ / +# b e(baz) f(master) +# \__ | __/ +# \ | / +# a +test_expect_success 'setup repository' ' + test_commit a && + git checkout -b o/foo && + test_commit b && + test_commit c && + git checkout -b o/bar b && + test_commit d && + git checkout -b baz a && + test_commit e && + git checkout master && + test_commit f +' + +test_expect_success 'config controls ref-in-want advertisement' ' + git serve --advertise-capabilities >out && + ! grep -a ref-in-want out && + + git config uploadpack.allowRefInWant false && + git serve --advertise-capabilities >out && + ! grep -a ref-in-want out && + + git config uploadpack.allowRefInWant true && + git serve --advertise-capabilities >out && + grep -a ref-in-want out +' + +test_expect_success 'invalid want-ref line' ' + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/non-existent + done + 0000 + EOF + + test_must_fail git serve --stateless-rpc 2>out <in && + grep "unknown ref" out +' + +test_expect_success 'basic want-ref' ' + cat >expected_refs <<-EOF && + $(git rev-parse f) refs/heads/master + EOF + git rev-parse f | sort >expected_commits && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/master + have $(git rev-parse a) + done + 0000 + EOF + + git serve --stateless-rpc >out <in && + check_output +' + +test_expect_success 'multiple want-ref lines' ' + cat >expected_refs <<-EOF && + $(git rev-parse c) refs/heads/o/foo + $(git rev-parse d) refs/heads/o/bar + EOF + git rev-parse c d | sort >expected_commits && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/o/foo + want-ref refs/heads/o/bar + have $(git rev-parse b) + done + 0000 + EOF + + git serve --stateless-rpc >out <in && + check_output +' + +test_expect_success 'mix want and want-ref' ' + cat >expected_refs <<-EOF && + $(git rev-parse f) refs/heads/master + EOF + git rev-parse e f | sort >expected_commits && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/master + want $(git rev-parse e) + have $(git rev-parse a) + done + 0000 + EOF + + git serve --stateless-rpc >out <in && + check_output +' + +test_expect_success 'want-ref with ref we already have commit for' ' + cat >expected_refs <<-EOF && + $(git rev-parse c) refs/heads/o/foo + EOF + >expected_commits && + + test-pkt-line pack >in <<-EOF && + command=fetch + 0001 + no-progress + want-ref refs/heads/o/foo + have $(git rev-parse c) + done + 0000 + EOF + + git serve --stateless-rpc >out <in && + check_output +' + +. "$TEST_DIRECTORY"/lib-httpd.sh +start_httpd + +REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo" +LOCAL_PRISTINE="$(pwd)/local_pristine" + +test_expect_success 'setup repos for change-while-negotiating test' ' + ( + git init "$REPO" && + cd "$REPO" && + >.git/git-daemon-export-ok && + test_commit m1 && + git tag -d m1 && + + # Local repo with many commits (so that negotiation will take + # more than 1 request/response pair) + git clone "http://127.0.0.1:$LIB_HTTPD_PORT/smart/repo" "$LOCAL_PRISTINE" && + cd "$LOCAL_PRISTINE" && + git checkout -b side && + for i in $(test_seq 1 33); do test_commit s$i; done && + + # Add novel commits to upstream + git checkout master && + cd "$REPO" && + test_commit m2 && + test_commit m3 && + git tag -d m2 m3 + ) && + git -C "$LOCAL_PRISTINE" remote set-url origin "http://127.0.0.1:$LIB_HTTPD_PORT/one_time_sed/repo" && + git -C "$LOCAL_PRISTINE" config protocol.version 2 +' + +inconsistency () { + # Simulate that the server initially reports $2 as the ref + # corresponding to $1, and after that, $1 as the ref corresponding to + # $1. This corresponds to the real-life situation where the server's + # repository appears to change during negotiation, for example, when + # different servers in a load-balancing arrangement serve (stateless) + # RPCs during a single negotiation. + printf "s/%s/%s/" \ + $(git -C "$REPO" rev-parse $1 | tr -d "\n") \ + $(git -C "$REPO" rev-parse $2 | tr -d "\n") \ + >"$HTTPD_ROOT_PATH/one-time-sed" +} + +test_expect_success 'server is initially ahead - no ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant false && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + inconsistency master 1234567890123456789012345678901234567890 && + test_must_fail git -C local fetch 2>err && + grep "ERR upload-pack: not our ref" err +' + +test_expect_success 'server is initially ahead - ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant true && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + inconsistency master 1234567890123456789012345678901234567890 && + git -C local fetch && + + git -C "$REPO" rev-parse --verify master >expected && + git -C local rev-parse --verify refs/remotes/origin/master >actual && + test_cmp expected actual +' + +test_expect_success 'server is initially behind - no ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant false && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + inconsistency master "master^" && + git -C local fetch && + + git -C "$REPO" rev-parse --verify "master^" >expected && + git -C local rev-parse --verify refs/remotes/origin/master >actual && + test_cmp expected actual +' + +test_expect_success 'server is initially behind - ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant true && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + inconsistency master "master^" && + git -C local fetch && + + git -C "$REPO" rev-parse --verify "master" >expected && + git -C local rev-parse --verify refs/remotes/origin/master >actual && + test_cmp expected actual +' + +test_expect_success 'server loses a ref - ref in want' ' + git -C "$REPO" config uploadpack.allowRefInWant true && + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + echo "s/master/raster/" >"$HTTPD_ROOT_PATH/one-time-sed" && + test_must_fail git -C local fetch 2>err && + + grep "ERR unknown ref refs/heads/raster" err +' + +stop_httpd + +REPO="$(pwd)/repo" +LOCAL_PRISTINE="$(pwd)/local_pristine" + +# $REPO +# c(o/foo) d(o/bar) +# \ / +# b e(baz) f(master) +# \__ | __/ +# \ | / +# a +# +# $LOCAL_PRISTINE +# s32(side) +# | +# . +# . +# | +# a(master) +test_expect_success 'setup repos for fetching with ref-in-want tests' ' + ( + git init "$REPO" && + cd "$REPO" && + test_commit a && + + # Local repo with many commits (so that negotiation will take + # more than 1 request/response pair) + rm -rf "$LOCAL_PRISTINE" && + git clone "file://$REPO" "$LOCAL_PRISTINE" && + cd "$LOCAL_PRISTINE" && + git checkout -b side && + for i in $(test_seq 1 33); do test_commit s$i; done && + + # Add novel commits to upstream + git checkout master && + cd "$REPO" && + git checkout -b o/foo && + test_commit b && + test_commit c && + git checkout -b o/bar b && + test_commit d && + git checkout -b baz a && + test_commit e && + git checkout master && + test_commit f + ) && + git -C "$REPO" config uploadpack.allowRefInWant true && + git -C "$LOCAL_PRISTINE" config protocol.version 2 +' + +test_expect_success 'fetching with exact OID' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \ + $(git -C "$REPO" rev-parse d):refs/heads/actual && + + git -C "$REPO" rev-parse "d" >expected && + git -C local rev-parse refs/heads/actual >actual && + test_cmp expected actual && + grep "want $(git -C "$REPO" rev-parse d)" log +' + +test_expect_success 'fetching multiple refs' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin master baz && + + git -C "$REPO" rev-parse "master" "baz" >expected && + git -C local rev-parse refs/remotes/origin/master refs/remotes/origin/baz >actual && + test_cmp expected actual && + grep "want-ref refs/heads/master" log && + grep "want-ref refs/heads/baz" log +' + +test_expect_success 'fetching ref and exact OID' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \ + master $(git -C "$REPO" rev-parse b):refs/heads/actual && + + git -C "$REPO" rev-parse "master" "b" >expected && + git -C local rev-parse refs/remotes/origin/master refs/heads/actual >actual && + test_cmp expected actual && + grep "want $(git -C "$REPO" rev-parse b)" log && + grep "want-ref refs/heads/master" log +' + +test_expect_success 'fetching with wildcard that does not match any refs' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + git -C local fetch origin refs/heads/none*:refs/heads/* >out && + test_must_be_empty out +' + +test_expect_success 'fetching with wildcard that matches multiple refs' ' + test_when_finished "rm -f log" && + + rm -rf local && + cp -r "$LOCAL_PRISTINE" local && + GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin refs/heads/o*:refs/heads/o* && + + git -C "$REPO" rev-parse "o/foo" "o/bar" >expected && + git -C local rev-parse "o/foo" "o/bar" >actual && + test_cmp expected actual && + grep "want-ref refs/heads/o/foo" log && + grep "want-ref refs/heads/o/bar" log +' + +test_done diff --git a/t/t5801-remote-helpers.sh b/t/t5801-remote-helpers.sh index 362b1581e0..aaaa722cca 100755 --- a/t/t5801-remote-helpers.sh +++ b/t/t5801-remote-helpers.sh @@ -96,7 +96,7 @@ test_expect_success 'push new branch with old:new refspec' ' test_expect_success 'push new branch with HEAD:new refspec' ' (cd local && - git checkout new-name + git checkout new-name && git push origin HEAD:new-refspec-2 ) && compare_refs local HEAD server refs/heads/new-refspec-2 @@ -126,7 +126,7 @@ test_expect_success 'forced push' ' test_expect_success 'cloning without refspec' ' GIT_REMOTE_TESTGIT_REFSPEC="" \ git clone "testgit::${PWD}/server" local2 2>error && - grep "This remote helper should implement refspec capability" error && + test_i18ngrep "this remote helper should implement refspec capability" error && compare_refs local2 HEAD server HEAD ' @@ -134,7 +134,7 @@ test_expect_success 'pulling without refspecs' ' (cd local2 && git reset --hard && GIT_REMOTE_TESTGIT_REFSPEC="" git pull 2>../error) && - grep "This remote helper should implement refspec capability" error && + test_i18ngrep "this remote helper should implement refspec capability" error && compare_refs local2 HEAD server HEAD ' @@ -146,7 +146,7 @@ test_expect_success 'pushing without refspecs' ' GIT_REMOTE_TESTGIT_REFSPEC="" && export GIT_REMOTE_TESTGIT_REFSPEC && test_must_fail git push 2>../error) && - grep "remote-helper doesn.t support push; refspec needed" error + test_i18ngrep "remote-helper doesn.t support push; refspec needed" error ' test_expect_success 'pulling without marks' ' @@ -246,7 +246,7 @@ test_expect_success 'proper failure checks for fetching' ' (cd local && test_must_fail env GIT_REMOTE_TESTGIT_FAILURE=1 git fetch 2>error && cat error && - grep -q "Error while running fast-import" error + test_i18ngrep -q "error while running fast-import" error ) ' diff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh index 969e4e9e52..fb4d295aa0 100755 --- a/t/t6000-rev-list-misc.sh +++ b/t/t6000-rev-list-misc.sh @@ -58,8 +58,7 @@ test_expect_success 'rev-list A..B and rev-list ^A B are the same' ' test_expect_success 'propagate uninteresting flag down correctly' ' git rev-list --objects ^HEAD^{tree} HEAD^{tree} >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'symleft flag bit is propagated down from tag' ' diff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh index 05ddc69cf2..7504ba4751 100755 --- a/t/t6001-rev-list-graft.sh +++ b/t/t6001-rev-list-graft.sh @@ -110,4 +110,13 @@ do " done + +test_expect_success 'show advice that grafts are deprecated' ' + git show HEAD 2>err && + test_i18ngrep "git replace" err && + test_config advice.graftFileDeprecated false && + git show HEAD 2>err && + test_i18ngrep ! "git replace" err +' + test_done diff --git a/t/t6006-rev-list-format.sh b/t/t6006-rev-list-format.sh index 98be78b4a2..ec42c2f779 100755 --- a/t/t6006-rev-list-format.sh +++ b/t/t6006-rev-list-format.sh @@ -447,8 +447,8 @@ test_expect_success '--abbrev' ' git log -1 --format="%h %h %h" HEAD >actual1 && git log -1 --abbrev=5 --format="%h %h %h" HEAD >actual2 && git log -1 --abbrev=5 --format="%H %H %H" HEAD >actual3 && - sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 && - sed -e "s/$_x40/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 && + sed -e "s/$OID_REGEX/LONG/g" -e "s/$_x05/SHORT/g" <actual2 >fuzzy2 && + sed -e "s/$OID_REGEX/LONG/g" -e "s/$_x05/SHORT/g" <actual3 >fuzzy3 && test_cmp expect2 fuzzy2 && test_cmp expect3 fuzzy3 && ! test_cmp actual1 actual2 diff --git a/t/t6009-rev-list-parent.sh b/t/t6009-rev-list-parent.sh index 20e3e2554a..916d9692bc 100755 --- a/t/t6009-rev-list-parent.sh +++ b/t/t6009-rev-list-parent.sh @@ -31,8 +31,7 @@ test_expect_success setup ' test_expect_success 'one is ancestor of others and should not be shown' ' git rev-list one --not four >result && - >expect && - test_cmp expect result + test_must_be_empty result ' @@ -144,8 +143,7 @@ test_expect_success 'ancestors with the same commit time' ' test_commit t$i done && git rev-list t1^! --not t$i >result && - >expect && - test_cmp expect result + test_must_be_empty result ' test_done diff --git a/t/t6010-merge-base.sh b/t/t6010-merge-base.sh index 31db7b5f91..44c726ea39 100755 --- a/t/t6010-merge-base.sh +++ b/t/t6010-merge-base.sh @@ -34,7 +34,7 @@ doit () { commit=$(echo $NAME | git commit-tree $T $PARENTS) && - echo $commit >.git/refs/tags/$NAME && + git update-ref "refs/tags/$NAME" "$commit" && echo $commit } @@ -245,7 +245,7 @@ test_expect_success 'using reflog to find the fork point' ' git commit --allow-empty -m "Derived #$count" && git rev-parse HEAD >derived$count && git checkout -B base $E || exit 1 - done + done && for count in 1 2 3 do diff --git a/t/t6011-rev-list-with-bad-commit.sh b/t/t6011-rev-list-with-bad-commit.sh index e51eb41f4b..545b461e51 100755 --- a/t/t6011-rev-list-with-bad-commit.sh +++ b/t/t6011-rev-list-with-bad-commit.sh @@ -41,10 +41,9 @@ test_expect_success 'corrupt second commit object' \ test_must_fail git fsck --full ' -test_expect_success 'rev-list should fail' \ - ' - test_must_fail git rev-list --all > /dev/null - ' +test_expect_success 'rev-list should fail' ' + test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git rev-list --all > /dev/null +' test_expect_success 'git repack _MUST_ fail' \ ' diff --git a/t/t6012-rev-list-simplify.sh b/t/t6012-rev-list-simplify.sh index 2a0fbb87b1..b5a1190ffe 100755 --- a/t/t6012-rev-list-simplify.sh +++ b/t/t6012-rev-list-simplify.sh @@ -9,7 +9,7 @@ note () { } unnote () { - git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g" + git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\)) |\1 |g" } test_expect_success setup ' diff --git a/t/t6018-rev-list-glob.sh b/t/t6018-rev-list-glob.sh index d3453c583c..db8a7834d8 100755 --- a/t/t6018-rev-list-glob.sh +++ b/t/t6018-rev-list-glob.sh @@ -255,32 +255,28 @@ test_expect_success 'rev-list accumulates multiple --exclude' ' compare rev-list "--exclude=refs/remotes/* --exclude=refs/tags/* --all" --branches ' -test_expect_failure 'rev-list should succeed with empty output on empty stdin' ' - >expect && - git rev-list --stdin <expect >actual && - test_cmp expect actual +test_expect_success 'rev-list should succeed with empty output on empty stdin' ' + git rev-list --stdin </dev/null >actual && + test_must_be_empty actual ' test_expect_success 'rev-list should succeed with empty output with all refs excluded' ' - >expect && git rev-list --exclude=* --all >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'rev-list should succeed with empty output with empty --all' ' ( test_create_repo empty && cd empty && - >expect && git rev-list --all >actual && - test_cmp expect actual + test_must_be_empty actual ) ' test_expect_success 'rev-list should succeed with empty output with empty glob' ' - >expect && git rev-list --glob=does-not-match-anything >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'shortlog accepts --glob/--tags/--remotes' ' diff --git a/t/t6019-rev-list-ancestry-path.sh b/t/t6019-rev-list-ancestry-path.sh index dabebaee0b..beadaf6cca 100755 --- a/t/t6019-rev-list-ancestry-path.sh +++ b/t/t6019-rev-list-ancestry-path.sh @@ -95,10 +95,9 @@ test_expect_success 'rev-list --ancestry-path F...I' ' # G.t is dropped in an "-s ours" merge test_expect_success 'rev-list G..M -- G.t' ' - >expect && git rev-list --format=%s G..M -- G.t | sed -e "/^commit /d" >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'rev-list --ancestry-path G..M -- G.t' ' diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh index 2af1beec5f..46b506b3b7 100755 --- a/t/t6020-merge-df.sh +++ b/t/t6020-merge-df.sh @@ -89,9 +89,6 @@ test_expect_success 'modify/delete + directory/file conflict' ' ' test_expect_success 'modify/delete + directory/file conflict; other way' ' - # Yes, we really need the double reset since "letters" appears as - # both a file and a directory. - git reset --hard && git reset --hard && git clean -f && git checkout modify^0 && diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index a1fad6980b..53cc9b2ffb 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -247,7 +247,7 @@ test_expect_success 'merge of identical changes in a renamed file' ' git reset --hard HEAD^ && git checkout change && GIT_MERGE_VERBOSITY=3 git merge change+rename >out && - test_i18ngrep "^Skipped B" out + test_i18ngrep ! "^Skipped B" out ' test_expect_success 'setup for rename + d/f conflicts' ' @@ -635,10 +635,9 @@ test_expect_success 'setup avoid unnecessary update, normal rename' ' test_expect_success 'avoid unnecessary update, normal rename' ' git checkout -q avoid-unnecessary-update-1^0 && - test-tool chmtime =1000000000 rename && - test-tool chmtime -v +0 rename >expect && + test-tool chmtime --get =1000000000 rename >expect && git merge merge-branch-1 && - test-tool chmtime -v +0 rename >actual && + test-tool chmtime --get rename >actual && test_cmp expect actual # "rename" should have stayed intact ' @@ -668,10 +667,9 @@ test_expect_success 'setup to test avoiding unnecessary update, with D/F conflic test_expect_success 'avoid unnecessary update, with D/F conflict' ' git checkout -q avoid-unnecessary-update-2^0 && - test-tool chmtime =1000000000 df && - test-tool chmtime -v +0 df >expect && + test-tool chmtime --get =1000000000 df >expect && git merge merge-branch-2 && - test-tool chmtime -v +0 df >actual && + test-tool chmtime --get df >actual && test_cmp expect actual # "df" should have stayed intact ' @@ -700,10 +698,9 @@ test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' ' test_expect_success 'avoid unnecessary update, dir->(file,nothing)' ' git checkout -q master^0 && - test-tool chmtime =1000000000 df && - test-tool chmtime -v +0 df >expect && + test-tool chmtime --get =1000000000 df >expect && git merge side && - test-tool chmtime -v +0 df >actual && + test-tool chmtime --get df >actual && test_cmp expect actual # "df" should have stayed intact ' @@ -730,10 +727,9 @@ test_expect_success 'setup avoid unnecessary update, modify/delete' ' test_expect_success 'avoid unnecessary update, modify/delete' ' git checkout -q master^0 && - test-tool chmtime =1000000000 file && - test-tool chmtime -v +0 file >expect && + test-tool chmtime --get =1000000000 file >expect && test_must_fail git merge side && - test-tool chmtime -v +0 file >actual && + test-tool chmtime --get file >actual && test_cmp expect actual # "file" should have stayed intact ' @@ -759,10 +755,9 @@ test_expect_success 'setup avoid unnecessary update, rename/add-dest' ' test_expect_success 'avoid unnecessary update, rename/add-dest' ' git checkout -q master^0 && - test-tool chmtime =1000000000 newfile && - test-tool chmtime -v +0 newfile >expect && + test-tool chmtime --get =1000000000 newfile >expect && git merge side && - test-tool chmtime -v +0 newfile >actual && + test-tool chmtime --get newfile >actual && test_cmp expect actual # "file" should have stayed intact ' @@ -898,8 +893,7 @@ test_expect_success 'do not follow renames for empty files' ' git mv empty1 empty2 && git commit -m rename && test_must_fail git merge empty-base && - >expect && - test_cmp expect empty2 + test_must_be_empty empty2 ' test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index 3f59e58dfb..27c7de90ce 100755 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -60,9 +60,9 @@ git update-index a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F ' -test_expect_success "combined merge conflicts" " - test_must_fail git merge -m final G -" +test_expect_success 'combined merge conflicts' ' + test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G +' cat > expect << EOF <<<<<<< HEAD diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh index 3e692454a7..793f0c8bf3 100755 --- a/t/t6029-merge-subtree.sh +++ b/t/t6029-merge-subtree.sh @@ -29,6 +29,34 @@ test_expect_success 'subtree available and works like recursive' ' ' +test_expect_success 'setup branch sub' ' + git checkout --orphan sub && + git rm -rf . && + test_commit foo +' + +test_expect_success 'setup branch main' ' + git checkout -b main master && + git merge -s ours --no-commit --allow-unrelated-histories sub && + git read-tree --prefix=dir/ -u sub && + git commit -m "initial merge of sub into main" && + test_path_is_file dir/foo.t && + test_path_is_file hello +' + +test_expect_success 'update branch sub' ' + git checkout sub && + test_commit bar +' + +test_expect_success 'update branch main' ' + git checkout main && + git merge -s subtree sub -m "second merge of sub into main" && + test_path_is_file dir/bar.t && + test_path_is_file dir/foo.t && + test_path_is_file hello +' + test_expect_success 'setup' ' mkdir git-gui && cd git-gui && @@ -55,7 +83,7 @@ test_expect_success 'initial merge' ' git checkout -b work && git ls-files -s >actual && ( - echo "100644 $o1 0 git-gui/git-gui.sh" + echo "100644 $o1 0 git-gui/git-gui.sh" && echo "100644 $o2 0 git.c" ) >expected && test_cmp expected actual @@ -72,7 +100,7 @@ test_expect_success 'merge update' ' git pull -s subtree gui master2 && git ls-files -s >actual && ( - echo "100644 $o3 0 git-gui/git-gui.sh" + echo "100644 $o3 0 git-gui/git-gui.sh" && echo "100644 $o2 0 git.c" ) >expected && test_cmp expected actual @@ -88,8 +116,8 @@ test_expect_success 'initial ambiguous subtree' ' git checkout -b work2 && git ls-files -s >actual && ( - echo "100644 $o1 0 git-gui/git-gui.sh" - echo "100644 $o1 0 git-gui2/git-gui.sh" + echo "100644 $o1 0 git-gui/git-gui.sh" && + echo "100644 $o1 0 git-gui2/git-gui.sh" && echo "100644 $o2 0 git.c" ) >expected && test_cmp expected actual @@ -101,8 +129,8 @@ test_expect_success 'merge using explicit' ' git pull -Xsubtree=git-gui gui master2 && git ls-files -s >actual && ( - echo "100644 $o3 0 git-gui/git-gui.sh" - echo "100644 $o1 0 git-gui2/git-gui.sh" + echo "100644 $o3 0 git-gui/git-gui.sh" && + echo "100644 $o1 0 git-gui2/git-gui.sh" && echo "100644 $o2 0 git.c" ) >expected && test_cmp expected actual @@ -114,8 +142,8 @@ test_expect_success 'merge2 using explicit' ' git pull -Xsubtree=git-gui2 gui master2 && git ls-files -s >actual && ( - echo "100644 $o1 0 git-gui/git-gui.sh" - echo "100644 $o3 0 git-gui2/git-gui.sh" + echo "100644 $o1 0 git-gui/git-gui.sh" && + echo "100644 $o3 0 git-gui2/git-gui.sh" && echo "100644 $o2 0 git.c" ) >expected && test_cmp expected actual diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 18aa88b5c0..59e52c5a09 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -4,12 +4,6 @@ test_description='recursive merge corner cases involving criss-cross merges' . ./test-lib.sh -get_clean_checkout () { - git reset --hard && - git clean -fdqx && - git checkout "$1" -} - # # L1 L2 # o---o @@ -21,51 +15,66 @@ get_clean_checkout () { # test_expect_success 'setup basic criss-cross + rename with no modifications' ' - ten="0 1 2 3 4 5 6 7 8 9" && - for i in $ten - do - echo line $i in a sample file - done >one && - for i in $ten - do - echo line $i in another sample file - done >two && - git add one two && - test_tick && git commit -m initial && - - git branch L1 && - git checkout -b R1 && - git mv one three && - test_tick && git commit -m R1 && - - git checkout L1 && - git mv two three && - test_tick && git commit -m L1 && - - git checkout L1^0 && - test_tick && git merge -s ours R1 && - git tag L2 && - - git checkout R1^0 && - test_tick && git merge -s ours L1 && - git tag R2 + test_create_repo basic-rename && + ( + cd basic-rename && + + ten="0 1 2 3 4 5 6 7 8 9" && + for i in $ten + do + echo line $i in a sample file + done >one && + for i in $ten + do + echo line $i in another sample file + done >two && + git add one two && + test_tick && git commit -m initial && + + git branch L1 && + git checkout -b R1 && + git mv one three && + test_tick && git commit -m R1 && + + git checkout L1 && + git mv two three && + test_tick && git commit -m L1 && + + git checkout L1^0 && + test_tick && git merge -s ours R1 && + git tag L2 && + + git checkout R1^0 && + test_tick && git merge -s ours L1 && + git tag R2 + ) ' test_expect_success 'merge simple rename+criss-cross with no modifications' ' - git reset --hard && - git checkout L2^0 && - - test_must_fail git merge -s recursive R2^0 && - - test 2 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 2 = $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:three) = $(git rev-parse L2:three) && - test $(git rev-parse :3:three) = $(git rev-parse R2:three) && - - test $(git rev-parse L2:three) = $(git hash-object three~HEAD) && - test $(git rev-parse R2:three) = $(git hash-object three~R2^0) + ( + cd basic-rename && + + git reset --hard && + git checkout L2^0 && + + test_must_fail git merge -s recursive R2^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >expect \ + L2:three R2:three \ + L2:three R2:three && + git rev-parse >actual \ + :2:three :3:three && + git hash-object >>actual \ + three~HEAD three~R2^0 && + test_cmp expect actual + ) ' # @@ -81,58 +90,67 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' ' # test_expect_success 'setup criss-cross + rename merges with basic modification' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - ten="0 1 2 3 4 5 6 7 8 9" && - for i in $ten - do - echo line $i in a sample file - done >one && - for i in $ten - do - echo line $i in another sample file - done >two && - git add one two && - test_tick && git commit -m initial && - - git branch L1 && - git checkout -b R1 && - git mv one three && - echo more >>two && - git add two && - test_tick && git commit -m R1 && - - git checkout L1 && - git mv two three && - test_tick && git commit -m L1 && - - git checkout L1^0 && - test_tick && git merge -s ours R1 && - git tag L2 && - - git checkout R1^0 && - test_tick && git merge -s ours L1 && - git tag R2 + test_create_repo rename-modify && + ( + cd rename-modify && + + ten="0 1 2 3 4 5 6 7 8 9" && + for i in $ten + do + echo line $i in a sample file + done >one && + for i in $ten + do + echo line $i in another sample file + done >two && + git add one two && + test_tick && git commit -m initial && + + git branch L1 && + git checkout -b R1 && + git mv one three && + echo more >>two && + git add two && + test_tick && git commit -m R1 && + + git checkout L1 && + git mv two three && + test_tick && git commit -m L1 && + + git checkout L1^0 && + test_tick && git merge -s ours R1 && + git tag L2 && + + git checkout R1^0 && + test_tick && git merge -s ours L1 && + git tag R2 + ) ' test_expect_success 'merge criss-cross + rename merges with basic modification' ' - git reset --hard && - git checkout L2^0 && - - test_must_fail git merge -s recursive R2^0 && - - test 2 = $(git ls-files -s | wc -l) && - test 2 = $(git ls-files -u | wc -l) && - test 2 = $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:three) = $(git rev-parse L2:three) && - test $(git rev-parse :3:three) = $(git rev-parse R2:three) && - - test $(git rev-parse L2:three) = $(git hash-object three~HEAD) && - test $(git rev-parse R2:three) = $(git hash-object three~R2^0) + ( + cd rename-modify && + + git checkout L2^0 && + + test_must_fail git merge -s recursive R2^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >expect \ + L2:three R2:three \ + L2:three R2:three && + git rev-parse >actual \ + :2:three :3:three && + git hash-object >>actual \ + three~HEAD three~R2^0 && + test_cmp expect actual + ) ' # @@ -156,64 +174,74 @@ test_expect_success 'merge criss-cross + rename merges with basic modification' # test_expect_success 'setup differently handled merges of rename/add conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a && - git add a && - test_tick && git commit -m A && - - git branch B && - git checkout -b C && - echo 10 >>a && - echo "other content" >>new_a && - git add a new_a && - test_tick && git commit -m C && - - git checkout B && - git mv a new_a && - test_tick && git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - git clean -f && - test_tick && git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - rm new_a~HEAD new_a && - printf "Incorrectly merged content" >>new_a && - git add -u && - test_tick && git commit -m E && - git tag E + test_create_repo rename-add && + ( + cd rename-add && + + printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a && + git add a && + test_tick && git commit -m A && + + git branch B && + git checkout -b C && + echo 10 >>a && + echo "other content" >>new_a && + git add a new_a && + test_tick && git commit -m C && + + git checkout B && + git mv a new_a && + test_tick && git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + git clean -f && + test_tick && git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + rm new_a~HEAD new_a && + printf "Incorrectly merged content" >>new_a && + git add -u && + test_tick && git commit -m E && + git tag E + ) ' test_expect_success 'git detects differently handled merges conflict' ' - git reset --hard && - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - test 3 = $(git ls-files -s | wc -l) && - test 3 = $(git ls-files -u | wc -l) && - test 0 = $(git ls-files -o | wc -l) && - - test $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) && - test $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) && - - git cat-file -p B:new_a >>merged && - git cat-file -p C:new_a >>merge-me && - >empty && - test_must_fail git merge-file \ - -L "Temporary merge branch 2" \ - -L "" \ - -L "Temporary merge branch 1" \ - merged empty merge-me && - sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && - test $(git rev-parse :1:new_a) = $(git hash-object merged-internal) + ( + cd rename-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + D:new_a E:new_a && + git rev-parse >actual \ + :2:new_a :3:new_a && + test_cmp expect actual && + + git cat-file -p B:new_a >ours && + git cat-file -p C:new_a >theirs && + >empty && + test_must_fail git merge-file \ + -L "Temporary merge branch 2" \ + -L "" \ + -L "Temporary merge branch 1" \ + ours empty theirs && + sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && + git cat-file -p :1:new_a >actual && + test_cmp expect actual + ) ' # @@ -236,220 +264,428 @@ test_expect_success 'git detects differently handled merges conflict' ' # Merging commits D & E should result in modify/delete conflict. test_expect_success 'setup criss-cross + modify/delete resolved differently' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - echo A >file && - git add file && - test_tick && - git commit -m A && - - git branch B && - git checkout -b C && - git rm file && - test_tick && - git commit -m C && - - git checkout B && - echo B >file && - git add file && - test_tick && - git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - echo B >file && - git add file && - test_tick && - git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - git rm file && - test_tick && - git commit -m E && - git tag E + test_create_repo modify-delete && + ( + cd modify-delete && + + echo A >file && + git add file && + test_tick && + git commit -m A && + + git branch B && + git checkout -b C && + git rm file && + test_tick && + git commit -m C && + + git checkout B && + echo B >file && + git add file && + test_tick && + git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + echo B >file && + git add file && + test_tick && + git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + git rm file && + test_tick && + git commit -m E && + git tag E + ) ' test_expect_success 'git detects conflict merging criss-cross+modify/delete' ' - git checkout D^0 && + ( + cd modify-delete && - test_must_fail git merge -s recursive E^0 && + git checkout D^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && + test_must_fail git merge -s recursive E^0 && - test $(git rev-parse :1:file) = $(git rev-parse master:file) && - test $(git rev-parse :2:file) = $(git rev-parse B:file) + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + + git rev-parse >expect \ + master:file B:file && + git rev-parse >actual \ + :1:file :2:file && + test_cmp expect actual + ) ' test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' ' - git reset --hard && - git checkout E^0 && + ( + cd modify-delete && + + git reset --hard && + git checkout E^0 && - test_must_fail git merge -s recursive D^0 && + test_must_fail git merge -s recursive D^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && - test $(git rev-parse :1:file) = $(git rev-parse master:file) && - test $(git rev-parse :3:file) = $(git rev-parse B:file) + git rev-parse >expect \ + master:file B:file && + git rev-parse >actual \ + :1:file :3:file && + test_cmp expect actual + ) ' +# SORRY FOR THE SUPER LONG DESCRIPTION, BUT THIS NEXT ONE IS HAIRY # # criss-cross + d/f conflict via add/add: # Commit A: Neither file 'a' nor directory 'a/' exists. # Commit B: Introduce 'a' # Commit C: Introduce 'a/file' -# Commit D: Merge B & C, keeping 'a' and deleting 'a/' -# -# Two different later cases: +# Commit D1: Merge B & C, keeping 'a' and deleting 'a/' # Commit E1: Merge B & C, deleting 'a' but keeping 'a/file' -# Commit E2: Merge B & C, deleting 'a' but keeping a slightly modified 'a/file' # -# B D +# B D1 or D2 # o---o # / \ / \ # A o X ? F # \ / \ / # o---o -# C E1 or E2 +# C E1 or E2 or E3 +# +# I'll describe D2, E2, & E3 (which are alternatives for D1 & E1) more below... +# +# Merging D1 & E1 requires we first create a virtual merge base X from +# merging A & B in memory. There are several possibilities for the merge-base: +# 1: Keep both 'a' and 'a/file' (assuming crazy filesystem allowing a tree +# with a directory and file at same path): results in merge of D1 & E1 +# being clean with both files deleted. Bad (no conflict detected). +# 2: Keep 'a' but not 'a/file': Merging D1 & E1 is clean and matches E1. Bad. +# 3: Keep 'a/file' but not 'a': Merging D1 & E1 is clean and matches D1. Bad. +# 4: Keep neither file: Merging D1 & E1 reports the D/F add/add conflict. # -# Merging D & E1 requires we first create a virtual merge base X from -# merging A & B in memory. Now, if X could keep both 'a' and 'a/file' in -# the index, then the merge of D & E1 could be resolved cleanly with both -# 'a' and 'a/file' removed. Since git does not currently allow creating -# such a tree, the best we can do is have X contain both 'a~<unique>' and -# 'a/file' resulting in the merge of D and E1 having a rename/delete -# conflict for 'a'. (Although this merge appears to be unsolvable with git -# currently, git could do a lot better than it currently does with these -# d/f conflicts, which is the purpose of this test.) +# So 4 sounds good for this case, but if we were to merge D1 & E3, where E3 +# is defined as: +# Commit E3: Merge B & C, keeping modified a, and deleting a/ +# then we'd get an add/add conflict for 'a', which seems suboptimal. A little +# creativity leads us to an alternate choice: +# 5: Keep 'a' as 'a~$UNIQUE' and a/file; results: +# Merge D1 & E1: rename/delete conflict for 'a'; a/file silently deleted +# Merge D1 & E3 is clean, as expected. # -# Merge of D & E2 has similar issues for path 'a', but should always result -# in a modify/delete conflict for path 'a/file'. +# So choice 5 at least provides some kind of conflict for the original case, +# and can merge cleanly as expected with D1 and E3. It also made things just +# slightly funny for merging D1 and e$, where E4 is defined as: +# Commit E4: Merge B & C, modifying 'a' and renaming to 'a2', and deleting 'a/' +# in this case, we'll get a rename/rename(1to2) conflict because a~$UNIQUE +# gets renamed to 'a' in D1 and to 'a2' in E4. But that's better than having +# two files (both 'a' and 'a2') sitting around without the user being notified +# that we could detect they were related and need to be merged. Also, choice +# 5 makes the handling of 'a/file' seem suboptimal. What if we were to merge +# D2 and E4, where D2 is: +# Commit D2: Merge B & C, renaming 'a'->'a2', keeping 'a/file' +# This would result in a clean merge with 'a2' having three-way merged +# contents (good), and deleting 'a/' (bad) -- it doesn't detect the +# conflict in how the different sides treated a/file differently. +# Continuing down the creative route: +# 6: Keep 'a' as 'a~$UNIQUE1' and keep 'a/' as 'a~$UNIQUE2/'; results: +# Merge D1 & E1: rename/delete conflict for 'a' and each path under 'a/'. +# Merge D1 & E3: clean, as expected. +# Merge D1 & E4: rename/rename(1to2) conflict on 'a' vs 'a2'. +# Merge D2 & E4: clean for 'a2', rename/delete for a/file # -# We run each merge in both directions, to check for directional issues -# with D/F conflict handling. +# Choice 6 could cause rename detection to take longer (providing more targets +# that need to be searched). Also, the conflict message for each path under +# 'a/' might be annoying unless we can detect it at the directory level, print +# it once, and then suppress it for individual filepaths underneath. +# +# +# As of time of writing, git uses choice 5. Directory rename detection and +# rename detection performance improvements might make choice 6 a desirable +# improvement. But we can at least document where we fall short for now... +# +# +# Historically, this testcase also used: +# Commit E2: Merge B & C, deleting 'a' but keeping slightly modified 'a/file' +# The merge of D1 & E2 is very similar to D1 & E1 -- it has similar issues for +# path 'a', but should always result in a modify/delete conflict for path +# 'a/file'. These tests ran the two merges +# D1 & E1 +# D1 & E2 +# in both directions, to check for directional issues with D/F conflict +# handling. Later we added +# D1 & E3 +# D1 & E4 +# D2 & E4 +# for good measure, though we only ran those one way because we had pretty +# good confidence in merge-recursive's directional handling of D/F issues. +# +# Just to summarize all the intermediate merge commits: +# Commit D1: Merge B & C, keeping a and deleting a/ +# Commit D2: Merge B & C, renaming a->a2, keeping a/file +# Commit E1: Merge B & C, deleting a but keeping a/file +# Commit E2: Merge B & C, deleting a but keeping slightly modified a/file +# Commit E3: Merge B & C, keeping modified a, and deleting a/ +# Commit E4: Merge B & C, modifying 'a' and renaming to 'a2', and deleting 'a/' # test_expect_success 'setup differently handled merges of directory/file conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >ignore-me && - git add ignore-me && - test_tick && - git commit -m A && - git tag A && - - git branch B && - git checkout -b C && - mkdir a && - echo 10 >a/file && - git add a/file && - test_tick && - git commit -m C && - - git checkout B && - echo 5 >a && - git add a && - test_tick && - git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - git clean -f && - rm -rf a/ && - echo 5 >a && - git add a && - test_tick && - git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - git clean -f && - git rm --cached a && - echo 10 >a/file && - git add a/file && - test_tick && - git commit -m E1 && - git tag E1 && - - git checkout C^0 && - test_must_fail git merge B && - git clean -f && - git rm --cached a && - printf "10\n11\n" >a/file && - git add a/file && - test_tick && - git commit -m E2 && - git tag E2 + test_create_repo directory-file && + ( + cd directory-file && + + >ignore-me && + git add ignore-me && + test_tick && + git commit -m A && + git tag A && + + git branch B && + git checkout -b C && + mkdir a && + test_write_lines a b c d e f g >a/file && + git add a/file && + test_tick && + git commit -m C && + + git checkout B && + test_write_lines 1 2 3 4 5 6 7 >a && + git add a && + test_tick && + git commit -m B && + + git checkout B^0 && + git merge -s ours -m D1 C^0 && + git tag D1 && + + git checkout B^0 && + test_must_fail git merge C^0 && + git clean -fd && + git rm -rf a/ && + git rm a && + git cat-file -p B:a >a2 && + git add a2 && + git commit -m D2 && + git tag D2 && + + git checkout C^0 && + git merge -s ours -m E1 B^0 && + git tag E1 && + + git checkout C^0 && + git merge -s ours -m E2 B^0 && + test_write_lines a b c d e f g h >a/file && + git add a/file && + git commit --amend -C HEAD && + git tag E2 && + + git checkout C^0 && + test_must_fail git merge B^0 && + git clean -fd && + git rm -rf a/ && + test_write_lines 1 2 3 4 5 6 7 8 >a && + git add a && + git commit -m E3 && + git tag E3 && + + git checkout C^0 && + test_must_fail git merge B^0 && + git clean -fd && + git rm -rf a/ && + git rm a && + test_write_lines 1 2 3 4 5 6 7 8 >a2 && + git add a2 && + git commit -m E4 && + git tag E4 + ) ' -test_expect_success 'merge of D & E1 fails but has appropriate contents' ' - get_clean_checkout D^0 && +test_expect_success 'merge of D1 & E1 fails but has appropriate contents' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D1^0 && + + test_must_fail git merge -s recursive E1^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a && + git rev-parse >actual \ + :0:ignore-me :2:a && + test_cmp expect actual + ) +' - test_must_fail git merge -s recursive E1^0 && +test_expect_success 'merge of E1 & D1 fails but has appropriate contents' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout E1^0 && + + test_must_fail git merge -s recursive D1^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a && + git rev-parse >actual \ + :0:ignore-me :3:a && + test_cmp expect actual + ) +' - test 2 -eq $(git ls-files -s | wc -l) && - test 1 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && +test_expect_success 'merge of D1 & E2 fails but has appropriate contents' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - test $(git rev-parse :2:a) = $(git rev-parse B:a) -' + git checkout D1^0 && -test_expect_success 'merge of E1 & D fails but has appropriate contents' ' - get_clean_checkout E1^0 && + test_must_fail git merge -s recursive E2^0 && - test_must_fail git merge -s recursive D^0 && + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && - test 2 -eq $(git ls-files -s | wc -l) && - test 1 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git rev-parse >expect \ + B:a E2:a/file C:a/file A:ignore-me && + git rev-parse >actual \ + :2:a :3:a/file :1:a/file :0:ignore-me && + test_cmp expect actual && - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && - test $(git rev-parse :3:a) = $(git rev-parse B:a) + test_path_is_file a~HEAD + ) ' -test_expect_success 'merge of D & E2 fails but has appropriate contents' ' - get_clean_checkout D^0 && +test_expect_success 'merge of E2 & D1 fails but has appropriate contents' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && - test_must_fail git merge -s recursive E2^0 && + git checkout E2^0 && - test 4 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && + test_must_fail git merge -s recursive D1^0 && - test $(git rev-parse :2:a) = $(git rev-parse B:a) && - test $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) && - test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) && - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && - test -f a~HEAD -' - -test_expect_success 'merge of E2 & D fails but has appropriate contents' ' - get_clean_checkout E2^0 && + git rev-parse >expect \ + B:a E2:a/file C:a/file A:ignore-me && + git rev-parse >actual \ + :3:a :2:a/file :1:a/file :0:ignore-me && + test_cmp expect actual && - test_must_fail git merge -s recursive D^0 && + test_path_is_file a~D1^0 + ) +' - test 4 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && +test_expect_success 'merge of D1 & E3 succeeds' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D1^0 && + + git merge -s recursive E3^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me E3:a && + git rev-parse >actual \ + :0:ignore-me :0:a && + test_cmp expect actual + ) +' - test $(git rev-parse :3:a) = $(git rev-parse B:a) && - test $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) && - test $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) && - test $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) && +test_expect_success 'merge of D1 & E4 notifies user a and a2 are related' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D1^0 && + + test_must_fail git merge -s recursive E4^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a D1:a E4:a2 && + git rev-parse >actual \ + :0:ignore-me :1:a~Temporary\ merge\ branch\ 2 :2:a :3:a2 && + test_cmp expect actual + ) +' - test -f a~D^0 +test_expect_failure 'merge of D2 & E4 merges a2s & reports conflict for a/file' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D2^0 && + + test_must_fail git merge -s recursive E4^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me E4:a2 D2:a/file && + git rev-parse >actual \ + :0:ignore-me :0:a2 :2:a/file && + test_cmp expect actual + ) ' # @@ -492,52 +728,58 @@ test_expect_success 'merge of E2 & D fails but has appropriate contents' ' # but that may cancel out at the final merge stage". test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' - git reset --hard && - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - echo 7 >>b && - git add -u && - git commit -m B && - - git checkout -b C A && - git mv a c && - git commit -m C && - - git checkout -q B^0 && - git merge --no-commit -s ours C^0 && - git mv b newname && - git commit -m "Merge commit C^0 into HEAD" && - git tag D && - - git checkout -q C^0 && - git merge --no-commit -s ours B^0 && - git mv c newname && - printf "7\n8\n" >>newname && - git add -u && - git commit -m "Merge commit B^0 into HEAD" && - git tag E + test_create_repo rename-squared-squared && + ( + cd rename-squared-squared && + + printf "1\n2\n3\n4\n5\n6\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + echo 7 >>b && + git add -u && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout -q B^0 && + git merge --no-commit -s ours C^0 && + git mv b newname && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout -q C^0 && + git merge --no-commit -s ours B^0 && + git mv c newname && + printf "7\n8\n" >>newname && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E + ) ' test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' - git checkout D^0 && + ( + cd rename-squared-squared && + + git checkout D^0 && - git merge -s recursive E^0 && + git merge -s recursive E^0 && - test 1 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && - test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname) + test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname) + ) ' # @@ -562,59 +804,72 @@ test_expect_success 'handle rename/rename(1to2)/modify followed by what looks li # renaming carefully (both in the virtual merge base and later), and getting # content merge handled. -test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "lots\nof\nwords\nand\ncontent\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a c && - printf "2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m C && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git checkout C -- a c && - mv a old_a && - echo 1 >a && - cat old_a >>a && - rm old_a && - git add -u && - git commit -m "Merge commit C^0 into HEAD" && - git tag D && - - git checkout C^0 && - git merge --no-commit -s ours B^0 && - git checkout B -- b && - echo 8 >>a && - git add -u && - git commit -m "Merge commit B^0 into HEAD" && - git tag E +test_expect_success 'setup criss-cross + rename/rename/add-source + modify/modify' ' + test_create_repo rename-rename-add-source && + ( + cd rename-rename-add-source && + + printf "lots\nof\nwords\nand\ncontent\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + printf "2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git checkout C -- a c && + mv a old_a && + echo 1 >a && + cat old_a >>a && + rm old_a && + git add -u && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout C^0 && + git merge --no-commit -s ours B^0 && + git checkout B -- b && + echo 8 >>a && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E + ) ' test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' - git checkout D^0 && - - git merge -s recursive E^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:c) = $(git rev-parse A:a) && - test "$(cat a)" = "$(printf "1\n2\n3\n4\n5\n6\n7\n8\n")" + ( + cd rename-rename-add-source && + + git checkout D^0 && + + git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >correct && + git rev-parse >expect \ + A:a A:a \ + correct && + git rev-parse >actual \ + :0:b :0:c && + git hash-object >>actual \ + a && + test_cmp expect actual + ) ' # @@ -638,52 +893,513 @@ test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' # base of B & C needs to not delete B:c for that to work, though... test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - printf "1\n2\n3\n4\n5\n6\n7\n" >c && - git add c && - git commit -m B && - - git checkout -b C A && - git mv a c && - git commit -m C && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git mv b a && - git commit -m "D is like B but renames b back to a" && - git tag D && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git mv b a && - echo 8 >>c && - git add c && - git commit -m "E like D but has mod in c" && - git tag E + test_create_repo rename-rename-add-dest && + ( + cd rename-rename-add-dest && + + >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + printf "1\n2\n3\n4\n5\n6\n7\n" >c && + git add c && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + git commit -m "D is like B but renames b back to a" && + git tag D && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + echo 8 >>c && + git add c && + git commit -m "E like D but has mod in c" && + git tag E + ) ' test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' ' - git checkout D^0 && + ( + cd rename-rename-add-dest && + + git checkout D^0 && + + git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:a E:c && + git rev-parse >actual \ + :0:a :0:c && + test_cmp expect actual + ) +' + +# +# criss-cross with modify/modify on a symlink: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: simple simlink fickle->lagoon +# Commit B: redirect fickle->disneyland +# Commit C: redirect fickle->home +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious modify/modify conflict for the symlink 'fickle'. Can +# git detect it? + +test_expect_success 'setup symlink modify/modify' ' + test_create_repo symlink-modify-modify && + ( + cd symlink-modify-modify && + + test_ln_s_add lagoon fickle && + git commit -m A && + git tag A && + + git checkout -b B A && + git rm fickle && + test_ln_s_add disneyland fickle && + git commit -m B && + + git checkout -b C A && + git rm fickle && + test_ln_s_add home fickle && + git add fickle && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check symlink modify/modify' ' + ( + cd symlink-modify-modify && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with add/add of a symlink: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: No symlink or path exists yet +# Commit B: set up symlink: fickle->disneyland +# Commit C: set up symlink: fickle->home +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious add/add conflict for the symlink 'fickle'. Can +# git detect it? + +test_expect_success 'setup symlink add/add' ' + test_create_repo symlink-add-add && + ( + cd symlink-add-add && + + touch ignoreme && + git add ignoreme && + git commit -m A && + git tag A && + + git checkout -b B A && + test_ln_s_add disneyland fickle && + git commit -m B && + + git checkout -b C A && + test_ln_s_add home fickle && + git add fickle && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check symlink add/add' ' + ( + cd symlink-add-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with modify/modify on a submodule: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: simple submodule repo +# Commit B: update repo +# Commit C: update repo differently +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious modify/modify conflict for the submodule 'repo'. Can +# git detect it? + +test_expect_success 'setup submodule modify/modify' ' + test_create_repo submodule-modify-modify && + ( + cd submodule-modify-modify && + + test_create_repo submod && + ( + cd submod && + touch file-A && + git add file-A && + git commit -m A && + git tag A && + + git checkout -b B A && + touch file-B && + git add file-B && + git commit -m B && + git tag B && + + git checkout -b C A && + touch file-C && + git add file-C && + git commit -m C && + git tag C + ) && + + git -C submod reset --hard A && + git add submod && + git commit -m A && + git tag A && + + git checkout -b B A && + git -C submod reset --hard B && + git add submod && + git commit -m B && + + git checkout -b C A && + git -C submod reset --hard C && + git add submod && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check submodule modify/modify' ' + ( + cd submodule-modify-modify && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with add/add on a submodule: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: nothing of note +# Commit B: introduce submodule repo +# Commit C: introduce submodule repo at different commit +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious add/add conflict for the submodule 'repo'. Can +# git detect it? + +test_expect_success 'setup submodule add/add' ' + test_create_repo submodule-add-add && + ( + cd submodule-add-add && + + test_create_repo submod && + ( + cd submod && + touch file-A && + git add file-A && + git commit -m A && + git tag A && + + git checkout -b B A && + touch file-B && + git add file-B && + git commit -m B && + git tag B && + + git checkout -b C A && + touch file-C && + git add file-C && + git commit -m C && + git tag C + ) && + + touch irrelevant-file && + git add irrelevant-file && + git commit -m A && + git tag A && + + git checkout -b B A && + git -C submod reset --hard B && + git add submod && + git commit -m B && + + git checkout -b C A && + git -C submod reset --hard C && + git add submod && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check submodule add/add' ' + ( + cd submodule-add-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with conflicting entry types: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: nothing of note +# Commit B: introduce submodule 'path' +# Commit C: introduce symlink 'path' +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious add/add conflict for 'path'. Can git detect it? + +test_expect_success 'setup conflicting entry types (submodule vs symlink)' ' + test_create_repo submodule-symlink-add-add && + ( + cd submodule-symlink-add-add && + + test_create_repo path && + ( + cd path && + touch file-B && + git add file-B && + git commit -m B && + git tag B + ) && + + touch irrelevant-file && + git add irrelevant-file && + git commit -m A && + git tag A && + + git checkout -b B A && + git -C path reset --hard B && + git add path && + git commit -m B && + + git checkout -b C A && + rm -rf path/ && + test_ln_s_add irrelevant-file path && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check conflicting entry types (submodule vs symlink)' ' + ( + cd submodule-symlink-add-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with regular files that have conflicting modes: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: nothing of note +# Commit B: introduce file source_me.bash, not executable +# Commit C: introduce file source_me.bash, executable +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious add/add mode conflict. Can git detect it? + +test_expect_success 'setup conflicting modes for regular file' ' + test_create_repo regular-file-mode-conflict && + ( + cd regular-file-mode-conflict && + + touch irrelevant-file && + git add irrelevant-file && + git commit -m A && + git tag A && + + git checkout -b B A && + echo "command_to_run" >source_me.bash && + git add source_me.bash && + git commit -m B && + + git checkout -b C A && + echo "command_to_run" >source_me.bash && + git add source_me.bash && + test_chmod +x source_me.bash && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check conflicting modes for regular file' ' + ( + cd regular-file-mode-conflict && - git merge -s recursive E^0 && + git checkout D^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + test_must_fail git merge -s recursive E^0 && - test $(git rev-parse HEAD:a) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:c) = $(git rev-parse E:c) + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out + ) ' test_done diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index 411550d2b6..b97aca7fa2 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -6,31 +6,40 @@ test_description="recursive merge corner cases w/ renames but not criss-crosses" . ./test-lib.sh test_expect_success 'setup rename/delete + untracked file' ' - echo "A pretty inscription" >ring && - git add ring && - test_tick && - git commit -m beginning && - - git branch people && - git checkout -b rename-the-ring && - git mv ring one-ring-to-rule-them-all && - test_tick && - git commit -m fullname && - - git checkout people && - git rm ring && - echo gollum >owner && - git add owner && - test_tick && - git commit -m track-people-instead-of-objects && - echo "Myyy PRECIOUSSS" >ring + test_create_repo rename-delete-untracked && + ( + cd rename-delete-untracked && + + echo "A pretty inscription" >ring && + git add ring && + test_tick && + git commit -m beginning && + + git branch people && + git checkout -b rename-the-ring && + git mv ring one-ring-to-rule-them-all && + test_tick && + git commit -m fullname && + + git checkout people && + git rm ring && + echo gollum >owner && + git add owner && + test_tick && + git commit -m track-people-instead-of-objects && + echo "Myyy PRECIOUSSS" >ring + ) ' test_expect_success "Does git preserve Gollum's precious artifact?" ' - test_must_fail git merge -s recursive rename-the-ring && + ( + cd rename-delete-untracked && - # Make sure git did not delete an untracked file - test -f ring + test_must_fail git merge -s recursive rename-the-ring && + + # Make sure git did not delete an untracked file + test_path_is_file ring + ) ' # Testcase setup for rename/modify/add-source: @@ -41,96 +50,125 @@ test_expect_success "Does git preserve Gollum's precious artifact?" ' # We should be able to merge B & C cleanly test_expect_success 'setup rename/modify/add-source conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - echo 8 >>a && - git add a && - git commit -m B && - - git checkout -b C A && - git mv a b && - echo something completely different >a && - git add a && - git commit -m C + test_create_repo rename-modify-add-source && + ( + cd rename-modify-add-source && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + echo 8 >>a && + git add a && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo something completely different >a && + git add a && + git commit -m C + ) ' test_expect_failure 'rename/modify/add-source conflict resolvable' ' - git checkout B^0 && + ( + cd rename-modify-add-source && - git merge -s recursive C^0 && + git checkout B^0 && - test $(git rev-parse B:a) = $(git rev-parse b) && - test $(git rev-parse C:a) = $(git rev-parse a) + git merge -s recursive C^0 && + + git rev-parse >expect \ + B:a C:a && + git rev-parse >actual \ + b c && + test_cmp expect actual + ) ' test_expect_success 'setup resolvable conflict missed if rename missed' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n" >a && - echo foo >b && - git add a b && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a c && - echo "Completely different content" >a && - git add a && - git commit -m B && - - git checkout -b C A && - echo 6 >>a && - git add a && - git commit -m C + test_create_repo break-detection-1 && + ( + cd break-detection-1 && + + printf "1\n2\n3\n4\n5\n" >a && + echo foo >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m B && + + git checkout -b C A && + echo 6 >>a && + git add a && + git commit -m C + ) ' test_expect_failure 'conflict caused if rename not detected' ' - git checkout -q C^0 && - git merge -s recursive B^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test_line_count = 6 c && - test $(git rev-parse HEAD:a) = $(git rev-parse B:a) && - test $(git rev-parse HEAD:b) = $(git rev-parse A:b) + ( + cd break-detection-1 && + + git checkout -q C^0 && + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_line_count = 6 c && + git rev-parse >expect \ + B:a A:b && + git rev-parse >actual \ + :0:a :0:b && + test_cmp expect actual + ) ' test_expect_success 'setup conflict resolved wrong if rename missed' ' - git reset --hard && - git clean -f && - - git checkout -b D A && - echo 7 >>a && - git add a && - git mv a c && - echo "Completely different content" >a && - git add a && - git commit -m D && - - git checkout -b E A && - git rm a && - echo "Completely different content" >>a && - git add a && - git commit -m E + test_create_repo break-detection-2 && + ( + cd break-detection-2 && + + printf "1\n2\n3\n4\n5\n" >a && + echo foo >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b D A && + echo 7 >>a && + git add a && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m D && + + git checkout -b E A && + git rm a && + echo "Completely different content" >>a && + git add a && + git commit -m E + ) ' test_expect_failure 'missed conflict if rename not detected' ' - git checkout -q E^0 && - test_must_fail git merge -s recursive D^0 + ( + cd break-detection-2 && + + git checkout -q E^0 && + test_must_fail git merge -s recursive D^0 + ) ' # Tests for undetected rename/add-source causing a file to erroneously be @@ -145,198 +183,232 @@ test_expect_failure 'missed conflict if rename not detected' ' # Commit C: rename a->b, add unrelated a test_expect_success 'setup undetected rename/add-source causes data loss' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a b && - echo foobar >a && - git add a && - git commit -m C + test_create_repo break-detection-3 && + ( + cd break-detection-3 && + + printf "1\n2\n3\n4\n5\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo foobar >a && + git add a && + git commit -m C + ) ' test_expect_failure 'detect rename/add-source and preserve all data' ' - git checkout B^0 && + ( + cd break-detection-3 && - git merge -s recursive C^0 && + git checkout B^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git merge -s recursive C^0 && - test -f a && - test -f b && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:a) = $(git rev-parse C:a) + test_path_is_file a && + test_path_is_file b && + + git rev-parse >expect \ + A:a C:a && + git rev-parse >actual \ + :0:b :0:a && + test_cmp expect actual + ) ' test_expect_failure 'detect rename/add-source and preserve all data, merge other way' ' - git checkout C^0 && + ( + cd break-detection-3 && + + git checkout C^0 && - git merge -s recursive B^0 && + git merge -s recursive B^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && - test -f a && - test -f b && + test_path_is_file a && + test_path_is_file b && - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) && - test $(git rev-parse HEAD:a) = $(git rev-parse C:a) + git rev-parse >expect \ + A:a C:a && + git rev-parse >actual \ + :0:b :0:a && + test_cmp expect actual + ) ' test_expect_success 'setup content merge + rename/directory conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n" >file && - git add file && - test_tick && - git commit -m base && - git tag base && - - git checkout -b right && - echo 7 >>file && - mkdir newfile && - echo junk >newfile/realfile && - git add file newfile/realfile && - test_tick && - git commit -m right && - - git checkout -b left-conflict base && - echo 8 >>file && - git add file && - git mv file newfile && - test_tick && - git commit -m left && - - git checkout -b left-clean base && - echo 0 >newfile && - cat file >>newfile && - git add newfile && - git rm file && - test_tick && - git commit -m left + test_create_repo rename-directory-1 && + ( + cd rename-directory-1 && + + printf "1\n2\n3\n4\n5\n6\n" >file && + git add file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>file && + mkdir newfile && + echo junk >newfile/realfile && + git add file newfile/realfile && + test_tick && + git commit -m right && + + git checkout -b left-conflict base && + echo 8 >>file && + git add file && + git mv file newfile && + test_tick && + git commit -m left && + + git checkout -b left-clean base && + echo 0 >newfile && + cat file >>newfile && + git add newfile && + git rm file && + test_tick && + git commit -m left + ) ' test_expect_success 'rename/directory conflict + clean content merge' ' - git reset --hard && - git reset --hard && - git clean -fdqx && + ( + cd rename-directory-1 && - git checkout left-clean^0 && + git checkout left-clean^0 && - test_must_fail git merge -s recursive right^0 && + test_must_fail git merge -s recursive right^0 && - test 2 -eq $(git ls-files -s | wc -l) && - test 1 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 2 out && - echo 0 >expect && - git cat-file -p base:file >>expect && - echo 7 >>expect && - test_cmp expect newfile~HEAD && + echo 0 >expect && + git cat-file -p base:file >>expect && + echo 7 >>expect && + test_cmp expect newfile~HEAD && - test $(git rev-parse :2:newfile) = $(git hash-object expect) && + test $(git rev-parse :2:newfile) = $(git hash-object expect) && - test -f newfile/realfile && - test -f newfile~HEAD + test_path_is_file newfile/realfile && + test_path_is_file newfile~HEAD + ) ' test_expect_success 'rename/directory conflict + content merge conflict' ' - git reset --hard && - git reset --hard && - git clean -fdqx && - - git checkout left-conflict^0 && - - test_must_fail git merge -s recursive right^0 && - - test 4 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && - - git cat-file -p left-conflict:newfile >left && - git cat-file -p base:file >base && - git cat-file -p right:file >right && - test_must_fail git merge-file \ - -L "HEAD:newfile" \ - -L "" \ - -L "right^0:file" \ - left base right && - test_cmp left newfile~HEAD && - - test $(git rev-parse :1:newfile) = $(git rev-parse base:file) && - test $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) && - test $(git rev-parse :3:newfile) = $(git rev-parse right:file) && - - test -f newfile/realfile && - test -f newfile~HEAD + ( + cd rename-directory-1 && + + git reset --hard && + git clean -fdqx && + + git checkout left-conflict^0 && + + test_must_fail git merge -s recursive right^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git cat-file -p left-conflict:newfile >left && + git cat-file -p base:file >base && + git cat-file -p right:file >right && + test_must_fail git merge-file \ + -L "HEAD:newfile" \ + -L "" \ + -L "right^0:file" \ + left base right && + test_cmp left newfile~HEAD && + + git rev-parse >expect \ + base:file left-conflict:newfile right:file && + git rev-parse >actual \ + :1:newfile :2:newfile :3:newfile && + test_cmp expect actual && + + test_path_is_file newfile/realfile && + test_path_is_file newfile~HEAD + ) ' test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' ' - git reset --hard && - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - mkdir sub && - printf "1\n2\n3\n4\n5\n6\n" >sub/file && - git add sub/file && - test_tick && - git commit -m base && - git tag base && - - git checkout -b right && - echo 7 >>sub/file && - git add sub/file && - test_tick && - git commit -m right && - - git checkout -b left base && - echo 0 >newfile && - cat sub/file >>newfile && - git rm sub/file && - mv newfile sub && - git add sub && - test_tick && - git commit -m left + test_create_repo rename-directory-2 && + ( + cd rename-directory-2 && + + mkdir sub && + printf "1\n2\n3\n4\n5\n6\n" >sub/file && + git add sub/file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>sub/file && + git add sub/file && + test_tick && + git commit -m right && + + git checkout -b left base && + echo 0 >newfile && + cat sub/file >>newfile && + git rm sub/file && + mv newfile sub && + git add sub && + test_tick && + git commit -m left + ) ' test_expect_success 'disappearing dir in rename/directory conflict handled' ' - git reset --hard && - git clean -fdqx && + ( + cd rename-directory-2 && - git checkout left^0 && + git checkout left^0 && - git merge -s recursive right^0 && + git merge -s recursive right^0 && - test 1 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && - echo 0 >expect && - git cat-file -p base:sub/file >>expect && - echo 7 >>expect && - test_cmp expect sub && + echo 0 >expect && + git cat-file -p base:sub/file >>expect && + echo 7 >>expect && + test_cmp expect sub && - test -f sub + test_path_is_file sub + ) ' # Test for all kinds of things that can go wrong with rename/rename (2to1): @@ -352,48 +424,59 @@ test_expect_success 'disappearing dir in rename/directory conflict handled' ' # * Nothing else should be present. Is anything? test_expect_success 'setup rename/rename (2to1) + modify/modify' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n" >a && - printf "5\n4\n3\n2\n1\n" >b && - git add a b && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a c && - echo 0 >>b && - git add b && - git commit -m B && - - git checkout -b C A && - git mv b c && - echo 6 >>a && - git add a && - git commit -m C + test_create_repo rename-rename-2to1 && + ( + cd rename-rename-2to1 && + + printf "1\n2\n3\n4\n5\n" >a && + printf "5\n4\n3\n2\n1\n" >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo 0 >>b && + git add b && + git commit -m B && + + git checkout -b C A && + git mv b c && + echo 6 >>a && + git add a && + git commit -m C + ) ' test_expect_success 'handle rename/rename (2to1) conflict correctly' ' - git checkout B^0 && - - test_must_fail git merge -s recursive C^0 >out && - test_i18ngrep "CONFLICT (rename/rename)" out && - - test 2 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u | wc -l) && - test 2 -eq $(git ls-files -u c | wc -l) && - test 3 -eq $(git ls-files -o | wc -l) && - - test ! -f a && - test ! -f b && - test -f c~HEAD && - test -f c~C^0 && - - test $(git hash-object c~HEAD) = $(git rev-parse C:a) && - test $(git hash-object c~C^0) = $(git rev-parse B:b) + ( + cd rename-rename-2to1 && + + git checkout B^0 && + + test_must_fail git merge -s recursive C^0 >out && + test_i18ngrep "CONFLICT (rename/rename)" out && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -u c >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + test_path_is_missing a && + test_path_is_missing b && + test_path_is_file c~HEAD && + test_path_is_file c~C^0 && + + git rev-parse >expect \ + C:a B:b && + git hash-object >actual \ + c~HEAD c~C^0 && + test_cmp expect actual + ) ' # Testcase setup for simple rename/rename (1to2) conflict: @@ -401,44 +484,53 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' ' # Commit B: rename a->b # Commit C: rename a->c test_expect_success 'setup simple rename/rename (1to2) conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - echo stuff >a && - git add a && - test_tick && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - test_tick && - git commit -m B && - - git checkout -b C A && - git mv a c && - test_tick && - git commit -m C + test_create_repo rename-rename-1to2 && + ( + cd rename-rename-1to2 && + + echo stuff >a && + git add a && + test_tick && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m B && + + git checkout -b C A && + git mv a c && + test_tick && + git commit -m C + ) ' test_expect_success 'merge has correct working tree contents' ' - git checkout C^0 && - - test_must_fail git merge -s recursive B^0 && - - test 3 -eq $(git ls-files -s | wc -l) && - test 3 -eq $(git ls-files -u | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :1:a) = $(git rev-parse A:a) && - test $(git rev-parse :3:b) = $(git rev-parse A:a) && - test $(git rev-parse :2:c) = $(git rev-parse A:a) && - - test ! -f a && - test $(git hash-object b) = $(git rev-parse A:a) && - test $(git hash-object c) = $(git rev-parse A:a) + ( + cd rename-rename-1to2 && + + git checkout C^0 && + + test_must_fail git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_path_is_missing a && + git rev-parse >expect \ + A:a A:a A:a \ + A:a A:a && + git rev-parse >actual \ + :1:a :3:b :2:c && + git hash-object >>actual \ + b c && + test_cmp expect actual + ) ' # Testcase setup for rename/rename(1to2)/add-source conflict: @@ -449,130 +541,400 @@ test_expect_success 'merge has correct working tree contents' ' # Merging of B & C should NOT be clean; there's a rename/rename conflict test_expect_success 'setup rename/rename(1to2)/add-source conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a c && - echo something completely different >a && - git add a && - git commit -m C + test_create_repo rename-rename-1to2-add-source-1 && + ( + cd rename-rename-1to2-add-source-1 && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + echo something completely different >a && + git add a && + git commit -m C + ) ' test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' ' - git checkout B^0 && + ( + cd rename-rename-1to2-add-source-1 && + + git checkout B^0 && - test_must_fail git merge -s recursive C^0 && + test_must_fail git merge -s recursive C^0 && - test 4 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -o >out && + test_line_count = 1 out && - test $(git rev-parse 3:a) = $(git rev-parse C:a) && - test $(git rev-parse 1:a) = $(git rev-parse A:a) && - test $(git rev-parse 2:b) = $(git rev-parse B:b) && - test $(git rev-parse 3:c) = $(git rev-parse C:c) && + git rev-parse >expect \ + C:a A:a B:b C:C && + git rev-parse >actual \ + :3:a :1:a :2:b :3:c && + test_cmp expect actual && - test -f a && - test -f b && - test -f c + test_path_is_file a && + test_path_is_file b && + test_path_is_file c + ) ' test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >a && - git add a && - test_tick && - git commit -m base && - git tag A && - - git checkout -b B A && - git mv a b && - test_tick && - git commit -m one && - - git checkout -b C A && - git mv a b && - echo important-info >a && - git add a && - test_tick && - git commit -m two + test_create_repo rename-rename-1to2-add-source-2 && + ( + cd rename-rename-1to2-add-source-2 && + + >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a b && + echo important-info >a && + git add a && + test_tick && + git commit -m two + ) ' test_expect_failure 'rename/rename/add-source still tracks new a file' ' - git checkout C^0 && - git merge -s recursive B^0 && - - test 2 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse HEAD:a) = $(git rev-parse C:a) && - test $(git rev-parse HEAD:b) = $(git rev-parse A:a) + ( + cd rename-rename-1to2-add-source-2 && + + git checkout C^0 && + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + C:a A:a && + git rev-parse >actual \ + :0:a :0:b && + test_cmp expect actual + ) ' test_expect_success 'setup rename/rename(1to2)/add-dest conflict' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - echo stuff >a && - git add a && - test_tick && - git commit -m base && - git tag A && - - git checkout -b B A && - git mv a b && - echo precious-data >c && - git add c && - test_tick && - git commit -m one && - - git checkout -b C A && - git mv a c && - echo important-info >b && - git add b && - test_tick && - git commit -m two + test_create_repo rename-rename-1to2-add-dest && + ( + cd rename-rename-1to2-add-dest && + + echo stuff >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + echo precious-data >c && + git add c && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a c && + echo important-info >b && + git add b && + test_tick && + git commit -m two + ) ' test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' ' - git checkout C^0 && - test_must_fail git merge -s recursive B^0 && - - test 5 -eq $(git ls-files -s | wc -l) && - test 2 -eq $(git ls-files -u b | wc -l) && - test 2 -eq $(git ls-files -u c | wc -l) && - test 4 -eq $(git ls-files -o | wc -l) && - - test $(git rev-parse :1:a) = $(git rev-parse A:a) && - test $(git rev-parse :2:b) = $(git rev-parse C:b) && - test $(git rev-parse :3:b) = $(git rev-parse B:b) && - test $(git rev-parse :2:c) = $(git rev-parse C:c) && - test $(git rev-parse :3:c) = $(git rev-parse B:c) && - - test $(git hash-object c~HEAD) = $(git rev-parse C:c) && - test $(git hash-object c~B\^0) = $(git rev-parse B:c) && - test $(git hash-object b~HEAD) = $(git rev-parse C:b) && - test $(git hash-object b~B\^0) = $(git rev-parse B:b) && - - test ! -f b && - test ! -f c + ( + cd rename-rename-1to2-add-dest && + + git checkout C^0 && + test_must_fail git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u b >out && + test_line_count = 2 out && + git ls-files -u c >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 5 out && + + git rev-parse >expect \ + A:a C:b B:b C:c B:c && + git rev-parse >actual \ + :1:a :2:b :3:b :2:c :3:c && + test_cmp expect actual && + + git rev-parse >expect \ + C:c B:c C:b B:b && + git hash-object >actual \ + c~HEAD c~B\^0 b~HEAD b~B\^0 && + test_cmp expect actual && + + test_path_is_missing b && + test_path_is_missing c + ) +' + +# Testcase rad, rename/add/delete +# Commit O: foo +# Commit A: rm foo, add different bar +# Commit B: rename foo->bar +# Expected: CONFLICT (rename/add/delete), two-way merged bar + +test_expect_success 'rad-setup: rename/add/delete conflict' ' + test_create_repo rad && + ( + cd rad && + echo "original file" >foo && + git add foo && + git commit -m "original" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm foo && + echo "different file" >bar && + git add bar && + git commit -m "Remove foo, add bar" && + + git checkout B && + git mv foo bar && + git commit -m "rename foo to bar" + ) +' + +test_expect_failure 'rad-check: rename/add/delete conflict' ' + ( + cd rad && + + git checkout B^0 && + test_must_fail git merge -s recursive A^0 >out 2>err && + + # Not sure whether the output should contain just one + # "CONFLICT (rename/add/delete)" line, or if it should break + # it into a pair of "CONFLICT (rename/delete)" and + # "CONFLICT (rename/add)"; allow for either. + test_i18ngrep "CONFLICT (rename.*add)" out && + test_i18ngrep "CONFLICT (rename.*delete)" out && + test_must_be_empty err && + + git ls-files -s >file_count && + test_line_count = 2 file_count && + git ls-files -u >file_count && + test_line_count = 2 file_count && + git ls-files -o >file_count && + test_line_count = 2 file_count && + + git rev-parse >actual \ + :2:bar :3:bar && + git rev-parse >expect \ + B:bar A:bar && + + test_cmp file_is_missing foo && + # bar should have two-way merged contents of the different + # versions of bar; check that content from both sides is + # present. + grep original bar && + grep different bar + ) +' + +# Testcase rrdd, rename/rename(2to1)/delete/delete +# Commit O: foo, bar +# Commit A: rename foo->baz, rm bar +# Commit B: rename bar->baz, rm foo +# Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz + +test_expect_success 'rrdd-setup: rename/rename(2to1)/delete/delete conflict' ' + test_create_repo rrdd && + ( + cd rrdd && + echo foo >foo && + echo bar >bar && + git add foo bar && + git commit -m O && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv foo baz && + git rm bar && + git commit -m "Rename foo, remove bar" && + + git checkout B && + git mv bar baz && + git rm foo && + git commit -m "Rename bar, remove foo" + ) +' + +test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' ' + ( + cd rrdd && + + git checkout A^0 && + test_must_fail git merge -s recursive B^0 >out 2>err && + + # Not sure whether the output should contain just one + # "CONFLICT (rename/rename/delete/delete)" line, or if it + # should break it into three: "CONFLICT (rename/rename)" and + # two "CONFLICT (rename/delete)" lines; allow for either. + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "CONFLICT (rename.*delete)" out && + test_must_be_empty err && + + git ls-files -s >file_count && + test_line_count = 2 file_count && + git ls-files -u >file_count && + test_line_count = 2 file_count && + git ls-files -o >file_count && + test_line_count = 2 file_count && + + git rev-parse >actual \ + :2:baz :3:baz && + git rev-parse >expect \ + O:foo O:bar && + + test_cmp file_is_missing foo && + test_cmp file_is_missing bar && + # baz should have two-way merged contents of the original + # contents of foo and bar; check that content from both sides + # is present. + grep foo baz && + grep bar baz + ) +' + +# Testcase mod6, chains of rename/rename(1to2) and rename/rename(2to1) +# Commit O: one, three, five +# Commit A: one->two, three->four, five->six +# Commit B: one->six, three->two, five->four +# Expected: six CONFLICT(rename/rename) messages, each path in two of the +# multi-way merged contents found in two, four, six + +test_expect_success 'mod6-setup: chains of rename/rename(1to2) and rename/rename(2to1)' ' + test_create_repo mod6 && + ( + cd mod6 && + test_seq 11 19 >one && + test_seq 31 39 >three && + test_seq 51 59 >five && + git add . && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 10 19 >one && + echo 40 >>three && + git add one three && + git mv one two && + git mv three four && + git mv five six && + test_tick && + git commit -m "A" && + + git checkout B && + echo 20 >>one && + echo forty >>three && + echo 60 >>five && + git add one three five && + git mv one six && + git mv three two && + git mv five four && + test_tick && + git commit -m "B" + ) +' + +test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' ' + ( + cd mod6 && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "CONFLICT (rename/rename)" out && + test_must_be_empty err && + + git ls-files -s >file_count && + test_line_count = 6 file_count && + git ls-files -u >file_count && + test_line_count = 6 file_count && + git ls-files -o >file_count && + test_line_count = 3 file_count && + + test_seq 10 20 >merged-one && + test_seq 51 60 >merged-five && + # Determine what the merge of three would give us. + test_seq 30 40 >three-side-A && + test_seq 31 39 >three-side-B && + echo forty >three-side-B && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + three-side-A empty three-side-B && + sed -e "s/^\([<=>]\)/\1\1\1/" three-side-A >merged-three && + + # Verify the index is as expected + git rev-parse >actual \ + :2:two :3:two \ + :2:four :3:four \ + :2:six :3:six && + git hash-object >expect \ + merged-one merged-three \ + merged-three merged-five \ + merged-five merged-one && + test_cmp expect actual && + + git cat-file -p :2:two >expect && + git cat-file -p :3:two >other && + test_must_fail git merge-file \ + -L "HEAD" -L "" -L "B^0" \ + expect empty other && + test_cmp expect two && + + git cat-file -p :2:four >expect && + git cat-file -p :3:four >other && + test_must_fail git merge-file \ + -L "HEAD" -L "" -L "B^0" \ + expect empty other && + test_cmp expect four && + + git cat-file -p :2:six >expect && + git cat-file -p :3:six >other && + test_must_fail git merge-file \ + -L "HEAD" -L "" -L "B^0" \ + expect empty other && + test_cmp expect six + ) ' test_done diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh new file mode 100755 index 0000000000..4a71f17edd --- /dev/null +++ b/t/t6043-merge-rename-directories.sh @@ -0,0 +1,3998 @@ +#!/bin/sh + +test_description="recursive merge with directory renames" +# includes checking of many corner cases, with a similar methodology to: +# t6042: corner cases with renames but not criss-cross merges +# t6036: corner cases with both renames and criss-cross merges +# +# The setup for all of them, pictorially, is: +# +# A +# o +# / \ +# O o ? +# \ / +# o +# B +# +# To help make it easier to follow the flow of tests, they have been +# divided into sections and each test will start with a quick explanation +# of what commits O, A, and B contain. +# +# Notation: +# z/{b,c} means files z/b and z/c both exist +# x/d_1 means file x/d exists with content d1. (Purpose of the +# underscore notation is to differentiate different +# files that might be renamed into each other's paths.) + +. ./test-lib.sh + + +########################################################################### +# SECTION 1: Basic cases we should be able to handle +########################################################################### + +# Testcase 1a, Basic directory rename. +# Commit O: z/{b,c} +# Commit A: y/{b,c} +# Commit B: z/{b,c,d,e/f} +# Expected: y/{b,c,d,e/f} + +test_expect_success '1a-setup: Simple directory rename detection' ' + test_create_repo 1a && + ( + cd 1a && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + echo d >z/d && + mkdir z/e && + echo f >z/e/f && + git add z/d z/e/f && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1a-check: Simple directory rename detection' ' + ( + cd 1a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e/f && + git rev-parse >expect \ + O:z/b O:z/c B:z/d B:z/e/f && + test_cmp expect actual && + + git hash-object y/d >actual && + git rev-parse B:z/d >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:z/d && + test_must_fail git rev-parse HEAD:z/e/f && + test_path_is_missing z/d && + test_path_is_missing z/e/f + ) +' + +# Testcase 1b, Merge a directory with another +# Commit O: z/{b,c}, y/d +# Commit A: z/{b,c,e}, y/d +# Commit B: y/{b,c,d} +# Expected: y/{b,c,d,e} + +test_expect_success '1b-setup: Merge a directory with another' ' + test_create_repo 1b && + ( + cd 1b && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir y && + echo d >y/d && + git add z y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo e >z/e && + git add z/e && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z/b y && + git mv z/c y && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1b-check: Merge a directory with another' ' + ( + cd 1b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e && + git rev-parse >expect \ + O:z/b O:z/c O:y/d A:z/e && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:z/e + ) +' + +# Testcase 1c, Transitive renaming +# (Related to testcases 3a and 6d -- when should a transitive rename apply?) +# (Related to testcases 9c and 9d -- can transitivity repeat?) +# (Related to testcase 12b -- joint-transitivity?) +# Commit O: z/{b,c}, x/d +# Commit A: y/{b,c}, x/d +# Commit B: z/{b,c,d} +# Expected: y/{b,c,d} (because x/d -> z/d -> y/d) + +test_expect_success '1c-setup: Transitive renaming' ' + test_create_repo 1c && + ( + cd 1c && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1c-check: Transitive renaming' ' + ( + cd 1c && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d && + git rev-parse >expect \ + O:z/b O:z/c O:x/d && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:x/d && + test_must_fail git rev-parse HEAD:z/d && + test_path_is_missing z/d + ) +' + +# Testcase 1d, Directory renames (merging two directories into one new one) +# cause a rename/rename(2to1) conflict +# (Related to testcases 1c and 7b) +# Commit O. z/{b,c}, y/{d,e} +# Commit A. x/{b,c}, y/{d,e,m,wham_1} +# Commit B. z/{b,c,n,wham_2}, x/{d,e} +# Expected: x/{b,c,d,e,m,n}, CONFLICT:(y/wham_1 & z/wham_2 -> x/wham) +# Note: y/m & z/n should definitely move into x. By the same token, both +# y/wham_1 & z/wham_2 should too...giving us a conflict. + +test_expect_success '1d-setup: Directory renames cause a rename/rename(2to1) conflict' ' + test_create_repo 1d && + ( + cd 1d && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir y && + echo d >y/d && + echo e >y/e && + git add z y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z x && + echo m >y/m && + echo wham1 >y/wham && + git add y && + test_tick && + git commit -m "A" && + + git checkout B && + git mv y x && + echo n >z/n && + echo wham2 >z/wham && + git add z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) conflict' ' + ( + cd 1d && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename)" out && + + git ls-files -s >out && + test_line_count = 8 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n && + git rev-parse >expect \ + O:z/b O:z/c O:y/d O:y/e A:y/m B:z/n && + test_cmp expect actual && + + test_must_fail git rev-parse :0:x/wham && + git rev-parse >actual \ + :2:x/wham :3:x/wham && + git rev-parse >expect \ + A:y/wham B:z/wham && + test_cmp expect actual && + + test_path_is_missing x/wham && + test_path_is_file x/wham~HEAD && + test_path_is_file x/wham~B^0 && + + git hash-object >actual \ + x/wham~HEAD x/wham~B^0 && + git rev-parse >expect \ + A:y/wham B:z/wham && + test_cmp expect actual + ) +' + +# Testcase 1e, Renamed directory, with all filenames being renamed too +# (Related to testcases 9f & 9g) +# Commit O: z/{oldb,oldc} +# Commit A: y/{newb,newc} +# Commit B: z/{oldb,oldc,d} +# Expected: y/{newb,newc,d} + +test_expect_success '1e-setup: Renamed directory, with all files being renamed too' ' + test_create_repo 1e && + ( + cd 1e && + + mkdir z && + echo b >z/oldb && + echo c >z/oldc && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + git mv z/oldb y/newb && + git mv z/oldc y/newc && + test_tick && + git commit -m "A" && + + git checkout B && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1e-check: Renamed directory, with all files being renamed too' ' + ( + cd 1e && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/newb HEAD:y/newc HEAD:y/d && + git rev-parse >expect \ + O:z/oldb O:z/oldc B:z/d && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:z/d + ) +' + +# Testcase 1f, Split a directory into two other directories +# (Related to testcases 3a, all of section 2, and all of section 4) +# Commit O: z/{b,c,d,e,f} +# Commit A: z/{b,c,d,e,f,g} +# Commit B: y/{b,c}, x/{d,e,f} +# Expected: y/{b,c}, x/{d,e,f,g} + +test_expect_success '1f-setup: Split a directory into two other directories' ' + test_create_repo 1f && + ( + cd 1f && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + echo e >z/e && + echo f >z/f && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo g >z/g && + git add z/g && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir y && + mkdir x && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d x/ && + git mv z/e x/ && + git mv z/f x/ && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1f-check: Split a directory into two other directories' ' + ( + cd 1f && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 6 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:x/d HEAD:x/e HEAD:x/f HEAD:x/g && + git rev-parse >expect \ + O:z/b O:z/c O:z/d O:z/e O:z/f A:z/g && + test_cmp expect actual && + test_path_is_missing z/g && + test_must_fail git rev-parse HEAD:z/g + ) +' + +########################################################################### +# Rules suggested by testcases in section 1: +# +# We should still detect the directory rename even if it wasn't just +# the directory renamed, but the files within it. (see 1b) +# +# If renames split a directory into two or more others, the directory +# with the most renames, "wins" (see 1c). However, see the testcases +# in section 2, plus testcases 3a and 4a. +########################################################################### + + +########################################################################### +# SECTION 2: Split into multiple directories, with equal number of paths +# +# Explore the splitting-a-directory rules a bit; what happens in the +# edge cases? +# +# Note that there is a closely related case of a directory not being +# split on either side of history, but being renamed differently on +# each side. See testcase 8e for that. +########################################################################### + +# Testcase 2a, Directory split into two on one side, with equal numbers of paths +# Commit O: z/{b,c} +# Commit A: y/b, w/c +# Commit B: z/{b,c,d} +# Expected: y/b, w/c, z/d, with warning about z/ -> (y/ vs. w/) conflict +test_expect_success '2a-setup: Directory split into two on one side, with equal numbers of paths' ' + test_create_repo 2a && + ( + cd 2a && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + mkdir w && + git mv z/b y/ && + git mv z/c w/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2a-check: Directory split into two on one side, with equal numbers of paths' ' + ( + cd 2a && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT.*directory rename split" out && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:w/c :0:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/d && + test_cmp expect actual + ) +' + +# Testcase 2b, Directory split into two on one side, with equal numbers of paths +# Commit O: z/{b,c} +# Commit A: y/b, w/c +# Commit B: z/{b,c}, x/d +# Expected: y/b, w/c, x/d; No warning about z/ -> (y/ vs. w/) conflict +test_expect_success '2b-setup: Directory split into two on one side, with equal numbers of paths' ' + test_create_repo 2b && + ( + cd 2b && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + mkdir w && + git mv z/b y/ && + git mv z/c w/ && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir x && + echo d >x/d && + git add x/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2b-check: Directory split into two on one side, with equal numbers of paths' ' + ( + cd 2b && + + git checkout A^0 && + + git merge -s recursive B^0 >out && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:w/c :0:x/d && + git rev-parse >expect \ + O:z/b O:z/c B:x/d && + test_cmp expect actual && + test_i18ngrep ! "CONFLICT.*directory rename split" out + ) +' + +########################################################################### +# Rules suggested by section 2: +# +# None; the rule was already covered in section 1. These testcases are +# here just to make sure the conflict resolution and necessary warning +# messages are handled correctly. +########################################################################### + + +########################################################################### +# SECTION 3: Path in question is the source path for some rename already +# +# Combining cases from Section 1 and trying to handle them could lead to +# directory renaming detection being over-applied. So, this section +# provides some good testcases to check that the implementation doesn't go +# too far. +########################################################################### + +# Testcase 3a, Avoid implicit rename if involved as source on other side +# (Related to testcases 1c, 1f, and 9h) +# Commit O: z/{b,c,d} +# Commit A: z/{b,c,d} (no change) +# Commit B: y/{b,c}, x/d +# Expected: y/{b,c}, x/d +test_expect_success '3a-setup: Avoid implicit rename if involved as source on other side' ' + test_create_repo 3a && + ( + cd 3a && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + git commit --allow-empty -m "A" && + + git checkout B && + mkdir y && + mkdir x && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d x/ && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '3a-check: Avoid implicit rename if involved as source on other side' ' + ( + cd 3a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:x/d && + git rev-parse >expect \ + O:z/b O:z/c O:z/d && + test_cmp expect actual + ) +' + +# Testcase 3b, Avoid implicit rename if involved as source on other side +# (Related to testcases 5c and 7c, also kind of 1e and 1f) +# Commit O: z/{b,c,d} +# Commit A: y/{b,c}, x/d +# Commit B: z/{b,c}, w/d +# Expected: y/{b,c}, CONFLICT:(z/d -> x/d vs. w/d) +# NOTE: We're particularly checking that since z/d is already involved as +# a source in a file rename on the same side of history, that we don't +# get it involved in directory rename detection. If it were, we might +# end up with CONFLICT:(z/d -> y/d vs. x/d vs. w/d), i.e. a +# rename/rename/rename(1to3) conflict, which is just weird. +test_expect_success '3b-setup: Avoid implicit rename if involved as source on current side' ' + test_create_repo 3b && + ( + cd 3b && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + mkdir x && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d x/ && + rmdir z && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir w && + git mv z/d w/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '3b-check: Avoid implicit rename if involved as source on current side' ' + ( + cd 3b && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep CONFLICT.*rename/rename.*z/d.*x/d.*w/d out && + test_i18ngrep ! CONFLICT.*rename/rename.*y/d out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :1:z/d :2:x/d :3:w/d && + git rev-parse >expect \ + O:z/b O:z/c O:z/d O:z/d O:z/d && + test_cmp expect actual && + + test_path_is_missing z/d && + git hash-object >actual \ + x/d w/d && + git rev-parse >expect \ + O:z/d O:z/d && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 3: +# +# Avoid directory-rename-detection for a path, if that path is the source +# of a rename on either side of a merge. +########################################################################### + + +########################################################################### +# SECTION 4: Partially renamed directory; still exists on both sides of merge +# +# What if we were to attempt to do directory rename detection when someone +# "mostly" moved a directory but still left some files around, or, +# equivalently, fully renamed a directory in one commmit and then recreated +# that directory in a later commit adding some new files and then tried to +# merge? +# +# It's hard to divine user intent in these cases, because you can make an +# argument that, depending on the intermediate history of the side being +# merged, that some users will want files in that directory to +# automatically be detected and renamed, while users with a different +# intermediate history wouldn't want that rename to happen. +# +# I think that it is best to simply not have directory rename detection +# apply to such cases. My reasoning for this is four-fold: (1) it's +# easiest for users in general to figure out what happened if we don't +# apply directory rename detection in any such case, (2) it's an easy rule +# to explain ["We don't do directory rename detection if the directory +# still exists on both sides of the merge"], (3) we can get some hairy +# edge/corner cases that would be really confusing and possibly not even +# representable in the index if we were to even try, and [related to 3] (4) +# attempting to resolve this issue of divining user intent by examining +# intermediate history goes against the spirit of three-way merges and is a +# path towards crazy corner cases that are far more complex than what we're +# already dealing with. +# +# Note that the wording of the rule ("We don't do directory rename +# detection if the directory still exists on both sides of the merge.") +# also excludes "renaming" of a directory into a subdirectory of itself +# (e.g. /some/dir/* -> /some/dir/subdir/*). It may be possible to carve +# out an exception for "renaming"-beneath-itself cases without opening +# weird edge/corner cases for other partial directory renames, but for now +# we are keeping the rule simple. +# +# This section contains a test for a partially-renamed-directory case. +########################################################################### + +# Testcase 4a, Directory split, with original directory still present +# (Related to testcase 1f) +# Commit O: z/{b,c,d,e} +# Commit A: y/{b,c,d}, z/e +# Commit B: z/{b,c,d,e,f} +# Expected: y/{b,c,d}, z/{e,f} +# NOTE: Even though most files from z moved to y, we don't want f to follow. + +test_expect_success '4a-setup: Directory split, with original directory still present' ' + test_create_repo 4a && + ( + cd 4a && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + echo e >z/e && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir y && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d y/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo f >z/f && + git add z/f && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '4a-check: Directory split, with original directory still present' ' + ( + cd 4a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/e HEAD:z/f && + git rev-parse >expect \ + O:z/b O:z/c O:z/d O:z/e B:z/f && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 4: +# +# Directory-rename-detection should be turned off for any directories (as +# a source for renames) that exist on both sides of the merge. (The "as +# a source for renames" clarification is due to cases like 1c where +# the target directory exists on both sides and we do want the rename +# detection.) But, sadly, see testcase 8b. +########################################################################### + + +########################################################################### +# SECTION 5: Files/directories in the way of subset of to-be-renamed paths +# +# Implicitly renaming files due to a detected directory rename could run +# into problems if there are files or directories in the way of the paths +# we want to rename. Explore such cases in this section. +########################################################################### + +# Testcase 5a, Merge directories, other side adds files to original and target +# Commit O: z/{b,c}, y/d +# Commit A: z/{b,c,e_1,f}, y/{d,e_2} +# Commit B: y/{b,c,d} +# Expected: z/e_1, y/{b,c,d,e_2,f} + CONFLICT warning +# NOTE: While directory rename detection is active here causing z/f to +# become y/f, we did not apply this for z/e_1 because that would +# give us an add/add conflict for y/e_1 vs y/e_2. This problem with +# this add/add, is that both versions of y/e are from the same side +# of history, giving us no way to represent this conflict in the +# index. + +test_expect_success '5a-setup: Merge directories, other side adds files to original and target' ' + test_create_repo 5a && + ( + cd 5a && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir y && + echo d >y/d && + git add z y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo e1 >z/e && + echo f >z/f && + echo e2 >y/e && + git add z/e z/f y/e && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z/b y/ && + git mv z/c y/ && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '5a-check: Merge directories, other side adds files to original and target' ' + ( + cd 5a && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT.*implicit dir rename" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:y/d :0:y/e :0:z/e :0:y/f && + git rev-parse >expect \ + O:z/b O:z/c O:y/d A:y/e A:z/e A:z/f && + test_cmp expect actual + ) +' + +# Testcase 5b, Rename/delete in order to get add/add/add conflict +# (Related to testcase 8d; these may appear slightly inconsistent to users; +# Also related to testcases 7d and 7e) +# Commit O: z/{b,c,d_1} +# Commit A: y/{b,c,d_2} +# Commit B: z/{b,c,d_1,e}, y/d_3 +# Expected: y/{b,c,e}, CONFLICT(add/add: y/d_2 vs. y/d_3) +# NOTE: If z/d_1 in commit B were to be involved in dir rename detection, as +# we normaly would since z/ is being renamed to y/, then this would be +# a rename/delete (z/d_1 -> y/d_1 vs. deleted) AND an add/add/add +# conflict of y/d_1 vs. y/d_2 vs. y/d_3. Add/add/add is not +# representable in the index, so the existence of y/d_3 needs to +# cause us to bail on directory rename detection for that path, falling +# back to git behavior without the directory rename detection. + +test_expect_success '5b-setup: Rename/delete in order to get add/add/add conflict' ' + test_create_repo 5b && + ( + cd 5b && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d1 >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/d && + git mv z y && + echo d2 >y/d && + git add y/d && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir y && + echo d3 >y/d && + echo e >z/e && + git add y/d z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '5b-check: Rename/delete in order to get add/add/add conflict' ' + ( + cd 5b && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (add/add).* y/d" out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:y/e :2:y/d :3:y/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/e A:y/d B:y/d && + test_cmp expect actual && + + test_must_fail git rev-parse :1:y/d && + test_path_is_file y/d + ) +' + +# Testcase 5c, Transitive rename would cause rename/rename/rename/add/add/add +# (Directory rename detection would result in transitive rename vs. +# rename/rename(1to2) and turn it into a rename/rename(1to3). Further, +# rename paths conflict with separate adds on the other side) +# (Related to testcases 3b and 7c) +# Commit O: z/{b,c}, x/d_1 +# Commit A: y/{b,c,d_2}, w/d_1 +# Commit B: z/{b,c,d_1,e}, w/d_3, y/d_4 +# Expected: A mess, but only a rename/rename(1to2)/add/add mess. Use the +# presence of y/d_4 in B to avoid doing transitive rename of +# x/d_1 -> z/d_1 -> y/d_1, so that the only paths we have at +# y/d are y/d_2 and y/d_4. We still do the move from z/e to y/e, +# though, because it doesn't have anything in the way. + +test_expect_success '5c-setup: Transitive rename would cause rename/rename/rename/add/add/add' ' + test_create_repo 5c && + ( + cd 5c && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d1 >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + echo d2 >y/d && + git add y/d && + git mv x w && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + mkdir w && + mkdir y && + echo d3 >w/d && + echo d4 >y/d && + echo e >z/e && + git add w/ y/ z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '5c-check: Transitive rename would cause rename/rename/rename/add/add/add' ' + ( + cd 5c && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*z/d" out && + test_i18ngrep "CONFLICT (add/add).* y/d" out && + + git ls-files -s >out && + test_line_count = 9 out && + git ls-files -u >out && + test_line_count = 6 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:y/e && + git rev-parse >expect \ + O:z/b O:z/c B:z/e && + test_cmp expect actual && + + test_must_fail git rev-parse :1:y/d && + git rev-parse >actual \ + :2:w/d :3:w/d :1:x/d :2:y/d :3:y/d :3:z/d && + git rev-parse >expect \ + O:x/d B:w/d O:x/d A:y/d B:y/d O:x/d && + test_cmp expect actual && + + git hash-object >actual \ + w/d~HEAD w/d~B^0 z/d && + git rev-parse >expect \ + O:x/d B:w/d O:x/d && + test_cmp expect actual && + test_path_is_missing x/d && + test_path_is_file y/d && + grep -q "<<<<" y/d # conflict markers should be present + ) +' + +# Testcase 5d, Directory/file/file conflict due to directory rename +# Commit O: z/{b,c} +# Commit A: y/{b,c,d_1} +# Commit B: z/{b,c,d_2,f}, y/d/e +# Expected: y/{b,c,d/e,f}, z/d_2, CONFLICT(file/directory), y/d_1~HEAD +# Note: The fact that y/d/ exists in B makes us bail on directory rename +# detection for z/d_2, but that doesn't prevent us from applying the +# directory rename detection for z/f -> y/f. + +test_expect_success '5d-setup: Directory/file/file conflict due to directory rename' ' + test_create_repo 5d && + ( + cd 5d && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + echo d1 >y/d && + git add y/d && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir -p y/d && + echo e >y/d/e && + echo d2 >z/d && + echo f >z/f && + git add y/d/e z/d z/f && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '5d-check: Directory/file/file conflict due to directory rename' ' + ( + cd 5d && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (file/directory).*y/d" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d :0:y/d/e && + git rev-parse >expect \ + O:z/b O:z/c B:z/d B:z/f A:y/d B:y/d/e && + test_cmp expect actual && + + git hash-object y/d~HEAD >actual && + git rev-parse A:y/d >expect && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 5: +# +# If a subset of to-be-renamed files have a file or directory in the way, +# "turn off" the directory rename for those specific sub-paths, falling +# back to old handling. But, sadly, see testcases 8a and 8b. +########################################################################### + + +########################################################################### +# SECTION 6: Same side of the merge was the one that did the rename +# +# It may sound obvious that you only want to apply implicit directory +# renames to directories if the _other_ side of history did the renaming. +# If you did make an implementation that didn't explicitly enforce this +# rule, the majority of cases that would fall under this section would +# also be solved by following the rules from the above sections. But +# there are still a few that stick out, so this section covers them just +# to make sure we also get them right. +########################################################################### + +# Testcase 6a, Tricky rename/delete +# Commit O: z/{b,c,d} +# Commit A: z/b +# Commit B: y/{b,c}, z/d +# Expected: y/b, CONFLICT(rename/delete, z/c -> y/c vs. NULL) +# Note: We're just checking here that the rename of z/b and z/c to put +# them under y/ doesn't accidentally catch z/d and make it look like +# it is also involved in a rename/delete conflict. + +test_expect_success '6a-setup: Tricky rename/delete' ' + test_create_repo 6a && + ( + cd 6a && + + mkdir z && + echo b >z/b && + echo c >z/c && + echo d >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/c && + git rm z/d && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir y && + git mv z/b y/ && + git mv z/c y/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6a-check: Tricky rename/delete' ' + ( + cd 6a && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/delete).*z/c.*y/c" out && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :3:y/c && + git rev-parse >expect \ + O:z/b O:z/c && + test_cmp expect actual + ) +' + +# Testcase 6b, Same rename done on both sides +# (Related to testcases 6c and 8e) +# Commit O: z/{b,c} +# Commit A: y/{b,c} +# Commit B: y/{b,c}, z/d +# Expected: y/{b,c}, z/d +# Note: If we did directory rename detection here, we'd move z/d into y/, +# but B did that rename and still decided to put the file into z/, +# so we probably shouldn't apply directory rename detection for it. + +test_expect_success '6b-setup: Same rename done on both sides' ' + test_create_repo 6b && + ( + cd 6b && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z y && + mkdir z && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6b-check: Same rename done on both sides' ' + ( + cd 6b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/d && + test_cmp expect actual + ) +' + +# Testcase 6c, Rename only done on same side +# (Related to testcases 6b and 8e) +# Commit O: z/{b,c} +# Commit A: z/{b,c} (no change) +# Commit B: y/{b,c}, z/d +# Expected: y/{b,c}, z/d +# NOTE: Seems obvious, but just checking that the implementation doesn't +# "accidentally detect a rename" and give us y/{b,c,d}. + +test_expect_success '6c-setup: Rename only done on same side' ' + test_create_repo 6c && + ( + cd 6c && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + git commit --allow-empty -m "A" && + + git checkout B && + git mv z y && + mkdir z && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6c-check: Rename only done on same side' ' + ( + cd 6c && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/d && + test_cmp expect actual + ) +' + +# Testcase 6d, We don't always want transitive renaming +# (Related to testcase 1c) +# Commit O: z/{b,c}, x/d +# Commit A: z/{b,c}, x/d (no change) +# Commit B: y/{b,c}, z/d +# Expected: y/{b,c}, z/d +# NOTE: Again, this seems obvious but just checking that the implementation +# doesn't "accidentally detect a rename" and give us y/{b,c,d}. + +test_expect_success '6d-setup: We do not always want transitive renaming' ' + test_create_repo 6d && + ( + cd 6d && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + git commit --allow-empty -m "A" && + + git checkout B && + git mv z y && + git mv x z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6d-check: We do not always want transitive renaming' ' + ( + cd 6d && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:z/d && + git rev-parse >expect \ + O:z/b O:z/c O:x/d && + test_cmp expect actual + ) +' + +# Testcase 6e, Add/add from one-side +# Commit O: z/{b,c} +# Commit A: z/{b,c} (no change) +# Commit B: y/{b,c,d_1}, z/d_2 +# Expected: y/{b,c,d_1}, z/d_2 +# NOTE: Again, this seems obvious but just checking that the implementation +# doesn't "accidentally detect a rename" and give us y/{b,c} + +# add/add conflict on y/d_1 vs y/d_2. + +test_expect_success '6e-setup: Add/add from one side' ' + test_create_repo 6e && + ( + cd 6e && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + git commit --allow-empty -m "A" && + + git checkout B && + git mv z y && + echo d1 > y/d && + mkdir z && + echo d2 > z/d && + git add y/d z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '6e-check: Add/add from one side' ' + ( + cd 6e && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:y/d B:z/d && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 6: +# +# Only apply implicit directory renames to directories if the other +# side of history is the one doing the renaming. +########################################################################### + + +########################################################################### +# SECTION 7: More involved Edge/Corner cases +# +# The ruleset we have generated in the above sections seems to provide +# well-defined merges. But can we find edge/corner cases that either (a) +# are harder for users to understand, or (b) have a resolution that is +# non-intuitive or suboptimal? +# +# The testcases in this section dive into cases that I've tried to craft in +# a way to find some that might be surprising to users or difficult for +# them to understand (the next section will look at non-intuitive or +# suboptimal merge results). Some of the testcases are similar to ones +# from past sections, but have been simplified to try to highlight error +# messages using a "modified" path (due to the directory rename). Are +# users okay with these? +# +# In my opinion, testcases that are difficult to understand from this +# section is due to difficulty in the testcase rather than the directory +# renaming (similar to how t6042 and t6036 have difficult resolutions due +# to the problem setup itself being complex). And I don't think the +# error messages are a problem. +# +# On the other hand, the testcases in section 8 worry me slightly more... +########################################################################### + +# Testcase 7a, rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file +# Commit O: z/{b,c} +# Commit A: y/{b,c} +# Commit B: w/b, x/c, z/d +# Expected: y/d, CONFLICT(rename/rename for both z/b and z/c) +# NOTE: There's a rename of z/ here, y/ has more renames, so z/d -> y/d. + +test_expect_success '7a-setup: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' ' + test_create_repo 7a && + ( + cd 7a && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir w && + mkdir x && + git mv z/b w/ && + git mv z/c x/ && + echo d > z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7a-check: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' ' + ( + cd 7a && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename).*z/b.*y/b.*w/b" out && + test_i18ngrep "CONFLICT (rename/rename).*z/c.*y/c.*x/c" out && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -u >out && + test_line_count = 6 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:x/c :0:y/d && + git rev-parse >expect \ + O:z/b O:z/b O:z/b O:z/c O:z/c O:z/c B:z/d && + test_cmp expect actual && + + git hash-object >actual \ + y/b w/b y/c x/c && + git rev-parse >expect \ + O:z/b O:z/b O:z/c O:z/c && + test_cmp expect actual + ) +' + +# Testcase 7b, rename/rename(2to1), but only due to transitive rename +# (Related to testcase 1d) +# Commit O: z/{b,c}, x/d_1, w/d_2 +# Commit A: y/{b,c,d_2}, x/d_1 +# Commit B: z/{b,c,d_1}, w/d_2 +# Expected: y/{b,c}, CONFLICT(rename/rename(2to1): x/d_1, w/d_2 -> y_d) + +test_expect_success '7b-setup: rename/rename(2to1), but only due to transitive rename' ' + test_create_repo 7b && + ( + cd 7b && + + mkdir z && + mkdir x && + mkdir w && + echo b >z/b && + echo c >z/c && + echo d1 > x/d && + echo d2 > w/d && + git add z x w && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git mv w/d y/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + rmdir x && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7b-check: rename/rename(2to1), but only due to transitive rename' ' + ( + cd 7b && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename)" out && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :2:y/d :3:y/d && + git rev-parse >expect \ + O:z/b O:z/c O:w/d O:x/d && + test_cmp expect actual && + + test_path_is_missing y/d && + test_path_is_file y/d~HEAD && + test_path_is_file y/d~B^0 && + + git hash-object >actual \ + y/d~HEAD y/d~B^0 && + git rev-parse >expect \ + O:w/d O:x/d && + test_cmp expect actual + ) +' + +# Testcase 7c, rename/rename(1to...2or3); transitive rename may add complexity +# (Related to testcases 3b and 5c) +# Commit O: z/{b,c}, x/d +# Commit A: y/{b,c}, w/d +# Commit B: z/{b,c,d} +# Expected: y/{b,c}, CONFLICT(x/d -> w/d vs. y/d) +# NOTE: z/ was renamed to y/ so we do want to report +# neither CONFLICT(x/d -> w/d vs. z/d) +# nor CONFLiCT x/d -> w/d vs. y/d vs. z/d) + +test_expect_success '7c-setup: rename/rename(1to...2or3); transitive rename may add complexity' ' + test_create_repo 7c && + ( + cd 7c && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git mv x w && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + rmdir x && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7c-check: rename/rename(1to...2or3); transitive rename may add complexity' ' + ( + cd 7c && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/rename).*x/d.*w/d.*y/d" out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :1:x/d :2:w/d :3:y/d && + git rev-parse >expect \ + O:z/b O:z/c O:x/d O:x/d O:x/d && + test_cmp expect actual + ) +' + +# Testcase 7d, transitive rename involved in rename/delete; how is it reported? +# (Related somewhat to testcases 5b and 8d) +# Commit O: z/{b,c}, x/d +# Commit A: y/{b,c} +# Commit B: z/{b,c,d} +# Expected: y/{b,c}, CONFLICT(delete x/d vs rename to y/d) +# NOTE: z->y so NOT CONFLICT(delete x/d vs rename to z/d) + +test_expect_success '7d-setup: transitive rename involved in rename/delete; how is it reported?' ' + test_create_repo 7d && + ( + cd 7d && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git rm -rf x && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + rmdir x && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7d-check: transitive rename involved in rename/delete; how is it reported?' ' + ( + cd 7d && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :3:y/d && + git rev-parse >expect \ + O:z/b O:z/c O:x/d && + test_cmp expect actual + ) +' + +# Testcase 7e, transitive rename in rename/delete AND dirs in the way +# (Very similar to 'both rename source and destination involved in D/F conflict' from t6022-merge-rename.sh) +# (Also related to testcases 9c and 9d) +# Commit O: z/{b,c}, x/d_1 +# Commit A: y/{b,c,d/g}, x/d/f +# Commit B: z/{b,c,d_1} +# Expected: rename/delete(x/d_1->y/d_1 vs. None) + D/F conflict on y/d +# y/{b,c,d/g}, y/d_1~B^0, x/d/f + +# NOTE: The main path of interest here is d_1 and where it ends up, but +# this is actually a case that has two potential directory renames +# involved and D/F conflict(s), so it makes sense to walk through +# each step. +# +# Commit A renames z/ -> y/. Thus everything that B adds to z/ +# should be instead moved to y/. This gives us the D/F conflict on +# y/d because x/d_1 -> z/d_1 -> y/d_1 conflicts with y/d/g. +# +# Further, commit B renames x/ -> z/, thus everything A adds to x/ +# should instead be moved to z/...BUT we removed z/ and renamed it +# to y/, so maybe everything should move not from x/ to z/, but +# from x/ to z/ to y/. Doing so might make sense from the logic so +# far, but note that commit A had both an x/ and a y/; it did the +# renaming of z/ to y/ and created x/d/f and it clearly made these +# things separate, so it doesn't make much sense to push these +# together. Doing so is what I'd call a doubly transitive rename; +# see testcases 9c and 9d for further discussion of this issue and +# how it's resolved. + +test_expect_success '7e-setup: transitive rename in rename/delete AND dirs in the way' ' + test_create_repo 7e && + ( + cd 7e && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d1 >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git rm x/d && + mkdir -p x/d && + mkdir -p y/d && + echo f >x/d/f && + echo g >y/d/g && + git add x/d/f y/d/g && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/ && + rmdir x && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '7e-check: transitive rename in rename/delete AND dirs in the way' ' + ( + cd 7e && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/delete).*x/d.*y/d" out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >actual \ + :0:x/d/f :0:y/d/g :0:y/b :0:y/c :3:y/d && + git rev-parse >expect \ + A:x/d/f A:y/d/g O:z/b O:z/c O:x/d && + test_cmp expect actual && + + git hash-object y/d~B^0 >actual && + git rev-parse O:x/d >expect && + test_cmp expect actual + ) +' + +########################################################################### +# SECTION 8: Suboptimal merges +# +# As alluded to in the last section, the ruleset we have built up for +# detecting directory renames unfortunately has some special cases where it +# results in slightly suboptimal or non-intuitive behavior. This section +# explores these cases. +# +# To be fair, we already had non-intuitive or suboptimal behavior for most +# of these cases in git before introducing implicit directory rename +# detection, but it'd be nice if there was a modified ruleset out there +# that handled these cases a bit better. +########################################################################### + +# Testcase 8a, Dual-directory rename, one into the others' way +# Commit O. x/{a,b}, y/{c,d} +# Commit A. x/{a,b,e}, y/{c,d,f} +# Commit B. y/{a,b}, z/{c,d} +# +# Possible Resolutions: +# w/o dir-rename detection: y/{a,b,f}, z/{c,d}, x/e +# Currently expected: y/{a,b,e,f}, z/{c,d} +# Optimal: y/{a,b,e}, z/{c,d,f} +# +# Note: Both x and y got renamed and it'd be nice to detect both, and we do +# better with directory rename detection than git did without, but the +# simple rule from section 5 prevents me from handling this as optimally as +# we potentially could. + +test_expect_success '8a-setup: Dual-directory rename, one into the others way' ' + test_create_repo 8a && + ( + cd 8a && + + mkdir x && + mkdir y && + echo a >x/a && + echo b >x/b && + echo c >y/c && + echo d >y/d && + git add x y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo e >x/e && + echo f >y/f && + git add x/e y/f && + test_tick && + git commit -m "A" && + + git checkout B && + git mv y z && + git mv x y && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8a-check: Dual-directory rename, one into the others way' ' + ( + cd 8a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/a HEAD:y/b HEAD:y/e HEAD:y/f HEAD:z/c HEAD:z/d && + git rev-parse >expect \ + O:x/a O:x/b A:x/e A:y/f O:y/c O:y/d && + test_cmp expect actual + ) +' + +# Testcase 8b, Dual-directory rename, one into the others' way, with conflicting filenames +# Commit O. x/{a_1,b_1}, y/{a_2,b_2} +# Commit A. x/{a_1,b_1,e_1}, y/{a_2,b_2,e_2} +# Commit B. y/{a_1,b_1}, z/{a_2,b_2} +# +# w/o dir-rename detection: y/{a_1,b_1,e_2}, z/{a_2,b_2}, x/e_1 +# Currently expected: <same> +# Scary: y/{a_1,b_1}, z/{a_2,b_2}, CONFLICT(add/add, e_1 vs. e_2) +# Optimal: y/{a_1,b_1,e_1}, z/{a_2,b_2,e_2} +# +# Note: Very similar to 8a, except instead of 'e' and 'f' in directories x and +# y, both are named 'e'. Without directory rename detection, neither file +# moves directories. Implement directory rename detection suboptimally, and +# you get an add/add conflict, but both files were added in commit A, so this +# is an add/add conflict where one side of history added both files -- +# something we can't represent in the index. Obviously, we'd prefer the last +# resolution, but our previous rules are too coarse to allow it. Using both +# the rules from section 4 and section 5 save us from the Scary resolution, +# making us fall back to pre-directory-rename-detection behavior for both +# e_1 and e_2. + +test_expect_success '8b-setup: Dual-directory rename, one into the others way, with conflicting filenames' ' + test_create_repo 8b && + ( + cd 8b && + + mkdir x && + mkdir y && + echo a1 >x/a && + echo b1 >x/b && + echo a2 >y/a && + echo b2 >y/b && + git add x y && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo e1 >x/e && + echo e2 >y/e && + git add x/e y/e && + test_tick && + git commit -m "A" && + + git checkout B && + git mv y z && + git mv x y && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8b-check: Dual-directory rename, one into the others way, with conflicting filenames' ' + ( + cd 8b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/a HEAD:y/b HEAD:z/a HEAD:z/b HEAD:x/e HEAD:y/e && + git rev-parse >expect \ + O:x/a O:x/b O:y/a O:y/b A:x/e A:y/e && + test_cmp expect actual + ) +' + +# Testcase 8c, modify/delete or rename+modify/delete? +# (Related to testcases 5b, 8d, and 9h) +# Commit O: z/{b,c,d} +# Commit A: y/{b,c} +# Commit B: z/{b,c,d_modified,e} +# Expected: y/{b,c,e}, CONFLICT(modify/delete: on z/d) +# +# Note: It could easily be argued that the correct resolution here is +# y/{b,c,e}, CONFLICT(rename/delete: z/d -> y/d vs deleted) +# and that the modifed version of d should be present in y/ after +# the merge, just marked as conflicted. Indeed, I previously did +# argue that. But applying directory renames to the side of +# history where a file is merely modified results in spurious +# rename/rename(1to2) conflicts -- see testcase 9h. See also +# notes in 8d. + +test_expect_success '8c-setup: modify/delete or rename+modify/delete?' ' + test_create_repo 8c && + ( + cd 8c && + + mkdir z && + echo b >z/b && + echo c >z/c && + test_seq 1 10 >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/d && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + echo 11 >z/d && + test_chmod +x z/d && + echo e >z/e && + git add z/d z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8c-check: modify/delete or rename+modify/delete' ' + ( + cd 8c && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (modify/delete).* z/d" out && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + :0:y/b :0:y/c :0:y/e :1:z/d :3:z/d && + git rev-parse >expect \ + O:z/b O:z/c B:z/e O:z/d B:z/d && + test_cmp expect actual && + + test_must_fail git rev-parse :2:z/d && + git ls-files -s z/d | grep ^100755 && + test_path_is_file z/d && + test_path_is_missing y/d + ) +' + +# Testcase 8d, rename/delete...or not? +# (Related to testcase 5b; these may appear slightly inconsistent to users; +# Also related to testcases 7d and 7e) +# Commit O: z/{b,c,d} +# Commit A: y/{b,c} +# Commit B: z/{b,c,d,e} +# Expected: y/{b,c,e} +# +# Note: It would also be somewhat reasonable to resolve this as +# y/{b,c,e}, CONFLICT(rename/delete: x/d -> y/d or deleted) +# +# In this case, I'm leaning towards: commit A was the one that deleted z/d +# and it did the rename of z to y, so the two "conflicts" (rename vs. +# delete) are both coming from commit A, which is illogical. Conflicts +# during merging are supposed to be about opposite sides doing things +# differently. + +test_expect_success '8d-setup: rename/delete...or not?' ' + test_create_repo 8d && + ( + cd 8d && + + mkdir z && + echo b >z/b && + echo c >z/c && + test_seq 1 10 >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/d && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + echo e >z/e && + git add z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8d-check: rename/delete...or not?' ' + ( + cd 8d && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/e && + git rev-parse >expect \ + O:z/b O:z/c B:z/e && + test_cmp expect actual + ) +' + +# Testcase 8e, Both sides rename, one side adds to original directory +# Commit O: z/{b,c} +# Commit A: y/{b,c} +# Commit B: w/{b,c}, z/d +# +# Possible Resolutions: +# w/o dir-rename detection: z/d, CONFLICT(z/b -> y/b vs. w/b), +# CONFLICT(z/c -> y/c vs. w/c) +# Currently expected: y/d, CONFLICT(z/b -> y/b vs. w/b), +# CONFLICT(z/c -> y/c vs. w/c) +# Optimal: ?? +# +# Notes: In commit A, directory z got renamed to y. In commit B, directory z +# did NOT get renamed; the directory is still present; instead it is +# considered to have just renamed a subset of paths in directory z +# elsewhere. Therefore, the directory rename done in commit A to z/ +# applies to z/d and maps it to y/d. +# +# It's possible that users would get confused about this, but what +# should we do instead? Silently leaving at z/d seems just as bad or +# maybe even worse. Perhaps we could print a big warning about z/d +# and how we're moving to y/d in this case, but when I started thinking +# about the ramifications of doing that, I didn't know how to rule out +# that opening other weird edge and corner cases so I just punted. + +test_expect_success '8e-setup: Both sides rename, one side adds to original directory' ' + test_create_repo 8e && + ( + cd 8e && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z w && + mkdir z && + echo d >z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '8e-check: Both sides rename, one side adds to original directory' ' + ( + cd 8e && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep CONFLICT.*rename/rename.*z/c.*y/c.*w/c out && + test_i18ngrep CONFLICT.*rename/rename.*z/b.*y/b.*w/b out && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -u >out && + test_line_count = 6 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >actual \ + :1:z/b :2:y/b :3:w/b :1:z/c :2:y/c :3:w/c :0:y/d && + git rev-parse >expect \ + O:z/b O:z/b O:z/b O:z/c O:z/c O:z/c B:z/d && + test_cmp expect actual && + + git hash-object >actual \ + y/b w/b y/c w/c && + git rev-parse >expect \ + O:z/b O:z/b O:z/c O:z/c && + test_cmp expect actual && + + test_path_is_missing z/b && + test_path_is_missing z/c + ) +' + +########################################################################### +# SECTION 9: Other testcases +# +# This section consists of miscellaneous testcases I thought of during +# the implementation which round out the testing. +########################################################################### + +# Testcase 9a, Inner renamed directory within outer renamed directory +# (Related to testcase 1f) +# Commit O: z/{b,c,d/{e,f,g}} +# Commit A: y/{b,c}, x/w/{e,f,g} +# Commit B: z/{b,c,d/{e,f,g,h},i} +# Expected: y/{b,c,i}, x/w/{e,f,g,h} +# NOTE: The only reason this one is interesting is because when a directory +# is split into multiple other directories, we determine by the weight +# of which one had the most paths going to it. A naive implementation +# of that could take the new file in commit B at z/i to x/w/i or x/i. + +test_expect_success '9a-setup: Inner renamed directory within outer renamed directory' ' + test_create_repo 9a && + ( + cd 9a && + + mkdir -p z/d && + echo b >z/b && + echo c >z/c && + echo e >z/d/e && + echo f >z/d/f && + echo g >z/d/g && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir x && + git mv z/d x/w && + git mv z y && + test_tick && + git commit -m "A" && + + git checkout B && + echo h >z/d/h && + echo i >z/i && + git add z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9a-check: Inner renamed directory within outer renamed directory' ' + ( + cd 9a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/i && + git rev-parse >expect \ + O:z/b O:z/c B:z/i && + test_cmp expect actual && + + git rev-parse >actual \ + HEAD:x/w/e HEAD:x/w/f HEAD:x/w/g HEAD:x/w/h && + git rev-parse >expect \ + O:z/d/e O:z/d/f O:z/d/g B:z/d/h && + test_cmp expect actual + ) +' + +# Testcase 9b, Transitive rename with content merge +# (Related to testcase 1c) +# Commit O: z/{b,c}, x/d_1 +# Commit A: y/{b,c}, x/d_2 +# Commit B: z/{b,c,d_3} +# Expected: y/{b,c,d_merged} + +test_expect_success '9b-setup: Transitive rename with content merge' ' + test_create_repo 9b && + ( + cd 9b && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + test_seq 1 10 >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + test_seq 1 11 >x/d && + git add x/d && + test_tick && + git commit -m "A" && + + git checkout B && + test_seq 0 10 >x/d && + git mv x/d z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9b-check: Transitive rename with content merge' ' + ( + cd 9b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + test_seq 0 11 >expected && + test_cmp expected y/d && + git add expected && + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d && + git rev-parse >expect \ + O:z/b O:z/c :0:expected && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:x/d && + test_must_fail git rev-parse HEAD:z/d && + test_path_is_missing z/d && + + test $(git rev-parse HEAD:y/d) != $(git rev-parse O:x/d) && + test $(git rev-parse HEAD:y/d) != $(git rev-parse A:x/d) && + test $(git rev-parse HEAD:y/d) != $(git rev-parse B:z/d) + ) +' + +# Testcase 9c, Doubly transitive rename? +# (Related to testcase 1c, 7e, and 9d) +# Commit O: z/{b,c}, x/{d,e}, w/f +# Commit A: y/{b,c}, x/{d,e,f,g} +# Commit B: z/{b,c,d,e}, w/f +# Expected: y/{b,c,d,e}, x/{f,g} +# +# NOTE: x/f and x/g may be slightly confusing here. The rename from w/f to +# x/f is clear. Let's look beyond that. Here's the logic: +# Commit B renamed x/ -> z/ +# Commit A renamed z/ -> y/ +# So, we could possibly further rename x/f to z/f to y/f, a doubly +# transient rename. However, where does it end? We can chain these +# indefinitely (see testcase 9d). What if there is a D/F conflict +# at z/f/ or y/f/? Or just another file conflict at one of those +# paths? In the case of an N-long chain of transient renamings, +# where do we "abort" the rename at? Can the user make sense of +# the resulting conflict and resolve it? +# +# To avoid this confusion I use the simple rule that if the other side +# of history did a directory rename to a path that your side renamed +# away, then ignore that particular rename from the other side of +# history for any implicit directory renames. + +test_expect_success '9c-setup: Doubly transitive rename?' ' + test_create_repo 9c && + ( + cd 9c && + + mkdir z && + echo b >z/b && + echo c >z/c && + mkdir x && + echo d >x/d && + echo e >x/e && + mkdir w && + echo f >w/f && + git add z x w && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + git mv w/f x/ && + echo g >x/g && + git add x/g && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/d && + git mv x/e z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9c-check: Doubly transitive rename?' ' + ( + cd 9c && + + git checkout A^0 && + + git merge -s recursive B^0 >out && + test_i18ngrep "WARNING: Avoiding applying x -> z rename to x/f" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:y/d HEAD:y/e HEAD:x/f HEAD:x/g && + git rev-parse >expect \ + O:z/b O:z/c O:x/d O:x/e O:w/f A:x/g && + test_cmp expect actual + ) +' + +# Testcase 9d, N-fold transitive rename? +# (Related to testcase 9c...and 1c and 7e) +# Commit O: z/a, y/b, x/c, w/d, v/e, u/f +# Commit A: y/{a,b}, w/{c,d}, u/{e,f} +# Commit B: z/{a,t}, x/{b,c}, v/{d,e}, u/f +# Expected: <see NOTE first> +# +# NOTE: z/ -> y/ (in commit A) +# y/ -> x/ (in commit B) +# x/ -> w/ (in commit A) +# w/ -> v/ (in commit B) +# v/ -> u/ (in commit A) +# So, if we add a file to z, say z/t, where should it end up? In u? +# What if there's another file or directory named 't' in one of the +# intervening directories and/or in u itself? Also, shouldn't the +# same logic that places 't' in u/ also move ALL other files to u/? +# What if there are file or directory conflicts in any of them? If +# we attempted to do N-way (N-fold? N-ary? N-uple?) transitive renames +# like this, would the user have any hope of understanding any +# conflicts or how their working tree ended up? I think not, so I'm +# ruling out N-ary transitive renames for N>1. +# +# Therefore our expected result is: +# z/t, y/a, x/b, w/c, u/d, u/e, u/f +# The reason that v/d DOES get transitively renamed to u/d is that u/ isn't +# renamed somewhere. A slightly sub-optimal result, but it uses fairly +# simple rules that are consistent with what we need for all the other +# testcases and simplifies things for the user. + +test_expect_success '9d-setup: N-way transitive rename?' ' + test_create_repo 9d && + ( + cd 9d && + + mkdir z y x w v u && + echo a >z/a && + echo b >y/b && + echo c >x/c && + echo d >w/d && + echo e >v/e && + echo f >u/f && + git add z y x w v u && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/a y/ && + git mv x/c w/ && + git mv v/e u/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo t >z/t && + git mv y/b x/ && + git mv w/d v/ && + git add z/t && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9d-check: N-way transitive rename?' ' + ( + cd 9d && + + git checkout A^0 && + + git merge -s recursive B^0 >out && + test_i18ngrep "WARNING: Avoiding applying z -> y rename to z/t" out && + test_i18ngrep "WARNING: Avoiding applying y -> x rename to y/a" out && + test_i18ngrep "WARNING: Avoiding applying x -> w rename to x/b" out && + test_i18ngrep "WARNING: Avoiding applying w -> v rename to w/c" out && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >actual \ + HEAD:z/t \ + HEAD:y/a HEAD:x/b HEAD:w/c \ + HEAD:u/d HEAD:u/e HEAD:u/f && + git rev-parse >expect \ + B:z/t \ + O:z/a O:y/b O:x/c \ + O:w/d O:v/e A:u/f && + test_cmp expect actual + ) +' + +# Testcase 9e, N-to-1 whammo +# (Related to testcase 9c...and 1c and 7e) +# Commit O: dir1/{a,b}, dir2/{d,e}, dir3/{g,h}, dirN/{j,k} +# Commit A: dir1/{a,b,c,yo}, dir2/{d,e,f,yo}, dir3/{g,h,i,yo}, dirN/{j,k,l,yo} +# Commit B: combined/{a,b,d,e,g,h,j,k} +# Expected: combined/{a,b,c,d,e,f,g,h,i,j,k,l}, CONFLICT(Nto1) warnings, +# dir1/yo, dir2/yo, dir3/yo, dirN/yo + +test_expect_success '9e-setup: N-to-1 whammo' ' + test_create_repo 9e && + ( + cd 9e && + + mkdir dir1 dir2 dir3 dirN && + echo a >dir1/a && + echo b >dir1/b && + echo d >dir2/d && + echo e >dir2/e && + echo g >dir3/g && + echo h >dir3/h && + echo j >dirN/j && + echo k >dirN/k && + git add dir* && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + echo c >dir1/c && + echo yo >dir1/yo && + echo f >dir2/f && + echo yo >dir2/yo && + echo i >dir3/i && + echo yo >dir3/yo && + echo l >dirN/l && + echo yo >dirN/yo && + git add dir* && + test_tick && + git commit -m "A" && + + git checkout B && + git mv dir1 combined && + git mv dir2/* combined/ && + git mv dir3/* combined/ && + git mv dirN/* combined/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success C_LOCALE_OUTPUT '9e-check: N-to-1 whammo' ' + ( + cd 9e && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + grep "CONFLICT (implicit dir rename): Cannot map more than one path to combined/yo" out >error_line && + grep -q dir1/yo error_line && + grep -q dir2/yo error_line && + grep -q dir3/yo error_line && + grep -q dirN/yo error_line && + + git ls-files -s >out && + test_line_count = 16 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >actual \ + :0:combined/a :0:combined/b :0:combined/c \ + :0:combined/d :0:combined/e :0:combined/f \ + :0:combined/g :0:combined/h :0:combined/i \ + :0:combined/j :0:combined/k :0:combined/l && + git rev-parse >expect \ + O:dir1/a O:dir1/b A:dir1/c \ + O:dir2/d O:dir2/e A:dir2/f \ + O:dir3/g O:dir3/h A:dir3/i \ + O:dirN/j O:dirN/k A:dirN/l && + test_cmp expect actual && + + git rev-parse >actual \ + :0:dir1/yo :0:dir2/yo :0:dir3/yo :0:dirN/yo && + git rev-parse >expect \ + A:dir1/yo A:dir2/yo A:dir3/yo A:dirN/yo && + test_cmp expect actual + ) +' + +# Testcase 9f, Renamed directory that only contained immediate subdirs +# (Related to testcases 1e & 9g) +# Commit O: goal/{a,b}/$more_files +# Commit A: priority/{a,b}/$more_files +# Commit B: goal/{a,b}/$more_files, goal/c +# Expected: priority/{a,b}/$more_files, priority/c + +test_expect_success '9f-setup: Renamed directory that only contained immediate subdirs' ' + test_create_repo 9f && + ( + cd 9f && + + mkdir -p goal/a && + mkdir -p goal/b && + echo foo >goal/a/foo && + echo bar >goal/b/bar && + echo baz >goal/b/baz && + git add goal && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv goal/ priority && + test_tick && + git commit -m "A" && + + git checkout B && + echo c >goal/c && + git add goal/c && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9f-check: Renamed directory that only contained immediate subdirs' ' + ( + cd 9f && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:priority/a/foo \ + HEAD:priority/b/bar \ + HEAD:priority/b/baz \ + HEAD:priority/c && + git rev-parse >expect \ + O:goal/a/foo \ + O:goal/b/bar \ + O:goal/b/baz \ + B:goal/c && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:goal/c + ) +' + +# Testcase 9g, Renamed directory that only contained immediate subdirs, immediate subdirs renamed +# (Related to testcases 1e & 9f) +# Commit O: goal/{a,b}/$more_files +# Commit A: priority/{alpha,bravo}/$more_files +# Commit B: goal/{a,b}/$more_files, goal/c +# Expected: priority/{alpha,bravo}/$more_files, priority/c + +test_expect_success '9g-setup: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' ' + test_create_repo 9g && + ( + cd 9g && + + mkdir -p goal/a && + mkdir -p goal/b && + echo foo >goal/a/foo && + echo bar >goal/b/bar && + echo baz >goal/b/baz && + git add goal && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir priority && + git mv goal/a/ priority/alpha && + git mv goal/b/ priority/beta && + rmdir goal/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo c >goal/c && + git add goal/c && + test_tick && + git commit -m "B" + ) +' + +test_expect_failure '9g-check: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' ' + ( + cd 9g && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:priority/alpha/foo \ + HEAD:priority/beta/bar \ + HEAD:priority/beta/baz \ + HEAD:priority/c && + git rev-parse >expect \ + O:goal/a/foo \ + O:goal/b/bar \ + O:goal/b/baz \ + B:goal/c && + test_cmp expect actual && + test_must_fail git rev-parse HEAD:goal/c + ) +' + +# Testcase 9h, Avoid implicit rename if involved as source on other side +# (Extremely closely related to testcase 3a) +# Commit O: z/{b,c,d_1} +# Commit A: z/{b,c,d_2} +# Commit B: y/{b,c}, x/d_1 +# Expected: y/{b,c}, x/d_2 +# NOTE: If we applied the z/ -> y/ rename to z/d, then we'd end up with +# a rename/rename(1to2) conflict (z/d -> y/d vs. x/d) +test_expect_success '9h-setup: Avoid dir rename on merely modified path' ' + test_create_repo 9h && + ( + cd 9h && + + mkdir z && + echo b >z/b && + echo c >z/c && + printf "1\n2\n3\n4\n5\n6\n7\n8\nd\n" >z/d && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_tick && + echo more >>z/d && + git add z/d && + git commit -m "A" && + + git checkout B && + mkdir y && + mkdir x && + git mv z/b y/ && + git mv z/c y/ && + git mv z/d x/ && + rmdir z && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '9h-check: Avoid dir rename on merely modified path' ' + ( + cd 9h && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + + git rev-parse >actual \ + HEAD:y/b HEAD:y/c HEAD:x/d && + git rev-parse >expect \ + O:z/b O:z/c A:z/d && + test_cmp expect actual + ) +' + +########################################################################### +# Rules suggested by section 9: +# +# If the other side of history did a directory rename to a path that your +# side renamed away, then ignore that particular rename from the other +# side of history for any implicit directory renames. +########################################################################### + +########################################################################### +# SECTION 10: Handling untracked files +# +# unpack_trees(), upon which the recursive merge algorithm is based, aborts +# the operation if untracked or dirty files would be deleted or overwritten +# by the merge. Unfortunately, unpack_trees() does not understand renames, +# and if it doesn't abort, then it muddies up the working directory before +# we even get to the point of detecting renames, so we need some special +# handling, at least in the case of directory renames. +########################################################################### + +# Testcase 10a, Overwrite untracked: normal rename/delete +# Commit O: z/{b,c_1} +# Commit A: z/b + untracked z/c + untracked z/d +# Commit B: z/{b,d_1} +# Expected: Aborted Merge + +# ERROR_MSG(untracked working tree files would be overwritten by merge) + +test_expect_success '10a-setup: Overwrite untracked with normal rename/delete' ' + test_create_repo 10a && + ( + cd 10a && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z/c z/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '10a-check: Overwrite untracked with normal rename/delete' ' + ( + cd 10a && + + git checkout A^0 && + echo very >z/c && + echo important >z/d && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "The following untracked working tree files would be overwritten by merge" err && + + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 4 out && + + echo very >expect && + test_cmp expect z/c && + + echo important >expect && + test_cmp expect z/d && + + git rev-parse HEAD:z/b >actual && + git rev-parse O:z/b >expect && + test_cmp expect actual + ) +' + +# Testcase 10b, Overwrite untracked: dir rename + delete +# Commit O: z/{b,c_1} +# Commit A: y/b + untracked y/{c,d,e} +# Commit B: z/{b,d_1,e} +# Expected: Failed Merge; y/b + untracked y/c + untracked y/d on disk + +# z/c_1 -> z/d_1 rename recorded at stage 3 for y/d + +# ERROR_MSG(refusing to lose untracked file at 'y/d') + +test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' ' + test_create_repo 10b && + ( + cd 10b && + + mkdir z && + echo b >z/b && + echo c >z/c && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm z/c && + git mv z/ y/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z/c z/d && + echo e >z/e && + git add z/e && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '10b-check: Overwrite untracked with dir rename + delete' ' + ( + cd 10b && + + git checkout A^0 && + echo very >y/c && + echo important >y/d && + echo contents >y/e && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/delete).*Version B\^0 of y/d left in tree at y/d~B\^0" out && + test_i18ngrep "Error: Refusing to lose untracked file at y/e; writing to y/e~B\^0 instead" out && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 5 out && + + git rev-parse >actual \ + :0:y/b :3:y/d :3:y/e && + git rev-parse >expect \ + O:z/b O:z/c B:z/e && + test_cmp expect actual && + + echo very >expect && + test_cmp expect y/c && + + echo important >expect && + test_cmp expect y/d && + + echo contents >expect && + test_cmp expect y/e + ) +' + +# Testcase 10c, Overwrite untracked: dir rename/rename(1to2) +# Commit O: z/{a,b}, x/{c,d} +# Commit A: y/{a,b}, w/c, x/d + different untracked y/c +# Commit B: z/{a,b,c}, x/d +# Expected: Failed Merge; y/{a,b} + x/d + untracked y/c + +# CONFLICT(rename/rename) x/c -> w/c vs y/c + +# y/c~B^0 + +# ERROR_MSG(Refusing to lose untracked file at y/c) + +test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2)' ' + test_create_repo 10c && + ( + cd 10c && + + mkdir z x && + echo a >z/a && + echo b >z/b && + echo c >x/c && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir w && + git mv x/c w/c && + git mv z/ y/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/c z/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)' ' + ( + cd 10c && + + git checkout A^0 && + echo important >y/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~B\^0 instead" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :3:y/c && + git rev-parse >expect \ + O:z/a O:z/b O:x/d O:x/c O:x/c O:x/c && + test_cmp expect actual && + + git hash-object y/c~B^0 >actual && + git rev-parse O:x/c >expect && + test_cmp expect actual && + + echo important >expect && + test_cmp expect y/c + ) +' + +# Testcase 10d, Delete untracked w/ dir rename/rename(2to1) +# Commit O: z/{a,b,c_1}, x/{d,e,f_2} +# Commit A: y/{a,b}, x/{d,e,f_2,wham_1} + untracked y/wham +# Commit B: z/{a,b,c_1,wham_2}, y/{d,e} +# Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+ +# CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham +# ERROR_MSG(Refusing to lose untracked file at y/wham) + +test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' ' + test_create_repo 10d && + ( + cd 10d && + + mkdir z x && + echo a >z/a && + echo b >z/b && + echo c >z/c && + echo d >x/d && + echo e >x/e && + echo f >x/f && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/c x/wham && + git mv z/ y/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/f z/wham && + git mv x/ y/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' ' + ( + cd 10d && + + git checkout A^0 && + echo important >y/wham && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose untracked file at y/wham" out && + + git ls-files -s >out && + test_line_count = 6 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 4 out && + + git rev-parse >actual \ + :0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham && + git rev-parse >expect \ + O:z/a O:z/b O:x/d O:x/e O:z/c O:x/f && + test_cmp expect actual && + + test_must_fail git rev-parse :1:y/wham && + + echo important >expect && + test_cmp expect y/wham && + + git hash-object >actual \ + y/wham~B^0 y/wham~HEAD && + git rev-parse >expect \ + O:x/f O:z/c && + test_cmp expect actual + ) +' + +# Testcase 10e, Does git complain about untracked file that's not in the way? +# Commit O: z/{a,b} +# Commit A: y/{a,b} + untracked z/c +# Commit B: z/{a,b,c} +# Expected: y/{a,b,c} + untracked z/c + +test_expect_success '10e-setup: Does git complain about untracked file that is not really in the way?' ' + test_create_repo 10e && + ( + cd 10e && + + mkdir z && + echo a >z/a && + echo b >z/b && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/ y/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo c >z/c && + git add z/c && + test_tick && + git commit -m "B" + ) +' + +test_expect_failure '10e-check: Does git complain about untracked file that is not really in the way?' ' + ( + cd 10e && + + git checkout A^0 && + mkdir z && + echo random >z/c && + + git merge -s recursive B^0 >out 2>err && + test_i18ngrep ! "following untracked working tree files would be overwritten by merge" err && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 3 out && + + git rev-parse >actual \ + :0:y/a :0:y/b :0:y/c && + git rev-parse >expect \ + O:z/a O:z/b B:z/c && + test_cmp expect actual && + + echo random >expect && + test_cmp expect z/c + ) +' + +########################################################################### +# SECTION 11: Handling dirty (not up-to-date) files +# +# unpack_trees(), upon which the recursive merge algorithm is based, aborts +# the operation if untracked or dirty files would be deleted or overwritten +# by the merge. Unfortunately, unpack_trees() does not understand renames, +# and if it doesn't abort, then it muddies up the working directory before +# we even get to the point of detecting renames, so we need some special +# handling. This was true even of normal renames, but there are additional +# codepaths that need special handling with directory renames. Add +# testcases for both renamed-by-directory-rename-detection and standard +# rename cases. +########################################################################### + +# Testcase 11a, Avoid losing dirty contents with simple rename +# Commit O: z/{a,b_v1}, +# Commit A: z/{a,c_v1}, and z/c_v1 has uncommitted mods +# Commit B: z/{a,b_v2} +# Expected: ERROR_MSG(Refusing to lose dirty file at z/c) + +# z/a, staged version of z/c has sha1sum matching B:z/b_v2, +# z/c~HEAD with contents of B:z/b_v2, +# z/c with uncommitted mods on top of A:z/c_v1 + +test_expect_success '11a-setup: Avoid losing dirty contents with simple rename' ' + test_create_repo 11a && + ( + cd 11a && + + mkdir z && + echo a >z/a && + test_seq 1 10 >z/b && + git add z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/b z/c && + test_tick && + git commit -m "A" && + + git checkout B && + echo 11 >>z/b && + git add z/b && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11a-check: Avoid losing dirty contents with simple rename' ' + ( + cd 11a && + + git checkout A^0 && + echo stuff >>z/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "Refusing to lose dirty file at z/c" out && + + test_seq 1 10 >expected && + echo stuff >>expected && + test_cmp expected z/c && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 4 out && + + git rev-parse >actual \ + :0:z/a :2:z/c && + git rev-parse >expect \ + O:z/a B:z/b && + test_cmp expect actual && + + git hash-object z/c~HEAD >actual && + git rev-parse B:z/b >expect && + test_cmp expect actual + ) +' + +# Testcase 11b, Avoid losing dirty file involved in directory rename +# Commit O: z/a, x/{b,c_v1} +# Commit A: z/{a,c_v1}, x/b, and z/c_v1 has uncommitted mods +# Commit B: y/a, x/{b,c_v2} +# Expected: y/{a,c_v2}, x/b, z/c_v1 with uncommitted mods untracked, +# ERROR_MSG(Refusing to lose dirty file at z/c) + + +test_expect_success '11b-setup: Avoid losing dirty file involved in directory rename' ' + test_create_repo 11b && + ( + cd 11b && + + mkdir z x && + echo a >z/a && + echo b >x/b && + test_seq 1 10 >x/c && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv x/c z/c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z y && + echo 11 >>x/c && + git add x/c && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11b-check: Avoid losing dirty file involved in directory rename' ' + ( + cd 11b && + + git checkout A^0 && + echo stuff >>z/c && + + git merge -s recursive B^0 >out 2>err && + test_i18ngrep "Refusing to lose dirty file at z/c" out && + + grep -q stuff z/c && + test_seq 1 10 >expected && + echo stuff >>expected && + test_cmp expected z/c && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -m >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 4 out && + + git rev-parse >actual \ + :0:x/b :0:y/a :0:y/c && + git rev-parse >expect \ + O:x/b O:z/a B:x/c && + test_cmp expect actual && + + git hash-object y/c >actual && + git rev-parse B:x/c >expect && + test_cmp expect actual + ) +' + +# Testcase 11c, Avoid losing not-up-to-date with rename + D/F conflict +# Commit O: y/a, x/{b,c_v1} +# Commit A: y/{a,c_v1}, x/b, and y/c_v1 has uncommitted mods +# Commit B: y/{a,c/d}, x/{b,c_v2} +# Expected: Abort_msg("following files would be overwritten by merge") + +# y/c left untouched (still has uncommitted mods) + +test_expect_success '11c-setup: Avoid losing not-uptodate with rename + D/F conflict' ' + test_create_repo 11c && + ( + cd 11c && + + mkdir y x && + echo a >y/a && + echo b >x/b && + test_seq 1 10 >x/c && + git add y x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv x/c y/c && + test_tick && + git commit -m "A" && + + git checkout B && + mkdir y/c && + echo d >y/c/d && + echo 11 >>x/c && + git add x/c y/c/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11c-check: Avoid losing not-uptodate with rename + D/F conflict' ' + ( + cd 11c && + + git checkout A^0 && + echo stuff >>y/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "following files would be overwritten by merge" err && + + grep -q stuff y/c && + test_seq 1 10 >expected && + echo stuff >>expected && + test_cmp expected y/c && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -m >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 3 out + ) +' + +# Testcase 11d, Avoid losing not-up-to-date with rename + D/F conflict +# Commit O: z/a, x/{b,c_v1} +# Commit A: z/{a,c_v1}, x/b, and z/c_v1 has uncommitted mods +# Commit B: y/{a,c/d}, x/{b,c_v2} +# Expected: D/F: y/c_v2 vs y/c/d) + +# Warning_Msg("Refusing to lose dirty file at z/c) + +# y/{a,c~HEAD,c/d}, x/b, now-untracked z/c_v1 with uncommitted mods + +test_expect_success '11d-setup: Avoid losing not-uptodate with rename + D/F conflict' ' + test_create_repo 11d && + ( + cd 11d && + + mkdir z x && + echo a >z/a && + echo b >x/b && + test_seq 1 10 >x/c && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv x/c z/c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv z y && + mkdir y/c && + echo d >y/c/d && + echo 11 >>x/c && + git add x/c y/c/d && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11d-check: Avoid losing not-uptodate with rename + D/F conflict' ' + ( + cd 11d && + + git checkout A^0 && + echo stuff >>z/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "Refusing to lose dirty file at z/c" out && + + grep -q stuff z/c && + test_seq 1 10 >expected && + echo stuff >>expected && + test_cmp expected z/c && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 5 out && + + git rev-parse >actual \ + :0:x/b :0:y/a :0:y/c/d :3:y/c && + git rev-parse >expect \ + O:x/b O:z/a B:y/c/d B:x/c && + test_cmp expect actual && + + git hash-object y/c~HEAD >actual && + git rev-parse B:x/c >expect && + test_cmp expect actual + ) +' + +# Testcase 11e, Avoid deleting not-up-to-date with dir rename/rename(1to2)/add +# Commit O: z/{a,b}, x/{c_1,d} +# Commit A: y/{a,b,c_2}, x/d, w/c_1, and y/c_2 has uncommitted mods +# Commit B: z/{a,b,c_1}, x/d +# Expected: Failed Merge; y/{a,b} + x/d + +# CONFLICT(rename/rename) x/c_1 -> w/c_1 vs y/c_1 + +# ERROR_MSG(Refusing to lose dirty file at y/c) +# y/c~B^0 has O:x/c_1 contents +# y/c~HEAD has A:y/c_2 contents +# y/c has dirty file from before merge + +test_expect_success '11e-setup: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' ' + test_create_repo 11e && + ( + cd 11e && + + mkdir z x && + echo a >z/a && + echo b >z/b && + echo c >x/c && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/ y/ && + echo different >y/c && + mkdir w && + git mv x/c w/ && + git add y/c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/c z/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' ' + ( + cd 11e && + + git checkout A^0 && + echo mods >>y/c && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose dirty file at y/c" out && + + git ls-files -s >out && + test_line_count = 7 out && + git ls-files -u >out && + test_line_count = 4 out && + git ls-files -o >out && + test_line_count = 4 out && + + echo different >expected && + echo mods >>expected && + test_cmp expected y/c && + + git rev-parse >actual \ + :0:y/a :0:y/b :0:x/d :1:x/c :2:w/c :2:y/c :3:y/c && + git rev-parse >expect \ + O:z/a O:z/b O:x/d O:x/c O:x/c A:y/c O:x/c && + test_cmp expect actual && + + git hash-object >actual \ + y/c~B^0 y/c~HEAD && + git rev-parse >expect \ + O:x/c A:y/c && + test_cmp expect actual + ) +' + +# Testcase 11f, Avoid deleting not-up-to-date w/ dir rename/rename(2to1) +# Commit O: z/{a,b}, x/{c_1,d_2} +# Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods +# Commit B: z/{a,b,wham_2}, x/c_1 +# Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} + +# y/wham with dirty changes from before merge + +# CONFLICT(rename/rename) x/c vs x/d -> y/wham +# ERROR_MSG(Refusing to lose dirty file at y/wham) + +test_expect_success '11f-setup: Avoid deleting not-uptodate with dir rename/rename(2to1)' ' + test_create_repo 11f && + ( + cd 11f && + + mkdir z x && + echo a >z/a && + echo b >z/b && + test_seq 1 10 >x/c && + echo d >x/d && + git add z x && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z/ y/ && + git mv x/c y/wham && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/wham && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rename(2to1)' ' + ( + cd 11f && + + git checkout A^0 && + echo important >>y/wham && + + test_must_fail git merge -s recursive B^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose dirty file at y/wham" out && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 4 out && + + test_seq 1 10 >expected && + echo important >>expected && + test_cmp expected y/wham && + + test_must_fail git rev-parse :1:y/wham && + git hash-object >actual \ + y/wham~B^0 y/wham~HEAD && + git rev-parse >expect \ + O:x/d O:x/c && + test_cmp expect actual && + + git rev-parse >actual \ + :0:y/a :0:y/b :2:y/wham :3:y/wham && + git rev-parse >expect \ + O:z/a O:z/b O:x/c O:x/d && + test_cmp expect actual + ) +' + +########################################################################### +# SECTION 12: Everything else +# +# Tests suggested by others. Tests added after implementation completed +# and submitted. Grab bag. +########################################################################### + +# Testcase 12a, Moving one directory hierarchy into another +# (Related to testcase 9a) +# Commit O: node1/{leaf1,leaf2}, node2/{leaf3,leaf4} +# Commit A: node1/{leaf1,leaf2,node2/{leaf3,leaf4}} +# Commit B: node1/{leaf1,leaf2,leaf5}, node2/{leaf3,leaf4,leaf6} +# Expected: node1/{leaf1,leaf2,leaf5,node2/{leaf3,leaf4,leaf6}} + +test_expect_success '12a-setup: Moving one directory hierarchy into another' ' + test_create_repo 12a && + ( + cd 12a && + + mkdir -p node1 node2 && + echo leaf1 >node1/leaf1 && + echo leaf2 >node1/leaf2 && + echo leaf3 >node2/leaf3 && + echo leaf4 >node2/leaf4 && + git add node1 node2 && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv node2/ node1/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo leaf5 >node1/leaf5 && + echo leaf6 >node2/leaf6 && + git add node1 node2 && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '12a-check: Moving one directory hierarchy into another' ' + ( + cd 12a && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 6 out && + + git rev-parse >actual \ + HEAD:node1/leaf1 HEAD:node1/leaf2 HEAD:node1/leaf5 \ + HEAD:node1/node2/leaf3 \ + HEAD:node1/node2/leaf4 \ + HEAD:node1/node2/leaf6 && + git rev-parse >expect \ + O:node1/leaf1 O:node1/leaf2 B:node1/leaf5 \ + O:node2/leaf3 \ + O:node2/leaf4 \ + B:node2/leaf6 && + test_cmp expect actual + ) +' + +# Testcase 12b, Moving two directory hierarchies into each other +# (Related to testcases 1c and 12c) +# Commit O: node1/{leaf1, leaf2}, node2/{leaf3, leaf4} +# Commit A: node1/{leaf1, leaf2, node2/{leaf3, leaf4}} +# Commit B: node2/{leaf3, leaf4, node1/{leaf1, leaf2}} +# Expected: node1/node2/node1/{leaf1, leaf2}, +# node2/node1/node2/{leaf3, leaf4} +# NOTE: Without directory renames, we would expect +# node2/node1/{leaf1, leaf2}, +# node1/node2/{leaf3, leaf4} +# with directory rename detection, we note that +# commit A renames node2/ -> node1/node2/ +# commit B renames node1/ -> node2/node1/ +# therefore, applying those directory renames to the initial result +# (making all four paths experience a transitive renaming), yields +# the expected result. +# +# You may ask, is it weird to have two directories rename each other? +# To which, I can do no more than shrug my shoulders and say that +# even simple rules give weird results when given weird inputs. + +test_expect_success '12b-setup: Moving one directory hierarchy into another' ' + test_create_repo 12b && + ( + cd 12b && + + mkdir -p node1 node2 && + echo leaf1 >node1/leaf1 && + echo leaf2 >node1/leaf2 && + echo leaf3 >node2/leaf3 && + echo leaf4 >node2/leaf4 && + git add node1 node2 && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv node2/ node1/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv node1/ node2/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '12b-check: Moving one directory hierarchy into another' ' + ( + cd 12b && + + git checkout A^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 4 out && + + git rev-parse >actual \ + HEAD:node1/node2/node1/leaf1 \ + HEAD:node1/node2/node1/leaf2 \ + HEAD:node2/node1/node2/leaf3 \ + HEAD:node2/node1/node2/leaf4 && + git rev-parse >expect \ + O:node1/leaf1 \ + O:node1/leaf2 \ + O:node2/leaf3 \ + O:node2/leaf4 && + test_cmp expect actual + ) +' + +# Testcase 12c, Moving two directory hierarchies into each other w/ content merge +# (Related to testcase 12b) +# Commit O: node1/{ leaf1_1, leaf2_1}, node2/{leaf3_1, leaf4_1} +# Commit A: node1/{ leaf1_2, leaf2_2, node2/{leaf3_2, leaf4_2}} +# Commit B: node2/{node1/{leaf1_3, leaf2_3}, leaf3_3, leaf4_3} +# Expected: Content merge conflicts for each of: +# node1/node2/node1/{leaf1, leaf2}, +# node2/node1/node2/{leaf3, leaf4} +# NOTE: This is *exactly* like 12c, except that every path is modified on +# each side of the merge. + +test_expect_success '12c-setup: Moving one directory hierarchy into another w/ content merge' ' + test_create_repo 12c && + ( + cd 12c && + + mkdir -p node1 node2 && + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf1\n" >node1/leaf1 && + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf2\n" >node1/leaf2 && + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf3\n" >node2/leaf3 && + printf "1\n2\n3\n4\n5\n6\n7\n8\nleaf4\n" >node2/leaf4 && + git add node1 node2 && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv node2/ node1/ && + for i in `git ls-files`; do echo side A >>$i; done && + git add -u && + test_tick && + git commit -m "A" && + + git checkout B && + git mv node1/ node2/ && + for i in `git ls-files`; do echo side B >>$i; done && + git add -u && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '12c-check: Moving one directory hierarchy into another w/ content merge' ' + ( + cd 12c && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 && + + git ls-files -u >out && + test_line_count = 12 out && + + git rev-parse >actual \ + :1:node1/node2/node1/leaf1 \ + :1:node1/node2/node1/leaf2 \ + :1:node2/node1/node2/leaf3 \ + :1:node2/node1/node2/leaf4 \ + :2:node1/node2/node1/leaf1 \ + :2:node1/node2/node1/leaf2 \ + :2:node2/node1/node2/leaf3 \ + :2:node2/node1/node2/leaf4 \ + :3:node1/node2/node1/leaf1 \ + :3:node1/node2/node1/leaf2 \ + :3:node2/node1/node2/leaf3 \ + :3:node2/node1/node2/leaf4 && + git rev-parse >expect \ + O:node1/leaf1 \ + O:node1/leaf2 \ + O:node2/leaf3 \ + O:node2/leaf4 \ + A:node1/leaf1 \ + A:node1/leaf2 \ + A:node1/node2/leaf3 \ + A:node1/node2/leaf4 \ + B:node2/node1/leaf1 \ + B:node2/node1/leaf2 \ + B:node2/leaf3 \ + B:node2/leaf4 && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t6044-merge-unrelated-index-changes.sh b/t/t6044-merge-unrelated-index-changes.sh index 23b86fb977..5e3779ebc9 100755 --- a/t/t6044-merge-unrelated-index-changes.sh +++ b/t/t6044-merge-unrelated-index-changes.sh @@ -82,7 +82,8 @@ test_expect_success 'ff update, important file modified' ' touch subdir/e && git add subdir/e && - test_must_fail git merge E^0 + test_must_fail git merge E^0 && + test_path_is_missing .git/MERGE_HEAD ' test_expect_success 'resolve, trivial' ' @@ -91,7 +92,8 @@ test_expect_success 'resolve, trivial' ' touch random_file && git add random_file && - test_must_fail git merge -s resolve C^0 + test_must_fail git merge -s resolve C^0 && + test_path_is_missing .git/MERGE_HEAD ' test_expect_success 'resolve, non-trivial' ' @@ -100,7 +102,8 @@ test_expect_success 'resolve, non-trivial' ' touch random_file && git add random_file && - test_must_fail git merge -s resolve D^0 + test_must_fail git merge -s resolve D^0 && + test_path_is_missing .git/MERGE_HEAD ' test_expect_success 'recursive' ' @@ -109,7 +112,8 @@ test_expect_success 'recursive' ' touch random_file && git add random_file && - test_must_fail git merge -s recursive C^0 + test_must_fail git merge -s recursive C^0 && + test_path_is_missing .git/MERGE_HEAD ' test_expect_success 'recursive, when merge branch matches merge base' ' @@ -118,7 +122,45 @@ test_expect_success 'recursive, when merge branch matches merge base' ' touch random_file && git add random_file && - test_must_fail git merge -s recursive F^0 + test_must_fail git merge -s recursive F^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'merge-recursive, when index==head but head!=HEAD' ' + git reset --hard && + git checkout C^0 && + + # Make index match B + git diff C B -- | git apply --cached && + # Merge B & F, with B as "head" + git merge-recursive A -- B F > out && + test_i18ngrep "Already up to date" out +' + +test_expect_success 'recursive, when file has staged changes not matching HEAD nor what a merge would give' ' + git reset --hard && + git checkout B^0 && + + mkdir subdir && + test_seq 1 10 >subdir/a && + git add subdir/a && + + # We have staged changes; merge should error out + test_must_fail git merge -s recursive E^0 2>err && + test_i18ngrep "changes to the following files would be overwritten" err +' + +test_expect_success 'recursive, when file has staged changes matching what a merge would give' ' + git reset --hard && + git checkout B^0 && + + mkdir subdir && + test_seq 1 11 >subdir/a && + git add subdir/a && + + # We have staged changes; merge should error out + test_must_fail git merge -s recursive E^0 2>err && + test_i18ngrep "changes to the following files would be overwritten" err ' test_expect_success 'octopus, unrelated file touched' ' @@ -127,7 +169,8 @@ test_expect_success 'octopus, unrelated file touched' ' touch random_file && git add random_file && - test_must_fail git merge C^0 D^0 + test_must_fail git merge C^0 D^0 && + test_path_is_missing .git/MERGE_HEAD ' test_expect_success 'octopus, related file removed' ' @@ -136,7 +179,8 @@ test_expect_success 'octopus, related file removed' ' git rm b && - test_must_fail git merge C^0 D^0 + test_must_fail git merge C^0 D^0 && + test_path_is_missing .git/MERGE_HEAD ' test_expect_success 'octopus, related file modified' ' @@ -145,7 +189,8 @@ test_expect_success 'octopus, related file modified' ' echo 12 >>a && git add a && - test_must_fail git merge C^0 D^0 + test_must_fail git merge C^0 D^0 && + test_path_is_missing .git/MERGE_HEAD ' test_expect_success 'ours' ' @@ -154,7 +199,8 @@ test_expect_success 'ours' ' touch random_file && git add random_file && - test_must_fail git merge -s ours C^0 + test_must_fail git merge -s ours C^0 && + test_path_is_missing .git/MERGE_HEAD ' test_expect_success 'subtree' ' @@ -163,7 +209,8 @@ test_expect_success 'subtree' ' touch random_file && git add random_file && - test_must_fail git merge -s subtree E^0 + test_must_fail git merge -s subtree E^0 && + test_path_is_missing .git/MERGE_HEAD ' test_done diff --git a/t/t6046-merge-skip-unneeded-updates.sh b/t/t6046-merge-skip-unneeded-updates.sh new file mode 100755 index 0000000000..38e24f787c --- /dev/null +++ b/t/t6046-merge-skip-unneeded-updates.sh @@ -0,0 +1,763 @@ +#!/bin/sh + +test_description="merge cases" + +# The setup for all of them, pictorially, is: +# +# A +# o +# / \ +# O o ? +# \ / +# o +# B +# +# To help make it easier to follow the flow of tests, they have been +# divided into sections and each test will start with a quick explanation +# of what commits O, A, and B contain. +# +# Notation: +# z/{b,c} means files z/b and z/c both exist +# x/d_1 means file x/d exists with content d1. (Purpose of the +# underscore notation is to differentiate different +# files that might be renamed into each other's paths.) + +. ./test-lib.sh + + +########################################################################### +# SECTION 1: Cases involving no renames (one side has subset of changes of +# the other side) +########################################################################### + +# Testcase 1a, Changes on A, subset of changes on B +# Commit O: b_1 +# Commit A: b_2 +# Commit B: b_3 +# Expected: b_2 + +test_expect_success '1a-setup: Modify(A)/Modify(B), change on B subset of A' ' + test_create_repo 1a && + ( + cd 1a && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '1a-check-L: Modify(A)/Modify(B), change on B subset of A' ' + test_when_finished "git -C 1a reset --hard" && + test_when_finished "git -C 1a clean -fd" && + ( + cd 1a && + + git checkout A^0 && + + test-tool chmtime =31337 b && + test-tool chmtime -v +0 b >expected-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "Skipped b" out && + test_must_be_empty err && + + test-tool chmtime -v +0 b >actual-mtime && + test_cmp expected-mtime actual-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:b && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object b >actual && + git rev-parse A:b >expect && + test_cmp expect actual + ) +' + +test_expect_success '1a-check-R: Modify(A)/Modify(B), change on B subset of A' ' + test_when_finished "git -C 1a reset --hard" && + test_when_finished "git -C 1a clean -fd" && + ( + cd 1a && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep "Auto-merging b" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:b && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object b >actual && + git rev-parse A:b >expect && + test_cmp expect actual + ) +' + + +########################################################################### +# SECTION 2: Cases involving basic renames +########################################################################### + +# Testcase 2a, Changes on A, rename on B +# Commit O: b_1 +# Commit A: b_2 +# Commit B: c_1 +# Expected: c_2 + +test_expect_success '2a-setup: Modify(A)/rename(B)' ' + test_create_repo 2a && + ( + cd 2a && + + test_seq 1 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 1 11 >b && + git add b && + test_tick && + git commit -m "A" && + + git checkout B && + git mv b c && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2a-check-L: Modify/rename, merge into modify side' ' + test_when_finished "git -C 2a reset --hard" && + test_when_finished "git -C 2a clean -fd" && + ( + cd 2a && + + git checkout A^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep ! "Skipped c" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:b >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +test_expect_success '2a-check-R: Modify/rename, merge into rename side' ' + test_when_finished "git -C 2a reset --hard" && + test_when_finished "git -C 2a clean -fd" && + ( + cd 2a && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep ! "Skipped c" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:b >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +# Testcase 2b, Changed and renamed on A, subset of changes on B +# Commit O: b_1 +# Commit A: c_2 +# Commit B: b_3 +# Expected: c_2 + +test_expect_success '2b-setup: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_create_repo 2b && + ( + cd 2b && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + git mv b c && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2b-check-L: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_when_finished "git -C 2b reset --hard" && + test_when_finished "git -C 2b clean -fd" && + ( + cd 2b && + + git checkout A^0 && + + test-tool chmtime =31337 c && + test-tool chmtime -v +0 c >expected-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "Skipped c" out && + test_must_be_empty err && + + test-tool chmtime -v +0 c >actual-mtime && + test_cmp expected-mtime actual-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:c && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:c >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +test_expect_success '2b-check-R: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_when_finished "git -C 2b reset --hard" && + test_when_finished "git -C 2b clean -fd" && + ( + cd 2b && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep "Auto-merging c" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:c && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:c >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +# Testcase 2c, Changes on A, rename on B +# Commit O: b_1 +# Commit A: b_2, c_3 +# Commit B: c_1 +# Expected: rename/add conflict c_2 vs c_3 +# +# NOTE: Since A modified b_1->b_2, and B renamed b_1->c_1, the threeway +# merge of those files should result in c_2. We then should have a +# rename/add conflict between c_2 and c_3. However, if we note in +# merge_content() that A had the right contents (b_2 has same +# contents as c_2, just at a different name), and that A had the +# right path present (c_3 existed) and thus decides that it can +# skip the update, then we're in trouble. This test verifies we do +# not make that particular mistake. + +test_expect_success '2c-setup: Modify b & add c VS rename b->c' ' + test_create_repo 2c && + ( + cd 2c && + + test_seq 1 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 1 11 >b && + echo whatever >c && + git add b c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv b c && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '2c-check: Modify b & add c VS rename b->c' ' + ( + cd 2c && + + git checkout A^0 && + + GIT_MERGE_VERBOSITY=3 && + export GIT_MERGE_VERBOSITY && + test_must_fail git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "CONFLICT (rename/add): Rename b->c" out && + test_i18ngrep ! "Skipped c" out && + test_must_be_empty err + + # FIXME: rename/add conflicts are horribly broken right now; + # when I get back to my patch series fixing it and + # rename/rename(2to1) conflicts to bring them in line with + # how add/add conflicts behave, then checks like the below + # could be added. But that patch series is waiting until + # the rename-directory-detection series lands, which this + # is part of. And in the mean time, I do not want to further + # enforce broken behavior. So for now, the main test is the + # one above that err is an empty file. + + #git ls-files -s >index_files && + #test_line_count = 2 index_files && + + #git rev-parse >actual :2:c :3:c && + #git rev-parse >expect A:b A:c && + #test_cmp expect actual && + + #git cat-file -p A:b >>merged && + #git cat-file -p A:c >>merge-me && + #>empty && + #test_must_fail git merge-file \ + # -L "Temporary merge branch 1" \ + # -L "" \ + # -L "Temporary merge branch 2" \ + # merged empty merge-me && + #sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && + + #git hash-object c >actual && + #git hash-object merged-internal >expect && + #test_cmp expect actual && + + #test_path_is_missing b + ) +' + + +########################################################################### +# SECTION 3: Cases involving directory renames +# +# NOTE: +# Directory renames only apply when one side renames a directory, and the +# other side adds or renames a path into that directory. Applying the +# directory rename to that new path creates a new pathname that didn't +# exist on either side of history. Thus, it is impossible for the +# merge contents to already be at the right path, so all of these checks +# exist just to make sure that updates are not skipped. +########################################################################### + +# Testcase 3a, Change + rename into dir foo on A, dir rename foo->bar on B +# Commit O: bq_1, foo/whatever +# Commit A: foo/{bq_2, whatever} +# Commit B: bq_1, bar/whatever +# Expected: bar/{bq_2, whatever} + +test_expect_success '3a-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_create_repo 3a && + ( + cd 3a && + + mkdir foo && + test_seq 1 10 >bq && + test_write_lines a b c d e f g h i j k >foo/whatever && + git add bq foo/whatever && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 1 11 >bq && + git add bq && + git mv bq foo/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv foo/ bar/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '3a-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_when_finished "git -C 3a reset --hard" && + test_when_finished "git -C 3a clean -fd" && + ( + cd 3a && + + git checkout A^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep ! "Skipped bar/bq" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect A:foo/bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse A:foo/bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +test_expect_success '3a-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_when_finished "git -C 3a reset --hard" && + test_when_finished "git -C 3a clean -fd" && + ( + cd 3a && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep ! "Skipped bar/bq" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect A:foo/bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse A:foo/bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +# Testcase 3b, rename into dir foo on A, dir rename foo->bar + change on B +# Commit O: bq_1, foo/whatever +# Commit A: foo/{bq_1, whatever} +# Commit B: bq_2, bar/whatever +# Expected: bar/{bq_2, whatever} + +test_expect_success '3b-setup: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_create_repo 3b && + ( + cd 3b && + + mkdir foo && + test_seq 1 10 >bq && + test_write_lines a b c d e f g h i j k >foo/whatever && + git add bq foo/whatever && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv bq foo/ && + test_tick && + git commit -m "A" && + + git checkout B && + test_seq 1 11 >bq && + git add bq && + git mv foo/ bar/ && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '3b-check-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_when_finished "git -C 3b reset --hard" && + test_when_finished "git -C 3b clean -fd" && + ( + cd 3b && + + git checkout A^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep ! "Skipped bar/bq" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect B:bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse B:bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +test_expect_success '3b-check-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_when_finished "git -C 3b reset --hard" && + test_when_finished "git -C 3b clean -fd" && + ( + cd 3b && + + git checkout B^0 && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + test_i18ngrep ! "Skipped bar/bq" out && + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect B:bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse B:bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +########################################################################### +# SECTION 4: Cases involving dirty changes +########################################################################### + +# Testcase 4a, Changed on A, subset of changes on B, locally modified +# Commit O: b_1 +# Commit A: b_2 +# Commit B: b_3 +# Working copy: b_4 +# Expected: b_2 for merge, b_4 in working copy + +test_expect_success '4a-setup: Change on A, change on B subset of A, dirty mods present' ' + test_create_repo 4a && + ( + cd 4a && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +' + +# NOTE: For as long as we continue using unpack_trees() without index_only +# set to true, it will error out on a case like this claiming the the locally +# modified file would be overwritten by the merge. Getting this testcase +# correct requires doing the merge in-memory first, then realizing that no +# updates to the file are necessary, and thus that we can just leave the path +# alone. +test_expect_failure '4a-check: Change on A, change on B subset of A, dirty mods present' ' + test_when_finished "git -C 4a reset --hard" && + test_when_finished "git -C 4a clean -fd" && + ( + cd 4a && + + git checkout A^0 && + echo "File rewritten" >b && + + test-tool chmtime =31337 b && + test-tool chmtime -v +0 b >expected-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "Skipped b" out && + test_must_be_empty err && + + test-tool chmtime -v +0 b >actual-mtime && + test_cmp expected-mtime actual-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual :0:b && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object b >actual && + echo "File rewritten" | git hash-object --stdin >expect && + test_cmp expect actual + ) +' + +# Testcase 4b, Changed+renamed on A, subset of changes on B, locally modified +# Commit O: b_1 +# Commit A: c_2 +# Commit B: b_3 +# Working copy: c_4 +# Expected: c_2 + +test_expect_success '4b-setup: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' + test_create_repo 4b && + ( + cd 4b && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + git mv b c && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +' + +test_expect_success '4b-check: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' + test_when_finished "git -C 4b reset --hard" && + test_when_finished "git -C 4b clean -fd" && + ( + cd 4b && + + git checkout A^0 && + echo "File rewritten" >c && + + test-tool chmtime =31337 c && + test-tool chmtime -v +0 c >expected-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "Skipped c" out && + test_must_be_empty err && + + test-tool chmtime -v +0 c >actual-mtime && + test_cmp expected-mtime actual-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual :0:c && + git rev-parse >expect A:c && + test_cmp expect actual && + + git hash-object c >actual && + echo "File rewritten" | git hash-object --stdin >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +test_done diff --git a/t/t6050-replace.sh b/t/t6050-replace.sh index c630aba657..86374a9c52 100755 --- a/t/t6050-replace.sh +++ b/t/t6050-replace.sh @@ -4,8 +4,6 @@ # test_description='Tests replace refs functionality' -exec </dev/null - . ./test-lib.sh . "$TEST_DIRECTORY/lib-gpg.sh" @@ -115,6 +113,12 @@ test_expect_success 'test GIT_NO_REPLACE_OBJECTS env variable' ' GIT_NO_REPLACE_OBJECTS=1 git show $HASH2 | grep "A U Thor" ' +test_expect_success 'test core.usereplacerefs config option' ' + test_config core.usereplacerefs false && + git cat-file commit $HASH2 | grep "author A U Thor" && + git show $HASH2 | grep "A U Thor" +' + cat >tag.sig <<EOF object $HASH2 type commit @@ -444,4 +448,32 @@ test_expect_success GPG '--graft on a commit with a mergetag' ' git replace -d $HASH10 ' +test_expect_success '--convert-graft-file' ' + git checkout -b with-graft-file && + test_commit root2 && + git reset --hard root2^ && + test_commit root1 && + test_commit after-root1 && + test_tick && + git merge -m merge-root2 root2 && + + : add and convert graft file && + printf "%s\n%s %s\n\n# comment\n%s\n" \ + $(git rev-parse HEAD^^ HEAD^ HEAD^^ HEAD^2) \ + >.git/info/grafts && + git replace --convert-graft-file && + test_path_is_missing .git/info/grafts && + + : verify that the history is now "grafted" && + git rev-list HEAD >out && + test_line_count = 4 out && + + : create invalid graft file and verify that it is not deleted && + test_when_finished "rm -f .git/info/grafts" && + echo $EMPTY_BLOB $EMPTY_TREE >.git/info/grafts && + test_must_fail git replace --convert-graft-file 2>err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" err && + test_i18ngrep "$EMPTY_BLOB $EMPTY_TREE" .git/info/grafts +' + test_done diff --git a/t/t6060-merge-index.sh b/t/t6060-merge-index.sh index debadbd299..ddf34f0115 100755 --- a/t/t6060-merge-index.sh +++ b/t/t6060-merge-index.sh @@ -44,8 +44,7 @@ test_expect_success 'read-tree does not resolve content merge' ' test_expect_success 'git merge-index git-merge-one-file resolves' ' git merge-index git-merge-one-file -a && git diff-files --name-only --diff-filter=U >unmerged && - >expect && - test_cmp expect unmerged && + test_must_be_empty unmerged && test_cmp expect-merged file && git cat-file blob :file >file-index && test_cmp expect-merged file-index diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 8c617981a3..7683e4a114 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -214,4 +214,12 @@ test_expect_success 'rev-list merge^-1x (garbage after ^-1)' ' test_must_fail git rev-list merge^-1x ' +test_expect_success 'rev-parse $garbage^@ does not segfault' ' + test_must_fail git rev-parse $EMPTY_TREE^@ +' + +test_expect_success 'rev-parse $garbage...$garbage does not segfault' ' + test_must_fail git rev-parse $EMPTY_TREE...$EMPTY_BLOB +' + test_done diff --git a/t/t6111-rev-list-treesame.sh b/t/t6111-rev-list-treesame.sh index 32474c23d3..4244638285 100755 --- a/t/t6111-rev-list-treesame.sh +++ b/t/t6111-rev-list-treesame.sh @@ -20,7 +20,7 @@ note () { } unnote () { - git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\))\([ ]\)|\1\2|g" + git name-rev --tags --stdin | sed -e "s|$OID_REGEX (tags/\([^)]*\))\([ ]\)|\1\2|g" } test_expect_success setup ' diff --git a/t/t6112-rev-list-filters-objects.sh b/t/t6112-rev-list-filters-objects.sh index 0a37dd5f97..d4ff0b3bef 100755 --- a/t/t6112-rev-list-filters-objects.sh +++ b/t/t6112-rev-list-filters-objects.sh @@ -113,12 +113,11 @@ test_expect_success 'verify blob:limit=1k' ' ' test_expect_success 'verify blob:limit=1m' ' - cat </dev/null >expected && git -C r2 rev-list HEAD --quiet --objects --filter-print-omitted --filter=blob:limit=1m \ | awk -f print_1.awk \ | sed "s/~//" \ | sort >observed && - test_cmp observed expected + test_must_be_empty observed ' # Test sparse:path=<path> filter. diff --git a/t/t6120-describe.sh b/t/t6120-describe.sh index bae78c4e89..d639d94696 100755 --- a/t/t6120-describe.sh +++ b/t/t6120-describe.sh @@ -121,10 +121,9 @@ test_expect_success 'describe --contains defaults to HEAD without commit-ish' ' test_cmp expect actual ' -: >err.expect check_describe tags/A --all A^0 test_expect_success 'no warning was displayed for A' ' - test_cmp err.expect err.actual + test_must_be_empty err.actual ' test_expect_success 'rename tag A to Q locally' ' @@ -383,7 +382,7 @@ test_expect_success 'describe complains about tree object' ' ' test_expect_success 'describe complains about missing object' ' - test_must_fail git describe $_z40 + test_must_fail git describe $ZERO_OID ' test_done diff --git a/t/t6130-pathspec-noglob.sh b/t/t6130-pathspec-noglob.sh index 658353277e..37760233a5 100755 --- a/t/t6130-pathspec-noglob.sh +++ b/t/t6130-pathspec-noglob.sh @@ -97,9 +97,8 @@ test_expect_success 'no-glob option matches literally (bracket)' ' ' test_expect_success 'no-glob option disables :(literal)' ' - : >expect && git --literal-pathspecs log --format=%s -- ":(literal)foo" >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'no-glob environment variable works' ' @@ -130,9 +129,8 @@ test_expect_success '**/ works with :(glob)' ' ' test_expect_success '**/ does not work with --noglob-pathspecs' ' - : >expect && git --noglob-pathspecs log --format=%s -- "**/bar" >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success '**/ works with :(glob) and --noglob-pathspecs' ' @@ -154,9 +152,8 @@ test_expect_success '**/ works with --glob-pathspecs' ' ' test_expect_success '**/ does not work with :(literal) and --glob-pathspecs' ' - : >expect && git --glob-pathspecs log --format=%s -- ":(literal)**/bar" >actual && - test_cmp expect actual + test_must_be_empty actual ' test_done diff --git a/t/t6200-fmt-merge-msg.sh b/t/t6200-fmt-merge-msg.sh index a54a52aaa4..93f23cfa82 100755 --- a/t/t6200-fmt-merge-msg.sh +++ b/t/t6200-fmt-merge-msg.sh @@ -366,8 +366,6 @@ test_expect_success 'merge-msg with nothing to merge' ' test_unconfig merge.log && test_config merge.summary yes && - >empty && - ( cd remote && git checkout -b unrelated && @@ -376,7 +374,7 @@ test_expect_success 'merge-msg with nothing to merge' ' git fmt-merge-msg <.git/FETCH_HEAD >../actual ) && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'merge-msg tag' ' diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh index 295d1475bd..97bfbee6e8 100755 --- a/t/t6300-for-each-ref.sh +++ b/t/t6300-for-each-ref.sh @@ -310,7 +310,7 @@ test_expect_success 'exercise strftime with odd fields' ' echo >expected && git for-each-ref --format="%(authordate:format:)" refs/heads >actual && test_cmp expected actual && - long="long format -- $_z40$_z40$_z40$_z40$_z40$_z40$_z40" && + long="long format -- $ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID$ZERO_OID" && echo $long >expected && git for-each-ref --format="%(authordate:format:$long)" refs/heads >actual && test_cmp expected actual @@ -715,6 +715,29 @@ test_expect_success 'basic atom: head contents:trailers' ' test_cmp expect actual.clean ' +test_expect_success 'trailer parsing not fooled by --- line' ' + git commit --allow-empty -F - <<-\EOF && + this is the subject + + This is the body. The message has a "---" line which would confuse a + message+patch parser. But here we know we have only a commit message, + so we get it right. + + trailer: wrong + --- + This is more body. + + trailer: right + EOF + + { + echo "trailer: right" && + echo + } >expect && + git for-each-ref --format="%(trailers)" refs/heads/master >actual && + test_cmp expect actual +' + test_expect_success 'Add symbolic ref for the following tests' ' git symbolic-ref refs/heads/sym refs/heads/master ' @@ -795,4 +818,14 @@ test_expect_success ':remotename and :remoteref' ' ) ' +test_expect_success 'for-each-ref --ignore-case ignores case' ' + git for-each-ref --format="%(refname)" refs/heads/MASTER >actual && + test_must_be_empty actual && + + echo refs/heads/master >expect && + git for-each-ref --format="%(refname)" --ignore-case \ + refs/heads/MASTER >actual && + test_cmp expect actual +' + test_done diff --git a/t/t6301-for-each-ref-errors.sh b/t/t6301-for-each-ref-errors.sh index c734ce2388..49cc65bb58 100755 --- a/t/t6301-for-each-ref-errors.sh +++ b/t/t6301-for-each-ref-errors.sh @@ -4,7 +4,7 @@ test_description='for-each-ref errors for broken refs' . ./test-lib.sh -ZEROS=$_z40 +ZEROS=$ZERO_OID MISSING=abababababababababababababababababababab test_expect_success setup ' diff --git a/t/t6500-gc.sh b/t/t6500-gc.sh index d5255dd576..818435f04e 100755 --- a/t/t6500-gc.sh +++ b/t/t6500-gc.sh @@ -5,6 +5,13 @@ test_description='basic git gc tests . ./test-lib.sh +test_expect_success 'setup' ' + # do not let the amount of physical memory affects gc + # behavior, make sure we always pack everything to one pack by + # default + git config gc.bigPackThreshold 2g +' + test_expect_success 'gc empty repository' ' git gc ' @@ -43,6 +50,31 @@ test_expect_success 'gc is not aborted due to a stale symref' ' ) ' +test_expect_success 'gc --keep-largest-pack' ' + test_create_repo keep-pack && + ( + cd keep-pack && + test_commit one && + test_commit two && + test_commit three && + git gc && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 1 pack-list && + BASE_PACK=.git/objects/pack/pack-*.pack && + test_commit four && + git repack -d && + test_commit five && + git repack -d && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 3 pack-list && + git gc --keep-largest-pack && + ( cd .git/objects/pack && ls *.pack ) >pack-list && + test_line_count = 2 pack-list && + test_path_is_file $BASE_PACK && + git fsck + ) +' + test_expect_success 'auto gc with too many loose objects does not attempt to create bitmaps' ' test_config gc.auto 3 && test_config gc.autodetach false && diff --git a/t/t6501-freshen-objects.sh b/t/t6501-freshen-objects.sh index 765cced60b..033871ee5f 100755 --- a/t/t6501-freshen-objects.sh +++ b/t/t6501-freshen-objects.sh @@ -72,8 +72,7 @@ for repack in '' true; do ' test_expect_success "simulate time passing ($title)" ' - find .git/objects -type f | - xargs test-tool chmtime -v -86400 + test-tool chmtime --get -86400 $(find .git/objects -type f) ' test_expect_success "start writing new commit with old blob ($title)" ' @@ -103,8 +102,7 @@ for repack in '' true; do test_expect_success "abandon objects again ($title)" ' git reset --hard HEAD^ && - find .git/objects -type f | - xargs test-tool chmtime -v -86400 + test-tool chmtime --get -86400 $(find .git/objects -type f) ' test_expect_success "start writing new commit with same tree ($title)" ' diff --git a/t/t6600-test-reach.sh b/t/t6600-test-reach.sh new file mode 100755 index 0000000000..d139a00d1d --- /dev/null +++ b/t/t6600-test-reach.sh @@ -0,0 +1,242 @@ +#!/bin/sh + +test_description='basic commit reachability tests' + +. ./test-lib.sh + +# Construct a grid-like commit graph with points (x,y) +# with 1 <= x <= 10, 1 <= y <= 10, where (x,y) has +# parents (x-1, y) and (x, y-1), keeping in mind that +# we drop a parent if a coordinate is nonpositive. +# +# (10,10) +# / \ +# (10,9) (9,10) +# / \ / \ +# (10,8) (9,9) (8,10) +# / \ / \ / \ +# ( continued...) +# \ / \ / \ / +# (3,1) (2,2) (1,3) +# \ / \ / +# (2,1) (2,1) +# \ / +# (1,1) +# +# We use branch 'commit-x-y' to refer to (x,y). +# This grid allows interesting reachability and +# non-reachability queries: (x,y) can reach (x',y') +# if and only if x' <= x and y' <= y. +test_expect_success 'setup' ' + for i in $(test_seq 1 10) + do + test_commit "1-$i" && + git branch -f commit-1-$i + done && + for j in $(test_seq 1 9) + do + git reset --hard commit-$j-1 && + x=$(($j + 1)) && + test_commit "$x-1" && + git branch -f commit-$x-1 && + + for i in $(test_seq 2 10) + do + git merge commit-$j-$i -m "$x-$i" && + git branch -f commit-$x-$i + done + done && + git commit-graph write --reachable && + mv .git/objects/info/commit-graph commit-graph-full && + git show-ref -s commit-5-5 | git commit-graph write --stdin-commits && + mv .git/objects/info/commit-graph commit-graph-half && + git config core.commitGraph true +' + +test_three_modes () { + test_when_finished rm -rf .git/objects/info/commit-graph && + test-tool reach $1 <input >actual && + test_cmp expect actual && + cp commit-graph-full .git/objects/info/commit-graph && + test-tool reach $1 <input >actual && + test_cmp expect actual && + cp commit-graph-half .git/objects/info/commit-graph && + test-tool reach $1 <input >actual && + test_cmp expect actual +} + +test_expect_success 'ref_newer:miss' ' + cat >input <<-\EOF && + A:commit-5-7 + B:commit-4-9 + EOF + echo "ref_newer(A,B):0" >expect && + test_three_modes ref_newer +' + +test_expect_success 'ref_newer:hit' ' + cat >input <<-\EOF && + A:commit-5-7 + B:commit-2-3 + EOF + echo "ref_newer(A,B):1" >expect && + test_three_modes ref_newer +' + +test_expect_success 'in_merge_bases:hit' ' + cat >input <<-\EOF && + A:commit-5-7 + B:commit-8-8 + EOF + echo "in_merge_bases(A,B):1" >expect && + test_three_modes in_merge_bases +' + +test_expect_success 'in_merge_bases:miss' ' + cat >input <<-\EOF && + A:commit-6-8 + B:commit-5-9 + EOF + echo "in_merge_bases(A,B):0" >expect && + test_three_modes in_merge_bases +' + +test_expect_success 'is_descendant_of:hit' ' + cat >input <<-\EOF && + A:commit-5-7 + X:commit-4-8 + X:commit-6-6 + X:commit-1-1 + EOF + echo "is_descendant_of(A,X):1" >expect && + test_three_modes is_descendant_of +' + +test_expect_success 'is_descendant_of:miss' ' + cat >input <<-\EOF && + A:commit-6-8 + X:commit-5-9 + X:commit-4-10 + X:commit-7-6 + EOF + echo "is_descendant_of(A,X):0" >expect && + test_three_modes is_descendant_of +' + +test_expect_success 'get_merge_bases_many' ' + cat >input <<-\EOF && + A:commit-5-7 + X:commit-4-8 + X:commit-6-6 + X:commit-8-3 + EOF + { + echo "get_merge_bases_many(A,X):" && + git rev-parse commit-5-6 \ + commit-4-7 | sort + } >expect && + test_three_modes get_merge_bases_many +' + +test_expect_success 'reduce_heads' ' + cat >input <<-\EOF && + X:commit-1-10 + X:commit-2-8 + X:commit-3-6 + X:commit-4-4 + X:commit-1-7 + X:commit-2-5 + X:commit-3-3 + X:commit-5-1 + EOF + { + echo "reduce_heads(X):" && + git rev-parse commit-5-1 \ + commit-4-4 \ + commit-3-6 \ + commit-2-8 \ + commit-1-10 | sort + } >expect && + test_three_modes reduce_heads +' + +test_expect_success 'can_all_from_reach:hit' ' + cat >input <<-\EOF && + X:commit-2-10 + X:commit-3-9 + X:commit-4-8 + X:commit-5-7 + X:commit-6-6 + X:commit-7-5 + X:commit-8-4 + X:commit-9-3 + Y:commit-1-9 + Y:commit-2-8 + Y:commit-3-7 + Y:commit-4-6 + Y:commit-5-5 + Y:commit-6-4 + Y:commit-7-3 + Y:commit-8-1 + EOF + echo "can_all_from_reach(X,Y):1" >expect && + test_three_modes can_all_from_reach +' + +test_expect_success 'can_all_from_reach:miss' ' + cat >input <<-\EOF && + X:commit-2-10 + X:commit-3-9 + X:commit-4-8 + X:commit-5-7 + X:commit-6-6 + X:commit-7-5 + X:commit-8-4 + X:commit-9-3 + Y:commit-1-9 + Y:commit-2-8 + Y:commit-3-7 + Y:commit-4-6 + Y:commit-5-5 + Y:commit-6-4 + Y:commit-8-5 + EOF + echo "can_all_from_reach(X,Y):0" >expect && + test_three_modes can_all_from_reach +' + +test_expect_success 'commit_contains:hit' ' + cat >input <<-\EOF && + A:commit-7-7 + X:commit-2-10 + X:commit-3-9 + X:commit-4-8 + X:commit-5-7 + X:commit-6-6 + X:commit-7-5 + X:commit-8-4 + X:commit-9-3 + EOF + echo "commit_contains(_,A,X,_):1" >expect && + test_three_modes commit_contains && + test_three_modes commit_contains --tag +' + +test_expect_success 'commit_contains:miss' ' + cat >input <<-\EOF && + A:commit-6-5 + X:commit-2-10 + X:commit-3-9 + X:commit-4-8 + X:commit-5-7 + X:commit-6-6 + X:commit-7-5 + X:commit-8-4 + X:commit-9-3 + EOF + echo "commit_contains(_,A,X,_):0" >expect && + test_three_modes commit_contains && + test_three_modes commit_contains --tag +' + +test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index e96cbdb105..36b50d0b4c 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -384,7 +384,7 @@ test_expect_success 'mv does not complain when no .gitmodules file is found' ' entry="$(git ls-files --stage sub | cut -f 1)" && mkdir mod && git mv sub mod/sub 2>actual.err && - ! test -s actual.err && + test_must_be_empty actual.err && ! test -e sub && [ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] && ( @@ -408,7 +408,7 @@ test_expect_success 'mv will error out on a modified .gitmodules file unless sta git diff-files --quiet -- sub && git add .gitmodules && git mv sub mod/sub 2>actual.err && - ! test -s actual.err && + test_must_be_empty actual.err && ! test -e sub && [ "$entry" = "$(git ls-files --stage mod/sub | cut -f 1)" ] && ( @@ -469,7 +469,7 @@ test_expect_success 'checking out a commit before submodule moved needs manual u git update-index --refresh && git diff-files --quiet -- sub .gitmodules && git status -s sub2 >actual && - ! test -s actual + test_must_be_empty actual ' test_expect_success 'mv -k does not accidentally destroy submodules' ' @@ -495,7 +495,7 @@ test_expect_success 'moving a submodule in nested directories' ' test_cmp expect actual ' -test_expect_failure 'moving nested submodules' ' +test_expect_success 'moving nested submodules' ' git commit -am "cleanup commit" && mkdir sub_nested_nested && (cd sub_nested_nested && @@ -509,7 +509,7 @@ test_expect_failure 'moving nested submodules' ' touch nested_level1 && git init && git add . && - git commit -m "nested level 1" + git commit -m "nested level 1" && git submodule add ../sub_nested_nested && git commit -m "add nested level 2" ) && diff --git a/t/t7003-filter-branch.sh b/t/t7003-filter-branch.sh index ec4b160ddb..e23de7d0b5 100755 --- a/t/t7003-filter-branch.sh +++ b/t/t7003-filter-branch.sh @@ -107,6 +107,21 @@ test_expect_success 'test that the directory was renamed' ' test dir/D = "$(cat diroh/D.t)" ' +V=$(git rev-parse HEAD) + +test_expect_success 'populate --state-branch' ' + git filter-branch --state-branch state -f --tree-filter "touch file || :" HEAD +' + +W=$(git rev-parse HEAD) + +test_expect_success 'using --state-branch to skip already rewritten commits' ' + test_when_finished git reset --hard $V && + git reset --hard $V && + git filter-branch --state-branch state -f --tree-filter "touch file || :" HEAD && + test_cmp_rev $W HEAD +' + git tag oldD HEAD~4 test_expect_success 'rewrite one branch, keeping a side branch' ' git branch modD oldD && diff --git a/t/t7004-tag.sh b/t/t7004-tag.sh index 2aac77af70..0b01862c23 100755 --- a/t/t7004-tag.sh +++ b/t/t7004-tag.sh @@ -325,11 +325,10 @@ test_expect_success \ test_cmp expect actual ' ->expect test_expect_success \ 'listing tags using v.* should print nothing because none have v.' ' git tag -l "v.*" > actual && - test_cmp expect actual + test_must_be_empty actual ' cat >expect <<EOF @@ -363,7 +362,7 @@ test_expect_success 'tag -l <pattern> -l <pattern> works, as our buggy documenta ' test_expect_success 'listing tags in column' ' - COLUMNS=40 git tag -l --column=row >actual && + COLUMNS=41 git tag -l --column=row >actual && cat >expected <<\EOF && a1 aa1 cba t210 t211 v0.2.1 v1.0 v1.0.1 v1.1.3 @@ -693,9 +692,8 @@ test_expect_success \ ' test_expect_success 'The -n 100 invocation means -n --list 100, not -n100' ' - >expect && git tag -n 100 >actual && - test_cmp expect actual && + test_must_be_empty actual && git tag -m "A msg" 100 && echo "100 A msg" >expect && @@ -974,9 +972,8 @@ test_expect_success GPG 'verifying a proper tag with --format pass and format ac ' test_expect_success GPG 'verifying a forged tag with --format should fail silently' ' - >expect && test_must_fail git tag -v --format="tagname : %(tag)" "forged-tag" >actual && - test_cmp expect actual + test_must_be_empty actual ' # blank and empty messages for signed tags: @@ -1056,7 +1053,18 @@ test_expect_success GPG \ git tag -s -F sigblanknonlfile blanknonlfile-signed-tag && get_tag_msg blanknonlfile-signed-tag >actual && test_cmp expect actual && - git tag -v signed-tag + git tag -v blanknonlfile-signed-tag +' + +test_expect_success GPG 'signed tag with embedded PGP message' ' + cat >msg <<-\EOF && + -----BEGIN PGP MESSAGE----- + + this is not a real PGP message + -----END PGP MESSAGE----- + EOF + git tag -s -F msg confusing-pgp-message && + git tag -v confusing-pgp-message ' # messages with commented lines for signed tags: @@ -1343,6 +1351,19 @@ test_expect_success GPG \ 'test_config gpg.program echo && test_must_fail git tag -s -m tail tag-gpg-failure' +# try to sign with bad user.signingkey +test_expect_success GPGSM \ + 'git tag -s fails if gpgsm is misconfigured (bad key)' \ + 'test_config user.signingkey BobTheMouse && + test_config gpg.format x509 && + test_must_fail git tag -s -m tail tag-gpg-failure' + +# try to produce invalid signature +test_expect_success GPGSM \ + 'git tag -s fails if gpgsm is misconfigured (bad signature format)' \ + 'test_config gpg.x509.program echo && + test_config gpg.format x509 && + test_must_fail git tag -s -m tail tag-gpg-failure' # try to verify without gpg: @@ -1371,9 +1392,8 @@ test_expect_success 'message in editor has initial comment: first line' ' test_expect_success \ 'message in editor has initial comment: remainder' ' # remove commented lines from the remainder -- should be empty - >rest.expect && sed -e 1d -e "/^#/d" <actual >rest.actual && - test_cmp rest.expect rest.actual + test_must_be_empty rest.actual ' get_tag_header reuse $commit commit $time >expect @@ -1455,19 +1475,18 @@ test_expect_success 'checking that first commit is in all tags (relative)' " # All the --contains tests above, but with --no-contains test_expect_success 'checking that first commit is not listed in any tag with --no-contains (hash)' " - >expected && git tag -l --no-contains $hash1 v* >actual && - test_cmp expected actual + test_must_be_empty actual " test_expect_success 'checking that first commit is in all tags (tag)' " git tag -l --no-contains v1.0 v* >actual && - test_cmp expected actual + test_must_be_empty actual " test_expect_success 'checking that first commit is in all tags (relative)' " git tag -l --no-contains HEAD~2 v* >actual && - test_cmp expected actual + test_must_be_empty actual " cat > expected <<EOF @@ -1491,12 +1510,9 @@ test_expect_success 'inverse of the last test, with --no-contains' " test_cmp expected actual " -cat > expected <<EOF -EOF - test_expect_success 'checking that third commit has no tags' " git tag -l --contains $hash3 v* >actual && - test_cmp expected actual + test_must_be_empty actual " cat > expected <<EOF @@ -1595,9 +1611,8 @@ test_expect_success 'checking that --contains can be used in non-list mode' ' ' test_expect_success 'checking that initial commit is in all tags with --no-contains' " - >expected && git tag -l --no-contains $hash1 v* >actual && - test_cmp expected actual + test_must_be_empty actual " # mixing modes and options: @@ -1894,7 +1909,6 @@ test_expect_success 'version sort with very long prerelease suffix' ' ' test_expect_success ULIMIT_STACK_SIZE '--contains and --no-contains work in a deep repo' ' - >expect && i=1 && while test $i -lt 8000 do @@ -1909,7 +1923,7 @@ EOF" git checkout master && git tag far-far-away HEAD^ && run_with_limited_stack git tag --contains HEAD >actual && - test_cmp expect actual && + test_must_be_empty actual && run_with_limited_stack git tag --no-contains HEAD >actual && test_line_count "-gt" 10 actual ' diff --git a/t/t7005-editor.sh b/t/t7005-editor.sh index 29e5043b94..b2ca77b338 100755 --- a/t/t7005-editor.sh +++ b/t/t7005-editor.sh @@ -111,14 +111,8 @@ do ' done -if echo 'echo space > "$1"' > "e space.sh" -then - # FS supports spaces in filenames - test_set_prereq SPACES_IN_FILENAMES -fi - -test_expect_success SPACES_IN_FILENAMES 'editor with a space' ' - +test_expect_success 'editor with a space' ' + echo "echo space >\$1" >"e space.sh" && chmod a+x "e space.sh" && GIT_EDITOR="./e\ space.sh" git commit --amend && test space = "$(git show -s --pretty=format:%s)" @@ -126,7 +120,7 @@ test_expect_success SPACES_IN_FILENAMES 'editor with a space' ' ' unset GIT_EDITOR -test_expect_success SPACES_IN_FILENAMES 'core.editor with a space' ' +test_expect_success 'core.editor with a space' ' git config core.editor \"./e\ space.sh\" && git commit --amend && diff --git a/t/t7006-pager.sh b/t/t7006-pager.sh index 7541ba5edb..00e09a375c 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -626,12 +626,11 @@ test_expect_success TTY 'sub-commands of externals use their own pager' ' test_expect_success TTY 'external command pagers override sub-commands' ' sane_unset PAGER GIT_PAGER && - >expect && >actual && test_config pager.external false && test_config pager.log "sed s/^/log:/ >actual" && test_terminal git --exec-path=. external log --format=%s -1 && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'command with underscores does not complain' ' diff --git a/t/t7008-grep-binary.sh b/t/t7008-grep-binary.sh index 615e7e0162..2d87c49b75 100755 --- a/t/t7008-grep-binary.sh +++ b/t/t7008-grep-binary.sh @@ -57,9 +57,8 @@ test_expect_success 'git grep -ah ina a' ' ' test_expect_success 'git grep -I ina a' ' - : >expect && test_must_fail git grep -I ina a >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'git grep -c ina a' ' @@ -81,9 +80,8 @@ test_expect_success 'git grep -L bar a' ' ' test_expect_success 'git grep -q ina a' ' - : >expect && git grep -q ina a >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'git grep -F ile a' ' diff --git a/t/t7009-filter-branch-null-sha1.sh b/t/t7009-filter-branch-null-sha1.sh index a8d9ec4987..9ba9f24ad2 100755 --- a/t/t7009-filter-branch-null-sha1.sh +++ b/t/t7009-filter-branch-null-sha1.sh @@ -12,7 +12,7 @@ test_expect_success 'setup: base commits' ' test_expect_success 'setup: a commit with a bogus null sha1 in the tree' ' { git ls-tree HEAD && - printf "160000 commit $_z40\\tbroken\\n" + printf "160000 commit $ZERO_OID\\tbroken\\n" } >broken-tree && echo "add broken entry" >msg && diff --git a/t/t7011-skip-worktree-reading.sh b/t/t7011-skip-worktree-reading.sh index 84f41451ec..37525cae3a 100755 --- a/t/t7011-skip-worktree-reading.sh +++ b/t/t7011-skip-worktree-reading.sh @@ -118,7 +118,7 @@ test_expect_success 'grep with skip-worktree file' ' test "$(git grep --no-ext-grep test)" = "1:test" ' -echo ":000000 100644 $_z40 $EMPTY_BLOB A 1" > expected +echo ":000000 100644 $ZERO_OID $EMPTY_BLOB A 1" > expected test_expect_success 'diff-index does not examine skip-worktree absent entries' ' setup_absent && git diff-index HEAD -- 1 > result && diff --git a/t/t7030-verify-tag.sh b/t/t7030-verify-tag.sh index b4b49eeb08..041e319e79 100755 --- a/t/t7030-verify-tag.sh +++ b/t/t7030-verify-tag.sh @@ -41,6 +41,13 @@ test_expect_success GPG 'create signed tags' ' git tag -uB7227189 -m eighth eighth-signed-alt ' +test_expect_success GPGSM 'create signed tags x509 ' ' + test_config gpg.format x509 && + test_config user.signingkey $GIT_COMMITTER_EMAIL && + echo 9 >file && test_tick && git commit -a -m "nineth gpgsm-signed" && + git tag -s -m nineth nineth-signed-x509 +' + test_expect_success GPG 'verify and show signatures' ' ( for tag in initial second merge fourth-signed sixth-signed seventh-signed @@ -72,9 +79,16 @@ test_expect_success GPG 'verify and show signatures' ' ) ' +test_expect_success GPGSM 'verify and show signatures x509' ' + git verify-tag nineth-signed-x509 2>actual && + grep "Good signature from" actual && + ! grep "BAD signature from" actual && + echo nineth-signed-x509 OK +' + test_expect_success GPG 'detect fudged signature' ' git cat-file tag seventh-signed >raw && - sed -e "s/seventh/7th forged/" raw >forged1 && + sed -e "/^tag / s/seventh/7th forged/" raw >forged1 && git hash-object -w -t tag forged1 >forged1.tag && test_must_fail git verify-tag $(cat forged1.tag) 2>actual1 && grep "BAD signature from" actual1 && @@ -112,6 +126,13 @@ test_expect_success GPG 'verify signatures with --raw' ' ) ' +test_expect_success GPGSM 'verify signatures with --raw x509' ' + git verify-tag --raw nineth-signed-x509 2>actual && + grep "GOODSIG" actual && + ! grep "BADSIG" actual && + echo nineth-signed-x509 OK +' + test_expect_success GPG 'verify multiple tags' ' tags="fourth-signed sixth-signed seventh-signed" && for i in $tags @@ -125,6 +146,19 @@ test_expect_success GPG 'verify multiple tags' ' test_cmp expect.stderr actual.stderr ' +test_expect_success GPGSM 'verify multiple tags x509' ' + tags="seventh-signed nineth-signed-x509" && + for i in $tags + do + git verify-tag -v --raw $i || return 1 + done >expect.stdout 2>expect.stderr.1 && + grep "^.GNUPG:." <expect.stderr.1 >expect.stderr && + git verify-tag -v --raw $tags >actual.stdout 2>actual.stderr.1 && + grep "^.GNUPG:." <actual.stderr.1 >actual.stderr && + test_cmp expect.stdout actual.stdout && + test_cmp expect.stderr actual.stderr +' + test_expect_success GPG 'verifying tag with --format' ' cat >expect <<-\EOF && tagname : fourth-signed @@ -134,9 +168,8 @@ test_expect_success GPG 'verifying tag with --format' ' ' test_expect_success GPG 'verifying a forged tag with --format should fail silently' ' - >expect && test_must_fail git verify-tag --format="tagname : %(tag)" $(cat forged1.tag) >actual-forged && - test_cmp expect actual-forged + test_must_be_empty actual-forged ' test_done diff --git a/t/t7063-status-untracked-cache.sh b/t/t7063-status-untracked-cache.sh index c61e304e97..2da57fce7b 100755 --- a/t/t7063-status-untracked-cache.sh +++ b/t/t7063-status-untracked-cache.sh @@ -26,9 +26,8 @@ avoid_racy() { } status_is_clean() { - >../status.expect && git status --porcelain >../status.actual && - test_cmp ../status.expect ../status.actual + test_must_be_empty ../status.actual } test_lazy_prereq UNTRACKED_CACHE ' @@ -666,7 +665,7 @@ test_expect_success 'test ident field is working' ' mkdir ../other_worktree && cp -R done dthree dtwo four three ../other_worktree && GIT_WORK_TREE=../other_worktree git status 2>../err && - echo "warning: Untracked cache is disabled on this system or location." >../expect && + echo "warning: untracked cache is disabled on this system or location" >../expect && test_i18ncmp ../expect ../err ' diff --git a/t/t7064-wtstatus-pv2.sh b/t/t7064-wtstatus-pv2.sh index 8f795327a0..11eccc231a 100755 --- a/t/t7064-wtstatus-pv2.sh +++ b/t/t7064-wtstatus-pv2.sh @@ -46,11 +46,11 @@ test_expect_success 'before initial commit, things added' ' cat >expect <<-EOF && # branch.oid (initial) # branch.head master - 1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a - 1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b - 1 A. N... 000000 100644 100644 $_z40 $OID_X file_x - 1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y - 1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_X file_x + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_Y file_y + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_Z file_z ? actual ? expect EOF @@ -63,11 +63,11 @@ test_expect_success 'before initial commit, things added (-z)' ' lf_to_nul >expect <<-EOF && # branch.oid (initial) # branch.head master - 1 A. N... 000000 100644 100644 $_z40 $OID_A dir1/file_a - 1 A. N... 000000 100644 100644 $_z40 $OID_B dir1/file_b - 1 A. N... 000000 100644 100644 $_z40 $OID_X file_x - 1 A. N... 000000 100644 100644 $_z40 $OID_Y file_y - 1 A. N... 000000 100644 100644 $_z40 $OID_Z file_z + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_A dir1/file_a + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_B dir1/file_b + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_X file_x + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_Y file_y + 1 A. N... 000000 100644 100644 $ZERO_OID $OID_Z file_z ? actual ? expect EOF @@ -128,7 +128,7 @@ test_expect_success 'after first commit, stage existing changes' ' # branch.oid $H0 # branch.head master 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x - 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + 1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z ? actual ? expect EOF @@ -145,7 +145,7 @@ test_expect_success 'rename causes 2 path lines' ' # branch.oid $H0 # branch.head master 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x - 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + 1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y ? actual ? expect @@ -163,7 +163,7 @@ test_expect_success 'rename causes 2 path lines (-z)' ' # branch.oid $H0 # branch.head master 1 M. N... 100644 100644 100644 $OID_X $OID_X1 file_x - 1 D. N... 100644 000000 000000 $OID_Z $_z40 file_z + 1 D. N... 100644 000000 000000 $OID_Z $ZERO_OID file_z 2 R. N... 100644 100644 100644 $OID_Y $OID_Y R100 renamed_yQfile_y ? actual ? expect @@ -246,8 +246,8 @@ test_expect_success 'verify --intent-to-add output' ' git add --intent-to-add intent1.add intent2.add && cat >expect <<-EOF && - 1 .A N... 000000 000000 100644 $_z40 $_z40 intent1.add - 1 .A N... 000000 000000 100644 $_z40 $_z40 intent2.add + 1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID intent1.add + 1 .A N... 000000 000000 100644 $ZERO_OID $ZERO_OID intent2.add EOF git status --porcelain=v2 >actual && @@ -280,7 +280,7 @@ test_expect_success 'verify AA (add-add) conflict' ' cat >expect <<-EOF && # branch.oid $HM # branch.head AA_M - u AA N... 000000 100644 100644 100644 $_z40 $OID_AA_B $OID_AA_A conflict.txt + u AA N... 000000 100644 100644 100644 $ZERO_OID $OID_AA_B $OID_AA_A conflict.txt EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -364,16 +364,13 @@ test_expect_success 'verify upstream fields in branch header' ' test_cmp expect actual && ## Repeat the above but without --branch. - cat >expect <<-EOF && - EOF - git status --porcelain=v2 --untracked-files=all >actual && - test_cmp expect actual && + test_must_be_empty actual && ## Test upstream-gone case. Fake this by pointing origin/master at ## a non-existing commit. OLD=$(git rev-parse origin/master) && - NEW=$_z40 && + NEW=$ZERO_OID && mv .git/packed-refs .git/old-packed-refs && sed "s/$OLD/$NEW/g" <.git/old-packed-refs >.git/packed-refs && @@ -469,8 +466,8 @@ test_expect_success 'create and add submodule, submodule appears clean (A. S...) # branch.head master # branch.upstream origin/master # branch.ab +0 -0 - 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules - 1 A. S... 000000 160000 160000 $_z40 $HSUB sub1 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 A. S... 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -494,8 +491,8 @@ test_expect_success 'untracked changes in added submodule (AM S..U)' ' # branch.head master # branch.upstream origin/master # branch.ab +0 -0 - 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules - 1 AM S..U 000000 160000 160000 $_z40 $HSUB sub1 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM S..U 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -519,8 +516,8 @@ test_expect_success 'staged changes in added submodule (AM S.M.)' ' # branch.head master # branch.upstream origin/master # branch.ab +0 -0 - 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules - 1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -546,8 +543,8 @@ test_expect_success 'staged and unstaged changes in added (AM S.M.)' ' # branch.head master # branch.upstream origin/master # branch.ab +0 -0 - 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules - 1 AM S.M. 000000 160000 160000 $_z40 $HSUB sub1 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM S.M. 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -573,8 +570,8 @@ test_expect_success 'staged and untracked changes in added submodule (AM S.MU)' # branch.head master # branch.upstream origin/master # branch.ab +0 -0 - 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules - 1 AM S.MU 000000 160000 160000 $_z40 $HSUB sub1 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM S.MU 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && @@ -600,8 +597,8 @@ test_expect_success 'commit within the submodule appears as new commit in super # branch.head master # branch.upstream origin/master # branch.ab +0 -0 - 1 A. N... 000000 100644 100644 $_z40 $HMOD .gitmodules - 1 AM SC.. 000000 160000 160000 $_z40 $HSUB sub1 + 1 A. N... 000000 100644 100644 $ZERO_OID $HMOD .gitmodules + 1 AM SC.. 000000 160000 160000 $ZERO_OID $HSUB sub1 EOF git status --porcelain=v2 --branch --untracked-files=all >actual && diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh index 95653a08ca..97be0d968d 100755 --- a/t/t7102-reset.sh +++ b/t/t7102-reset.sh @@ -549,8 +549,7 @@ test_expect_success 'reset -N keeps removed files as intent-to-add' ' tree=$(git write-tree) && git ls-tree $tree new-file >actual && - >expect && - test_cmp expect actual && + test_must_be_empty actual && git diff --name-only >actual && echo new-file >expect && @@ -563,9 +562,8 @@ test_expect_success 'reset --mixed sets up work tree' ' cd mixed_worktree && test_commit dummy ) && - : >expect && git --git-dir=mixed_worktree/.git --work-tree=mixed_worktree reset >actual && - test_cmp expect actual + test_must_be_empty actual ' test_done diff --git a/t/t7105-reset-patch.sh b/t/t7105-reset-patch.sh index 98b7d7b969..bd10a96727 100755 --- a/t/t7105-reset-patch.sh +++ b/t/t7105-reset-patch.sh @@ -19,20 +19,20 @@ test_expect_success PERL 'setup' ' test_expect_success PERL 'saying "n" does nothing' ' set_and_save_state dir/foo work work && - (echo n; echo n) | git reset -p && + test_write_lines n n | git reset -p && verify_saved_state dir/foo && verify_saved_state bar ' test_expect_success PERL 'git reset -p' ' - (echo n; echo y) | git reset -p >output && + test_write_lines n y | git reset -p >output && verify_state dir/foo work head && verify_saved_state bar && test_i18ngrep "Unstage" output ' test_expect_success PERL 'git reset -p HEAD^' ' - (echo n; echo y) | git reset -p HEAD^ >output && + test_write_lines n y | git reset -p HEAD^ >output && verify_state dir/foo work parent && verify_saved_state bar && test_i18ngrep "Apply" output @@ -45,20 +45,20 @@ test_expect_success PERL 'git reset -p HEAD^' ' test_expect_success PERL 'git reset -p dir' ' set_state dir/foo work work && - (echo y; echo n) | git reset -p dir && + 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)' ' set_state dir/foo work work && - (echo y; echo n) | (cd dir && git reset -p -- foo) && + 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' ' - (echo y; echo n) | 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 ' diff --git a/t/t7106-reset-unborn-branch.sh b/t/t7106-reset-unborn-branch.sh index 0f95f00477..ecb85c3b82 100755 --- a/t/t7106-reset-unborn-branch.sh +++ b/t/t7106-reset-unborn-branch.sh @@ -12,9 +12,8 @@ test_expect_success 'reset' ' git add a b && git reset && - >expect && git ls-files >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'reset HEAD' ' @@ -39,9 +38,8 @@ test_expect_success PERL 'reset -p' ' echo y >yes && git reset -p <yes >output && - >expect && git ls-files >actual && - test_cmp expect actual && + test_must_be_empty actual && test_i18ngrep "Unstage" output ' @@ -61,9 +59,8 @@ test_expect_success 'reset --hard' ' test_when_finished "echo a >a" && git reset --hard && - >expect && git ls-files >actual && - test_cmp expect actual && + test_must_be_empty actual && test_path_is_missing a ' diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 76c223c967..324933acfe 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -65,7 +65,7 @@ test_expect_success setup ' test_expect_success "checkout from non-existing branch" ' git checkout -b delete-me master && - rm .git/refs/heads/delete-me && + git update-ref -d --no-deref refs/heads/delete-me && test refs/heads/delete-me = "$(git symbolic-ref HEAD)" && git checkout master && test refs/heads/master = "$(git symbolic-ref HEAD)" @@ -116,9 +116,8 @@ test_expect_success "checkout -m with dirty tree" ' git diff --name-status side >current.side && test_cmp expect.side current.side && - : >expect.index && git diff --cached >current.index && - test_cmp expect.index current.index + test_must_be_empty current.index ' test_expect_success "checkout -m with dirty tree, renamed" ' @@ -139,7 +138,7 @@ test_expect_success "checkout -m with dirty tree, renamed" ' test_cmp expect uno && ! test -f one && git diff --cached >current && - ! test -s current + test_must_be_empty current ' @@ -163,7 +162,7 @@ test_expect_success 'checkout -m with merge conflict' ' fill d2 aT d7 aS >expect && test_cmp current expect && git diff --cached two >current && - ! test -s current + test_must_be_empty current ' test_expect_success 'format of merge conflict from checkout -m' ' @@ -528,10 +527,10 @@ test_expect_success 'checkout with --merge' ' cat sample >filf && git checkout -m -- fild file filf && ( - echo "<<<<<<< ours" - echo ourside - echo "=======" - echo theirside + echo "<<<<<<< ours" && + echo ourside && + echo "=======" && + echo theirside && echo ">>>>>>> theirs" ) >merged && test_cmp expect fild && @@ -549,12 +548,12 @@ test_expect_success 'checkout with --merge, in diff3 -m style' ' cat sample >filf && git checkout -m -- fild file filf && ( - echo "<<<<<<< ours" - echo ourside - echo "||||||| base" - echo original - echo "=======" - echo theirside + echo "<<<<<<< ours" && + echo ourside && + echo "||||||| base" && + echo original && + echo "=======" && + echo theirside && echo ">>>>>>> theirs" ) >merged && test_cmp expect fild && @@ -572,10 +571,10 @@ test_expect_success 'checkout --conflict=merge, overriding config' ' cat sample >filf && git checkout --conflict=merge -- fild file filf && ( - echo "<<<<<<< ours" - echo ourside - echo "=======" - echo theirside + echo "<<<<<<< ours" && + echo ourside && + echo "=======" && + echo theirside && echo ">>>>>>> theirs" ) >merged && test_cmp expect fild && @@ -593,12 +592,12 @@ test_expect_success 'checkout --conflict=diff3' ' cat sample >filf && git checkout --conflict=diff3 -- fild file filf && ( - echo "<<<<<<< ours" - echo ourside - echo "||||||| base" - echo original - echo "=======" - echo theirside + echo "<<<<<<< ours" && + echo ourside && + echo "||||||| base" && + echo original && + echo "=======" && + echo theirside && echo ">>>>>>> theirs" ) >merged && test_cmp expect fild && @@ -673,7 +672,6 @@ test_expect_success 'custom merge driver with checkout -m' ' do grep $t arm || exit 1 done - exit 0 ) && mv arm expect && diff --git a/t/t7301-clean-interactive.sh b/t/t7301-clean-interactive.sh index 1bf9789c8a..a07e8b86de 100755 --- a/t/t7301-clean-interactive.sh +++ b/t/t7301-clean-interactive.sh @@ -107,7 +107,7 @@ test_expect_success 'git clean -id (filter all)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo f; echo "*"; echo; echo c) | \ + test_write_lines f "*" "" c | git clean -id && test -f Makefile && test -f README && @@ -129,7 +129,7 @@ test_expect_success 'git clean -id (filter patterns)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo f; echo "part3.* *.out"; echo; echo c) | \ + test_write_lines f "part3.* *.out" "" c | git clean -id && test -f Makefile && test -f README && @@ -151,7 +151,7 @@ test_expect_success 'git clean -id (filter patterns 2)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo f; echo "* !*.out"; echo; echo c) | \ + test_write_lines f "* !*.out" "" c | git clean -id && test -f Makefile && test -f README && @@ -173,7 +173,7 @@ test_expect_success 'git clean -id (select - all)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo s; echo "*"; echo; echo c) | \ + test_write_lines s "*" "" c | git clean -id && test -f Makefile && test -f README && @@ -195,7 +195,7 @@ test_expect_success 'git clean -id (select - none)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo s; echo; echo c) | \ + test_write_lines s "" c | git clean -id && test -f Makefile && test -f README && @@ -217,7 +217,7 @@ test_expect_success 'git clean -id (select - number)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo s; echo 3; echo; echo c) | \ + test_write_lines s 3 "" c | git clean -id && test -f Makefile && test -f README && @@ -239,7 +239,7 @@ test_expect_success 'git clean -id (select - number 2)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo s; echo 2 3; echo 5; echo; echo c) | \ + test_write_lines s "2 3" 5 "" c | git clean -id && test -f Makefile && test -f README && @@ -261,7 +261,7 @@ test_expect_success 'git clean -id (select - number 3)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo s; echo 3,4 5; echo; echo c) | \ + test_write_lines s "3,4 5" "" c | git clean -id && test -f Makefile && test -f README && @@ -282,7 +282,7 @@ test_expect_success 'git clean -id (select - filenames)' ' mkdir -p build docs && touch a.out foo.txt bar.txt baz.txt && - (echo s; echo a.out fo ba bar; echo; echo c) | \ + test_write_lines s "a.out fo ba bar" "" c | git clean -id && test -f Makefile && test ! -f a.out && @@ -298,7 +298,7 @@ test_expect_success 'git clean -id (select - range)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo s; echo 1,3-4; echo 2; echo; echo c) | \ + test_write_lines s "1,3-4" 2 "" c | git clean -id && test -f Makefile && test -f README && @@ -320,7 +320,7 @@ test_expect_success 'git clean -id (select - range 2)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo s; echo 4- 1; echo; echo c) | \ + test_write_lines s "4- 1" "" c | git clean -id && test -f Makefile && test -f README && @@ -342,7 +342,7 @@ test_expect_success 'git clean -id (inverse select)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo s; echo "*"; echo -5- 1 -2; echo; echo c) | \ + test_write_lines s "*" "-5- 1 -2" "" c | git clean -id && test -f Makefile && test -f README && @@ -364,7 +364,7 @@ test_expect_success 'git clean -id (ask)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + test_write_lines a Y y no yes bad "" | git clean -id && test -f Makefile && test -f README && @@ -386,7 +386,7 @@ test_expect_success 'git clean -id (ask - Ctrl+D)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (echo a; echo Y; echo no; echo yes; echo "\04") | \ + test_write_lines a Y no yes "\04" | git clean -id && test -f Makefile && test -f README && @@ -408,8 +408,8 @@ test_expect_success 'git clean -id with prefix and path (filter)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (cd build/ && \ - (echo f; echo "docs"; echo "*.h"; echo ; echo c) | \ + (cd build/ && + test_write_lines f docs "*.h" "" c | git clean -id ..) && test -f Makefile && test -f README && @@ -431,9 +431,8 @@ test_expect_success 'git clean -id with prefix and path (select by name)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (cd build/ && \ - (echo s; echo "../docs/"; echo "../src/part3.c"; \ - echo "../src/part4.c"; echo; echo c) | \ + (cd build/ && + test_write_lines s ../docs/ ../src/part3.c ../src/part4.c "" c | git clean -id ..) && test -f Makefile && test -f README && @@ -455,8 +454,8 @@ test_expect_success 'git clean -id with prefix and path (ask)' ' mkdir -p build docs && touch a.out src/part3.c src/part3.h src/part4.c src/part4.h \ docs/manual.txt obj.o build/lib.so && - (cd build/ && \ - (echo a; echo Y; echo y; echo no; echo yes; echo bad; echo) | \ + (cd build/ && + test_write_lines a Y y no yes bad "" | git clean -id ..) && test -f Makefile && test -f README && diff --git a/t/t7400-submodule-basic.sh b/t/t7400-submodule-basic.sh index 152104412f..c0ffc1022a 100755 --- a/t/t7400-submodule-basic.sh +++ b/t/t7400-submodule-basic.sh @@ -101,7 +101,6 @@ inspect() { test_expect_success 'submodule add' ' echo "refs/heads/master" >expect && - >empty && ( cd addtest && @@ -123,7 +122,25 @@ test_expect_success 'submodule add' ' inspect addtest/submod ../.. && test_cmp expect heads && test_cmp expect head && - test_cmp empty untracked + test_must_be_empty untracked +' + +test_expect_success 'setup parent and one repository' ' + test_create_repo parent && + test_commit -C parent one +' + +test_expect_success 'redirected submodule add does not show progress' ' + git -C addtest submodule add "file://$submodurl/parent" submod-redirected \ + 2>err && + ! grep % err && + test_i18ngrep ! "Checking connectivity" err +' + +test_expect_success 'redirected submodule add --progress does show progress' ' + git -C addtest submodule add --progress "file://$submodurl/parent" \ + submod-redirected-progress 2>err && \ + grep % err ' test_expect_success 'submodule add to .gitignored path fails' ' @@ -153,12 +170,13 @@ test_expect_success 'submodule add to .gitignored path with --force' ' test_expect_success 'submodule add to reconfigure existing submodule with --force' ' ( cd addtest-ignore && - git submodule add --force bogus-url submod && - git submodule add -b initial "$submodurl" submod-branch && - test "bogus-url" = "$(git config -f .gitmodules submodule.submod.url)" && - test "bogus-url" = "$(git config submodule.submod.url)" && + bogus_url="$(pwd)/bogus-url" && + git submodule add --force "$bogus_url" submod && + git submodule add --force -b initial "$submodurl" submod-branch && + test "$bogus_url" = "$(git config -f .gitmodules submodule.submod.url)" && + test "$bogus_url" = "$(git config submodule.submod.url)" && # Restore the url - git submodule add --force "$submodurl" submod + git submodule add --force "$submodurl" submod && test "$submodurl" = "$(git config -f .gitmodules submodule.submod.url)" && test "$submodurl" = "$(git config submodule.submod.url)" ) @@ -170,7 +188,6 @@ test_expect_success 'submodule add --branch' ' refs/heads/initial refs/heads/master EOF - >empty && ( cd addtest && @@ -183,12 +200,11 @@ test_expect_success 'submodule add --branch' ' inspect addtest/submod-branch ../.. && test_cmp expect-heads heads && test_cmp expect-head head && - test_cmp empty untracked + test_must_be_empty untracked ' test_expect_success 'submodule add with ./ in path' ' echo "refs/heads/master" >expect && - >empty && ( cd addtest && @@ -200,12 +216,11 @@ test_expect_success 'submodule add with ./ in path' ' inspect addtest/dotsubmod/frotz ../../.. && test_cmp expect heads && test_cmp expect head && - test_cmp empty untracked + test_must_be_empty untracked ' test_expect_success 'submodule add with /././ in path' ' echo "refs/heads/master" >expect && - >empty && ( cd addtest && @@ -217,12 +232,11 @@ test_expect_success 'submodule add with /././ in path' ' inspect addtest/dotslashdotsubmod/frotz ../../.. && test_cmp expect heads && test_cmp expect head && - test_cmp empty untracked + test_must_be_empty untracked ' test_expect_success 'submodule add with // in path' ' echo "refs/heads/master" >expect && - >empty && ( cd addtest && @@ -234,12 +248,11 @@ test_expect_success 'submodule add with // in path' ' inspect addtest/slashslashsubmod/frotz ../../.. && test_cmp expect heads && test_cmp expect head && - test_cmp empty untracked + test_must_be_empty untracked ' test_expect_success 'submodule add with /.. in path' ' echo "refs/heads/master" >expect && - >empty && ( cd addtest && @@ -251,12 +264,11 @@ test_expect_success 'submodule add with /.. in path' ' inspect addtest/realsubmod ../.. && test_cmp expect heads && test_cmp expect head && - test_cmp empty untracked + test_must_be_empty untracked ' test_expect_success 'submodule add with ./, /.. and // in path' ' echo "refs/heads/master" >expect && - >empty && ( cd addtest && @@ -268,7 +280,7 @@ test_expect_success 'submodule add with ./, /.. and // in path' ' inspect addtest/realsubmod2 ../.. && test_cmp expect heads && test_cmp expect head && - test_cmp empty untracked + test_must_be_empty untracked ' test_expect_success !CYGWIN 'submodule add with \\ in path' ' @@ -287,7 +299,6 @@ test_expect_success !CYGWIN 'submodule add with \\ in path' ' test_expect_success 'submodule add in subdirectory' ' echo "refs/heads/master" >expect && - >empty && mkdir addtest/sub && ( @@ -300,7 +311,7 @@ test_expect_success 'submodule add in subdirectory' ' inspect addtest/realsubmod3 ../.. && test_cmp expect heads && test_cmp expect head && - test_cmp empty untracked + test_must_be_empty untracked ' test_expect_success 'submodule add in subdirectory with relative path should fail' ' @@ -359,7 +370,7 @@ test_expect_success 'init should register submodule url in .git/config' ' test_failure_with_unknown_submodule () { test_must_fail git submodule $1 no-such-submodule 2>output.err && - grep "^error: .*no-such-submodule" output.err + test_i18ngrep "^error: .*no-such-submodule" output.err } test_expect_success 'init should fail with unknown submodule' ' @@ -483,8 +494,6 @@ test_expect_success 'checkout superproject with subproject already present' ' ' test_expect_success 'apply submodule diff' ' - >empty && - git branch second && ( cd init && @@ -499,7 +508,7 @@ test_expect_success 'apply submodule diff' ' git apply --index P.diff && git diff --cached master >staged && - test_cmp empty staged + test_must_be_empty staged ' test_expect_success 'update --init' ' @@ -800,7 +809,7 @@ test_expect_success '../bar/a/b/c works with relative local path - ../foo/bar.gi cp pristine-.git-config .git/config && cp pristine-.gitmodules .gitmodules && mkdir -p a/b/c && - (cd a/b/c; git init) && + (cd a/b/c && git init) && git config remote.origin.url ../foo/bar.git && git submodule add ../bar/a/b/c ./a/b/c && git submodule init && diff --git a/t/t7401-submodule-summary.sh b/t/t7401-submodule-summary.sh index 4e4c455502..9bc841d085 100755 --- a/t/t7401-submodule-summary.sh +++ b/t/t7401-submodule-summary.sh @@ -64,8 +64,7 @@ test_expect_success 'added submodule (subdirectory only)' " cd sub && git submodule summary . >../actual ) && - >expected && - test_cmp expected actual + test_must_be_empty actual " test_expect_success 'added submodule (subdirectory with explicit path)' " @@ -301,7 +300,7 @@ test_expect_success 'should not fail in an empty repo' " git init xyzzy && cd xyzzy && git submodule summary >output 2>&1 && - test_cmp output /dev/null + test_must_be_empty output " test_done diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh index 7bfb2f498d..7855bd8648 100755 --- a/t/t7405-submodule-merge.sh +++ b/t/t7405-submodule-merge.sh @@ -279,4 +279,177 @@ test_expect_success 'recursive merge with submodule' ' grep "$(cat expect3)" actual > /dev/null) ' +# File/submodule conflict +# Commit O: <empty> +# Commit A: path (submodule) +# Commit B: path +# Expected: path/ is submodule and file contents for B's path are somewhere + +test_expect_success 'setup file/submodule conflict' ' + test_create_repo file-submodule && + ( + cd file-submodule && + + git commit --allow-empty -m O && + + git branch A && + git branch B && + + git checkout B && + echo content >path && + git add path && + git commit -m B && + + git checkout A && + test_create_repo path && + test_commit -C path world && + git submodule add ./path && + git commit -m A + ) +' + +test_expect_failure 'file/submodule conflict' ' + test_when_finished "git -C file-submodule reset --hard" && + ( + cd file-submodule && + + git checkout A^0 && + test_must_fail git merge B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + + # path/ is still a submodule + test_path_is_dir path/.git && + + # There is a submodule at "path", so B:path cannot be written + # there. We expect it to be written somewhere in the same + # directory, though, so just grep for its content in all + # files, and ignore "grep: path: Is a directory" message + echo Checking if contents from B:path showed up anywhere && + grep -q content * 2>/dev/null + ) +' + +test_expect_success 'file/submodule conflict; merge --abort works afterward' ' + test_when_finished "git -C file-submodule reset --hard" && + ( + cd file-submodule && + + git checkout A^0 && + test_must_fail git merge B^0 >out 2>err && + + test_path_is_file .git/MERGE_HEAD && + git merge --abort + ) +' + +# Directory/submodule conflict +# Commit O: <empty> +# Commit A: path (submodule), with sole tracked file named 'world' +# Commit B1: path/file +# Commit B2: path/world +# +# Expected from merge of A & B1: +# Contents under path/ from commit B1 are renamed elsewhere; we do not +# want to write files from one of our tracked directories into a submodule +# +# Expected from merge of A & B2: +# Similar to last merge, but with a slight twist: we don't want paths +# under the submodule to be treated as untracked or in the way. + +test_expect_success 'setup directory/submodule conflict' ' + test_create_repo directory-submodule && + ( + cd directory-submodule && + + git commit --allow-empty -m O && + + git branch A && + git branch B1 && + git branch B2 && + + git checkout B1 && + mkdir path && + echo contents >path/file && + git add path/file && + git commit -m B1 && + + git checkout B2 && + mkdir path && + echo contents >path/world && + git add path/world && + git commit -m B2 && + + git checkout A && + test_create_repo path && + test_commit -C path hello world && + git submodule add ./path && + git commit -m A + ) +' + +test_expect_failure 'directory/submodule conflict; keep submodule clean' ' + test_when_finished "git -C directory-submodule reset --hard" && + ( + cd directory-submodule && + + git checkout A^0 && + test_must_fail git merge B1^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 1 out && + + # path/ is still a submodule + test_path_is_dir path/.git && + + echo Checking if contents from B1:path/file showed up && + # Would rather use grep -r, but that is GNU extension... + git ls-files -co | xargs grep -q contents 2>/dev/null && + + # However, B1:path/file should NOT have shown up at path/file, + # because we should not write into the submodule + test_path_is_missing path/file + ) +' + +test_expect_failure 'directory/submodule conflict; should not treat submodule files as untracked or in the way' ' + test_when_finished "git -C directory-submodule/path reset --hard" && + test_when_finished "git -C directory-submodule reset --hard" && + ( + cd directory-submodule && + + git checkout A^0 && + test_must_fail git merge B2^0 >out 2>err && + + # We do not want files within the submodule to prevent the + # merge from starting; we should not be writing to such paths + # anyway. + test_i18ngrep ! "refusing to lose untracked file at" err + ) +' + +test_expect_failure 'directory/submodule conflict; merge --abort works afterward' ' + test_when_finished "git -C directory-submodule/path reset --hard" && + test_when_finished "git -C directory-submodule reset --hard" && + ( + cd directory-submodule && + + git checkout A^0 && + test_must_fail git merge B2^0 && + test_path_is_file .git/MERGE_HEAD && + + # merge --abort should succeed, should clear .git/MERGE_HEAD, + # and should not leave behind any conflicted files + git merge --abort && + test_path_is_missing .git/MERGE_HEAD && + git ls-files -u >conflicts && + test_must_be_empty conflicts + ) +' + test_done diff --git a/t/t7406-submodule-update.sh b/t/t7406-submodule-update.sh index 6f083c4d68..10dc91620a 100755 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@ -65,7 +65,7 @@ test_expect_success 'setup a submodule tree' ' git commit -m "none" ) && git clone . recursivesuper && - ( cd recursivesuper + ( cd recursivesuper && git submodule add ../super super ) ' @@ -115,17 +115,17 @@ Submodule path '../super/submodule': checked out '$submodulesha1' EOF cat <<EOF >expect2 +Cloning into '$pwd/recursivesuper/super/merging'... +Cloning into '$pwd/recursivesuper/super/none'... +Cloning into '$pwd/recursivesuper/super/rebasing'... +Cloning into '$pwd/recursivesuper/super/submodule'... Submodule 'merging' ($pwd/merging) registered for path '../super/merging' Submodule 'none' ($pwd/none) registered for path '../super/none' Submodule 'rebasing' ($pwd/rebasing) registered for path '../super/rebasing' Submodule 'submodule' ($pwd/submodule) registered for path '../super/submodule' -Cloning into '$pwd/recursivesuper/super/merging'... done. -Cloning into '$pwd/recursivesuper/super/none'... done. -Cloning into '$pwd/recursivesuper/super/rebasing'... done. -Cloning into '$pwd/recursivesuper/super/submodule'... done. EOF @@ -137,7 +137,8 @@ test_expect_success 'submodule update --init --recursive from subdirectory' ' git submodule update --init --recursive ../super >../../actual 2>../../actual2 ) && test_i18ncmp expect actual && - test_i18ncmp expect2 actual2 + sort actual2 >actual2.sorted && + test_i18ncmp expect2 actual2.sorted ' cat <<EOF >expect2 @@ -174,7 +175,7 @@ test_expect_success 'submodule update does not fetch already present commits' ' git submodule update > ../actual 2> ../actual.err ) && test_i18ncmp expected actual && - ! test -s actual.err + test_must_be_empty actual.err ' test_expect_success 'submodule update should fail due to local changes' ' @@ -245,13 +246,13 @@ test_expect_success 'submodule update --remote should fetch upstream changes wit ( cd super && git submodule update --remote --force submodule && - git -C submodule log -1 --oneline >actual - git -C ../submodule log -1 --oneline master >expect + git -C submodule log -1 --oneline >actual && + git -C ../submodule log -1 --oneline master >expect && test_cmp expect actual && git checkout -b test-branch && git submodule update --remote --force submodule && - git -C submodule log -1 --oneline >actual - git -C ../submodule log -1 --oneline test-branch >expect + git -C submodule log -1 --oneline >actual && + git -C ../submodule log -1 --oneline test-branch >expect && test_cmp expect actual && git checkout master && git branch -d test-branch && @@ -481,7 +482,8 @@ test_expect_success 'recursive submodule update - command in .git/config catches test_expect_success 'submodule init does not copy command into .git/config' ' (cd super && - H=$(git ls-files -s submodule | cut -d" " -f2) && + git ls-files -s submodule >out && + H=$(cut -d" " -f2 out) && mkdir submodule1 && git update-index --add --cacheinfo 160000 $H submodule1 && git config -f .gitmodules submodule.submodule1.path submodule1 && @@ -579,9 +581,11 @@ test_expect_success 'submodule update - update=none in .git/config' ' git checkout master && compare_head ) && - git diff --raw | grep " submodule" && + git diff --name-only >out && + grep ^submodule$ out && git submodule update && - git diff --raw | grep " submodule" && + git diff --name-only >out && + grep ^submodule$ out && (cd submodule && compare_head ) && @@ -597,11 +601,13 @@ test_expect_success 'submodule update - update=none in .git/config but --checkou git checkout master && compare_head ) && - git diff --raw | grep " submodule" && + git diff --name-only >out && + grep ^submodule$ out && git submodule update --checkout && - test_must_fail git diff --raw \| grep " submodule" && + git diff --name-only >out && + ! grep ^submodule$ out && (cd submodule && - test_must_fail compare_head + ! compare_head ) && git config --unset submodule.submodule.update ) @@ -615,8 +621,8 @@ test_expect_success 'submodule update --init skips submodule with update=none' ' git clone super cloned && (cd cloned && git submodule update --init && - test -e submodule/.git && - test_must_fail test -e none/.git + test_path_exists submodule/.git && + test_path_is_missing none/.git ) ' @@ -865,9 +871,9 @@ test_expect_success 'submodule update places git-dir in superprojects git-dir re (cd submodule/subsubmodule && git log > ../../expected ) && - (cd .git/modules/submodule/modules/subsubmodule + (cd .git/modules/submodule/modules/subsubmodule && git log > ../../../../../actual - ) + ) && test_cmp actual expected ) ' @@ -885,16 +891,18 @@ test_expect_success 'submodule update properly revives a moved submodule' ' H=$(git rev-parse --short HEAD) && git commit -am "pre move" && H2=$(git rev-parse --short HEAD) && - git status | sed "s/$H/XXX/" >expect && - H=$(cd submodule2; git rev-parse HEAD) && + git status >out && + sed "s/$H/XXX/" out >expect && + H=$(cd submodule2 && git rev-parse HEAD) && git rm --cached submodule2 && rm -rf submodule2 && mkdir -p "moved/sub module" && git update-index --add --cacheinfo 160000 $H "moved/sub module" && - git config -f .gitmodules submodule.submodule2.path "moved/sub module" + git config -f .gitmodules submodule.submodule2.path "moved/sub module" && git commit -am "post move" && git submodule update && - git status | sed "s/$H2/XXX/" >actual && + git status > out && + sed "s/$H2/XXX/" out >actual && test_cmp expect actual ) ' @@ -912,7 +920,7 @@ test_expect_success SYMLINKS 'submodule update can handle symbolic links in pwd' test_expect_success 'submodule update clone shallow submodule' ' test_when_finished "rm -rf super3" && - first=$(git -C cloned submodule status submodule |cut -c2-41) && + first=$(git -C cloned rev-parse HEAD:submodule) && second=$(git -C submodule rev-parse HEAD) && commit_count=$(git -C submodule rev-list --count $first^..$second) && git clone cloned super3 && @@ -922,7 +930,8 @@ test_expect_success 'submodule update clone shallow submodule' ' sed -e "s#url = ../#url = file://$pwd/#" <.gitmodules >.gitmodules.tmp && mv -f .gitmodules.tmp .gitmodules && git submodule update --init --depth=$commit_count && - test 1 = $(git -C submodule log --oneline | wc -l) + git -C submodule log --oneline >out && + test_line_count = 1 out ) ' @@ -938,7 +947,8 @@ test_expect_success 'submodule update clone shallow submodule outside of depth' test_i18ngrep "Direct fetching of that commit failed." actual && git -C ../submodule config uploadpack.allowReachableSHA1InWant true && git submodule update --init --depth=1 >actual && - test 1 = $(git -C submodule log --oneline | wc -l) + git -C submodule log --oneline >out && + test_line_count = 1 out ) ' diff --git a/t/t7407-submodule-foreach.sh b/t/t7407-submodule-foreach.sh index 6ba5daf42e..77729ac4aa 100755 --- a/t/t7407-submodule-foreach.sh +++ b/t/t7407-submodule-foreach.sh @@ -82,16 +82,16 @@ test_expect_success 'test basic "submodule foreach" usage' ' cat >expect <<EOF Entering '../sub1' -$pwd/clone-foo1-../sub1-$sub1sha1 +$pwd/clone-foo1-sub1-../sub1-$sub1sha1 Entering '../sub3' -$pwd/clone-foo3-../sub3-$sub3sha1 +$pwd/clone-foo3-sub3-../sub3-$sub3sha1 EOF test_expect_success 'test "submodule foreach" from subdirectory' ' mkdir clone/sub && ( cd clone/sub && - git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$sha1" >../../actual + git submodule foreach "echo \$toplevel-\$name-\$sm_path-\$displaypath-\$sha1" >../../actual ) && test_i18ncmp expect actual ' @@ -196,6 +196,38 @@ test_expect_success 'test messages from "foreach --recursive" from subdirectory' ) && test_i18ncmp expect actual ' +sub1sha1=$(cd clone2/sub1 && git rev-parse HEAD) +sub2sha1=$(cd clone2/sub2 && git rev-parse HEAD) +sub3sha1=$(cd clone2/sub3 && git rev-parse HEAD) +nested1sha1=$(cd clone2/nested1 && git rev-parse HEAD) +nested2sha1=$(cd clone2/nested1/nested2 && git rev-parse HEAD) +nested3sha1=$(cd clone2/nested1/nested2/nested3 && git rev-parse HEAD) +submodulesha1=$(cd clone2/nested1/nested2/nested3/submodule && git rev-parse HEAD) + +cat >expect <<EOF +Entering '../nested1' +toplevel: $pwd/clone2 name: nested1 path: nested1 displaypath: ../nested1 hash: $nested1sha1 +Entering '../nested1/nested2' +toplevel: $pwd/clone2/nested1 name: nested2 path: nested2 displaypath: ../nested1/nested2 hash: $nested2sha1 +Entering '../nested1/nested2/nested3' +toplevel: $pwd/clone2/nested1/nested2 name: nested3 path: nested3 displaypath: ../nested1/nested2/nested3 hash: $nested3sha1 +Entering '../nested1/nested2/nested3/submodule' +toplevel: $pwd/clone2/nested1/nested2/nested3 name: submodule path: submodule displaypath: ../nested1/nested2/nested3/submodule hash: $submodulesha1 +Entering '../sub1' +toplevel: $pwd/clone2 name: foo1 path: sub1 displaypath: ../sub1 hash: $sub1sha1 +Entering '../sub2' +toplevel: $pwd/clone2 name: foo2 path: sub2 displaypath: ../sub2 hash: $sub2sha1 +Entering '../sub3' +toplevel: $pwd/clone2 name: foo3 path: sub3 displaypath: ../sub3 hash: $sub3sha1 +EOF + +test_expect_success 'test "submodule foreach --recursive" from subdirectory' ' + ( + cd clone2/untracked && + git submodule foreach --recursive "echo toplevel: \$toplevel name: \$name path: \$sm_path displaypath: \$displaypath hash: \$sha1" >../../actual + ) && + test_i18ncmp expect actual +' cat > expect <<EOF nested1-nested1 diff --git a/t/t7408-submodule-reference.sh b/t/t7408-submodule-reference.sh index e159fc5035..34ac28c056 100755 --- a/t/t7408-submodule-reference.sh +++ b/t/t7408-submodule-reference.sh @@ -59,6 +59,16 @@ test_expect_success 'submodule add --reference uses alternates' ' test_alternate_is_used super/.git/modules/sub/objects/info/alternates super/sub ' +test_expect_success 'submodule add --reference with --dissociate does not use alternates' ' + ( + cd super && + git submodule add --reference ../B --dissociate "file://$base_dir/A" sub-dissociate && + git commit -m B-super-added && + git repack -ad + ) && + test_path_is_missing super/.git/modules/sub-dissociate/objects/info/alternates +' + test_expect_success 'that reference gets used with add' ' ( cd super/sub && @@ -82,6 +92,13 @@ test_expect_success 'updating superproject keeps alternates' ' test_alternate_is_used super-clone/.git/modules/sub/objects/info/alternates super-clone/sub ' +test_expect_success 'updating superproject with --dissociate does not keep alternates' ' + test_when_finished "rm -rf super-clone" && + git clone super super-clone && + git -C super-clone submodule update --init --reference ../B --dissociate && + test_path_is_missing super-clone/.git/modules/sub/objects/info/alternates +' + test_expect_success 'submodules use alternates when cloning a superproject' ' test_when_finished "rm -rf super-clone" && git clone --reference super --recursive super super-clone && @@ -131,7 +148,7 @@ test_expect_success 'preparing second superproject with a nested submodule plus cd supersuper && echo "I am super super." >file && git add file && - git commit -m B-super-super-initial + git commit -m B-super-super-initial && git submodule add "file://$base_dir/super" subwithsub && git commit -m B-super-super-added && git submodule update --init --recursive && diff --git a/t/t7410-submodule-checkout-to.sh b/t/t7410-submodule-checkout-to.sh index 1acef32647..f1b492ebc4 100755 --- a/t/t7410-submodule-checkout-to.sh +++ b/t/t7410-submodule-checkout-to.sh @@ -6,55 +6,72 @@ test_description='Combination of submodules and multiple workdirs' base_path=$(pwd -P) -test_expect_success 'setup: make origin' \ - 'mkdir -p origin/sub && ( cd origin/sub && git init && - echo file1 >file1 && - git add file1 && - git commit -m file1 ) && - mkdir -p origin/main && ( cd origin/main && git init && - git submodule add ../sub && - git commit -m "add sub" ) && - ( cd origin/sub && - echo file1updated >file1 && - git add file1 && - git commit -m "file1 updated" ) && - ( cd origin/main/sub && git pull ) && - ( cd origin/main && - git add sub && - git commit -m "sub updated" )' - -test_expect_success 'setup: clone' \ - 'mkdir clone && ( cd clone && - git clone --recursive "$base_path/origin/main")' +test_expect_success 'setup: make origin' ' + mkdir -p origin/sub && + ( + cd origin/sub && git init && + echo file1 >file1 && + git add file1 && + git commit -m file1 + ) && + mkdir -p origin/main && + ( + cd origin/main && git init && + git submodule add ../sub && + git commit -m "add sub" + ) && + ( + cd origin/sub && + echo file1updated >file1 && + git add file1 && + git commit -m "file1 updated" + ) && + git -C origin/main/sub pull && + ( + cd origin/main && + git add sub && + git commit -m "sub updated" + ) +' + +test_expect_success 'setup: clone' ' + mkdir clone && + git -C clone clone --recursive "$base_path/origin/main" +' rev1_hash_main=$(git --git-dir=origin/main/.git show --pretty=format:%h -q "HEAD~1") rev1_hash_sub=$(git --git-dir=origin/sub/.git show --pretty=format:%h -q "HEAD~1") -test_expect_success 'checkout main' \ - 'mkdir default_checkout && - (cd clone/main && - git worktree add "$base_path/default_checkout/main" "$rev1_hash_main")' +test_expect_success 'checkout main' ' + mkdir default_checkout && + git -C clone/main worktree add "$base_path/default_checkout/main" "$rev1_hash_main" +' -test_expect_failure 'can see submodule diffs just after checkout' \ - '(cd default_checkout/main && git diff --submodule master"^!" | grep "file1 updated")' +test_expect_failure 'can see submodule diffs just after checkout' ' + git -C default_checkout/main diff --submodule master"^!" >out && + grep "file1 updated" out +' -test_expect_success 'checkout main and initialize independed clones' \ - 'mkdir fully_cloned_submodule && - (cd clone/main && - git worktree add "$base_path/fully_cloned_submodule/main" "$rev1_hash_main") && - (cd fully_cloned_submodule/main && git submodule update)' +test_expect_success 'checkout main and initialize independent clones' ' + mkdir fully_cloned_submodule && + git -C clone/main worktree add "$base_path/fully_cloned_submodule/main" "$rev1_hash_main" && + git -C fully_cloned_submodule/main submodule update +' -test_expect_success 'can see submodule diffs after independed cloning' \ - '(cd fully_cloned_submodule/main && git diff --submodule master"^!" | grep "file1 updated")' +test_expect_success 'can see submodule diffs after independent cloning' ' + git -C fully_cloned_submodule/main diff --submodule master"^!" >out && + grep "file1 updated" out +' -test_expect_success 'checkout sub manually' \ - 'mkdir linked_submodule && - (cd clone/main && - git worktree add "$base_path/linked_submodule/main" "$rev1_hash_main") && - (cd clone/main/sub && - git worktree add "$base_path/linked_submodule/main/sub" "$rev1_hash_sub")' +test_expect_success 'checkout sub manually' ' + mkdir linked_submodule && + git -C clone/main worktree add "$base_path/linked_submodule/main" "$rev1_hash_main" && + git -C clone/main/sub worktree add "$base_path/linked_submodule/main/sub" "$rev1_hash_sub" +' -test_expect_success 'can see submodule diffs after manual checkout of linked submodule' \ - '(cd linked_submodule/main && git diff --submodule master"^!" | grep "file1 updated")' +test_expect_success 'can see submodule diffs after manual checkout of linked submodule' ' + git -C linked_submodule/main diff --submodule master"^!" >out && + grep "file1 updated" out +' test_done diff --git a/t/t7415-submodule-names.sh b/t/t7415-submodule-names.sh new file mode 100755 index 0000000000..293e2e1963 --- /dev/null +++ b/t/t7415-submodule-names.sh @@ -0,0 +1,194 @@ +#!/bin/sh + +test_description='check handling of .. in submodule names + +Exercise the name-checking function on a variety of names, and then give a +real-world setup that confirms we catch this in practice. +' +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-pack.sh + +test_expect_success 'check names' ' + cat >expect <<-\EOF && + valid + valid/with/paths + EOF + + git submodule--helper check-name >actual <<-\EOF && + valid + valid/with/paths + + ../foo + /../foo + ..\foo + \..\foo + foo/.. + foo/../ + foo\.. + foo\..\ + foo/../bar + EOF + + test_cmp expect actual +' + +test_expect_success 'create innocent subrepo' ' + git init innocent && + git -C innocent commit --allow-empty -m foo +' + +test_expect_success 'submodule add refuses invalid names' ' + test_must_fail \ + git submodule add --name ../../modules/evil "$PWD/innocent" evil +' + +test_expect_success 'add evil submodule' ' + git submodule add "$PWD/innocent" evil && + + mkdir modules && + cp -r .git/modules/evil modules && + write_script modules/evil/hooks/post-checkout <<-\EOF && + echo >&2 "RUNNING POST CHECKOUT" + EOF + + git config -f .gitmodules submodule.evil.update checkout && + git config -f .gitmodules --rename-section \ + submodule.evil submodule.../../modules/evil && + git add modules && + git commit -am evil +' + +# This step seems like it shouldn't be necessary, since the payload is +# contained entirely in the evil submodule. But due to the vagaries of the +# submodule code, checking out the evil module will fail unless ".git/modules" +# exists. Adding another submodule (with a name that sorts before "evil") is an +# easy way to make sure this is the case in the victim clone. +test_expect_success 'add other submodule' ' + git submodule add "$PWD/innocent" another-module && + git add another-module && + git commit -am another +' + +test_expect_success 'clone evil superproject' ' + git clone --recurse-submodules . victim >output 2>&1 && + ! grep "RUNNING POST CHECKOUT" output +' + +test_expect_success 'fsck detects evil superproject' ' + test_must_fail git fsck +' + +test_expect_success 'transfer.fsckObjects detects evil superproject (unpack)' ' + rm -rf dst.git && + git init --bare dst.git && + git -C dst.git config transfer.fsckObjects true && + test_must_fail git push dst.git HEAD +' + +test_expect_success 'transfer.fsckObjects detects evil superproject (index)' ' + rm -rf dst.git && + git init --bare dst.git && + git -C dst.git config transfer.fsckObjects true && + git -C dst.git config transfer.unpackLimit 1 && + test_must_fail git push dst.git HEAD +' + +# Normally our packs contain commits followed by trees followed by blobs. This +# reverses the order, which requires backtracking to find the context of a +# blob. We'll start with a fresh gitmodules-only tree to make it simpler. +test_expect_success 'create oddly ordered pack' ' + git checkout --orphan odd && + git rm -rf --cached . && + git add .gitmodules && + git commit -m odd && + { + pack_header 3 && + pack_obj $(git rev-parse HEAD:.gitmodules) && + pack_obj $(git rev-parse HEAD^{tree}) && + pack_obj $(git rev-parse HEAD) + } >odd.pack && + pack_trailer odd.pack +' + +test_expect_success 'transfer.fsckObjects handles odd pack (unpack)' ' + rm -rf dst.git && + git init --bare dst.git && + test_must_fail git -C dst.git unpack-objects --strict <odd.pack +' + +test_expect_success 'transfer.fsckObjects handles odd pack (index)' ' + rm -rf dst.git && + git init --bare dst.git && + test_must_fail git -C dst.git index-pack --strict --stdin <odd.pack +' + +test_expect_success 'index-pack --strict works for non-repo pack' ' + rm -rf dst.git && + git init --bare dst.git && + cp odd.pack dst.git && + test_must_fail git -C dst.git index-pack --strict odd.pack 2>output && + # Make sure we fail due to bad gitmodules content, not because we + # could not read the blob in the first place. + grep gitmodulesName output +' + +test_expect_success 'fsck detects symlinked .gitmodules file' ' + git init symlink && + ( + cd symlink && + + # Make the tree directly to avoid index restrictions. + # + # Because symlinks store the target as a blob, choose + # a pathname that could be parsed as a .gitmodules file + # to trick naive non-symlink-aware checking. + tricky="[foo]bar=true" && + content=$(git hash-object -w ../.gitmodules) && + target=$(printf "$tricky" | git hash-object -w --stdin) && + { + printf "100644 blob $content\t$tricky\n" && + printf "120000 blob $target\t.gitmodules\n" + } | git mktree && + + # Check not only that we fail, but that it is due to the + # symlink detector; this grep string comes from the config + # variable name and will not be translated. + test_must_fail git fsck 2>output && + grep gitmodulesSymlink output + ) +' + +test_expect_success 'fsck detects non-blob .gitmodules' ' + git init non-blob && + ( + cd non-blob && + + # As above, make the funny tree directly to avoid index + # restrictions. + mkdir subdir && + cp ../.gitmodules subdir/file && + git add subdir/file && + git commit -m ok && + git ls-tree HEAD | sed s/subdir/.gitmodules/ | git mktree && + + test_must_fail git fsck 2>output && + grep gitmodulesBlob output + ) +' + +test_expect_success 'fsck detects corrupt .gitmodules' ' + git init corrupt && + ( + cd corrupt && + + echo "[broken" >.gitmodules && + git add .gitmodules && + git commit -m "broken gitmodules" && + + git fsck 2>output && + grep gitmodulesParse output && + test_i18ngrep ! "bad config" output + ) +' + +test_done diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh index fa61b1a4ee..1a6773ee68 100755 --- a/t/t7501-commit.sh +++ b/t/t7501-commit.sh @@ -47,11 +47,23 @@ test_expect_success 'paths and -a do not mix' ' test_expect_success PERL 'can use paths with --interactive' ' echo bong-o-bong >file && # 2: update, 1:st path, that is all, 7: quit - ( echo 2; echo 1; echo; echo 7 ) | + test_write_lines 2 1 "" 7 | git commit -m foo --interactive file && git reset --hard HEAD^ ' +test_expect_success 'removed files and relative paths' ' + test_when_finished "rm -rf foo" && + git init foo && + >foo/foo.txt && + git -C foo add foo.txt && + git -C foo commit -m first && + git -C foo rm foo.txt && + + mkdir -p foo/bar && + git -C foo/bar commit -m second ../foo.txt +' + test_expect_success 'using invalid commit with -C' ' test_must_fail git commit --allow-empty -C bogus ' @@ -281,7 +293,7 @@ test_expect_success PERL 'interactive add' ' test_expect_success PERL "commit --interactive doesn't change index if editor aborts" ' echo zoo >file && test_must_fail git diff --exit-code >diff1 && - (echo u ; echo "*" ; echo q) | + test_write_lines u "*" q | ( EDITOR=: && export EDITOR && @@ -399,8 +411,8 @@ test_expect_success 'sign off (1)' ' git commit -s -m "thank you" && git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && ( - echo thank you - echo + echo thank you && + echo && git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ) >expected && @@ -418,9 +430,9 @@ test_expect_success 'sign off (2)' ' $existing" && git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && ( - echo thank you - echo - echo $existing + echo thank you && + echo && + echo $existing && git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ) >expected && @@ -438,9 +450,9 @@ test_expect_success 'signoff gap' ' $alt" && git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && ( - echo welcome - echo - echo $alt + echo welcome && + echo && + echo $alt && git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ) >expected && @@ -458,11 +470,11 @@ We have now $alt" && git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && ( - echo welcome - echo - echo We have now - echo $alt - echo + echo welcome && + echo && + echo We have now && + echo $alt && + echo && git var GIT_COMMITTER_IDENT | sed -e "s/>.*/>/" -e "s/^/Signed-off-by: /" ) >expected && @@ -479,11 +491,11 @@ non-trailer line Myfooter: x" && git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && ( - echo subject - echo - echo non-trailer line - echo Myfooter: x - echo + echo subject && + echo && + echo non-trailer line && + echo Myfooter: x && + echo && echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" ) >expected && test_cmp expected actual && @@ -496,15 +508,31 @@ non-trailer line Myfooter: x" && git cat-file commit HEAD | sed -e "1,/^\$/d" > actual && ( - echo subject - echo - echo non-trailer line - echo Myfooter: x + echo subject && + echo && + echo non-trailer line && + echo Myfooter: x && echo "Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>" ) >expected && test_cmp expected actual ' +test_expect_success 'signoff not confused by ---' ' + cat >expected <<-EOF && + subject + + body + --- + these dashes confuse the parser! + + Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> + EOF + # should be a noop, since we already signed + git commit --allow-empty --signoff -F expected && + git log -1 --pretty=format:%B >actual && + test_cmp expected actual +' + test_expect_success 'multiple -m' ' >negative && @@ -512,10 +540,10 @@ test_expect_success 'multiple -m' ' git commit -m "one" -m "two" -m "three" && git cat-file commit HEAD | sed -e "1,/^\$/d" >actual && ( - echo one - echo - echo two - echo + echo one && + echo && + echo two && + echo && echo three ) >expected && test_cmp expected actual @@ -570,13 +598,11 @@ test_expect_success 'same tree (merge and amend merge)' ' git merge -s ours side -m "empty ok" && git diff HEAD^ HEAD >actual && - : >expected && - test_cmp expected actual && + test_must_be_empty actual && git commit --amend -m "empty really ok" && git diff HEAD^ HEAD >actual && - : >expected && - test_cmp expected actual + test_must_be_empty actual ' @@ -665,7 +691,7 @@ test_expect_success '--dry-run with conflicts fixed from a merge' ' git checkout -b branch-2 HEAD^1 && echo "commit-2-state" >test-file && git commit -m "commit 2" -i test-file && - ! $(git merge --no-commit commit-1) && + test_must_fail git merge --no-commit commit-1 && echo "commit-2-state" >test-file && git add test-file && git commit --dry-run && diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index d33a3cb331..ca4a740da0 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -393,7 +393,6 @@ EOF test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' ' >.git/result && - >expect && echo >>negative && ( @@ -403,7 +402,7 @@ test_expect_success !AUTOIDENT 'do not fire editor when committer is bogus' ' export GIT_EDITOR && test_must_fail git commit -e -m sample -a ) && - test_cmp expect .git/result + test_must_be_empty .git/result ' test_expect_success 'do not fire editor if -m <msg> was given' ' diff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh index 302a3a2082..31b9c6a2c1 100755 --- a/t/t7504-commit-msg-hook.sh +++ b/t/t7504-commit-msg-hook.sh @@ -157,6 +157,7 @@ test_expect_success 'merge bypasses failing hook with --no-verify' ' test_when_finished "git branch -D newbranch" && test_when_finished "git checkout -f master" && git checkout --orphan newbranch && + git rm -f file && : >file2 && git add file2 && git commit --no-verify file2 -m in-side-branch && @@ -168,7 +169,7 @@ test_expect_success 'merge bypasses failing hook with --no-verify' ' chmod -x "$HOOK" test_expect_success POSIXPERM 'with non-executable hook' ' - echo "content" >> file && + echo "content" >file && git add file && git commit -m "content" @@ -249,6 +250,7 @@ test_expect_success 'hook called in git-merge picks up commit message' ' test_when_finished "git branch -D newbranch" && test_when_finished "git checkout -f master" && git checkout --orphan newbranch && + git rm -f file && : >file2 && git add file2 && git commit --no-verify file2 -m in-side-branch && diff --git a/t/t7506-status-submodule.sh b/t/t7506-status-submodule.sh index 9edf6572ed..943708fb04 100755 --- a/t/t7506-status-submodule.sh +++ b/t/t7506-status-submodule.sh @@ -18,7 +18,7 @@ test_create_repo_with_commit () { } sanitize_output () { - sed -e "s/$_x40/HASH/" -e "s/$_x40/HASH/" output >output2 && + sed -e "s/$OID_REGEX/HASH/" -e "s/$OID_REGEX/HASH/" output >output2 && mv output2 output } @@ -193,9 +193,9 @@ test_expect_success 'status with added and untracked file in modified submodule test_expect_success 'setup .git file for sub' ' (cd sub && - rm -f new-file + rm -f new-file && REAL="$(pwd)/../.real" && - mv .git "$REAL" + mv .git "$REAL" && echo "gitdir: $REAL" >.git) && echo .real >>.gitignore && git commit -m "added .real to .gitignore" .gitignore @@ -209,12 +209,12 @@ test_expect_success 'status with added file in modified submodule with .git file test_expect_success 'status with a lot of untracked files in the submodule' ' ( - cd sub + cd sub && i=0 && while test $i -lt 1024 do - >some-file-$i - i=$(( $i + 1 )) + >some-file-$i && + i=$(( $i + 1 )) || exit 1 done ) && git status --porcelain sub 2>err.actual && diff --git a/t/t7508-status.sh b/t/t7508-status.sh index 7afadb175a..e1f11293e2 100755 --- a/t/t7508-status.sh +++ b/t/t7508-status.sh @@ -1099,6 +1099,7 @@ EOF ' test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' ' + test_when_finished "chmod 775 .git" && ( chmod a-w .git && # make dir1/tracked stat-dirty @@ -1108,9 +1109,6 @@ test_expect_success POSIXPERM,SANITY 'status succeeds in a read-only repository' # make sure "status" succeeded without writing index out git diff-files | grep dir1/tracked ) - status=$? - chmod 775 .git - (exit $status) ' (cd sm && echo > bar && git add bar && git commit -q -m 'Add bar') && git add sm @@ -1674,10 +1672,10 @@ test_expect_success '"Initial commit" should not be noted in commit template' ' test_expect_success '--no-optional-locks prevents index update' ' test-tool chmtime =1234567890 .git/index && git --no-optional-locks status && - test-tool chmtime -v +0 .git/index >out && + test-tool chmtime --get .git/index >out && grep ^1234567890 out && git status && - test-tool chmtime -v +0 .git/index >out && + test-tool chmtime --get .git/index >out && ! grep ^1234567890 out ' diff --git a/t/t7510-signed-commit.sh b/t/t7510-signed-commit.sh index 762135adea..4e37ff8f16 100755 --- a/t/t7510-signed-commit.sh +++ b/t/t7510-signed-commit.sh @@ -142,10 +142,9 @@ test_expect_success GPG 'show signed commit with signature' ' test_expect_success GPG 'detect fudged signature' ' git cat-file commit seventh-signed >raw && - - sed -e "s/seventh/7th forged/" raw >forged1 && + sed -e "s/^seventh/7th forged/" raw >forged1 && git hash-object -w -t commit forged1 >forged1.commit && - ! git verify-commit $(cat forged1.commit) && + test_must_fail git verify-commit $(cat forged1.commit) && git show --pretty=short --show-signature $(cat forged1.commit) >actual1 && grep "BAD signature from" actual1 && ! grep "Good signature from" actual1 @@ -156,7 +155,7 @@ test_expect_success GPG 'detect fudged signature with NUL' ' cat raw >forged2 && echo Qwik | tr "Q" "\000" >>forged2 && git hash-object -w -t commit forged2 >forged2.commit && - ! git verify-commit $(cat forged2.commit) && + test_must_fail git verify-commit $(cat forged2.commit) && git show --pretty=short --show-signature $(cat forged2.commit) >actual2 && grep "BAD signature from" actual2 && ! grep "Good signature from" actual2 @@ -228,4 +227,11 @@ test_expect_success GPG 'log.showsignature behaves like --show-signature' ' grep "gpg: Good signature" actual ' +test_expect_success GPG 'check config gpg.format values' ' + test_config gpg.format openpgp && + git commit -S --amend -m "success" && + test_config gpg.format OpEnPgP && + test_must_fail git commit -S --amend -m "fail" +' + test_done diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh index 164719d1c9..c441861331 100755 --- a/t/t7513-interpret-trailers.sh +++ b/t/t7513-interpret-trailers.sh @@ -1417,4 +1417,46 @@ test_expect_success 'unfold' ' test_cmp expected actual ' +test_expect_success 'handling of --- lines in input' ' + echo "real-trailer: just right" >expected && + + git interpret-trailers --parse >actual <<-\EOF && + subject + + body + + not-a-trailer: too soon + ------ this is just a line in the commit message with a bunch of + ------ dashes; it does not have any syntactic meaning. + + real-trailer: just right + --- + below the dashed line may be a patch, etc. + + not-a-trailer: too late + EOF + + test_cmp expected actual +' + +test_expect_success 'suppress --- handling' ' + echo "real-trailer: just right" >expected && + + git interpret-trailers --parse --no-divider >actual <<-\EOF && + subject + + This commit message has a "---" in it, but because we tell + interpret-trailers not to respect that, it has no effect. + + not-a-trailer: too soon + --- + + This is still the commit message body. + + real-trailer: just right + EOF + + test_cmp expected actual +' + test_done diff --git a/t/t7525-status-rename.sh b/t/t7525-status-rename.sh new file mode 100755 index 0000000000..ef8b1b3078 --- /dev/null +++ b/t/t7525-status-rename.sh @@ -0,0 +1,113 @@ +#!/bin/sh + +test_description='git status rename detection options' + +. ./test-lib.sh + +test_expect_success 'setup' ' + echo 1 >original && + git add . && + git commit -m"Adding original file." && + mv original renamed && + echo 2 >> renamed && + git add . && + cat >.gitignore <<-\EOF + .gitignore + expect* + actual* + EOF +' + +test_expect_success 'status no-options' ' + git status >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'status --no-renames' ' + git status --no-renames >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status.renames inherits from diff.renames false' ' + git -c diff.renames=false status >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status.renames inherits from diff.renames true' ' + git -c diff.renames=true status >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'status.renames overrides diff.renames false' ' + git -c diff.renames=true -c status.renames=false status >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status.renames overrides from diff.renames true' ' + git -c diff.renames=false -c status.renames=true status >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'status status.renames=false' ' + git -c status.renames=false status >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status status.renames=true' ' + git -c status.renames=true status >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'commit honors status.renames=false' ' + git -c status.renames=false commit --dry-run >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'commit honors status.renames=true' ' + git -c status.renames=true commit --dry-run >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'status config overridden' ' + git -c status.renames=true status --no-renames >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status score=100%' ' + git status -M=100% >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual && + + git status --find-rename=100% >actual && + test_i18ngrep "deleted:" actual && + test_i18ngrep "new file:" actual +' + +test_expect_success 'status score=01%' ' + git status -M=01% >actual && + test_i18ngrep "renamed:" actual && + + git status --find-rename=01% >actual && + test_i18ngrep "renamed:" actual +' + +test_expect_success 'copies not overridden by find-rename' ' + cp renamed copy && + git add copy && + + git -c status.renames=copies status -M=01% >actual && + test_i18ngrep "copied:" actual && + test_i18ngrep "renamed:" actual && + + git -c status.renames=copies status --find-rename=01% >actual && + test_i18ngrep "copied:" actual && + test_i18ngrep "renamed:" actual +' + +test_done diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh index 6736d8d131..106148254d 100755 --- a/t/t7600-merge.sh +++ b/t/t7600-merge.sh @@ -38,7 +38,6 @@ printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1 printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5 printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9 printf '%s\n' 1 2 3 4 5 6 7 8 '9 Z' >result.9z ->empty create_merge_msgs () { echo "Merge tag 'c2'" >msg.1-5 && @@ -58,8 +57,6 @@ create_merge_msgs () { echo && git log --no-merges ^HEAD c2 c3 } >squash.1-5-9 && - : >msg.nologff && - : >msg.nolognoff && { echo "* tag 'c3':" && echo " commit 3" @@ -519,7 +516,7 @@ test_expect_success 'tolerate unknown values for merge.ff' ' test_tick && git merge c1 2>message && verify_head "$c1" && - test_cmp empty message + test_must_be_empty message ' test_expect_success 'combining --squash and --no-ff is refused' ' @@ -551,13 +548,13 @@ test_expect_success 'merge log message' ' git reset --hard c0 && git merge --no-log c2 && git show -s --pretty=format:%b HEAD >msg.act && - test_cmp msg.nologff msg.act && + test_must_be_empty msg.act && git reset --hard c0 && test_config branch.master.mergeoptions "--no-ff" && git merge --no-log c2 && git show -s --pretty=format:%b HEAD >msg.act && - test_cmp msg.nolognoff msg.act && + test_must_be_empty msg.act && git merge --log c3 && git show -s --pretty=format:%b HEAD >msg.act && diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh index 9c422bcd7c..dd8ab7ede1 100755 --- a/t/t7607-merge-overwrite.sh +++ b/t/t7607-merge-overwrite.sh @@ -92,7 +92,7 @@ test_expect_success 'will not overwrite removed file with staged changes' ' test_cmp important c1.c ' -test_expect_failure 'will not overwrite unstaged changes in renamed file' ' +test_expect_success 'will not overwrite unstaged changes in renamed file' ' git reset --hard c1 && git mv c1.c other.c && git commit -m rename && diff --git a/t/t7610-mergetool.sh b/t/t7610-mergetool.sh index 1a430b9c40..a9fb971615 100755 --- a/t/t7610-mergetool.sh +++ b/t/t7610-mergetool.sh @@ -57,18 +57,18 @@ test_expect_success 'setup' ' git checkout -b delete-base branch1 && mkdir -p a/a && - (echo one; echo two; echo 3; echo 4) >a/a/file.txt && + test_write_lines one two 3 4 >a/a/file.txt && git add a/a/file.txt && git commit -m"base file" && git checkout -b move-to-b delete-base && mkdir -p b/b && git mv a/a/file.txt b/b/file.txt && - (echo one; echo two; echo 4) >b/b/file.txt && + test_write_lines one two 4 >b/b/file.txt && git commit -a -m"move to b" && git checkout -b move-to-c delete-base && mkdir -p c/c && git mv a/a/file.txt c/c/file.txt && - (echo one; echo two; echo 3) >c/c/file.txt && + test_write_lines one two 3 >c/c/file.txt && git commit -a -m"move to c" && git checkout -b stash1 master && @@ -328,9 +328,8 @@ test_expect_success 'mergetool produces no errors when keepBackup is used' ' git checkout -b test$test_count move-to-c && test_config mergetool.keepBackup true && test_must_fail git merge move-to-b && - : >expect && echo d | git mergetool a/a/file.txt 2>actual && - test_cmp expect actual && + test_must_be_empty actual && ! test -d a ' @@ -349,7 +348,7 @@ test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' ' git checkout -b test$test_count move-to-c && test_config mergetool.keepTemporaries true && test_must_fail git merge move-to-b && - ! (echo a; echo n) | git mergetool a/a/file.txt && + ! test_write_lines a n | git mergetool a/a/file.txt && test -d a/a && cat >expect <<-\EOF && file_BASE_.txt @@ -620,8 +619,7 @@ test_expect_success 'file with no base' ' git checkout -b test$test_count branch1 && test_must_fail git merge master && git mergetool --no-prompt --tool mybase -- both && - >expected && - test_cmp expected both + test_must_be_empty both ' test_expect_success 'custom commands override built-ins' ' diff --git a/t/t7611-merge-abort.sh b/t/t7611-merge-abort.sh index 7b4798e8e4..7c84a518aa 100755 --- a/t/t7611-merge-abort.sh +++ b/t/t7611-merge-abort.sh @@ -187,31 +187,6 @@ test_expect_success 'Fail clean merge with matching dirty worktree' ' test_cmp expect actual ' -test_expect_success 'Abort clean merge with matching dirty index' ' - git add bar && - git diff --staged > expect && - git merge --no-commit clean_branch && - test -f .git/MERGE_HEAD && - ### When aborting the merge, git will discard all staged changes, - ### including those that were staged pre-merge. In other words, - ### --abort will LOSE any staged changes (the staged changes that - ### are lost must match the merge result, or the merge would not - ### have been allowed to start). Change expectations accordingly: - rm expect && - touch expect && - # Abort merge - git merge --abort && - test ! -f .git/MERGE_HEAD && - test "$pre_merge_head" = "$(git rev-parse HEAD)" && - git diff --staged > actual && - test_cmp expect actual && - test -z "$(git diff)" -' - -test_expect_success 'Reset worktree changes' ' - git reset --hard "$pre_merge_head" -' - test_expect_success 'Fail conflicting merge with matching dirty worktree' ' echo barf > bar && git diff > expect && @@ -223,97 +198,4 @@ test_expect_success 'Fail conflicting merge with matching dirty worktree' ' test_cmp expect actual ' -test_expect_success 'Abort conflicting merge with matching dirty index' ' - git add bar && - git diff --staged > expect && - test_must_fail git merge conflict_branch && - test -f .git/MERGE_HEAD && - ### When aborting the merge, git will discard all staged changes, - ### including those that were staged pre-merge. In other words, - ### --abort will LOSE any staged changes (the staged changes that - ### are lost must match the merge result, or the merge would not - ### have been allowed to start). Change expectations accordingly: - rm expect && - touch expect && - # Abort merge - git merge --abort && - test ! -f .git/MERGE_HEAD && - test "$pre_merge_head" = "$(git rev-parse HEAD)" && - git diff --staged > actual && - test_cmp expect actual && - test -z "$(git diff)" -' - -test_expect_success 'Reset worktree changes' ' - git reset --hard "$pre_merge_head" -' - -test_expect_success 'Abort merge with pre- and post-merge worktree changes' ' - # Pre-merge worktree changes - echo xyzzy > foo && - echo barf > bar && - git add bar && - git diff > expect && - git diff --staged > expect-staged && - # Perform merge - test_must_fail git merge conflict_branch && - test -f .git/MERGE_HEAD && - # Post-merge worktree changes - echo yzxxz > foo && - echo blech > baz && - ### When aborting the merge, git will discard staged changes (bar) - ### and unmerged changes (baz). Other changes that are neither - ### staged nor marked as unmerged (foo), will be preserved. For - ### these changed, git cannot tell pre-merge changes apart from - ### post-merge changes, so the post-merge changes will be - ### preserved. Change expectations accordingly: - git diff -- foo > expect && - rm expect-staged && - touch expect-staged && - # Abort merge - git merge --abort && - test ! -f .git/MERGE_HEAD && - test "$pre_merge_head" = "$(git rev-parse HEAD)" && - git diff > actual && - test_cmp expect actual && - git diff --staged > actual-staged && - test_cmp expect-staged actual-staged -' - -test_expect_success 'Reset worktree changes' ' - git reset --hard "$pre_merge_head" -' - -test_expect_success 'Abort merge with pre- and post-merge index changes' ' - # Pre-merge worktree changes - echo xyzzy > foo && - echo barf > bar && - git add bar && - git diff > expect && - git diff --staged > expect-staged && - # Perform merge - test_must_fail git merge conflict_branch && - test -f .git/MERGE_HEAD && - # Post-merge worktree changes - echo yzxxz > foo && - echo blech > baz && - git add foo bar && - ### When aborting the merge, git will discard all staged changes - ### (foo, bar and baz), and no changes will be preserved. Whether - ### the changes were staged pre- or post-merge does not matter - ### (except for not preventing starting the merge). - ### Change expectations accordingly: - rm expect expect-staged && - touch expect && - touch expect-staged && - # Abort merge - git merge --abort && - test ! -f .git/MERGE_HEAD && - test "$pre_merge_head" = "$(git rev-parse HEAD)" && - git diff > actual && - test_cmp expect actual && - git diff --staged > actual-staged && - test_cmp expect-staged actual-staged -' - test_done diff --git a/t/t7612-merge-verify-signatures.sh b/t/t7612-merge-verify-signatures.sh index e797c74112..e2b1df817a 100755 --- a/t/t7612-merge-verify-signatures.sh +++ b/t/t7612-merge-verify-signatures.sh @@ -23,7 +23,7 @@ test_expect_success GPG 'create signed commits' ' echo 3 >bar && git add bar && test_tick && git commit -S -m "bad on side" && git cat-file commit side-bad >raw && - sed -e "s/bad/forged bad/" raw >forged && + sed -e "s/^bad/forged bad/" raw >forged && git hash-object -w -t commit forged >forged.commit && git checkout initial && diff --git a/t/t7700-repack.sh b/t/t7700-repack.sh index 6061a04147..6162e2a8e6 100755 --- a/t/t7700-repack.sh +++ b/t/t7700-repack.sh @@ -4,6 +4,12 @@ test_description='git repack works correctly' . ./test-lib.sh +commit_and_pack() { + test_commit "$@" >/dev/null && + SHA1=$(git pack-objects --all --unpacked --incremental .git/objects/pack/pack </dev/null) && + echo pack-${SHA1}.pack +} + test_expect_success 'objects in packs marked .keep are not repacked' ' echo content1 > file1 && echo content2 > file2 && @@ -194,7 +200,26 @@ test_expect_success 'objects made unreachable by grafts only are kept' ' git reflog expire --expire=$test_tick --expire-unreachable=$test_tick --all && git repack -a -d && git cat-file -t $H1 - ' +' + +test_expect_success 'repack --keep-pack' ' + test_create_repo keep-pack && + ( + cd keep-pack && + P1=$(commit_and_pack 1) && + P2=$(commit_and_pack 2) && + P3=$(commit_and_pack 3) && + P4=$(commit_and_pack 4) && + ls .git/objects/pack/*.pack >old-counts && + test_line_count = 4 old-counts && + git repack -a -d --keep-pack $P1 --keep-pack $P4 && + ls .git/objects/pack/*.pack >new-counts && + grep -q $P1 new-counts && + grep -q $P4 new-counts && + test_line_count = 3 new-counts && + git fsck + ) +' test_done diff --git a/t/t7701-repack-unpack-unreachable.sh b/t/t7701-repack-unpack-unreachable.sh index 8a586ab021..48261ba080 100755 --- a/t/t7701-repack-unpack-unreachable.sh +++ b/t/t7701-repack-unpack-unreachable.sh @@ -55,8 +55,8 @@ test_expect_success '-A with -d option leaves unreachable objects unpacked' ' compare_mtimes () { - read tref rest && - while read t rest; do + read tref && + while read t; do test "$tref" = "$t" || return 1 done } @@ -90,7 +90,7 @@ test_expect_success 'unpacked objects receive timestamp of pack file' ' tmppack=".git/objects/pack/tmp_pack" && ln "$packfile" "$tmppack" && git repack -A -l -d && - test-tool chmtime -v +0 "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" \ + test-tool chmtime --get "$tmppack" "$fsha1path" "$csha1path" "$tsha1path" \ > mtimes && compare_mtimes < mtimes ' diff --git a/t/t7810-grep.sh b/t/t7810-grep.sh index 1797f632a3..be5c1bd553 100755 --- a/t/t7810-grep.sh +++ b/t/t7810-grep.sh @@ -99,6 +99,101 @@ do test_cmp expected actual ' + test_expect_success "grep -w $L (with --column)" ' + { + echo ${HC}file:5:foo mmap bar + echo ${HC}file:14:foo_mmap bar mmap + echo ${HC}file:5:foo mmap bar_mmap + echo ${HC}file:14:foo_mmap bar mmap baz + } >expected && + git grep --column -w -e mmap $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with --column, extended OR)" ' + { + echo ${HC}file:14:foo_mmap bar mmap + echo ${HC}file:19:foo_mmap bar mmap baz + } >expected && + git grep --column -w -e mmap$ --or -e baz $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with --column, --invert)" ' + { + echo ${HC}file:1:foo mmap bar + echo ${HC}file:1:foo_mmap bar + echo ${HC}file:1:foo_mmap bar mmap + echo ${HC}file:1:foo mmap bar_mmap + } >expected && + git grep --column --invert -w -e baz $H -- file >actual && + test_cmp expected actual + ' + + test_expect_success "grep $L (with --column, --invert, extended OR)" ' + { + echo ${HC}hello_world:6:HeLLo_world + } >expected && + git grep --column --invert -e ll --or --not -e _ $H -- hello_world \ + >actual && + test_cmp expected actual + ' + + test_expect_success "grep $L (with --column, --invert, extended AND)" ' + { + echo ${HC}hello_world:3:Hello world + echo ${HC}hello_world:3:Hello_world + echo ${HC}hello_world:6:HeLLo_world + } >expected && + git grep --column --invert --not -e _ --and --not -e ll $H -- hello_world \ + >actual && + test_cmp expected actual + ' + + test_expect_success "grep $L (with --column, double-negation)" ' + { + echo ${HC}file:1:foo_mmap bar mmap baz + } >expected && + git grep --column --not \( --not -e foo --or --not -e baz \) $H -- file \ + >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with --column, -C)" ' + { + echo ${HC}file:5:foo mmap bar + echo ${HC}file-foo_mmap bar + echo ${HC}file:14:foo_mmap bar mmap + echo ${HC}file:5:foo mmap bar_mmap + echo ${HC}file:14:foo_mmap bar mmap baz + } >expected && + git grep --column -w -C1 -e mmap $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with --line-number, --column)" ' + { + echo ${HC}file:1:5:foo mmap bar + echo ${HC}file:3:14:foo_mmap bar mmap + echo ${HC}file:4:5:foo mmap bar_mmap + echo ${HC}file:5:14:foo_mmap bar mmap baz + } >expected && + git grep -n --column -w -e mmap $H >actual && + test_cmp expected actual + ' + + test_expect_success "grep -w $L (with non-extended patterns, --column)" ' + { + echo ${HC}file:5:foo mmap bar + echo ${HC}file:10:foo_mmap bar + echo ${HC}file:10:foo_mmap bar mmap + echo ${HC}file:5:foo mmap bar_mmap + echo ${HC}file:10:foo_mmap bar mmap baz + } >expected && + git grep --column -w -e bar -e mmap $H >actual && + test_cmp expected actual + ' + test_expect_success "grep -w $L" ' { echo ${HC}file:1:foo mmap bar @@ -122,9 +217,8 @@ do ' test_expect_success "grep -w $L (w)" ' - : >expected && test_must_fail git grep -n -w -e "^w" $H >actual && - test_cmp expected actual + test_must_be_empty actual ' test_expect_success "grep -w $L (x)" ' @@ -144,29 +238,42 @@ do ' test_expect_success "grep -w $L (y-2)" ' - : >expected && if git grep -n -w -e "^y y" $H >actual then echo should not have matched cat actual false else - test_cmp expected actual + test_must_be_empty actual fi ' test_expect_success "grep -w $L (z)" ' - : >expected && if git grep -n -w -e "^z" $H >actual then echo should not have matched cat actual false else - test_cmp expected actual + test_must_be_empty actual fi ' + test_expect_success "grep $L (with --column, --only-matching)" ' + { + echo ${HC}file:1:5:mmap + echo ${HC}file:2:5:mmap + echo ${HC}file:3:5:mmap + echo ${HC}file:3:13:mmap + echo ${HC}file:4:5:mmap + echo ${HC}file:4:13:mmap + echo ${HC}file:5:5:mmap + echo ${HC}file:5:13:mmap + } >expected && + git grep --column -n -o -e mmap $H >actual && + test_cmp expected actual + ' + test_expect_success "grep $L (t-1)" ' echo "${HC}t/t:1:test" >expected && git grep -n -e test $H >actual && @@ -388,7 +495,7 @@ test_expect_success 'grep -L -C' ' test_expect_success 'grep --files-without-match --quiet' ' git grep --files-without-match --quiet nonexistent_string >actual && - test_cmp /dev/null actual + test_must_be_empty actual ' cat >expected <<EOF @@ -509,11 +616,10 @@ z:zzz EOF test_expect_success 'grep -q, silently report matches' ' - >empty && git grep -q mmap >actual && - test_cmp empty actual && + test_must_be_empty actual && test_must_fail git grep -q qfwfq >actual && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'grep -C1 hunk mark between files' ' @@ -581,8 +687,7 @@ test_expect_success 'log grep (5)' ' test_expect_success 'log grep (6)' ' git log --author=-0700 --pretty=tformat:%s >actual && - >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'log grep (7)' ' @@ -607,8 +712,7 @@ test_expect_success 'log grep (9)' ' test_expect_success 'log grep (9)' ' git log -g --grep-reflog="commit: third" --author="non-existent" --pretty=tformat:%s >actual && - : >expect && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'log --grep-reflog can only be used under -g' ' @@ -698,15 +802,13 @@ test_expect_success 'log --all-match --grep --grep --author takes intersection' ' test_expect_success 'log --author does not search in timestamp' ' - : >expect && git log --author="$GIT_AUTHOR_DATE" >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'log --committer does not search in timestamp' ' - : >expect && git log --committer="$GIT_COMMITTER_DATE" >actual && - test_cmp expect actual + test_must_be_empty actual ' test_expect_success 'grep with CE_VALID file' ' @@ -845,10 +947,9 @@ test_expect_success 'grep from a subdirectory to search wider area (1)' ' test_expect_success 'grep from a subdirectory to search wider area (2)' ' mkdir -p s && ( - cd s || exit 1 - ( git grep xxyyzz .. >out ; echo $? >status ) - ! test -s out && - test 1 = $(cat status) + cd s && + test_expect_code 1 git grep xxyyzz .. >out && + test_must_be_empty out ) ' @@ -957,13 +1058,12 @@ test_expect_success 'inside git repository but with --no-index' ' echo ".gitignore:.*o*" && cat is/expect.unignored } >is/expect.full && - : >is/expect.empty && echo file2:world >is/expect.sub && ( cd is/git && git init && test_must_fail git grep o >../actual.full && - test_cmp ../expect.empty ../actual.full && + test_must_be_empty ../actual.full && git grep --untracked o >../actual.unignored && test_cmp ../expect.unignored ../actual.unignored && @@ -976,7 +1076,7 @@ test_expect_success 'inside git repository but with --no-index' ' cd sub && test_must_fail git grep o >../../actual.sub && - test_cmp ../../expect.empty ../../actual.sub && + test_must_be_empty ../../actual.sub && git grep --no-index o >../../actual.sub && test_cmp ../../expect.sub ../../actual.sub && @@ -1142,10 +1242,9 @@ test_expect_success !PCRE 'grep -P pattern errors without PCRE' ' ' test_expect_success 'grep pattern with grep.extendedRegexp=true' ' - >empty && test_must_fail git -c grep.extendedregexp=true \ grep "\p{Ps}.*?\p{Pe}" hello.c >actual && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success PCRE 'grep -P pattern with grep.extendedRegexp=true' ' diff --git a/t/t7811-grep-open.sh b/t/t7811-grep-open.sh index e1951a5cbb..d1ebfd88c7 100755 --- a/t/t7811-grep-open.sh +++ b/t/t7811-grep-open.sh @@ -51,14 +51,13 @@ test_expect_success SIMPLEPAGER 'git grep -O' ' grep.h EOF echo grep.h >expect.notless && - >empty && PATH=.:$PATH git grep -O GREP_PATTERN >out && { test_cmp expect.less pager-args || test_cmp expect.notless pager-args } && - test_cmp empty out + test_must_be_empty out ' test_expect_success 'git grep -O --cached' ' @@ -72,7 +71,6 @@ test_expect_success 'git grep -O --no-index' ' grep.h untracked EOF - >empty && ( GIT_PAGER='\''printf "%s\n" >pager-args'\'' && @@ -80,7 +78,7 @@ test_expect_success 'git grep -O --no-index' ' git grep --no-index -O GREP_PATTERN >out ) && test_cmp expect pager-args && - test_cmp empty out + test_must_be_empty out ' test_expect_success 'setup: fake "less"' ' @@ -96,15 +94,14 @@ test_expect_success 'git grep -O jumps to line in less' ' +/*GREP_PATTERN grep.h EOF - >empty && GIT_PAGER=./less git grep -O GREP_PATTERN >out && test_cmp expect actual && - test_cmp empty out && + test_must_be_empty out && git grep -O./less GREP_PATTERN >out2 && test_cmp expect actual && - test_cmp empty out2 + test_must_be_empty out2 ' test_expect_success 'modified file' ' @@ -122,7 +119,7 @@ test_expect_success 'modified file' ' test_when_finished "git checkout HEAD unrelated" && GIT_PAGER=./less git grep -F -O "enum grep_pat_token" >out && test_cmp expect actual && - test_cmp empty out + test_must_be_empty out ' test_expect_success 'copes with color settings' ' @@ -138,7 +135,6 @@ test_expect_success 'copes with color settings' ' test_expect_success 'run from subdir' ' rm -f actual && echo grep.c >expect && - >empty && ( cd subdir && @@ -156,8 +152,8 @@ test_expect_success 'run from subdir' ' ;; esac && test_cmp expect args && - test_cmp empty out && - test_cmp empty out2 + test_must_be_empty out && + test_must_be_empty out2 ' test_done diff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh index 661f9d430d..c92a47b6d5 100755 --- a/t/t8003-blame-corner-cases.sh +++ b/t/t8003-blame-corner-cases.sh @@ -216,14 +216,18 @@ test_expect_success 'blame -L with invalid start' ' ' test_expect_success 'blame -L with invalid end' ' - test_must_fail git blame -L1,5 tres 2>errors && - test_i18ngrep "has only 2 lines" errors + git blame -L1,5 tres >out && + test_line_count = 2 out ' test_expect_success 'blame parses <end> part of -L' ' git blame -L1,1 tres >out && - cat out && - test $(wc -l < out) -eq 1 + test_line_count = 1 out +' + +test_expect_success 'blame -Ln,-(n+1)' ' + git blame -L3,-4 nine_lines >out && + test_line_count = 3 out ' test_expect_success 'indent of line numbers, nine lines' ' diff --git a/t/t8010-cat-file-filters.sh b/t/t8010-cat-file-filters.sh index 0f86c19174..31de4b64dc 100755 --- a/t/t8010-cat-file-filters.sh +++ b/t/t8010-cat-file-filters.sh @@ -47,7 +47,7 @@ test_expect_success 'cat-file --textconv --path=<path> works' ' test_expect_success '--path=<path> complains without --textconv/--filters' ' sha1=$(git rev-parse -q --verify HEAD:world.txt) && test_must_fail git cat-file --path=hello.txt blob $sha1 >actual 2>err && - test ! -s actual && + test_must_be_empty actual && grep "path.*needs.*filters" err ' diff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh new file mode 100755 index 0000000000..ed38f74de9 --- /dev/null +++ b/t/t8012-blame-colors.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +test_description='colored git blame' +. ./test-lib.sh + +PROG='git blame -c' +. "$TEST_DIRECTORY"/annotate-tests.sh + +test_expect_success 'colored blame colors contiguous lines' ' + git -c color.blame.repeatedLines=yellow blame --color-lines --abbrev=12 hello.c >actual.raw && + git -c color.blame.repeatedLines=yellow -c blame.coloring=repeatedLines blame --abbrev=12 hello.c >actual.raw.2 && + test_cmp actual.raw actual.raw.2 && + test_decode_color <actual.raw >actual && + grep "<YELLOW>" <actual >darkened && + grep "(F" darkened > F.expect && + grep "(H" darkened > H.expect && + test_line_count = 2 F.expect && + test_line_count = 3 H.expect +' + +test_expect_success 'color by age consistently colors old code' ' + git blame --color-by-age hello.c >actual.raw && + git -c blame.coloring=highlightRecent blame hello.c >actual.raw.2 && + test_cmp actual.raw actual.raw.2 && + test_decode_color <actual.raw >actual && + grep "<BLUE>" <actual >colored && + test_line_count = 10 colored +' + +test_expect_success 'blame color by age: new code is different' ' + cat >>hello.c <<-EOF && + void qfunc(); + EOF + git add hello.c && + GIT_AUTHOR_DATE="" git commit -m "new commit" && + + git -c color.blame.highlightRecent="yellow,1 month ago, cyan" blame --color-by-age hello.c >actual.raw && + test_decode_color <actual.raw >actual && + + grep "<YELLOW>" <actual >colored && + test_line_count = 10 colored && + + grep "<CYAN>" <actual >colored && + test_line_count = 1 colored && + grep qfunc colored +' + +test_done diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh index e80eacbb1b..1ef1a19003 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -225,6 +225,8 @@ X-Mailer: X-MAILER-STRING In-Reply-To: <unique-message-id@example.com> References: <unique-message-id@example.com> Reply-To: Reply <reply@example.com> +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -251,10 +253,9 @@ test_suppress_self () { mv msgtxt1 msgtxt1-$3 && sed -e '/^$/q' msgtxt1-$3 >"msghdr1-$3" && - >"expected-no-cc-$3" && (grep '^Cc:' msghdr1-$3 >"actual-no-cc-$3"; - test_cmp expected-no-cc-$3 actual-no-cc-$3) + test_must_be_empty actual-no-cc-$3) } test_suppress_self_unquoted () { @@ -330,7 +331,7 @@ test_expect_success $PREREQ 'Show all headers' ' test_expect_success $PREREQ 'Prompting works' ' clean_fake_sendmail && - (echo "to@example.com" + (echo "to@example.com" && echo "" ) | GIT_SEND_EMAIL_NOTTY=1 git send-email \ --smtp-server="$(pwd)/fake.sendmail" \ @@ -415,6 +416,7 @@ test_expect_success $PREREQ 'reject long lines' ' --from="Example <nobody@example.com>" \ --to=nobody@example.com \ --smtp-server="$(pwd)/fake.sendmail" \ + --transfer-encoding=8bit \ $patches longline.patch \ 2>errors && grep longline.patch errors @@ -456,6 +458,42 @@ test_expect_success $PREREQ 'allow long lines with --no-validate' ' 2>errors ' +test_expect_success $PREREQ 'short lines with auto encoding are 8bit' ' + clean_fake_sendmail && + git send-email \ + --from="A <author@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + --transfer-encoding=auto \ + $patches && + grep "Content-Transfer-Encoding: 8bit" msgtxt1 +' + +test_expect_success $PREREQ 'long lines with auto encoding are quoted-printable' ' + clean_fake_sendmail && + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + --transfer-encoding=auto \ + --no-validate \ + longline.patch && + grep "Content-Transfer-Encoding: quoted-printable" msgtxt1 +' + +for enc in auto quoted-printable base64 +do + test_expect_success $PREREQ "--validate passes with encoding $enc" ' + git send-email \ + --from="Example <nobody@example.com>" \ + --to=nobody@example.com \ + --smtp-server="$(pwd)/fake.sendmail" \ + --transfer-encoding=$enc \ + --validate \ + $patches longline.patch + ' +done + test_expect_success $PREREQ 'Invalid In-Reply-To' ' clean_fake_sendmail && git send-email \ @@ -470,8 +508,8 @@ test_expect_success $PREREQ 'Invalid In-Reply-To' ' test_expect_success $PREREQ 'Valid In-Reply-To when prompting' ' clean_fake_sendmail && - (echo "From Example <from@example.com>" - echo "To Example <to@example.com>" + (echo "From Example <from@example.com>" && + echo "To Example <to@example.com>" && echo "" ) | GIT_SEND_EMAIL_NOTTY=1 git send-email \ --smtp-server="$(pwd)/fake.sendmail" \ @@ -573,6 +611,8 @@ Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -617,6 +657,8 @@ Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -652,6 +694,8 @@ Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -678,6 +722,8 @@ Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -712,6 +758,8 @@ Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -743,6 +791,8 @@ Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -774,6 +824,8 @@ Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -809,6 +861,8 @@ Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -837,6 +891,8 @@ Subject: [PATCH 1/1] Second. Date: DATE-STRING Message-Id: MESSAGE-ID-STRING X-Mailer: X-MAILER-STRING +MIME-Version: 1.0 +Content-Transfer-Encoding: 8bit Result: OK EOF @@ -1966,11 +2022,11 @@ test_expect_success $PREREQ 'invoke hook' ' # Verify error message when a patch is rejected by the hook sed -e "s/add master/x/" ../0001-add-master.patch >../another.patch && - git send-email \ + test_must_fail git send-email \ --from="Example <nobody@example.com>" \ --to=nobody@example.com \ --smtp-server="$(pwd)/../fake.sendmail" \ - ../another.patch 2>err + ../another.patch 2>err && test_i18ngrep "rejected by sendemail-validate hook" err ) ' diff --git a/t/t9010-svn-fe.sh b/t/t9010-svn-fe.sh index 8eaaca6f99..0b20b07e68 100755 --- a/t/t9010-svn-fe.sh +++ b/t/t9010-svn-fe.sh @@ -473,7 +473,7 @@ test_expect_failure 'change file mode but keep old content' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && git show HEAD:greeting >actual.blob && git show HEAD^:greeting >actual.target && @@ -573,7 +573,7 @@ test_expect_success 'NUL in log message, file content, and property name' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && { git cat-file commit HEAD | nul_to_q && @@ -659,7 +659,7 @@ test_expect_success 'change file mode and reiterate content' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && git show HEAD:greeting >actual.blob && git show HEAD^:greeting >actual.target && @@ -792,7 +792,7 @@ test_expect_success 'property deltas supported' ' { git rev-list HEAD | git diff-tree --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && test_cmp expect actual ' @@ -846,7 +846,7 @@ test_expect_success 'properties on /' ' { git rev-list HEAD | git diff-tree --root --always --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && test_cmp expect actual ' @@ -931,7 +931,7 @@ test_expect_success 'deltas for typechange' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && test_cmp expect actual ' @@ -1030,7 +1030,7 @@ test_expect_success 'deltas need not consume the whole preimage' ' { git rev-list HEAD | git diff-tree --root --stdin | - sed "s/$_x40/OBJID/g" + sed "s/$OID_REGEX/OBJID/g" } >actual && test_cmp expect actual && git show HEAD:postimage >actual.3 && diff --git a/t/t9011-svn-da.sh b/t/t9011-svn-da.sh index b38d16f9db..ab1ef28fd9 100755 --- a/t/t9011-svn-da.sh +++ b/t/t9011-svn-da.sh @@ -18,7 +18,7 @@ test_expect_success 'reject empty delta' ' test_expect_success 'delta can empty file' ' printf "SVNQ" | q_to_nul >clear.delta && test-svn-fe -d preimage clear.delta 4 >actual && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'reject svndiff2' ' @@ -29,7 +29,7 @@ test_expect_success 'reject svndiff2' ' test_expect_success 'one-window empty delta' ' printf "SVNQ%s" "QQQQQ" | q_to_nul >clear.onewindow && test-svn-fe -d preimage clear.onewindow 9 >actual && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'reject incomplete window header' ' @@ -50,7 +50,7 @@ test_expect_success 'two-window empty delta' ' printf "SVNQ%s%s" "QQQQQ" "QQQQQ" | q_to_nul >clear.twowindow && test-svn-fe -d preimage clear.twowindow 14 >actual && test_must_fail test-svn-fe -d preimage clear.twowindow 13 && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'noisy zeroes' ' @@ -60,7 +60,7 @@ test_expect_success 'noisy zeroes' ' q_to_nul >clear.noisy && len=$(wc -c <clear.noisy) && test-svn-fe -d preimage clear.noisy $len && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'reject variable-length int in magic' ' @@ -83,7 +83,7 @@ test_expect_success 'reject truncated integer' ' test_expect_success 'nonempty (but unused) preimage view' ' printf "SVNQ%b" "Q\003QQQ" | q_to_nul >clear.readpreimage && test-svn-fe -d preimage clear.readpreimage 9 >actual && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'preimage view: right endpoint cannot backtrack' ' @@ -99,7 +99,7 @@ test_expect_success 'preimage view: left endpoint can advance' ' q_to_nul >clear.shrinkbacktrack && test-svn-fe -d preimage clear.preshrink 14 >actual && test_must_fail test-svn-fe -d preimage clear.shrinkbacktrack 14 && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'preimage view: offsets compared by value' ' @@ -109,7 +109,7 @@ test_expect_success 'preimage view: offsets compared by value' ' q_to_nul >clear.noisyadvance && test_must_fail test-svn-fe -d preimage clear.noisybacktrack 15 && test-svn-fe -d preimage clear.noisyadvance 15 && - test_cmp empty actual + test_must_be_empty actual ' test_expect_success 'preimage view: reject truncated preimage' ' diff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh index c937330a5f..9af6078844 100755 --- a/t/t9100-git-svn-basic.sh +++ b/t/t9100-git-svn-basic.sh @@ -31,7 +31,7 @@ test_expect_success \ ( cd import && echo foo >foo && - ln -s foo foo.link + ln -s foo foo.link && mkdir -p dir/a/b/c/d/e && echo "deep dir" >dir/a/b/c/d/e/file && mkdir bar && diff --git a/t/t9101-git-svn-props.sh b/t/t9101-git-svn-props.sh index 07bfb63777..8a5c8dc1aa 100755 --- a/t/t9101-git-svn-props.sh +++ b/t/t9101-git-svn-props.sh @@ -149,7 +149,7 @@ test_expect_success 'test show-ignore' " svn_cmd up && svn_cmd propset -R svn:ignore ' no-such-file* -' . +' . && svn_cmd commit -m 'propset svn:ignore' ) && git svn show-ignore > show-ignore.got && diff --git a/t/t9104-git-svn-follow-parent.sh b/t/t9104-git-svn-follow-parent.sh index a735fa3717..5e0ad19177 100755 --- a/t/t9104-git-svn-follow-parent.sh +++ b/t/t9104-git-svn-follow-parent.sh @@ -215,7 +215,10 @@ test_expect_success "multi-fetch continues to work" " " test_expect_success "multi-fetch works off a 'clean' repository" ' - rm -r "$GIT_DIR/svn" "$GIT_DIR/refs/remotes" "$GIT_DIR/logs" && + rm -rf "$GIT_DIR/svn" && + git for-each-ref --format="option no-deref%0adelete %(refname)" refs/remotes | + git update-ref --stdin && + git reflog expire --all --expire=all && mkdir "$GIT_DIR/svn" && git svn multi-fetch ' diff --git a/t/t9119-git-svn-info.sh b/t/t9119-git-svn-info.sh index 88241baee3..8201c3e808 100755 --- a/t/t9119-git-svn-info.sh +++ b/t/t9119-git-svn-info.sh @@ -22,8 +22,8 @@ esac # same value as "svn info" (i.e. the commit timestamp that touched the # path most recently); do not expect that field to match. test_cmp_info () { - sed -e '/^Text Last Updated:/d' "$1" >tmp.expect - sed -e '/^Text Last Updated:/d' "$2" >tmp.actual + sed -e '/^Text Last Updated:/d' "$1" >tmp.expect && + sed -e '/^Text Last Updated:/d' "$2" >tmp.actual && test_cmp tmp.expect tmp.actual && rm -f tmp.expect tmp.actual } @@ -59,24 +59,24 @@ test_expect_success 'setup repository and import' ' ' test_expect_success 'info' " - (cd svnwc; svn info) > expected.info && - (cd gitwc; git svn info) > actual.info && + (cd svnwc && svn info) > expected.info && + (cd gitwc && git svn info) > actual.info && test_cmp_info expected.info actual.info " test_expect_success 'info --url' ' - test "$(cd gitwc; git svn info --url)" = "$quoted_svnrepo" + test "$(cd gitwc && git svn info --url)" = "$quoted_svnrepo" ' test_expect_success 'info .' " - (cd svnwc; svn info .) > expected.info-dot && - (cd gitwc; git svn info .) > actual.info-dot && + (cd svnwc && svn info .) > expected.info-dot && + (cd gitwc && git svn info .) > actual.info-dot && test_cmp_info expected.info-dot actual.info-dot " test_expect_success 'info $(pwd)' ' - (cd svnwc; svn info "$(pwd)") >expected.info-pwd && - (cd gitwc; git svn info "$(pwd)") >actual.info-pwd && + (cd svnwc && svn info "$(pwd)") >expected.info-pwd && + (cd gitwc && git svn info "$(pwd)") >actual.info-pwd && grep -v ^Path: <expected.info-pwd >expected.info-np && grep -v ^Path: <actual.info-pwd >actual.info-np && test_cmp_info expected.info-np actual.info-np && @@ -85,8 +85,8 @@ test_expect_success 'info $(pwd)' ' ' test_expect_success 'info $(pwd)/../___wc' ' - (cd svnwc; svn info "$(pwd)/../svnwc") >expected.info-pwd && - (cd gitwc; git svn info "$(pwd)/../gitwc") >actual.info-pwd && + (cd svnwc && svn info "$(pwd)/../svnwc") >expected.info-pwd && + (cd gitwc && git svn info "$(pwd)/../gitwc") >actual.info-pwd && grep -v ^Path: <expected.info-pwd >expected.info-np && grep -v ^Path: <actual.info-pwd >actual.info-np && test_cmp_info expected.info-np actual.info-np && @@ -95,8 +95,8 @@ test_expect_success 'info $(pwd)/../___wc' ' ' test_expect_success 'info $(pwd)/../___wc//file' ' - (cd svnwc; svn info "$(pwd)/../svnwc//file") >expected.info-pwd && - (cd gitwc; git svn info "$(pwd)/../gitwc//file") >actual.info-pwd && + (cd svnwc && svn info "$(pwd)/../svnwc//file") >expected.info-pwd && + (cd gitwc && git svn info "$(pwd)/../gitwc//file") >actual.info-pwd && grep -v ^Path: <expected.info-pwd >expected.info-np && grep -v ^Path: <actual.info-pwd >actual.info-np && test_cmp_info expected.info-np actual.info-np && @@ -105,56 +105,56 @@ test_expect_success 'info $(pwd)/../___wc//file' ' ' test_expect_success 'info --url .' ' - test "$(cd gitwc; git svn info --url .)" = "$quoted_svnrepo" + test "$(cd gitwc && git svn info --url .)" = "$quoted_svnrepo" ' test_expect_success 'info file' " - (cd svnwc; svn info file) > expected.info-file && - (cd gitwc; git svn info file) > actual.info-file && + (cd svnwc && svn info file) > expected.info-file && + (cd gitwc && git svn info file) > actual.info-file && test_cmp_info expected.info-file actual.info-file " test_expect_success 'info --url file' ' - test "$(cd gitwc; git svn info --url file)" = "$quoted_svnrepo/file" + test "$(cd gitwc && git svn info --url file)" = "$quoted_svnrepo/file" ' test_expect_success 'info directory' " - (cd svnwc; svn info directory) > expected.info-directory && - (cd gitwc; git svn info directory) > actual.info-directory && + (cd svnwc && svn info directory) > expected.info-directory && + (cd gitwc && git svn info directory) > actual.info-directory && test_cmp_info expected.info-directory actual.info-directory " test_expect_success 'info inside directory' " - (cd svnwc/directory; svn info) > expected.info-inside-directory && - (cd gitwc/directory; git svn info) > actual.info-inside-directory && + (cd svnwc/directory && svn info) > expected.info-inside-directory && + (cd gitwc/directory && git svn info) > actual.info-inside-directory && test_cmp_info expected.info-inside-directory actual.info-inside-directory " test_expect_success 'info --url directory' ' - test "$(cd gitwc; git svn info --url directory)" = "$quoted_svnrepo/directory" + test "$(cd gitwc && git svn info --url directory)" = "$quoted_svnrepo/directory" ' test_expect_success 'info symlink-file' " - (cd svnwc; svn info symlink-file) > expected.info-symlink-file && - (cd gitwc; git svn info symlink-file) > actual.info-symlink-file && + (cd svnwc && svn info symlink-file) > expected.info-symlink-file && + (cd gitwc && git svn info symlink-file) > actual.info-symlink-file && test_cmp_info expected.info-symlink-file actual.info-symlink-file " test_expect_success 'info --url symlink-file' ' - test "$(cd gitwc; git svn info --url symlink-file)" \ + test "$(cd gitwc && git svn info --url symlink-file)" \ = "$quoted_svnrepo/symlink-file" ' test_expect_success 'info symlink-directory' " - (cd svnwc; svn info symlink-directory) \ + (cd svnwc && svn info symlink-directory) \ > expected.info-symlink-directory && - (cd gitwc; git svn info symlink-directory) \ + (cd gitwc && git svn info symlink-directory) \ > actual.info-symlink-directory && test_cmp_info expected.info-symlink-directory actual.info-symlink-directory " test_expect_success 'info --url symlink-directory' ' - test "$(cd gitwc; git svn info --url symlink-directory)" \ + test "$(cd gitwc && git svn info --url symlink-directory)" \ = "$quoted_svnrepo/symlink-directory" ' @@ -169,13 +169,13 @@ test_expect_success 'info added-file' " cd svnwc && svn_cmd add added-file > /dev/null ) && - (cd svnwc; svn info added-file) > expected.info-added-file && - (cd gitwc; git svn info added-file) > actual.info-added-file && + (cd svnwc && svn info added-file) > expected.info-added-file && + (cd gitwc && git svn info added-file) > actual.info-added-file && test_cmp_info expected.info-added-file actual.info-added-file " test_expect_success 'info --url added-file' ' - test "$(cd gitwc; git svn info --url added-file)" \ + test "$(cd gitwc && git svn info --url added-file)" \ = "$quoted_svnrepo/added-file" ' @@ -190,15 +190,15 @@ test_expect_success 'info added-directory' " cd gitwc && git add added-directory ) && - (cd svnwc; svn info added-directory) \ + (cd svnwc && svn info added-directory) \ > expected.info-added-directory && - (cd gitwc; git svn info added-directory) \ + (cd gitwc && git svn info added-directory) \ > actual.info-added-directory && test_cmp_info expected.info-added-directory actual.info-added-directory " test_expect_success 'info --url added-directory' ' - test "$(cd gitwc; git svn info --url added-directory)" \ + test "$(cd gitwc && git svn info --url added-directory)" \ = "$quoted_svnrepo/added-directory" ' @@ -213,16 +213,16 @@ test_expect_success 'info added-symlink-file' " ln -s added-file added-symlink-file && svn_cmd add added-symlink-file > /dev/null ) && - (cd svnwc; svn info added-symlink-file) \ + (cd svnwc && svn info added-symlink-file) \ > expected.info-added-symlink-file && - (cd gitwc; git svn info added-symlink-file) \ + (cd gitwc && git svn info added-symlink-file) \ > actual.info-added-symlink-file && test_cmp_info expected.info-added-symlink-file \ actual.info-added-symlink-file " test_expect_success 'info --url added-symlink-file' ' - test "$(cd gitwc; git svn info --url added-symlink-file)" \ + test "$(cd gitwc && git svn info --url added-symlink-file)" \ = "$quoted_svnrepo/added-symlink-file" ' @@ -237,16 +237,16 @@ test_expect_success 'info added-symlink-directory' " ln -s added-directory added-symlink-directory && svn_cmd add added-symlink-directory > /dev/null ) && - (cd svnwc; svn info added-symlink-directory) \ + (cd svnwc && svn info added-symlink-directory) \ > expected.info-added-symlink-directory && - (cd gitwc; git svn info added-symlink-directory) \ + (cd gitwc && git svn info added-symlink-directory) \ > actual.info-added-symlink-directory && test_cmp_info expected.info-added-symlink-directory \ actual.info-added-symlink-directory " test_expect_success 'info --url added-symlink-directory' ' - test "$(cd gitwc; git svn info --url added-symlink-directory)" \ + test "$(cd gitwc && git svn info --url added-symlink-directory)" \ = "$quoted_svnrepo/added-symlink-directory" ' @@ -259,13 +259,13 @@ test_expect_success 'info deleted-file' " cd svnwc && svn_cmd rm --force file > /dev/null ) && - (cd svnwc; svn info file) >expected.info-deleted-file && - (cd gitwc; git svn info file) >actual.info-deleted-file && + (cd svnwc && svn info file) >expected.info-deleted-file && + (cd gitwc && git svn info file) >actual.info-deleted-file && test_cmp_info expected.info-deleted-file actual.info-deleted-file " test_expect_success 'info --url file (deleted)' ' - test "$(cd gitwc; git svn info --url file)" \ + test "$(cd gitwc && git svn info --url file)" \ = "$quoted_svnrepo/file" ' @@ -278,13 +278,13 @@ test_expect_success 'info deleted-directory' " cd svnwc && svn_cmd rm --force directory > /dev/null ) && - (cd svnwc; svn info directory) >expected.info-deleted-directory && - (cd gitwc; git svn info directory) >actual.info-deleted-directory && + (cd svnwc && svn info directory) >expected.info-deleted-directory && + (cd gitwc && git svn info directory) >actual.info-deleted-directory && test_cmp_info expected.info-deleted-directory actual.info-deleted-directory " test_expect_success 'info --url directory (deleted)' ' - test "$(cd gitwc; git svn info --url directory)" \ + test "$(cd gitwc && git svn info --url directory)" \ = "$quoted_svnrepo/directory" ' @@ -297,13 +297,13 @@ test_expect_success 'info deleted-symlink-file' " cd svnwc && svn_cmd rm --force symlink-file > /dev/null ) && - (cd svnwc; svn info symlink-file) >expected.info-deleted-symlink-file && - (cd gitwc; git svn info symlink-file) >actual.info-deleted-symlink-file && + (cd svnwc && svn info symlink-file) >expected.info-deleted-symlink-file && + (cd gitwc && git svn info symlink-file) >actual.info-deleted-symlink-file && test_cmp_info expected.info-deleted-symlink-file actual.info-deleted-symlink-file " test_expect_success 'info --url symlink-file (deleted)' ' - test "$(cd gitwc; git svn info --url symlink-file)" \ + test "$(cd gitwc && git svn info --url symlink-file)" \ = "$quoted_svnrepo/symlink-file" ' @@ -316,13 +316,13 @@ test_expect_success 'info deleted-symlink-directory' " cd svnwc && svn_cmd rm --force symlink-directory > /dev/null ) && - (cd svnwc; svn info symlink-directory) >expected.info-deleted-symlink-directory && - (cd gitwc; git svn info symlink-directory) >actual.info-deleted-symlink-directory && + (cd svnwc && svn info symlink-directory) >expected.info-deleted-symlink-directory && + (cd gitwc && git svn info symlink-directory) >actual.info-deleted-symlink-directory && test_cmp_info expected.info-deleted-symlink-directory actual.info-deleted-symlink-directory " test_expect_success 'info --url symlink-directory (deleted)' ' - test "$(cd gitwc; git svn info --url symlink-directory)" \ + test "$(cd gitwc && git svn info --url symlink-directory)" \ = "$quoted_svnrepo/symlink-directory" ' @@ -331,27 +331,27 @@ test_expect_success 'info --url symlink-directory (deleted)' ' test_expect_success 'info unknown-file' " echo two > gitwc/unknown-file && - (cd gitwc; test_must_fail git svn info unknown-file) \ + (cd gitwc && test_must_fail git svn info unknown-file) \ 2> actual.info-unknown-file && grep unknown-file actual.info-unknown-file " test_expect_success 'info --url unknown-file' ' echo two > gitwc/unknown-file && - (cd gitwc; test_must_fail git svn info --url unknown-file) \ + (cd gitwc && test_must_fail git svn info --url unknown-file) \ 2> actual.info-url-unknown-file && grep unknown-file actual.info-url-unknown-file ' test_expect_success 'info unknown-directory' " mkdir gitwc/unknown-directory svnwc/unknown-directory && - (cd gitwc; test_must_fail git svn info unknown-directory) \ + (cd gitwc && test_must_fail git svn info unknown-directory) \ 2> actual.info-unknown-directory && grep unknown-directory actual.info-unknown-directory " test_expect_success 'info --url unknown-directory' ' - (cd gitwc; test_must_fail git svn info --url unknown-directory) \ + (cd gitwc && test_must_fail git svn info --url unknown-directory) \ 2> actual.info-url-unknown-directory && grep unknown-directory actual.info-url-unknown-directory ' @@ -361,13 +361,13 @@ test_expect_success 'info unknown-symlink-file' " cd gitwc && ln -s unknown-file unknown-symlink-file ) && - (cd gitwc; test_must_fail git svn info unknown-symlink-file) \ + (cd gitwc && test_must_fail git svn info unknown-symlink-file) \ 2> actual.info-unknown-symlink-file && grep unknown-symlink-file actual.info-unknown-symlink-file " test_expect_success 'info --url unknown-symlink-file' ' - (cd gitwc; test_must_fail git svn info --url unknown-symlink-file) \ + (cd gitwc && test_must_fail git svn info --url unknown-symlink-file) \ 2> actual.info-url-unknown-symlink-file && grep unknown-symlink-file actual.info-url-unknown-symlink-file ' @@ -377,13 +377,13 @@ test_expect_success 'info unknown-symlink-directory' " cd gitwc && ln -s unknown-directory unknown-symlink-directory ) && - (cd gitwc; test_must_fail git svn info unknown-symlink-directory) \ + (cd gitwc && test_must_fail git svn info unknown-symlink-directory) \ 2> actual.info-unknown-symlink-directory && grep unknown-symlink-directory actual.info-unknown-symlink-directory " test_expect_success 'info --url unknown-symlink-directory' ' - (cd gitwc; test_must_fail git svn info --url unknown-symlink-directory) \ + (cd gitwc && test_must_fail git svn info --url unknown-symlink-directory) \ 2> actual.info-url-unknown-symlink-directory && grep unknown-symlink-directory actual.info-url-unknown-symlink-directory ' diff --git a/t/t9122-git-svn-author.sh b/t/t9122-git-svn-author.sh index 30013b7bb9..9e8fe38e7e 100755 --- a/t/t9122-git-svn-author.sh +++ b/t/t9122-git-svn-author.sh @@ -7,8 +7,8 @@ test_expect_success 'setup svn repository' ' svn_cmd checkout "$svnrepo" work.svn && ( cd work.svn && - echo >file - svn_cmd add file + echo >file && + svn_cmd add file && svn_cmd commit -m "first commit" file ) ' @@ -17,7 +17,7 @@ test_expect_success 'interact with it via git svn' ' mkdir work.git && ( cd work.git && - git svn init "$svnrepo" + git svn init "$svnrepo" && git svn fetch && echo modification >file && diff --git a/t/t9129-git-svn-i18n-commitencoding.sh b/t/t9129-git-svn-i18n-commitencoding.sh index 8dbd6476fa..2c213ae654 100755 --- a/t/t9129-git-svn-i18n-commitencoding.sh +++ b/t/t9129-git-svn-i18n-commitencoding.sh @@ -51,7 +51,7 @@ do git add F && git commit -a -F "$TEST_DIRECTORY"/t3900/$H.txt && E=$(git cat-file commit HEAD | sed -ne "s/^encoding //p") && - test "z$E" = "z$H" + test "z$E" = "z$H" && compare_git_head_with "$TEST_DIRECTORY"/t3900/$H.txt ) ' diff --git a/t/t9130-git-svn-authors-file.sh b/t/t9130-git-svn-authors-file.sh index d8262854bb..cb764bcadc 100755 --- a/t/t9130-git-svn-authors-file.sh +++ b/t/t9130-git-svn-authors-file.sh @@ -25,7 +25,7 @@ test_expect_success 'start import with incomplete authors file' ' test_expect_success 'imported 2 revisions successfully' ' ( - cd x + cd x && git rev-list refs/remotes/git-svn >actual && test_line_count = 2 actual && git rev-list -1 --pretty=raw refs/remotes/git-svn >actual && @@ -42,7 +42,7 @@ EOF test_expect_success 'continues to import once authors have been added' ' ( - cd x + cd x && git svn fetch --authors-file=../svn-authors && git rev-list refs/remotes/git-svn >actual && test_line_count = 4 actual && diff --git a/t/t9131-git-svn-empty-symlink.sh b/t/t9131-git-svn-empty-symlink.sh index f762038f0e..3bf4255aa3 100755 --- a/t/t9131-git-svn-empty-symlink.sh +++ b/t/t9131-git-svn-empty-symlink.sh @@ -85,7 +85,7 @@ EOF test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" x' test_expect_success 'enable broken symlink workaround' \ '(cd x && git config svn.brokenSymlinkWorkaround true)' -test_expect_success '"bar" is an empty file' 'test -f x/bar && ! test -s x/bar' +test_expect_success '"bar" is an empty file' 'test_must_be_empty x/bar' test_expect_success 'get "bar" => symlink fix from svn' \ '(cd x && git svn rebase)' test_expect_success SYMLINKS '"bar" becomes a symlink' 'test -h x/bar' @@ -94,14 +94,14 @@ test_expect_success SYMLINKS '"bar" becomes a symlink' 'test -h x/bar' test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" y' test_expect_success 'disable broken symlink workaround' \ '(cd y && git config svn.brokenSymlinkWorkaround false)' -test_expect_success '"bar" is an empty file' 'test -f y/bar && ! test -s y/bar' +test_expect_success '"bar" is an empty file' 'test_must_be_empty y/bar' test_expect_success 'get "bar" => symlink fix from svn' \ '(cd y && git svn rebase)' test_expect_success '"bar" does not become a symlink' '! test -L y/bar' # svn.brokenSymlinkWorkaround is unset test_expect_success 'clone using git svn' 'git svn clone -r1 "$svnrepo" z' -test_expect_success '"bar" is an empty file' 'test -f z/bar && ! test -s z/bar' +test_expect_success '"bar" is an empty file' 'test_must_be_empty z/bar' test_expect_success 'get "bar" => symlink fix from svn' \ '(cd z && git svn rebase)' test_expect_success '"bar" does not become a symlink' '! test -L z/bar' diff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh index 09ff10cd9b..fff49c4100 100755 --- a/t/t9134-git-svn-ignore-paths.sh +++ b/t/t9134-git-svn-ignore-paths.sh @@ -82,7 +82,7 @@ test_expect_success 'update git svn-cloned repo (option ignore)' ' test_expect_success 'SVN-side change inside of ignored www' ' ( cd s && - echo zaq >> www/test_www.txt + echo zaq >> www/test_www.txt && svn_cmd commit -m "SVN-side change inside of www/test_www.txt" && svn_cmd up && svn_cmd log -v | fgrep "SVN-side change inside of www/test_www.txt" @@ -114,8 +114,8 @@ test_expect_success 'update git svn-cloned repo (option ignore)' ' test_expect_success 'SVN-side change in and out of ignored www' ' ( cd s && - echo cvf >> www/test_www.txt - echo ygg >> qqq/test_qqq.txt + echo cvf >> www/test_www.txt && + echo ygg >> qqq/test_qqq.txt && svn_cmd commit -m "SVN-side change in and out of ignored www" && svn_cmd up && svn_cmd log -v | fgrep "SVN-side change in and out of ignored www" diff --git a/t/t9135-git-svn-moved-branch-empty-file.sh b/t/t9135-git-svn-moved-branch-empty-file.sh index 93db45db7d..2f80b216fe 100755 --- a/t/t9135-git-svn-moved-branch-empty-file.sh +++ b/t/t9135-git-svn-moved-branch-empty-file.sh @@ -13,8 +13,7 @@ test_expect_success 'test that b1 exists and is empty' ' ( cd x && git reset --hard origin/branch-c && - test -f b1 && - ! test -s b1 + test_must_be_empty b1 ) ' diff --git a/t/t9137-git-svn-dcommit-clobber-series.sh b/t/t9137-git-svn-dcommit-clobber-series.sh index 5fa07a369f..067b15bad2 100755 --- a/t/t9137-git-svn-dcommit-clobber-series.sh +++ b/t/t9137-git-svn-dcommit-clobber-series.sh @@ -7,7 +7,7 @@ test_description='git svn dcommit clobber series' test_expect_success 'initialize repo' ' mkdir import && (cd import && - awk "BEGIN { for (i = 1; i < 64; i++) { print i } }" > file + awk "BEGIN { for (i = 1; i < 64; i++) { print i } }" > file && svn_cmd import -m "initial" . "$svnrepo" ) && git svn init "$svnrepo" && diff --git a/t/t9138-git-svn-authors-prog.sh b/t/t9138-git-svn-authors-prog.sh index 93ef44fae8..027b416720 100755 --- a/t/t9138-git-svn-authors-prog.sh +++ b/t/t9138-git-svn-authors-prog.sh @@ -38,7 +38,7 @@ test_expect_success 'import authors with prog and file' ' test_expect_success 'imported 6 revisions successfully' ' ( - cd x + cd x && git rev-list refs/remotes/git-svn >actual && test_line_count = 6 actual ) @@ -46,7 +46,7 @@ test_expect_success 'imported 6 revisions successfully' ' test_expect_success 'authors-prog ran correctly' ' ( - cd x + cd x && git rev-list -1 --pretty=raw refs/remotes/git-svn~1 >actual && grep "^author ee-foo <ee-foo@example\.com> " actual && git rev-list -1 --pretty=raw refs/remotes/git-svn~2 >actual && @@ -62,7 +62,7 @@ test_expect_success 'authors-prog ran correctly' ' test_expect_success 'authors-file overrode authors-prog' ' ( - cd x + cd x && git rev-list -1 --pretty=raw refs/remotes/git-svn >actual && grep "^author FFFFFFF FFFFFFF <fFf@other\.example\.com> " actual ) diff --git a/t/t9146-git-svn-empty-dirs.sh b/t/t9146-git-svn-empty-dirs.sh index 6d3130e618..5f91c0d68b 100755 --- a/t/t9146-git-svn-empty-dirs.sh +++ b/t/t9146-git-svn-empty-dirs.sh @@ -21,7 +21,7 @@ test_expect_success 'empty directories exist' ' do if ! test -d "$i" then - echo >&2 "$i does not exist" + echo >&2 "$i does not exist" && exit 1 fi done @@ -38,7 +38,7 @@ test_expect_success 'option automkdirs set to false' ' do if test -d "$i" then - echo >&2 "$i exists" + echo >&2 "$i exists" && exit 1 fi done @@ -63,7 +63,7 @@ test_expect_success 'git svn mkdirs recreates empty directories' ' do if ! test -d "$i" then - echo >&2 "$i does not exist" + echo >&2 "$i does not exist" && exit 1 fi done @@ -79,21 +79,21 @@ test_expect_success 'git svn mkdirs -r works' ' do if ! test -d "$i" then - echo >&2 "$i does not exist" + echo >&2 "$i does not exist" && exit 1 fi - done + done && if test -d "! !" then - echo >&2 "$i should not exist" + echo >&2 "$i should not exist" && exit 1 - fi + fi && git svn mkdirs -r8 && if ! test -d "! !" then - echo >&2 "$i not exist" + echo >&2 "$i not exist" && exit 1 fi ) @@ -115,7 +115,7 @@ test_expect_success 'empty directories in trunk exist' ' do if ! test -d "$i" then - echo >&2 "$i does not exist" + echo >&2 "$i does not exist" && exit 1 fi done @@ -148,7 +148,7 @@ test_expect_success 'git svn gc-ed files work' ' do if ! test -d "$i" then - echo >&2 "$i does not exist" + echo >&2 "$i does not exist" && exit 1 fi done diff --git a/t/t9147-git-svn-include-paths.sh b/t/t9147-git-svn-include-paths.sh index a90ff58629..d292bf9f55 100755 --- a/t/t9147-git-svn-include-paths.sh +++ b/t/t9147-git-svn-include-paths.sh @@ -84,7 +84,7 @@ test_expect_success 'update git svn-cloned repo (option include)' ' test_expect_success 'SVN-side change inside of ignored www' ' ( cd s && - echo zaq >> www/test_www.txt + echo zaq >> www/test_www.txt && svn_cmd commit -m "SVN-side change inside of www/test_www.txt" && svn_cmd up && svn_cmd log -v | fgrep "SVN-side change inside of www/test_www.txt" @@ -116,8 +116,8 @@ test_expect_success 'update git svn-cloned repo (option include)' ' test_expect_success 'SVN-side change in and out of included qqq' ' ( cd s && - echo cvf >> www/test_www.txt - echo ygg >> qqq/test_qqq.txt + echo cvf >> www/test_www.txt && + echo ygg >> qqq/test_qqq.txt && svn_cmd commit -m "SVN-side change in and out of ignored www" && svn_cmd up && svn_cmd log -v | fgrep "SVN-side change in and out of ignored www" diff --git a/t/t9152-svn-empty-dirs-after-gc.sh b/t/t9152-svn-empty-dirs-after-gc.sh index 301e779709..89f285d082 100755 --- a/t/t9152-svn-empty-dirs-after-gc.sh +++ b/t/t9152-svn-empty-dirs-after-gc.sh @@ -30,7 +30,7 @@ test_expect_success 'git svn mkdirs recreates empty directories after git svn gc do if ! test -d "$i" then - echo >&2 "$i does not exist" + echo >&2 "$i does not exist" && exit 1 fi done diff --git a/t/t9164-git-svn-dcommit-concurrent.sh b/t/t9164-git-svn-dcommit-concurrent.sh index d8464d4218..90346ff4e9 100755 --- a/t/t9164-git-svn-dcommit-concurrent.sh +++ b/t/t9164-git-svn-dcommit-concurrent.sh @@ -12,7 +12,7 @@ test_expect_success 'setup svn repository' ' svn_cmd checkout "$svnrepo" work.svn && ( cd work.svn && - echo >file && echo > auto_updated_file + echo >file && echo > auto_updated_file && svn_cmd add file auto_updated_file && svn_cmd commit -m "initial commit" ) && diff --git a/t/t9165-git-svn-fetch-merge-branch-of-branch.sh b/t/t9165-git-svn-fetch-merge-branch-of-branch.sh index fa3ef3b1f7..a4813c2b09 100755 --- a/t/t9165-git-svn-fetch-merge-branch-of-branch.sh +++ b/t/t9165-git-svn-fetch-merge-branch-of-branch.sh @@ -39,7 +39,7 @@ test_expect_success 'initialize source svn repo' ' svn_cmd commit -m trunk && svn_cmd switch "$svnrepo"/branches/branch2 && svn_cmd merge "$svnrepo"/trunk && - svn_cmd commit -m "merge trunk" + svn_cmd commit -m "merge trunk" && svn_cmd switch "$svnrepo"/trunk && svn_cmd merge --reintegrate "$svnrepo"/branches/branch2 && svn_cmd commit -m "merge branch2" diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index 1319415ba8..c5946cb0b8 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -43,11 +43,11 @@ check_entries () { sed -ne '/^\//p' "$1/CVS/Entries" | sort | cut -d/ -f2,3,5 >actual if test -z "$2" then - >expected + test_must_be_empty actual else printf '%s\n' "$2" | tr '|' '\012' >expected + test_cmp expected actual fi - test_cmp expected actual } test_expect_success \ @@ -187,7 +187,7 @@ test_expect_success \ git commit -a -m "Update with spaces" && id=$(git rev-list --max-count=1 HEAD) && (cd "$CVSWORK" && - git cvsexportcommit -c $id + git cvsexportcommit -c $id && check_entries "G g" "with spaces.png/1.2/-kb|with spaces.txt/1.2/" )' @@ -245,7 +245,7 @@ test_expect_success FILEMODE \ git add G/off && git commit -a -m "Execute test" && (cd "$CVSWORK" && - git cvsexportcommit -c HEAD + git cvsexportcommit -c HEAD && test -x G/on && ! test -x G/off )' @@ -303,7 +303,7 @@ test_expect_success 're-commit a removed filename which remains in CVS attic' ' git add attic_gremlin && git commit -m "Added attic_gremlin" && git cvsexportcommit -w "$CVSWORK" -c HEAD && - (cd "$CVSWORK"; cvs -Q update -d) && + (cd "$CVSWORK" && cvs -Q update -d) && test -f "$CVSWORK/attic_gremlin" ' diff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh index dc79df7b04..40fe7e4976 100755 --- a/t/t9300-fast-import.sh +++ b/t/t9300-fast-import.sh @@ -1185,7 +1185,7 @@ test_expect_success PIPE 'N: empty directory reads as missing' ' test_cmp expect.response response && git rev-list read-empty | git diff-tree -r --root --stdin | - sed "s/$_x40/OBJNAME/g" >actual && + sed "s/$OID_REGEX/OBJNAME/g" >actual && test_cmp expect actual ' @@ -1271,7 +1271,7 @@ test_expect_success 'N: delete directory by copying' ' git fast-import <input && git rev-list N-delete | git diff-tree -r --stdin --root --always | - sed -e "s/$_x40/OBJID/g" >actual && + sed -e "s/$OID_REGEX/OBJID/g" >actual && test_cmp expect actual ' @@ -2191,12 +2191,11 @@ test_expect_success 'R: --import-marks-if-exists' ' test_expect_success 'R: feature import-marks-if-exists' ' rm -f io.marks && - >expect && git fast-import --export-marks=io.marks <<-\EOF && feature import-marks-if-exists=not_io.marks EOF - test_cmp expect io.marks && + test_must_be_empty io.marks && blob=$(echo hi | git hash-object --stdin) && @@ -2227,13 +2226,11 @@ test_expect_success 'R: feature import-marks-if-exists' ' EOF test_cmp expect io.marks && - >expect && - git fast-import --import-marks-if-exists=not_io.marks \ --export-marks=io.marks <<-\EOF && feature import-marks-if-exists=io.marks EOF - test_cmp expect io.marks + test_must_be_empty io.marks ' test_expect_success 'R: import to output marks works without any content' ' @@ -2602,7 +2599,7 @@ test_expect_success 'R: terminating "done" within commit' ' EOF git rev-list done-ends | git diff-tree -r --stdin --root --always | - sed -e "s/$_x40/OBJID/g" >actual && + sed -e "s/$OID_REGEX/OBJID/g" >actual && test_cmp expect actual ' @@ -3147,7 +3144,10 @@ background_import_then_checkpoint () { echo $! >V.pid # We don't mind if fast-import has already died by the time the test # ends. - test_when_finished "exec 8>&-; exec 9>&-; kill $(cat V.pid) || true" + test_when_finished " + exec 8>&-; exec 9>&-; + kill $(cat V.pid) && wait $(cat V.pid) + true" # Start in the background to ensure we adhere strictly to (blocking) # pipes writing sequence. We want to assume that the write below could diff --git a/t/t9302-fast-import-unpack-limit.sh b/t/t9302-fast-import-unpack-limit.sh index a04de14677..bb1c39cfcc 100755 --- a/t/t9302-fast-import-unpack-limit.sh +++ b/t/t9302-fast-import-unpack-limit.sh @@ -80,7 +80,7 @@ test_expect_success 'lookups after checkpoint works' ' do if test $n -gt 30 then - echo >&2 "checkpoint did not update branch" + echo >&2 "checkpoint did not update branch" && exit 1 else n=$(($n + 1)) diff --git a/t/t9350-fast-export.sh b/t/t9350-fast-export.sh index d5679ffb87..6a392e87bc 100755 --- a/t/t9350-fast-export.sh +++ b/t/t9350-fast-export.sh @@ -538,4 +538,22 @@ test_expect_success 'when using -C, do not declare copy when source of copy is a test_cmp expected actual ' +test_expect_success 'merge commit gets exported with --import-marks' ' + test_create_repo merging && + ( + cd merging && + test_commit initial && + git checkout -b topic && + test_commit on-topic && + git checkout master && + test_commit on-master && + test_tick && + git merge --no-ff -m Yeah topic && + + echo ":1 $(git rev-parse HEAD^^)" >marks && + git fast-export --import-marks=marks master >out && + grep Yeah out + ) +' + test_done diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index 06742748e9..a5e5dca753 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -328,7 +328,7 @@ test_expect_success 'cvs update (subdirectories)' \ '(for dir in A A/B A/B/C A/D E; do mkdir $dir && echo "test file in $dir" >"$dir/file_in_$(echo $dir|sed -e "s#/# #g")" && - git add $dir; + git add $dir done) && git commit -q -m "deep sub directory structure" && git push gitcvs.git >/dev/null && @@ -371,7 +371,7 @@ test_expect_success 'cvs update (merge)' \ 'echo Line 0 >expected && for i in 1 2 3 4 5 6 7 do - echo Line $i >>merge + echo Line $i >>merge && echo Line $i >>expected done && echo Line 8 >>expected && @@ -382,7 +382,7 @@ test_expect_success 'cvs update (merge)' \ GIT_CONFIG="$git_config" cvs -Q update && test "$(echo $(grep merge CVS/Entries|cut -d/ -f2,3,5))" = "merge/1.1/" && test_cmp merge ../merge && - ( echo Line 0; cat merge ) >merge.tmp && + ( echo Line 0 && cat merge ) >merge.tmp && mv merge.tmp merge && cd "$WORKDIR" && echo Line 8 >>merge && @@ -410,7 +410,7 @@ do done test_expect_success 'cvs update (conflict merge)' \ - '( echo LINE 0; cat merge ) >merge.tmp && + '( echo LINE 0 && cat merge ) >merge.tmp && mv merge.tmp merge && git add merge && git commit -q -m "Merge test (conflict)" && diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh index 804ce3850f..5dfee07d9a 100755 --- a/t/t9600-cvsimport.sh +++ b/t/t9600-cvsimport.sh @@ -135,7 +135,7 @@ test_expect_success PERL 'second update has correct .git/cvs-revisions' ' (cd module-git && git log --format="o_fortuna 1.1 %H" -1 HEAD^^ && - git log --format="o_fortuna 1.2 %H" -1 HEAD^ + git log --format="o_fortuna 1.2 %H" -1 HEAD^ && git log --format="tick 1.1 %H" -1 HEAD) > expected && test_cmp expected module-git/.git/cvs-revisions ' diff --git a/t/t9800-git-p4-basic.sh b/t/t9800-git-p4-basic.sh index 4849edc4ef..729cd25770 100755 --- a/t/t9800-git-p4-basic.sh +++ b/t/t9800-git-p4-basic.sh @@ -261,6 +261,35 @@ test_expect_success 'unresolvable host in P4PORT should display error' ' ) ' +# Test following scenarios: +# - Without ".git/hooks/p4-pre-submit" , submit should continue +# - With the hook returning 0, submit should continue +# - With the hook returning 1, submit should abort +test_expect_success 'run hook p4-pre-submit before submit' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + echo "hello world" >hello.txt && + git add hello.txt && + git commit -m "add hello.txt" && + git config git-p4.skipSubmitEdit true && + git p4 submit --dry-run >out && + grep "Would apply" out && + mkdir -p .git/hooks && + write_script .git/hooks/p4-pre-submit <<-\EOF && + exit 0 + EOF + git p4 submit --dry-run >out && + grep "Would apply" out && + write_script .git/hooks/p4-pre-submit <<-\EOF && + exit 1 + EOF + test_must_fail git p4 submit --dry-run >errs 2>&1 && + ! grep "Would apply" errs + ) +' + test_expect_success 'submit from detached head' ' test_when_finished cleanup_git && git p4 clone --dest="$git" //depot && diff --git a/t/t9802-git-p4-filetype.sh b/t/t9802-git-p4-filetype.sh index 1fc9b33aeb..9978352d78 100755 --- a/t/t9802-git-p4-filetype.sh +++ b/t/t9802-git-p4-filetype.sh @@ -310,7 +310,7 @@ test_expect_success SYMLINKS 'empty symlink target' ' # p4 to sync here will make it generate errors. cd "$cli" && p4 print -q //depot/empty-symlink#2 >out && - test ! -s out + test_must_be_empty out ) && test_when_finished cleanup_git && diff --git a/t/t9806-git-p4-options.sh b/t/t9806-git-p4-options.sh index 1ab76c4246..3f5291b857 100755 --- a/t/t9806-git-p4-options.sh +++ b/t/t9806-git-p4-options.sh @@ -134,7 +134,7 @@ test_expect_success 'clone --changesfile' ' ( cd "$git" && git log --oneline p4/master >lines && - test_line_count = 2 lines + test_line_count = 2 lines && test_path_is_file file1 && test_path_is_missing file2 && test_path_is_file file3 diff --git a/t/t9807-git-p4-submit.sh b/t/t9807-git-p4-submit.sh index 71cae2874d..2325599ee6 100755 --- a/t/t9807-git-p4-submit.sh +++ b/t/t9807-git-p4-submit.sh @@ -155,6 +155,46 @@ test_expect_success 'allow submit from branch with same revision but different n ) ' +# make two commits, but tell it to apply only one + +test_expect_success 'submit --commit one' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + test_commit "file9" && + test_commit "file10" && + git config git-p4.skipSubmitEdit true && + git p4 submit --commit HEAD + ) && + ( + cd "$cli" && + test_path_is_missing "file9.t" && + test_path_is_file "file10.t" + ) +' + +# make three commits, but tell it to apply only range + +test_expect_success 'submit --commit range' ' + test_when_finished cleanup_git && + git p4 clone --dest="$git" //depot && + ( + cd "$git" && + test_commit "file11" && + test_commit "file12" && + test_commit "file13" && + git config git-p4.skipSubmitEdit true && + git p4 submit --commit HEAD~2..HEAD + ) && + ( + cd "$cli" && + test_path_is_missing "file11.t" && + test_path_is_file "file12.t" && + test_path_is_file "file13.t" + ) +' + # # Basic submit tests, the five handled cases # diff --git a/t/t9810-git-p4-rcs.sh b/t/t9810-git-p4-rcs.sh index 8134ab439b..cc53debe19 100755 --- a/t/t9810-git-p4-rcs.sh +++ b/t/t9810-git-p4-rcs.sh @@ -161,7 +161,7 @@ test_expect_success 'cleanup after failure' ' test_expect_success 'ktext expansion should not expand multi-line $File::' ' ( cd "$cli" && - cat >lv.pm <<-\EOF + cat >lv.pm <<-\EOF && my $wanted = sub { my $f = $File::Find::name; if ( -f && $f =~ /foo/ ) { EOF diff --git a/t/t9811-git-p4-label-import.sh b/t/t9811-git-p4-label-import.sh index decb66ba30..602b0a5d5c 100755 --- a/t/t9811-git-p4-label-import.sh +++ b/t/t9811-git-p4-label-import.sh @@ -133,7 +133,7 @@ test_expect_success 'export git tags to p4' ' p4 labels ... | grep LIGHTWEIGHT_TAG && p4 label -o GIT_TAG_1 | grep "tag created in git:xyzzy" && p4 sync ...@GIT_TAG_1 && - ! test -f main/f10 + ! test -f main/f10 && p4 sync ...@GIT_TAG_2 && test -f main/f10 ) diff --git a/t/t9814-git-p4-rename.sh b/t/t9814-git-p4-rename.sh index e7e0268e98..60baa06e27 100755 --- a/t/t9814-git-p4-rename.sh +++ b/t/t9814-git-p4-rename.sh @@ -9,23 +9,11 @@ test_expect_success 'start p4d' ' ' # We rely on this behavior to detect for p4 move availability. -test_expect_success 'p4 help unknown returns 1' ' +test_expect_success '"p4 help unknown" errors out' ' ( cd "$cli" && - ( - p4 help client >errs 2>&1 - echo $? >retval - ) - echo 0 >expected && - test_cmp expected retval && - rm retval && - ( - p4 help nosuchcommand >errs 2>&1 - echo $? >retval - ) - echo 1 >expected && - test_cmp expected retval && - rm retval + p4 help client && + ! p4 help nosuchcommand ) ' diff --git a/t/t9815-git-p4-submit-fail.sh b/t/t9815-git-p4-submit-fail.sh index 37b42d03a2..eaf03a6563 100755 --- a/t/t9815-git-p4-submit-fail.sh +++ b/t/t9815-git-p4-submit-fail.sh @@ -394,7 +394,7 @@ test_expect_success 'cleanup rename after submit cancel' ' ( cd "$cli" && test_path_is_missing text2 && - p4 fstat -T action text2 2>&1 | grep "no such file" + p4 fstat -T action text2 2>&1 | grep "no such file" && test_path_is_file text && ! p4 fstat -T action text ) diff --git a/t/t9818-git-p4-block.sh b/t/t9818-git-p4-block.sh index 8840a183ac..ce7cb22ad3 100755 --- a/t/t9818-git-p4-block.sh +++ b/t/t9818-git-p4-block.sh @@ -129,6 +129,7 @@ test_expect_success 'Create a repo with multiple depot paths' ' ' test_expect_success 'Clone repo with multiple depot paths' ' + test_when_finished cleanup_git && ( cd "$git" && git p4 clone --changes-block-size=4 //depot/pathA@all //depot/pathB@all \ @@ -138,6 +139,13 @@ test_expect_success 'Clone repo with multiple depot paths' ' ) ' +test_expect_success 'Clone repo with self-sizing block size' ' + test_when_finished cleanup_git && + git p4 clone --changes-block-size=1000000 //depot@all --destination="$git" && + git -C "$git" log --oneline >log && + test_line_count \> 10 log +' + test_expect_success 'kill p4d' ' kill_p4d ' diff --git a/t/t9830-git-p4-symlink-dir.sh b/t/t9830-git-p4-symlink-dir.sh index 3dc528bb1e..2ad1b0810d 100755 --- a/t/t9830-git-p4-symlink-dir.sh +++ b/t/t9830-git-p4-symlink-dir.sh @@ -30,7 +30,7 @@ test_expect_success 'symlinked directory' ' ( cd "$cli" && p4 sync && - test -L some/sub/directory/subdir2 + test -L some/sub/directory/subdir2 && test_path_is_file some/sub/directory/subdir2/file.t ) diff --git a/t/t9831-git-p4-triggers.sh b/t/t9831-git-p4-triggers.sh index bbcf14c664..be44c9751a 100755 --- a/t/t9831-git-p4-triggers.sh +++ b/t/t9831-git-p4-triggers.sh @@ -13,7 +13,7 @@ test_expect_success 'init depot' ' cd "$cli" && echo file1 >file1 && p4 add file1 && - p4 submit -d "change 1" + p4 submit -d "change 1" && echo file2 >file2 && p4 add file2 && p4 submit -d "change 2" diff --git a/t/t9832-unshelve.sh b/t/t9832-unshelve.sh new file mode 100755 index 0000000000..48ec7679b8 --- /dev/null +++ b/t/t9832-unshelve.sh @@ -0,0 +1,138 @@ +#!/bin/sh + +last_shelved_change () { + p4 changes -s shelved -m1 | cut -d " " -f 2 +} + +test_description='git p4 unshelve' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'init depot' ' + ( + cd "$cli" && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "change 1" && + : >file_to_delete && + p4 add file_to_delete && + p4 submit -d "file to delete" + ) +' + +test_expect_success 'initial clone' ' + git p4 clone --dest="$git" //depot/@all +' + +test_expect_success 'create shelved changelist' ' + ( + cd "$cli" && + p4 edit file1 && + echo "a change" >>file1 && + echo "new file" >file2 && + p4 add file2 && + p4 delete file_to_delete && + p4 opened && + p4 shelve -i <<EOF +Change: new +Description: + Test commit + + Further description +Files: + //depot/file1 + //depot/file2 + //depot/file_to_delete +EOF + + ) && + ( + cd "$git" && + change=$(last_shelved_change) && + git p4 unshelve $change && + git show refs/remotes/p4/unshelved/$change | grep -q "Further description" && + git cherry-pick refs/remotes/p4/unshelved/$change && + test_path_is_file file2 && + test_cmp file1 "$cli"/file1 && + test_cmp file2 "$cli"/file2 && + test_path_is_missing file_to_delete + ) +' + +test_expect_success 'update shelved changelist and re-unshelve' ' + test_when_finished cleanup_git && + ( + cd "$cli" && + change=$(last_shelved_change) && + echo "file3" >file3 && + p4 add -c $change file3 && + p4 shelve -i -r <<EOF && +Change: $change +Description: + Test commit + + Further description +Files: + //depot/file1 + //depot/file2 + //depot/file3 + //depot/file_to_delete +EOF + p4 describe $change + ) && + ( + cd "$git" && + change=$(last_shelved_change) && + git p4 unshelve $change && + git diff refs/remotes/p4/unshelved/$change.0 refs/remotes/p4/unshelved/$change | grep -q file3 + ) +' + +# This is the tricky case where the shelved changelist base revision doesn't +# match git-p4's idea of the base revision +# +# We will attempt to unshelve a change that is based on a change one commit +# ahead of p4/master + +test_expect_success 'create shelved changelist based on p4 change ahead of p4/master' ' + git p4 clone --dest="$git" //depot/@all && + ( + cd "$cli" && + p4 revert ... && + p4 edit file1 && + echo "foo" >>file1 && + p4 submit -d "change:foo" && + p4 edit file1 && + echo "bar" >>file1 && + p4 shelve -i <<EOF && +Change: new +Description: + Change to be unshelved +Files: + //depot/file1 +EOF + change=$(last_shelved_change) && + p4 describe -S $change | grep -q "Change to be unshelved" + ) +' + +# Now try to unshelve it. git-p4 should refuse to do so. +test_expect_success 'try to unshelve the change' ' + test_when_finished cleanup_git && + ( + change=$(last_shelved_change) && + cd "$git" && + test_must_fail git p4 unshelve $change 2>out.txt && + grep -q "cannot unshelve" out.txt + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + +test_done diff --git a/t/t9833-errors.sh b/t/t9833-errors.sh new file mode 100755 index 0000000000..277d347012 --- /dev/null +++ b/t/t9833-errors.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +test_description='git p4 errors' + +. ./lib-git-p4.sh + +test_expect_success 'start p4d' ' + start_p4d +' + +test_expect_success 'add p4 files' ' + ( + cd "$cli" && + echo file1 >file1 && + p4 add file1 && + p4 submit -d "file1" + ) +' + +# after this test, the default user requires a password +test_expect_success 'error handling' ' + git p4 clone --dest="$git" //depot@all && + ( + cd "$git" && + P4PORT=: test_must_fail git p4 submit 2>errmsg + ) && + p4 passwd -P newpassword && + ( + P4PASSWD=badpassword && + export P4PASSWD && + test_must_fail git p4 clone //depot/foo 2>errmsg && + grep -q "failure accessing depot.*P4PASSWD" errmsg + ) +' + +test_expect_success 'ticket logged out' ' + P4TICKETS="$cli/tickets" && + echo "newpassword" | p4 login && + ( + cd "$git" && + test_commit "ticket-auth-check" && + p4 logout && + test_must_fail git p4 submit 2>errmsg && + grep -q "failure accessing depot" errmsg + ) +' + +test_expect_success 'create group with short ticket expiry' ' + P4TICKETS="$cli/tickets" && + echo "newpassword" | p4 login && + p4_add_user short_expiry_user && + p4 -u short_expiry_user passwd -P password && + p4 group -i <<-EOF && + Group: testgroup + Timeout: 3 + Users: short_expiry_user + EOF + + p4 users | grep short_expiry_user +' + +test_expect_success 'git operation with expired ticket' ' + P4TICKETS="$cli/tickets" && + P4USER=short_expiry_user && + echo "password" | p4 login && + ( + cd "$git" && + git p4 sync && + sleep 5 && + test_must_fail git p4 sync 2>errmsg && + grep "failure accessing depot" errmsg + ) +' + +test_expect_success 'kill p4d' ' + kill_p4d +' + + +test_done diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index 1b34caa1e1..175f83d704 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -13,7 +13,7 @@ complete () return 0 } -# Be careful when updating this list: +# Be careful when updating these lists: # # (1) The build tree may have build artifact from different branch, or # the user's $PATH may have a random executable that may begin @@ -30,7 +30,8 @@ complete () # completion for "git <TAB>", and a plumbing is excluded. "add", # "filter-branch" and "ls-files" are listed for this. -GIT_TESTING_COMMAND_COMPLETION='add checkout check-attr filter-branch ls-files' +GIT_TESTING_ALL_COMMAND_LIST='add checkout check-attr filter-branch ls-files' +GIT_TESTING_PORCELAIN_COMMAND_LIST='add checkout filter-branch' . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" @@ -84,10 +85,11 @@ test_completion () then printf '%s\n' "$2" >expected else - sed -e 's/Z$//' >expected + sed -e 's/Z$//' |sort >expected fi && run_completion "$1" && - test_cmp expected out + sort out >out_sorted && + test_cmp expected out_sorted } # Test __gitcomp. @@ -179,7 +181,7 @@ test_expect_success '__git_find_repo_path - cwd is a .git directory' ' test_expect_success '__git_find_repo_path - parent is a .git directory' ' echo "$ROOT/.git" >expected && ( - cd .git/refs/heads && + cd .git/objects && __git_find_repo_path && echo "$__git_repo_path" >"$actual" ) && @@ -400,6 +402,46 @@ test_expect_success '__gitdir - remote as argument' ' test_cmp expected "$actual" ' + +test_expect_success '__git_dequote - plain unquoted word' ' + __git_dequote unquoted-word && + verbose test unquoted-word = "$dequoted_word" +' + +# input: b\a\c\k\'\\\"s\l\a\s\h\es +# expected: back'\"slashes +test_expect_success '__git_dequote - backslash escaped' ' + __git_dequote "b\a\c\k\\'\''\\\\\\\"s\l\a\s\h\es" && + verbose test "back'\''\\\"slashes" = "$dequoted_word" +' + +# input: sin'gle\' '"quo'ted +# expected: single\ "quoted +test_expect_success '__git_dequote - single quoted' ' + __git_dequote "'"sin'gle\\\\' '\\\"quo'ted"'" && + verbose test '\''single\ "quoted'\'' = "$dequoted_word" +' + +# input: dou"ble\\" "\"\quot"ed +# expected: double\ "\quoted +test_expect_success '__git_dequote - double quoted' ' + __git_dequote '\''dou"ble\\" "\"\quot"ed'\'' && + verbose test '\''double\ "\quoted'\'' = "$dequoted_word" +' + +# input: 'open single quote +test_expect_success '__git_dequote - open single quote' ' + __git_dequote "'\''open single quote" && + verbose test "open single quote" = "$dequoted_word" +' + +# input: "open double quote +test_expect_success '__git_dequote - open double quote' ' + __git_dequote "\"open double quote" && + verbose test "open double quote" = "$dequoted_word" +' + + test_expect_success '__gitcomp_direct - puts everything into COMPREPLY as-is' ' sed -e "s/Z$//g" >expected <<-EOF && with-trailing-space Z @@ -459,6 +501,42 @@ test_expect_success '__gitcomp - suffix' ' EOF ' +test_expect_success '__gitcomp - ignore optional negative options' ' + test_gitcomp "--" "--abc --def --no-one -- --no-two" <<-\EOF + --abc Z + --def Z + --no-one Z + --no-... Z + EOF +' + +test_expect_success '__gitcomp - ignore/narrow optional negative options' ' + test_gitcomp "--a" "--abc --abcdef --no-one -- --no-two" <<-\EOF + --abc Z + --abcdef Z + EOF +' + +test_expect_success '__gitcomp - ignore/narrow optional negative options' ' + test_gitcomp "--n" "--abc --def --no-one -- --no-two" <<-\EOF + --no-one Z + --no-... Z + EOF +' + +test_expect_success '__gitcomp - expand all negative options' ' + test_gitcomp "--no-" "--abc --def --no-one -- --no-two" <<-\EOF + --no-one Z + --no-two Z + EOF +' + +test_expect_success '__gitcomp - expand/narrow all negative options' ' + test_gitcomp "--no-o" "--abc --def --no-one -- --no-two" <<-\EOF + --no-one Z + EOF +' + test_expect_success '__gitcomp - doesnt fail because of invalid variable name' ' __gitcomp "$invalid_variable_name" ' @@ -1025,7 +1103,7 @@ test_expect_success '__git_complete_refs - remote' ' master-in-other Z EOF ( - cur= + cur= && __git_complete_refs --remote=other && print_comp ) && @@ -1044,7 +1122,7 @@ test_expect_success '__git_complete_refs - track' ' master-in-other Z EOF ( - cur= + cur= && __git_complete_refs --track && print_comp ) && @@ -1168,6 +1246,124 @@ test_expect_success 'teardown after ref completion' ' git remote remove other ' + +test_path_completion () +{ + test $# = 2 || error "bug in the test script: not 2 parameters to test_path_completion" + + local cur="$1" expected="$2" + echo "$expected" >expected && + ( + # In the following tests calling this function we only + # care about how __git_complete_index_file() deals with + # unusual characters in path names. By requesting only + # untracked files we dont have to bother adding any + # paths to the index in those tests. + __git_complete_index_file --others && + print_comp + ) && + test_cmp expected out +} + +test_expect_success 'setup for path completion tests' ' + mkdir simple-dir \ + "spaces in dir" \ + árvíztűrő && + touch simple-dir/simple-file \ + "spaces in dir/spaces in file" \ + "árvíztűrő/Сайн яваарай" && + if test_have_prereq !MINGW && + mkdir BS\\dir \ + '$'separators\034in\035dir'' && + touch BS\\dir/DQ\"file \ + '$'separators\034in\035dir/sep\036in\037file'' + then + test_set_prereq FUNNIERNAMES + else + rm -rf BS\\dir '$'separators\034in\035dir'' + fi +' + +test_expect_success '__git_complete_index_file - simple' ' + test_path_completion simple simple-dir && # Bash is supposed to + # add the trailing /. + test_path_completion simple-dir/simple simple-dir/simple-file +' + +test_expect_success \ + '__git_complete_index_file - escaped characters on cmdline' ' + test_path_completion spac "spaces in dir" && # Bash will turn this + # into "spaces\ in\ dir" + test_path_completion "spaces\\ i" \ + "spaces in dir" && + test_path_completion "spaces\\ in\\ dir/s" \ + "spaces in dir/spaces in file" && + test_path_completion "spaces\\ in\\ dir/spaces\\ i" \ + "spaces in dir/spaces in file" +' + +test_expect_success \ + '__git_complete_index_file - quoted characters on cmdline' ' + # Testing with an opening but without a corresponding closing + # double quote is important. + test_path_completion \"spac "spaces in dir" && + test_path_completion "\"spaces i" \ + "spaces in dir" && + test_path_completion "\"spaces in dir/s" \ + "spaces in dir/spaces in file" && + test_path_completion "\"spaces in dir/spaces i" \ + "spaces in dir/spaces in file" +' + +test_expect_success '__git_complete_index_file - UTF-8 in ls-files output' ' + test_path_completion á árvíztűrő && + test_path_completion árvíztűrő/С "árvíztűrő/Сайн яваарай" +' + +test_expect_success FUNNIERNAMES \ + '__git_complete_index_file - C-style escapes in ls-files output' ' + test_path_completion BS \ + BS\\dir && + test_path_completion BS\\\\d \ + BS\\dir && + test_path_completion BS\\\\dir/DQ \ + BS\\dir/DQ\"file && + test_path_completion BS\\\\dir/DQ\\\"f \ + BS\\dir/DQ\"file +' + +test_expect_success FUNNIERNAMES \ + '__git_complete_index_file - \nnn-escaped characters in ls-files output' ' + test_path_completion sep '$'separators\034in\035dir'' && + test_path_completion '$'separators\034i'' \ + '$'separators\034in\035dir'' && + test_path_completion '$'separators\034in\035dir/sep'' \ + '$'separators\034in\035dir/sep\036in\037file'' && + test_path_completion '$'separators\034in\035dir/sep\036i'' \ + '$'separators\034in\035dir/sep\036in\037file'' +' + +test_expect_success FUNNYNAMES \ + '__git_complete_index_file - removing repeated quoted path components' ' + test_when_finished rm -r repeated-quoted && + mkdir repeated-quoted && # A directory whose name in itself + # would not be quoted ... + >repeated-quoted/0-file && + >repeated-quoted/1\"file && # ... but here the file makes the + # dirname quoted ... + >repeated-quoted/2-file && + >repeated-quoted/3\"file && # ... and here, too. + + # Still, we shold only list the directory name only once. + test_path_completion repeated repeated-quoted +' + +test_expect_success 'teardown after path completion tests' ' + rm -rf simple-dir "spaces in dir" árvíztűrő \ + BS\\dir '$'separators\034in\035dir'' +' + + test_expect_success '__git_get_config_variables' ' cat >expect <<-EOF && name-1 @@ -1191,17 +1387,6 @@ test_expect_success '__git_pretty_aliases' ' test_cmp expect actual ' -test_expect_success '__git_aliases' ' - cat >expect <<-EOF && - ci - co - EOF - test_config alias.ci commit && - test_config alias.co checkout && - __git_aliases >actual && - test_cmp expect actual -' - test_expect_success 'basic' ' run_completion "git " && # built-in @@ -1249,8 +1434,8 @@ test_expect_success 'double dash "git checkout"' ' --ignore-other-worktrees Z --recurse-submodules Z --progress Z - --no-track Z - --no-recurse-submodules Z + --no-quiet Z + --no-... Z EOF ' @@ -1365,6 +1550,7 @@ test_expect_success 'complete files' ' echo "expected" > .gitignore && echo "out" >> .gitignore && + echo "out_sorted" >> .gitignore && git add .gitignore && test_completion "git commit " ".gitignore" && @@ -1457,6 +1643,7 @@ test_expect_success 'completion used <cmd> completion for alias: !f() { : git <c test_expect_success 'completion without explicit _git_xxx function' ' test_completion "git version --" <<-\EOF --build-options Z + --no-build-options Z EOF ' @@ -1510,13 +1697,6 @@ test_expect_success 'sourcing the completion script clears cached commands' ' verbose test -z "$__git_all_commands" ' -test_expect_success 'sourcing the completion script clears cached porcelain commands' ' - __git_compute_porcelain_commands && - verbose test -n "$__git_porcelain_commands" && - . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && - verbose test -z "$__git_porcelain_commands" -' - test_expect_success !GETTEXT_POISON 'sourcing the completion script clears cached merge strategies' ' __git_compute_merge_strategies && verbose test -n "$__git_merge_strategies" && diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 8f5c811dd7..81a5179e28 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -63,18 +63,15 @@ test_expect_success 'prompt - unborn branch' ' test_cmp expected "$actual" ' -repo_with_newline='repo -with -newline' - -if test_have_prereq !MINGW && mkdir "$repo_with_newline" 2>/dev/null -then - test_set_prereq FUNNYNAMES -else +if test_have_prereq !FUNNYNAMES; then say 'Your filesystem does not allow newlines in filenames.' fi test_expect_success FUNNYNAMES 'prompt - with newline in path' ' + repo_with_newline="repo +with +newline" && + mkdir "$repo_with_newline" && printf " (master)" >expected && git init "$repo_with_newline" && test_when_finished "rm -rf \"$repo_with_newline\"" && @@ -148,7 +145,7 @@ test_expect_success 'prompt - inside .git directory' ' test_expect_success 'prompt - deep inside .git directory' ' printf " (GIT_DIR!)" >expected && ( - cd .git/refs/heads && + cd .git/objects && __git_ps1 >"$actual" ) && test_cmp expected "$actual" @@ -516,10 +513,9 @@ test_expect_success 'prompt - format string starting with dash' ' test_expect_success 'prompt - pc mode' ' printf "BEFORE: (\${__git_ps1_branch_name}):AFTER\\nmaster" >expected && - printf "" >expected_output && ( __git_ps1 "BEFORE:" ":AFTER" >"$actual" && - test_cmp expected_output "$actual" && + test_must_be_empty "$actual" && printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual" ) && test_cmp expected "$actual" @@ -529,7 +525,7 @@ test_expect_success 'prompt - bash color pc mode - branch name' ' printf "BEFORE: (${c_green}\${__git_ps1_branch_name}${c_clear}):AFTER\\nmaster" >expected && ( GIT_PS1_SHOWCOLORHINTS=y && - __git_ps1 "BEFORE:" ":AFTER" >"$actual" + __git_ps1 "BEFORE:" ":AFTER" >"$actual" && printf "%s\\n%s" "$PS1" "${__git_ps1_branch_name}" >"$actual" ) && test_cmp expected "$actual" @@ -715,13 +711,12 @@ test_expect_success 'prompt - hide if pwd ignored - env var set, config disabled ' test_expect_success 'prompt - hide if pwd ignored - env var set, config unset' ' - printf "" >expected && ( cd ignored_dir && GIT_PS1_HIDE_IF_PWD_IGNORED=y && __git_ps1 >"$actual" ) && - test_cmp expected "$actual" + test_must_be_empty "$actual" ' test_expect_success 'prompt - hide if pwd ignored - env var set, config unset, pc mode' ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 7d620bf2a9..d82fac9d79 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -42,6 +42,8 @@ test_decode_color () { function name(n) { if (n == 0) return "RESET"; if (n == 1) return "BOLD"; + if (n == 2) return "FAINT"; + if (n == 3) return "ITALIC"; if (n == 7) return "REVERSE"; if (n == 30) return "BLACK"; if (n == 31) return "RED"; @@ -145,12 +147,28 @@ test_pause () { "$SHELL_PATH" <&6 >&5 2>&7 } -# Wrap git in gdb. Adding this to a command can make it easier to -# understand what is going on in a failing test. +# Wrap git with a debugger. Adding this to a command can make it easier +# to understand what is going on in a failing test. # -# Example: "debug git checkout master". +# Examples: +# debug git checkout master +# debug --debugger=nemiver git $ARGS +# debug -d "valgrind --tool=memcheck --track-origins=yes" git $ARGS debug () { - GIT_TEST_GDB=1 "$@" <&6 >&5 2>&7 + case "$1" in + -d) + GIT_DEBUGGER="$2" && + shift 2 + ;; + --debugger=*) + GIT_DEBUGGER="${1#*=}" && + shift 1 + ;; + *) + GIT_DEBUGGER=1 + ;; + esac && + GIT_DEBUGGER="${GIT_DEBUGGER}" "$@" <&6 >&5 2>&7 } # Call test_commit with the arguments @@ -278,8 +296,20 @@ write_script () { # The single parameter is the prerequisite tag (a simple word, in all # capital letters by convention). +test_unset_prereq () { + ! test_have_prereq "$1" || + satisfied_prereq="${satisfied_prereq% $1 *} ${satisfied_prereq#* $1 }" +} + test_set_prereq () { - satisfied_prereq="$satisfied_prereq$1 " + case "$1" in + !*) + test_unset_prereq "${1#!}" + ;; + *) + satisfied_prereq="$satisfied_prereq$1 " + ;; + esac } satisfied_prereq=" " lazily_testable_prereq= lazily_tested_prereq= @@ -537,6 +567,14 @@ test_path_is_dir () { fi } +test_path_exists () { + if ! test -e "$1" + then + echo "Path $1 doesn't exist. $2" + false + fi +} + # Check if the directory exists and is empty as expected, barf otherwise. test_dir_is_empty () { test_path_is_dir "$1" && diff --git a/t/test-lib.sh b/t/test-lib.sh index 483c8d6d7c..3f95bfda60 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -184,6 +184,8 @@ _x40="$_x35$_x05" # Zero SHA-1 _z40=0000000000000000000000000000000000000000 +OID_REGEX="$_x40" +ZERO_OID=$_z40 EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904 EMPTY_BLOB=e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 @@ -195,7 +197,7 @@ LF=' # when case-folding filenames u200c=$(printf '\342\200\214') -export _x05 _x35 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB +export _x05 _x35 _x40 _z40 LF u200c EMPTY_TREE EMPTY_BLOB ZERO_OID OID_REGEX # Each test should start with something like this, after copyright notices: # @@ -673,7 +675,8 @@ test_run_ () { trace= # 117 is magic because it is unlikely to match the exit # code of other programs - if test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)" + if $(printf '%s\n' "$1" | sed -f "$GIT_BUILD_DIR/t/chainlint.sed" | grep -q '?![A-Z][A-Z]*?!') || + test "OK-117" != "$(test_eval_ "(exit 117) && $1${LF}${LF}echo OK-\$?" 3>&1)" then error "bug in the test script: broken &&-chain or run-away HERE-DOC: $1" fi @@ -864,7 +867,7 @@ then # handle only executables, unless they are shell libraries that # need to be in the exec-path. test -x "$1" || - test "# " = "$(head -c 2 <"$1")" || + test "# " = "$(test_copy_bytes 2 <"$1")" || return; base=$(basename "$1") @@ -879,7 +882,7 @@ then # do not override scripts if test -x "$symlink_target" && test ! -d "$symlink_target" && - test "#!" != "$(head -c 2 < "$symlink_target")" + test "#!" != "$(test_copy_bytes 2 <"$symlink_target")" then symlink_target=../valgrind.sh fi @@ -1080,6 +1083,12 @@ else test_set_prereq C_LOCALE_OUTPUT fi +if test -z "$GIT_TEST_CHECK_CACHE_TREE" +then + GIT_TEST_CHECK_CACHE_TREE=true + export GIT_TEST_CHECK_CACHE_TREE +fi + test_lazy_prereq PIPE ' # test whether the filesystem supports FIFOs test_have_prereq !MINGW,!CYGWIN && @@ -1101,17 +1110,26 @@ test_lazy_prereq CASE_INSENSITIVE_FS ' test "$(cat CamelCase)" != good ' +test_lazy_prereq FUNNYNAMES ' + test_have_prereq !MINGW && + touch -- \ + "FUNNYNAMES tab embedded" \ + "FUNNYNAMES \"quote embedded\"" \ + "FUNNYNAMES newline +embedded" 2>/dev/null && + rm -- \ + "FUNNYNAMES tab embedded" \ + "FUNNYNAMES \"quote embedded\"" \ + "FUNNYNAMES newline +embedded" 2>/dev/null +' + test_lazy_prereq UTF8_NFD_TO_NFC ' # check whether FS converts nfd unicode to nfc auml=$(printf "\303\244") aumlcdiar=$(printf "\141\314\210") >"$auml" && - case "$(echo *)" in - "$aumlcdiar") - true ;; - *) - false ;; - esac + test -f "$aumlcdiar" ' test_lazy_prereq AUTOIDENT ' @@ -1208,3 +1226,14 @@ test_lazy_prereq LONG_IS_64BIT ' test_lazy_prereq TIME_IS_64BIT 'test-tool date is64bit' test_lazy_prereq TIME_T_IS_64BIT 'test-tool date time_t-is64bit' + +test_lazy_prereq CURL ' + curl --version +' + +# SHA1 is a test if the hash algorithm in use is SHA-1. This is both for tests +# which will not work with other hash algorithms and tests that work but don't +# test anything meaningful (e.g. special values which cause short collisions). +test_lazy_prereq SHA1 ' + test $(git hash-object /dev/null) = e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 +' |