diff options
Diffstat (limited to 't')
37 files changed, 4681 insertions, 246 deletions
@@ -84,9 +84,10 @@ appropriately before running "make". -x:: Turn on shell tracing (i.e., `set -x`) during the tests - themselves. Implies `--verbose`. Note that in non-bash shells, - this can cause failures in some tests which redirect and test - the output of shell functions. Use with caution. + themselves. Implies `--verbose`. + Ignored in test scripts that set the variable 'test_untraceable' + to a non-empty value, unless it's run with a Bash version + supporting BASH_XTRACEFD, i.e. v4.1 or later. -d:: --debug:: @@ -452,6 +453,22 @@ Don't: causing the next test to start in an unexpected directory. Do so inside a subshell if necessary. + - save and verify the standard error of compound commands, i.e. group + commands, subshells, and shell functions (except test helper + functions like 'test_must_fail') like this: + + ( cd dir && git cmd ) 2>error && + test_cmp expect error + + When running the test with '-x' tracing, then the trace of commands + executed in the compound command will be included in standard error + as well, quite possibly throwing off the subsequent checks examining + the output. Instead, save only the relevant git command's standard + error: + + ( cd dir && git cmd 2>../error ) && + test_cmp expect error + - Break the TAP output The raw output from your test may be interpreted by a TAP harness. TAP diff --git a/t/lib-terminal.sh b/t/lib-terminal.sh index cd220e378e..e3809dcead 100644 --- a/t/lib-terminal.sh +++ b/t/lib-terminal.sh @@ -9,8 +9,8 @@ test_terminal () { echo >&4 "test_terminal: need to declare TTY prerequisite" return 127 fi - perl "$TEST_DIRECTORY"/test-terminal.perl "$@" -} + perl "$TEST_DIRECTORY"/test-terminal.perl "$@" 2>&7 +} 7>&2 2>&4 test_lazy_prereq TTY ' test_have_prereq PERL && diff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh index 0c2fc81d7b..04d474c84f 100755 --- a/t/t0040-parse-options.sh +++ b/t/t0040-parse-options.sh @@ -291,7 +291,7 @@ test_expect_success 'OPT_CALLBACK() and OPT_BIT() work' ' 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_i18ncmp expect.err output.err + test_must_be_empty output.err ' cat >expect <<\EOF diff --git a/t/t0041-usage.sh b/t/t0041-usage.sh new file mode 100755 index 0000000000..5b927b76fe --- /dev/null +++ b/t/t0041-usage.sh @@ -0,0 +1,107 @@ +#!/bin/sh + +test_description='Test commands behavior when given invalid argument value' + +. ./test-lib.sh + +test_expect_success 'setup ' ' + test_commit "v1.0" +' + +test_expect_success 'tag --contains <existent_tag>' ' + git tag --contains "v1.0" >actual 2>actual.err && + grep "v1.0" actual && + test_line_count = 0 actual.err +' + +test_expect_success 'tag --contains <inexistent_tag>' ' + test_must_fail git tag --contains "notag" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'tag --no-contains <existent_tag>' ' + git tag --no-contains "v1.0" >actual 2>actual.err && + test_line_count = 0 actual && + test_line_count = 0 actual.err +' + +test_expect_success 'tag --no-contains <inexistent_tag>' ' + test_must_fail git tag --no-contains "notag" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'tag usage error' ' + test_must_fail git tag --noopt >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "usage" actual.err +' + +test_expect_success 'branch --contains <existent_commit>' ' + git branch --contains "master" >actual 2>actual.err && + test_i18ngrep "master" actual && + test_line_count = 0 actual.err +' + +test_expect_success 'branch --contains <inexistent_commit>' ' + test_must_fail git branch --no-contains "nocommit" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'branch --no-contains <existent_commit>' ' + git branch --no-contains "master" >actual 2>actual.err && + test_line_count = 0 actual && + test_line_count = 0 actual.err +' + +test_expect_success 'branch --no-contains <inexistent_commit>' ' + test_must_fail git branch --no-contains "nocommit" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'branch usage error' ' + test_must_fail git branch --noopt >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "usage" actual.err +' + +test_expect_success 'for-each-ref --contains <existent_object>' ' + git for-each-ref --contains "master" >actual 2>actual.err && + test_line_count = 2 actual && + test_line_count = 0 actual.err +' + +test_expect_success 'for-each-ref --contains <inexistent_object>' ' + test_must_fail git for-each-ref --no-contains "noobject" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'for-each-ref --no-contains <existent_object>' ' + git for-each-ref --no-contains "master" >actual 2>actual.err && + test_line_count = 0 actual && + test_line_count = 0 actual.err +' + +test_expect_success 'for-each-ref --no-contains <inexistent_object>' ' + test_must_fail git for-each-ref --no-contains "noobject" >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "error" actual.err && + test_i18ngrep ! "usage" actual.err +' + +test_expect_success 'for-each-ref usage error' ' + test_must_fail git for-each-ref --noopt >actual 2>actual.err && + test_line_count = 0 actual && + test_i18ngrep "usage" actual.err +' + +test_done diff --git a/t/t1507-rev-parse-upstream.sh b/t/t1507-rev-parse-upstream.sh index b23c4e3fab..2ce68cc277 100755 --- a/t/t1507-rev-parse-upstream.sh +++ b/t/t1507-rev-parse-upstream.sh @@ -42,7 +42,7 @@ commit_subject () { error_message () { (cd clone && - test_must_fail git rev-parse --verify "$@") + test_must_fail git rev-parse --verify "$@" 2>../error) } test_expect_success '@{upstream} resolves to correct full name' ' @@ -159,8 +159,8 @@ test_expect_success 'branch@{u} error message when no upstream' ' cat >expect <<-EOF && fatal: no upstream configured for branch ${sq}non-tracking${sq} EOF - error_message non-tracking@{u} 2>actual && - test_i18ncmp expect actual + error_message non-tracking@{u} && + test_i18ncmp expect error ' test_expect_success '@{u} error message when no upstream' ' @@ -175,8 +175,8 @@ test_expect_success 'branch@{u} error message with misspelt branch' ' cat >expect <<-EOF && fatal: no such branch: ${sq}no-such-branch${sq} EOF - error_message no-such-branch@{u} 2>actual && - test_i18ncmp expect actual + error_message no-such-branch@{u} && + test_i18ncmp expect error ' test_expect_success '@{u} error message when not on a branch' ' @@ -192,8 +192,8 @@ test_expect_success 'branch@{u} error message if upstream branch not fetched' ' cat >expect <<-EOF && fatal: upstream branch ${sq}refs/heads/side${sq} not stored as a remote-tracking branch EOF - error_message bad-upstream@{u} 2>actual && - test_i18ncmp expect actual + error_message bad-upstream@{u} && + test_i18ncmp expect error ' test_expect_success 'pull works when tracking a local branch' ' diff --git a/t/t1510-repo-setup.sh b/t/t1510-repo-setup.sh index 13ae12dfa7..e6854b828e 100755 --- a/t/t1510-repo-setup.sh +++ b/t/t1510-repo-setup.sh @@ -39,6 +39,10 @@ A few rules for repo setup: 11. When user's cwd is outside worktree, cwd remains unchanged, prefix is NULL. " + +# This test heavily relies on the standard error of nested function calls. +test_untraceable=UnfortunatelyYes + . ./test-lib.sh here=$(pwd) diff --git a/t/t2026-worktree-prune.sh b/t/t2026-worktree-prune.sh index a0f1e3bb80..b7d6d5d45a 100755 --- a/t/t2026-worktree-prune.sh +++ b/t/t2026-worktree-prune.sh @@ -78,10 +78,9 @@ test_expect_success 'not prune locked checkout' ' test_expect_success 'not prune recent checkouts' ' test_when_finished rm -r .git/worktrees && - mkdir zz && - mkdir -p .git/worktrees/jlm && - echo "$(pwd)"/zz >.git/worktrees/jlm/gitdir && - rmdir zz && + git worktree add jlm HEAD && + test -d .git/worktrees/jlm && + rm -rf jlm && git worktree prune --verbose --expire=2.days.ago && test -d .git/worktrees/jlm ' diff --git a/t/t2028-worktree-move.sh b/t/t2028-worktree-move.sh index 8298aaf97f..5d5b3632ba 100755 --- a/t/t2028-worktree-move.sh +++ b/t/t2028-worktree-move.sh @@ -7,7 +7,8 @@ test_description='test git worktree move, remove, lock and unlock' test_expect_success 'setup' ' test_commit init && git worktree add source && - git worktree list --porcelain | grep "^worktree" >actual && + git worktree list --porcelain >out && + grep "^worktree" out >actual && cat <<-EOF >expected && worktree $(pwd) worktree $(pwd)/source @@ -59,4 +60,86 @@ test_expect_success 'unlock worktree twice' ' test_path_is_missing .git/worktrees/source/locked ' +test_expect_success 'move non-worktree' ' + mkdir abc && + test_must_fail git worktree move abc def +' + +test_expect_success 'move locked worktree' ' + git worktree lock source && + test_when_finished "git worktree unlock source" && + test_must_fail git worktree move source destination +' + +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 && + git -C destination log --format=%s >actual2 && + echo init >expected2 && + test_cmp expected2 actual2 +' + +test_expect_success 'move main worktree' ' + test_must_fail git worktree move . def +' + +test_expect_success 'move worktree to another dir' ' + mkdir some-dir && + git worktree move destination some-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 && + git -C some-dir/destination log --format=%s >actual2 && + echo init >expected2 && + test_cmp expected2 actual2 +' + +test_expect_success 'remove main worktree' ' + test_must_fail git worktree remove . +' + +test_expect_success 'remove locked worktree' ' + git worktree lock destination && + test_when_finished "git worktree unlock destination" && + test_must_fail git worktree remove destination +' + +test_expect_success 'remove worktree with dirty tracked file' ' + echo dirty >>destination/init.t && + test_when_finished "git -C destination checkout init.t" && + test_must_fail git worktree remove destination +' + +test_expect_success 'remove worktree with untracked file' ' + : >destination/untracked && + test_must_fail git worktree remove destination +' + +test_expect_success 'force remove worktree with untracked file' ' + git worktree remove --force destination && + test_path_is_missing destination +' + +test_expect_success 'remove missing worktree' ' + git worktree add to-be-gone && + test -d .git/worktrees/to-be-gone && + mv to-be-gone gone && + git worktree remove to-be-gone && + test_path_is_missing .git/worktrees/to-be-gone +' + +test_expect_success 'NOT remove missing-but-locked worktree' ' + git worktree add gone-but-locked && + git worktree lock gone-but-locked && + test -d .git/worktrees/gone-but-locked && + mv gone-but-locked really-gone-now && + test_must_fail git worktree remove gone-but-locked && + test_path_is_dir .git/worktrees/gone-but-locked +' + test_done diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index cdc38fe5d1..3563e77b37 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -525,20 +525,22 @@ test_expect_success 'merge-recursive w/ empty work tree - ours has rename' ' GIT_INDEX_FILE="$PWD/ours-has-rename-index" && export GIT_INDEX_FILE && mkdir "$GIT_WORK_TREE" && - git read-tree -i -m $c7 && - git update-index --ignore-missing --refresh && - git merge-recursive $c0 -- $c7 $c3 && - git ls-files -s >actual-files - ) 2>actual-err && - >expected-err && + git read-tree -i -m $c7 2>actual-err && + test_must_be_empty actual-err && + git update-index --ignore-missing --refresh 2>actual-err && + test_must_be_empty actual-err && + git merge-recursive $c0 -- $c7 $c3 2>actual-err && + test_must_be_empty actual-err && + git ls-files -s >actual-files 2>actual-err && + test_must_be_empty actual-err + ) && cat >expected-files <<-EOF && 100644 $o3 0 b/c 100644 $o0 0 c 100644 $o0 0 d/e 100644 $o0 0 e EOF - test_cmp expected-files actual-files && - test_cmp expected-err actual-err + test_cmp expected-files actual-files ' test_expect_success 'merge-recursive w/ empty work tree - theirs has rename' ' @@ -548,20 +550,22 @@ test_expect_success 'merge-recursive w/ empty work tree - theirs has rename' ' GIT_INDEX_FILE="$PWD/theirs-has-rename-index" && export GIT_INDEX_FILE && mkdir "$GIT_WORK_TREE" && - git read-tree -i -m $c3 && - git update-index --ignore-missing --refresh && - git merge-recursive $c0 -- $c3 $c7 && - git ls-files -s >actual-files - ) 2>actual-err && - >expected-err && + git read-tree -i -m $c3 2>actual-err && + test_must_be_empty actual-err && + git update-index --ignore-missing --refresh 2>actual-err && + test_must_be_empty actual-err && + git merge-recursive $c0 -- $c3 $c7 2>actual-err && + test_must_be_empty actual-err && + git ls-files -s >actual-files 2>actual-err && + test_must_be_empty actual-err + ) && cat >expected-files <<-EOF && 100644 $o3 0 b/c 100644 $o0 0 c 100644 $o0 0 d/e 100644 $o0 0 e EOF - test_cmp expected-files actual-files && - test_cmp expected-err actual-err + test_cmp expected-files actual-files ' test_expect_success 'merge removes empty directories' ' diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 503a88d029..6c0b7ea4ad 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -528,7 +528,7 @@ test_expect_success 'git branch -c -f o/q o/p should work when o/p exists' ' git branch -c -f o/q o/p ' -test_expect_success 'git branch -c qq rr/qq should fail when r exists' ' +test_expect_success 'git branch -c qq rr/qq should fail when rr exists' ' git branch qq && git branch rr && test_must_fail git branch -c qq rr/qq diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 3b905406df..c59d0384fd 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -927,10 +927,8 @@ test_expect_success 'rebase --exec works without -i ' ' test_expect_success 'rebase -i --exec without <CMD>' ' git reset --hard execute && set_fake_editor && - test_must_fail git rebase -i --exec 2>tmp && - sed -e "1d" tmp >actual && - test_must_fail git rebase -h >expected && - test_cmp expected actual && + test_must_fail git rebase -i --exec 2>actual && + test_i18ngrep "requires a value" actual && git checkout master ' diff --git a/t/t3501-revert-cherry-pick.sh b/t/t3501-revert-cherry-pick.sh index 783bdbf59d..0d89f6d0f6 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 && diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh index 058698df6a..b170fb02b8 100755 --- a/t/t3701-add-interactive.sh +++ b/t/t3701-add-interactive.sh @@ -10,6 +10,19 @@ then test_done fi +diff_cmp () { + for x + do + sed -e '/^index/s/[0-9a-f]*[1-9a-f][0-9a-f]*\.\./1234567../' \ + -e '/^index/s/\.\.[0-9a-f]*[1-9a-f][0-9a-f]*/..9abcdef/' \ + -e '/^index/s/ 00*\.\./ 0000000../' \ + -e '/^index/s/\.\.00*$/..0000000/' \ + -e '/^index/s/\.\.00* /..0000000 /' \ + "$x" >"$x.filtered" + done + test_cmp "$1.filtered" "$2.filtered" +} + test_expect_success 'setup (initial)' ' echo content >file && git add file && @@ -22,20 +35,20 @@ test_expect_success 'status works (initial)' ' ' test_expect_success 'setup expected' ' -cat >expected <<EOF -new file mode 100644 -index 0000000..d95f3ad ---- /dev/null -+++ b/file -@@ -0,0 +1 @@ -+content -EOF + cat >expected <<-\EOF + new file mode 100644 + index 0000000..d95f3ad + --- /dev/null + +++ b/file + @@ -0,0 +1 @@ + +content + EOF ' test_expect_success 'diff works (initial)' ' (echo d; echo 1) | git add -i >output && sed -ne "/new file/,/content/p" <output >diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'revert works (initial)' ' git add file && @@ -59,20 +72,20 @@ test_expect_success 'status works (commit)' ' ' test_expect_success 'setup expected' ' -cat >expected <<EOF -index 180b47c..b6f2c08 100644 ---- a/file -+++ b/file -@@ -1 +1,2 @@ - baseline -+content -EOF + cat >expected <<-\EOF + index 180b47c..b6f2c08 100644 + --- a/file + +++ b/file + @@ -1 +1,2 @@ + baseline + +content + EOF ' test_expect_success 'diff works (commit)' ' (echo d; echo 1) | git add -i >output && sed -ne "/^index/,/content/p" <output >diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'revert works (commit)' ' git add file && @@ -83,39 +96,32 @@ test_expect_success 'revert works (commit)' ' test_expect_success 'setup expected' ' -cat >expected <<EOF -EOF -' - -test_expect_success 'setup fake editor' ' - >fake_editor.sh && - chmod a+x fake_editor.sh && - test_set_editor "$(pwd)/fake_editor.sh" + cat >expected <<-\EOF + EOF ' test_expect_success 'dummy edit works' ' + test_set_editor : && (echo e; echo a) | git add -p && git diff > diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'setup patch' ' -cat >patch <<EOF -@@ -1,1 +1,4 @@ - this -+patch --does not - apply -EOF + cat >patch <<-\EOF + @@ -1,1 +1,4 @@ + this + +patch + -does not + apply + EOF ' test_expect_success 'setup fake editor' ' - echo "#!$SHELL_PATH" >fake_editor.sh && - cat >>fake_editor.sh <<\EOF && -mv -f "$1" oldpatch && -mv -f patch "$1" -EOF - chmod a+x fake_editor.sh && + write_script "fake_editor.sh" <<-\EOF && + mv -f "$1" oldpatch && + mv -f patch "$1" + EOF test_set_editor "$(pwd)/fake_editor.sh" ' @@ -126,10 +132,10 @@ test_expect_success 'bad edit rejected' ' ' test_expect_success 'setup patch' ' -cat >patch <<EOF -this patch -is garbage -EOF + cat >patch <<-\EOF + this patch + is garbage + EOF ' test_expect_success 'garbage edit rejected' ' @@ -139,34 +145,34 @@ test_expect_success 'garbage edit rejected' ' ' test_expect_success 'setup patch' ' -cat >patch <<EOF -@@ -1,0 +1,0 @@ - baseline -+content -+newcontent -+lines -EOF + cat >patch <<-\EOF + @@ -1,0 +1,0 @@ + baseline + +content + +newcontent + +lines + EOF ' test_expect_success 'setup expected' ' -cat >expected <<EOF -diff --git a/file b/file -index b5dd6c9..f910ae9 100644 ---- a/file -+++ b/file -@@ -1,4 +1,4 @@ - baseline - content --newcontent -+more - lines -EOF + cat >expected <<-\EOF + diff --git a/file b/file + index b5dd6c9..f910ae9 100644 + --- a/file + +++ b/file + @@ -1,4 +1,4 @@ + baseline + content + -newcontent + +more + lines + EOF ' test_expect_success 'real edit works' ' (echo e; echo n; echo d) | git add -p && git diff >output && - test_cmp expected output + diff_cmp expected output ' test_expect_success 'skip files similarly as commit -a' ' @@ -178,7 +184,7 @@ test_expect_success 'skip files similarly as commit -a' ' git reset && git commit -am commit && git diff >expected && - test_cmp expected output && + diff_cmp expected output && git reset --hard HEAD^ ' rm -f .gitignore @@ -222,52 +228,67 @@ test_expect_success 'setup again' ' # Write the patch file with a new line at the top and bottom test_expect_success 'setup patch' ' -cat >patch <<EOF -index 180b47c..b6f2c08 100644 ---- a/file -+++ b/file -@@ -1,2 +1,4 @@ -+firstline - baseline - content -+lastline -EOF -' - -# Expected output, similar to the patch but w/ diff at the top + cat >patch <<-\EOF + index 180b47c..b6f2c08 100644 + --- a/file + +++ b/file + @@ -1,2 +1,4 @@ + +firstline + baseline + content + +lastline + \ No newline at end of file + EOF +' + +# Expected output, diff is similar to the patch but w/ diff at the top test_expect_success 'setup expected' ' -cat >expected <<EOF -diff --git a/file b/file -index b6f2c08..61b9053 100755 ---- a/file -+++ b/file -@@ -1,2 +1,4 @@ -+firstline - baseline - content -+lastline -EOF + echo diff --git a/file b/file >expected && + cat patch |sed "/^index/s/ 100644/ 100755/" >>expected && + cat >expected-output <<-\EOF + --- a/file + +++ b/file + @@ -1,2 +1,4 @@ + +firstline + baseline + content + +lastline + \ No newline at end of file + @@ -1,2 +1,3 @@ + +firstline + baseline + content + @@ -1,2 +2,3 @@ + baseline + content + +lastline + \ No newline at end of file + EOF ' # Test splitting the first patch, then adding both -test_expect_success 'add first line works' ' +test_expect_success C_LOCALE_OUTPUT 'add first line works' ' git commit -am "clear local changes" && git apply patch && - (echo s; echo y; echo y) | git add -p file && - git diff --cached > diff && - test_cmp expected diff + printf "%s\n" s y y | git add -p file 2>error | + sed -n -e "s/^Stage this hunk[^@]*\(@@ .*\)/\1/" \ + -e "/^[-+@ \\\\]"/p >output && + test_must_be_empty error && + git diff --cached >diff && + diff_cmp expected diff && + test_cmp expected-output output ' test_expect_success 'setup expected' ' -cat >expected <<EOF -diff --git a/non-empty b/non-empty -deleted file mode 100644 -index d95f3ad..0000000 ---- a/non-empty -+++ /dev/null -@@ -1 +0,0 @@ --content -EOF + cat >expected <<-\EOF + diff --git a/non-empty b/non-empty + deleted file mode 100644 + index d95f3ad..0000000 + --- a/non-empty + +++ /dev/null + @@ -1 +0,0 @@ + -content + EOF ' test_expect_success 'deleting a non-empty file' ' @@ -278,15 +299,15 @@ test_expect_success 'deleting a non-empty file' ' rm non-empty && echo y | git add -p non-empty && git diff --cached >diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'setup expected' ' -cat >expected <<EOF -diff --git a/empty b/empty -deleted file mode 100644 -index e69de29..0000000 -EOF + cat >expected <<-\EOF + diff --git a/empty b/empty + deleted file mode 100644 + index e69de29..0000000 + EOF ' test_expect_success 'deleting an empty file' ' @@ -297,23 +318,17 @@ test_expect_success 'deleting an empty file' ' rm empty && echo y | git add -p empty && git diff --cached >diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success 'split hunk setup' ' git reset --hard && - for i in 10 20 30 40 50 60 - do - echo $i - done >test && + test_write_lines 10 20 30 40 50 60 >test && git add test && test_tick && git commit -m test && - for i in 10 15 20 21 22 23 24 30 40 50 60 - do - echo $i - done >test + test_write_lines 10 15 20 21 22 23 24 30 40 50 60 >test ' test_expect_success 'split hunk "add -p (edit)"' ' @@ -334,17 +349,7 @@ test_expect_success 'split hunk "add -p (edit)"' ' ' test_expect_failure 'split hunk "add -p (no, yes, edit)"' ' - cat >test <<-\EOF && - 5 - 10 - 20 - 21 - 30 - 31 - 40 - 50 - 60 - EOF + test_write_lines 5 10 20 21 30 31 40 50 60 >test && git reset && # test sequence is s(plit), n(o), y(es), e(dit) # q n q q is there to make sure we exit at the end. @@ -378,7 +383,7 @@ test_expect_success 'patch mode ignores unmerged entries' ' +changed EOF git diff --cached >diff && - test_cmp expected diff + diff_cmp expected diff ' test_expect_success TTY 'diffs can be colorized' ' @@ -392,6 +397,26 @@ test_expect_success TTY 'diffs can be colorized' ' grep "$(printf "\\033")" output ' +test_expect_success TTY 'diffFilter filters diff' ' + git reset --hard && + + echo content >test && + test_config interactive.diffFilter "sed s/^/foo:/" && + printf y | test_terminal git add -p >output 2>&1 && + + # avoid depending on the exact coloring or content of the prompts, + # and just make sure we saw our diff prefixed + grep foo:.*content output +' + +test_expect_success TTY 'detect bogus diffFilter output' ' + git reset --hard && + + echo content >test && + test_config interactive.diffFilter "echo too-short" && + printf y | test_must_fail test_terminal git add -p +' + test_expect_success 'patch-mode via -i prompts for files' ' git reset --hard && @@ -407,7 +432,7 @@ test_expect_success 'patch-mode via -i prompts for files' ' echo test >expect && git diff --cached --name-only >actual && - test_cmp expect actual + diff_cmp expect actual ' test_expect_success 'add -p handles globs' ' @@ -541,4 +566,34 @@ test_expect_success 'status ignores dirty submodules (except HEAD)' ' ! grep dirty-otherwise output ' +test_expect_success 'set up pathological context' ' + git reset --hard && + test_write_lines a a a a a a a a a a a >a && + git add a && + git commit -m a && + test_write_lines c b a a a a a a a b a a a a >a && + test_write_lines a a a a a a a b a a a a >expected-1 && + test_write_lines b a a a a a a a b a a a a >expected-2 && + # check editing can cope with missing header and deleted context lines + # as well as changes to other lines + test_write_lines +b " a" >patch +' + +test_expect_success 'add -p works with pathological context lines' ' + git reset && + printf "%s\n" n y | + git add -p && + git cat-file blob :a >actual && + test_cmp expected-1 actual +' + +test_expect_success 'add -p patch editing works with pathological context lines' ' + git reset && + # n q q below is in case edit fails + printf "%s\n" e y n q q | + git add -p && + git cat-file blob :a >actual && + test_cmp expected-2 actual +' + test_done diff --git a/t/t3905-stash-include-untracked.sh b/t/t3905-stash-include-untracked.sh index bfde4057ad..3ea5b9bb3f 100755 --- a/t/t3905-stash-include-untracked.sh +++ b/t/t3905-stash-include-untracked.sh @@ -228,4 +228,56 @@ test_expect_success 'stash previously ignored file' ' test_path_is_file ignored.d/foo ' +test_expect_success 'stash -u -- <untracked> doesnt print error' ' + >untracked && + git stash push -u -- untracked 2>actual && + test_path_is_missing untracked && + test_line_count = 0 actual +' + +test_expect_success 'stash -u -- <untracked> leaves rest of working tree in place' ' + >tracked && + git add tracked && + >untracked && + git stash push -u -- untracked && + test_path_is_missing untracked && + test_path_is_file tracked +' + +test_expect_success 'stash -u -- <tracked> <untracked> clears changes in both' ' + >tracked && + git add tracked && + >untracked && + git stash push -u -- tracked untracked && + test_path_is_missing tracked && + test_path_is_missing untracked +' + +test_expect_success 'stash --all -- <ignored> stashes ignored file' ' + >ignored.d/bar && + git stash push --all -- ignored.d/bar && + test_path_is_missing ignored.d/bar +' + +test_expect_success 'stash --all -- <tracked> <ignored> clears changes in both' ' + >tracked && + git add tracked && + >ignored.d/bar && + git stash push --all -- tracked ignored.d/bar && + test_path_is_missing tracked && + test_path_is_missing ignored.d/bar +' + +test_expect_success 'stash -u -- <ignored> leaves ignored file alone' ' + >ignored.d/bar && + git stash push -u -- ignored.d/bar && + test_path_is_file ignored.d/bar +' + +test_expect_success 'stash -u -- <non-existant> shows no changes when there are none' ' + git stash push -u -- non-existant >actual && + echo "No local changes to save" >expect && + test_i18ncmp expect actual +' + test_done diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh index f10798b2df..3f9a24fd56 100755 --- a/t/t4013-diff-various.sh +++ b/t/t4013-diff-various.sh @@ -361,6 +361,11 @@ diff --no-index --raw dir2 dir diff --no-index --raw --abbrev=4 dir2 dir :noellipses diff --no-index --raw --abbrev=4 dir2 dir diff --no-index --raw --no-abbrev dir2 dir + +diff-tree --pretty --root --stat --compact-summary initial +diff-tree --pretty -R --root --stat --compact-summary initial +diff-tree --stat --compact-summary initial mode +diff-tree -R --stat --compact-summary initial mode EOF test_expect_success 'log -S requires an argument' ' diff --git a/t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial b/t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial new file mode 100644 index 0000000000..d6451ff7cc --- /dev/null +++ b/t/t4013/diff.diff-tree_--pretty_--root_--stat_--compact-summary_initial @@ -0,0 +1,12 @@ +$ git diff-tree --pretty --root --stat --compact-summary initial +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial + + dir/sub (new) | 2 ++ + file0 (new) | 3 +++ + file2 (new) | 3 +++ + 3 files changed, 8 insertions(+) +$ diff --git a/t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial b/t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial new file mode 100644 index 0000000000..1989e55cd0 --- /dev/null +++ b/t/t4013/diff.diff-tree_--pretty_-R_--root_--stat_--compact-summary_initial @@ -0,0 +1,12 @@ +$ git diff-tree --pretty -R --root --stat --compact-summary initial +commit 444ac553ac7612cc88969031b02b3767fb8a353a +Author: A U Thor <author@example.com> +Date: Mon Jun 26 00:00:00 2006 +0000 + + Initial + + dir/sub (gone) | 2 -- + file0 (gone) | 3 --- + file2 (gone) | 3 --- + 3 files changed, 8 deletions(-) +$ diff --git a/t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode b/t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode new file mode 100644 index 0000000000..9c7c8f63af --- /dev/null +++ b/t/t4013/diff.diff-tree_--stat_--compact-summary_initial_mode @@ -0,0 +1,4 @@ +$ git diff-tree --stat --compact-summary initial mode + file0 (mode +x) | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) +$ diff --git a/t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode b/t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode new file mode 100644 index 0000000000..e38f3d3bfb --- /dev/null +++ b/t/t4013/diff.diff-tree_-R_--stat_--compact-summary_initial_mode @@ -0,0 +1,4 @@ +$ git diff-tree -R --stat --compact-summary initial mode + file0 (mode -x) | 0 + 1 file changed, 0 insertions(+), 0 deletions(-) +$ diff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh index 9473c2779e..16432781d2 100755 --- a/t/t4151-am-abort.sh +++ b/t/t4151-am-abort.sh @@ -46,9 +46,8 @@ do test_expect_success "am$with3 --skip continue after failed am$with3" ' test_must_fail git am$with3 --skip >output && - test_i18ngrep "^Applying" output >output.applying && - test_i18ngrep "^Applying: 6$" output.applying && - test_i18ncmp file-2-expect file-2 && + test_i18ngrep "^Applying: 6$" output && + test_cmp file-2-expect file-2 && test ! -f .git/MERGE_RR ' diff --git a/t/t4201-shortlog.sh b/t/t4201-shortlog.sh index da10478f59..ff6649ed9a 100755 --- a/t/t4201-shortlog.sh +++ b/t/t4201-shortlog.sh @@ -127,6 +127,11 @@ test_expect_success !MINGW 'shortlog can read --format=raw output' ' test_cmp expect out ' +test_expect_success 'shortlog from non-git directory refuses extra arguments' ' + test_must_fail env GIT_DIR=non-existing git shortlog foo 2>out && + test_i18ngrep "too many arguments" out +' + test_expect_success 'shortlog should add newline when input line matches wraplen' ' cat >expect <<\EOF && A U Thor (2): diff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh index c2fc584dac..d695a6082e 100755 --- a/t/t5302-pack-index.sh +++ b/t/t5302-pack-index.sh @@ -262,4 +262,9 @@ EOF grep "^warning:.* expected .tagger. line" err ' +test_expect_success 'index-pack --fsck-objects also warns upon missing tagger in tag' ' + git index-pack --fsck-objects tag-test-${pack1}.pack 2>err && + grep "^warning:.* expected .tagger. line" err +' + test_done diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index ec9ba9bf6e..0680dec808 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -482,24 +482,24 @@ test_expect_success 'set up tests of missing reference' ' test_expect_success 'test lonely missing ref' ' ( cd client && - test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy - ) >/dev/null 2>error-m && + test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy 2>../error-m + ) && test_i18ncmp expect-error error-m ' test_expect_success 'test missing ref after existing' ' ( cd client && - test_must_fail git fetch-pack --no-progress .. refs/heads/A refs/heads/xyzzy - ) >/dev/null 2>error-em && + test_must_fail git fetch-pack --no-progress .. refs/heads/A refs/heads/xyzzy 2>../error-em + ) && test_i18ncmp expect-error error-em ' test_expect_success 'test missing ref before existing' ' ( cd client && - test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy refs/heads/A - ) >/dev/null 2>error-me && + test_must_fail git fetch-pack --no-progress .. refs/heads/xyzzy refs/heads/A 2>../error-me + ) && test_i18ncmp expect-error error-me ' diff --git a/t/t5526-fetch-submodules.sh b/t/t5526-fetch-submodules.sh index 74486c73b0..9cc4b569c0 100755 --- a/t/t5526-fetch-submodules.sh +++ b/t/t5526-fetch-submodules.sh @@ -85,7 +85,7 @@ test_expect_success "fetch --recurse-submodules -j2 has the same output behaviou add_upstream_commit && ( cd downstream && - GIT_TRACE=$(pwd)/../trace.out git fetch --recurse-submodules -j2 2>../actual.err + GIT_TRACE="$TRASH_DIRECTORY/trace.out" git fetch --recurse-submodules -j2 2>../actual.err ) && test_must_be_empty actual.out && test_i18ncmp expect.err actual.err && diff --git a/t/t5536-fetch-conflicts.sh b/t/t5536-fetch-conflicts.sh index 644736b8a3..91f28c2f78 100755 --- a/t/t5536-fetch-conflicts.sh +++ b/t/t5536-fetch-conflicts.sh @@ -18,14 +18,6 @@ setup_repository () { ) } -verify_stderr () { - cat >expected && - # We're not interested in the error - # "fatal: The remote end hung up unexpectedly": - test_i18ngrep -E '^(fatal|warning):' error | grep -v 'hung up' >actual | sort && - test_i18ncmp expected actual -} - test_expect_success 'setup' ' git commit --allow-empty -m "Initial" && git branch branch1 && @@ -48,9 +40,7 @@ test_expect_success 'fetch conflict: config vs. config' ' "+refs/heads/branch2:refs/remotes/origin/branch1" && ( cd ccc && test_must_fail git fetch origin 2>error && - verify_stderr <<-\EOF - fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1 - EOF + test_i18ngrep "fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1" error ) ' @@ -77,9 +67,7 @@ test_expect_success 'fetch conflict: arg vs. arg' ' test_must_fail git fetch origin \ refs/heads/*:refs/remotes/origin/* \ refs/heads/branch2:refs/remotes/origin/branch1 2>error && - verify_stderr <<-\EOF - fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1 - EOF + test_i18ngrep "fatal: Cannot fetch both refs/heads/branch1 and refs/heads/branch2 to refs/remotes/origin/branch1" error ) ' @@ -90,10 +78,8 @@ test_expect_success 'fetch conflict: criss-cross args' ' git fetch origin \ refs/heads/branch1:refs/remotes/origin/branch2 \ refs/heads/branch2:refs/remotes/origin/branch1 2>error && - verify_stderr <<-\EOF - warning: refs/remotes/origin/branch1 usually tracks refs/heads/branch1, not refs/heads/branch2 - warning: refs/remotes/origin/branch2 usually tracks refs/heads/branch2, not refs/heads/branch1 - EOF + test_i18ngrep "warning: refs/remotes/origin/branch1 usually tracks refs/heads/branch1, not refs/heads/branch2" error && + test_i18ngrep "warning: refs/remotes/origin/branch2 usually tracks refs/heads/branch2, not refs/heads/branch1" error ) ' diff --git a/t/t5570-git-daemon.sh b/t/t5570-git-daemon.sh index 755b05a8ae..0d4c52016b 100755 --- a/t/t5570-git-daemon.sh +++ b/t/t5570-git-daemon.sh @@ -50,7 +50,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 && + (cd clone && git fetch 2>../stderr) && ! test -s stderr ' diff --git a/t/t5616-partial-clone.sh b/t/t5616-partial-clone.sh index 29d8631184..cee5565367 100755 --- a/t/t5616-partial-clone.sh +++ b/t/t5616-partial-clone.sh @@ -143,4 +143,15 @@ test_expect_success 'manual prefetch of missing objects' ' test_line_count = 0 observed.oids ' +test_expect_success 'partial clone with transfer.fsckobjects=1 uses index-pack --fsck-objects' ' + git init src && + test_commit -C src x && + test_config -C src uploadpack.allowfilter 1 && + test_config -C src uploadpack.allowanysha1inwant 1 && + + GIT_TRACE="$(pwd)/trace" git -c transfer.fsckobjects=1 \ + clone --filter="blob:none" "file://$(pwd)/src" dst && + grep "git index-pack.*--fsck-objects" trace +' + test_done diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh new file mode 100755 index 0000000000..2e28f2908d --- /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/t7006-pager.sh b/t/t7006-pager.sh index f5f46a95b4..7541ba5edb 100755 --- a/t/t7006-pager.sh +++ b/t/t7006-pager.sh @@ -110,13 +110,6 @@ test_expect_success TTY 'configuration can disable pager' ' ! test -e paginated.out ' -test_expect_success TTY 'git config uses a pager if configured to' ' - rm -f paginated.out && - test_config pager.config true && - test_terminal git config --list && - test -e paginated.out -' - test_expect_success TTY 'configuration can enable pager (from subdir)' ' rm -f paginated.out && mkdir -p subdir && @@ -252,6 +245,48 @@ test_expect_success TTY 'git branch --set-upstream-to ignores pager.branch' ' ! test -e paginated.out ' +test_expect_success TTY 'git config ignores pager.config when setting' ' + rm -f paginated.out && + test_terminal git -c pager.config config foo.bar bar && + ! test -e paginated.out +' + +test_expect_success TTY 'git config --edit ignores pager.config' ' + rm -f paginated.out editor.used && + write_script editor <<-\EOF && + touch editor.used + EOF + EDITOR=./editor test_terminal git -c pager.config config --edit && + ! test -e paginated.out && + test -e editor.used +' + +test_expect_success TTY 'git config --get ignores pager.config' ' + rm -f paginated.out && + test_terminal git -c pager.config config --get foo.bar && + ! test -e paginated.out +' + +test_expect_success TTY 'git config --get-urlmatch defaults to paging' ' + rm -f paginated.out && + test_terminal git -c http."https://foo.com/".bar=foo \ + config --get-urlmatch http https://foo.com && + test -e paginated.out +' + +test_expect_success TTY 'git config --get-all respects pager.config' ' + rm -f paginated.out && + test_terminal git -c pager.config=false config --get-all foo.bar && + ! test -e paginated.out +' + +test_expect_success TTY 'git config --list defaults to paging' ' + rm -f paginated.out && + test_terminal git config --list && + test -e paginated.out +' + + # A colored commit log will begin with an appropriate ANSI escape # for the first color; the text "commit" comes later. colorful() { 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/t9001-send-email.sh b/t/t9001-send-email.sh index 19601fb546..e80eacbb1b 100755 --- a/t/t9001-send-email.sh +++ b/t/t9001-send-email.sh @@ -224,6 +224,7 @@ Message-Id: MESSAGE-ID-STRING 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> Result: OK EOF @@ -316,6 +317,7 @@ test_expect_success $PREREQ 'Show all headers' ' --dry-run \ --suppress-cc=sob \ --from="Example <from@example.com>" \ + --reply-to="Reply <reply@example.com>" \ --to=to@example.com \ --cc=cc@example.com \ --bcc=bcc@example.com \ diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh index c30660d606..06742748e9 100755 --- a/t/t9400-git-cvsserver-server.sh +++ b/t/t9400-git-cvsserver-server.sh @@ -447,12 +447,10 @@ test_expect_success 'cvs update (-p)' ' git push gitcvs.git >/dev/null && cd cvswork && GIT_CONFIG="$git_config" cvs update && - rm -f failures && for i in merge no-lf empty really-empty; do - GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out - test_cmp $i.out ../$i >>failures 2>&1 - done && - test -z "$(cat failures)" + GIT_CONFIG="$git_config" cvs update -p "$i" >$i.out && + test_cmp $i.out ../$i || return 1 + done ' cd "$WORKDIR" diff --git a/t/t9402-git-cvsserver-refs.sh b/t/t9402-git-cvsserver-refs.sh index 6d2d3c8739..cf31ace667 100755 --- a/t/t9402-git-cvsserver-refs.sh +++ b/t/t9402-git-cvsserver-refs.sh @@ -455,20 +455,20 @@ test_expect_success 'cvs up -r $(git rev-parse v1)' ' ' test_expect_success 'cvs diff -r v1 -u' ' - ( cd cvswork && cvs -f diff -r v1 -u ) >cvsDiff.out 2>cvs.log && + ( cd cvswork && cvs -f diff -r v1 -u >../cvsDiff.out 2>../cvs.log ) && test_must_be_empty cvsDiff.out && test_must_be_empty cvs.log ' test_expect_success 'cvs diff -N -r v2 -u' ' - ( cd cvswork && ! cvs -f diff -N -r v2 -u ) >cvsDiff.out 2>cvs.log && + ( cd cvswork && ! cvs -f diff -N -r v2 -u >../cvsDiff.out 2>../cvs.log ) && test_must_be_empty cvs.log && test -s cvsDiff.out && check_diff cvsDiff.out v2 v1 >check_diff.out 2>&1 ' test_expect_success 'cvs diff -N -r v2 -r v1.2' ' - ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u ) >cvsDiff.out 2>cvs.log && + ( cd cvswork && ! cvs -f diff -N -r v2 -r v1.2 -u >../cvsDiff.out 2>../cvs.log ) && test_must_be_empty cvs.log && test -s cvsDiff.out && check_diff cvsDiff.out v2 v1.2 >check_diff.out 2>&1 @@ -487,7 +487,7 @@ test_expect_success 'apply early [cvswork3] diff to b3' ' ' test_expect_success 'check [cvswork3] diff' ' - ( cd cvswork3 && ! cvs -f diff -N -u ) >"$WORKDIR/cvsDiff.out" 2>cvs.log && + ( cd cvswork3 && ! cvs -f diff -N -u >"$WORKDIR/cvsDiff.out" 2>../cvs.log ) && test_must_be_empty cvs.log && test -s cvsDiff.out && test $(grep Index: cvsDiff.out | wc -l) = 3 && diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh index fc614dcbfa..b7f5b1e632 100755 --- a/t/t9902-completion.sh +++ b/t/t9902-completion.sh @@ -1237,17 +1237,19 @@ test_expect_success 'double dash "git" itself' ' test_expect_success 'double dash "git checkout"' ' test_completion "git checkout --" <<-\EOF --quiet Z + --detach Z + --track Z + --orphan=Z --ours Z --theirs Z - --track Z - --no-track Z --merge Z - --conflict= - --orphan Z + --conflict=Z --patch Z - --detach Z --ignore-skip-worktree-bits Z + --ignore-other-worktrees Z --recurse-submodules Z + --progress Z + --no-track Z --no-recurse-submodules Z EOF ' @@ -1495,4 +1497,35 @@ do ' done +test_expect_success 'sourcing the completion script clears cached commands' ' + __git_compute_all_commands && + verbose test -n "$__git_all_commands" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + 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" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + verbose test -z "$__git_merge_strategies" +' + +test_expect_success 'sourcing the completion script clears cached --options' ' + __gitcomp_builtin checkout && + verbose test -n "$__gitcomp_builtin_checkout" && + __gitcomp_builtin notes_edit && + verbose test -n "$__gitcomp_builtin_notes_edit" && + . "$GIT_BUILD_DIR/contrib/completion/git-completion.bash" && + verbose test -z "$__gitcomp_builtin_checkout" && + verbose test -z "$__gitcomp_builtin_notes_edit" +' + test_done diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh index 97c9b32c2e..8f5c811dd7 100755 --- a/t/t9903-bash-prompt.sh +++ b/t/t9903-bash-prompt.sh @@ -735,22 +735,12 @@ test_expect_success 'prompt - hide if pwd ignored - env var set, config unset, p test_cmp expected "$actual" ' -test_expect_success 'prompt - hide if pwd ignored - inside gitdir (stdout)' ' +test_expect_success 'prompt - hide if pwd ignored - inside gitdir' ' printf " (GIT_DIR!)" >expected && ( GIT_PS1_HIDE_IF_PWD_IGNORED=y && cd .git && - __git_ps1 >"$actual" 2>/dev/null - ) && - test_cmp expected "$actual" -' - -test_expect_success 'prompt - hide if pwd ignored - inside gitdir (stderr)' ' - printf "" >expected && - ( - GIT_PS1_HIDE_IF_PWD_IGNORED=y && - cd .git && - __git_ps1 >/dev/null 2>"$actual" + __git_ps1 >"$actual" ) && test_cmp expected "$actual" ' diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 6e342804f9..b895366fee 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -629,7 +629,7 @@ test_must_fail () { _test_ok= ;; esac - "$@" + "$@" 2>&7 exit_code=$? if test $exit_code -eq 0 && ! list_contains "$_test_ok" success then @@ -652,7 +652,7 @@ test_must_fail () { return 1 fi return 0 -} +} 7>&2 2>&4 # Similar to test_must_fail, but tolerates success, too. This is # meant to be used in contexts like: @@ -668,8 +668,8 @@ test_must_fail () { # Accepts the same options as test_must_fail. test_might_fail () { - test_must_fail ok=success "$@" -} + test_must_fail ok=success "$@" 2>&7 +} 7>&2 2>&4 # Similar to test_must_fail and test_might_fail, but check that a # given command exited with a given exit code. Meant to be used as: @@ -681,7 +681,7 @@ test_might_fail () { test_expect_code () { want_code=$1 shift - "$@" + "$@" 2>&7 exit_code=$? if test $exit_code = $want_code then @@ -690,7 +690,7 @@ test_expect_code () { echo >&4 "test_expect_code: command exited with $exit_code, we wanted $want_code $*" return 1 -} +} 7>&2 2>&4 # test_cmp is a helper function to compare actual and expected output. # You can use it like: @@ -896,8 +896,8 @@ test_write_lines () { } perl () { - command "$PERL_PATH" "$@" -} + command "$PERL_PATH" "$@" 2>&7 +} 7>&2 2>&4 # Is the value one of the various ways to spell a boolean true/false? test_normalize_bool () { @@ -1037,13 +1037,13 @@ test_env () { shift ;; *) - "$@" + "$@" 2>&7 exit ;; esac done ) -} +} 7>&2 2>&4 # Returns true if the numeric exit code in "$2" represents the expected signal # in "$1". Signals should be given numerically. @@ -1085,9 +1085,9 @@ nongit () { GIT_CEILING_DIRECTORIES=$(pwd) && export GIT_CEILING_DIRECTORIES && cd non-repo && - "$@" + "$@" 2>&7 ) -} +} 7>&2 2>&4 # convert stdin to pktline representation; note that empty input becomes an # empty packet, not a flush packet (for that you can just print 0000 yourself). diff --git a/t/test-lib.sh b/t/test-lib.sh index 9535d2e0a9..7740d511d2 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -264,7 +264,24 @@ do GIT_TEST_CHAIN_LINT=0 shift ;; -x) - trace=t + # Some test scripts can't be reliably traced with '-x', + # unless the test is run with a Bash version supporting + # BASH_XTRACEFD (introduced in Bash v4.1). Check whether + # this test is marked as such, and ignore '-x' if it + # isn't executed with a suitable Bash version. + if test -z "$test_untraceable" || { + test -n "$BASH_VERSION" && { + test ${BASH_VERSINFO[0]} -gt 4 || { + test ${BASH_VERSINFO[0]} -eq 4 && + test ${BASH_VERSINFO[1]} -ge 1 + } + } + } + then + trace=t + else + echo >&2 "warning: ignoring -x; '$0' is untraceable without BASH_XTRACEFD" + fi shift ;; --verbose-log) verbose_log=t |