From 919df3195553af05c884d51588d12134d8dfab2a Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Mon, 10 Aug 2020 22:29:09 +0000 Subject: Collect merge-related tests to t64xx The tests for the merge machinery are spread over several places. Collect them into t64xx for simplicity. Some notes: t60[234]*.sh: Merge tests started in t602*, overgrew bisect and remote tracking tests in t6030, t6040, and t6041, and nearly overtook replace tests in t6050. This made picking out relevant tests that I wanted to run in a tighter loop slightly more annoying for years. t303*.sh: These started out as tests for the 'merge-recursive' toplevel command, but did not restrict to that and had lots of overlap with the underlying merge machinery. t7405, t7613: submodule-specific merge logic started out in submodule.c but was moved to merge-recursive.c in commit 18cfc08866 ("submodule.c: move submodule merging to merge-recursive.c", 2018-05-15). Since these tests are about the logic found in the merge machinery, moving these tests to be with the merge tests makes sense. t7607, t7609: Having tests spread all over the place makes it more likely that additional tests related to a certain piece of logic grow in all those other places. Much like t303*.sh, these two tests were about the underlying merge machinery rather than outer levels. Tests that were NOT moved: t76[01]*.sh: Other than the four tests mentioned above, the remaining tests in t76[01]*.sh are related to non-recursive merge strategies, parameter parsing, and other stuff associated with the highlevel builtin/merge.c rather than the recursive merge machinery. t3[45]*.sh: The rebase testcases in t34*.sh also test the merge logic pretty heavily; sometimes changes I make only trigger failures in the rebase tests. The rebase tests are already nicely coupled together, though, and I didn't want to mess that up. Similar comments apply for the cherry-pick tests in t35*.sh. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh deleted file mode 100755 index d48d211..0000000 --- a/t/t3030-merge-recursive.sh +++ /dev/null @@ -1,770 +0,0 @@ -#!/bin/sh - -test_description='merge-recursive backend test' - -. ./test-lib.sh - -test_expect_success 'setup 1' ' - - echo hello >a && - o0=$(git hash-object a) && - cp a b && - cp a c && - mkdir d && - cp a d/e && - - test_tick && - git add a b c d/e && - git commit -m initial && - c0=$(git rev-parse --verify HEAD) && - git branch side && - git branch df-1 && - git branch df-2 && - git branch df-3 && - git branch remove && - git branch submod && - git branch copy && - git branch rename && - git branch rename-ln && - - echo hello >>a && - cp a d/e && - o1=$(git hash-object a) && - - git add a d/e && - - test_tick && - git commit -m "master modifies a and d/e" && - c1=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o1 a" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o1 d/e" && - echo "100644 $o1 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o1 0 d/e" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'setup 2' ' - - rm -rf [abcd] && - git checkout side && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o0 a" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 $o0 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" - ) >expected && - test_cmp expected actual && - - echo goodbye >>a && - o2=$(git hash-object a) && - - git add a && - - test_tick && - git commit -m "side modifies a" && - c2=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o2 a" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 $o2 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'setup 3' ' - - rm -rf [abcd] && - git checkout df-1 && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o0 a" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 $o0 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" - ) >expected && - test_cmp expected actual && - - rm -f b && mkdir b && echo df-1 >b/c && git add b/c && - o3=$(git hash-object b/c) && - - test_tick && - git commit -m "df-1 makes b/c" && - c3=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o0 a" && - echo "100644 blob $o3 b/c" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 $o0 0 a" && - echo "100644 $o3 0 b/c" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'setup 4' ' - - rm -rf [abcd] && - git checkout df-2 && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o0 a" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 $o0 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" - ) >expected && - test_cmp expected actual && - - rm -f a && mkdir a && echo df-2 >a/c && git add a/c && - o4=$(git hash-object a/c) && - - test_tick && - git commit -m "df-2 makes a/c" && - c4=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o4 a/c" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 $o4 0 a/c" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'setup 5' ' - - rm -rf [abcd] && - git checkout remove && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o0 a" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 $o0 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" - ) >expected && - test_cmp expected actual && - - rm -f b && - echo remove-conflict >a && - - git add a && - git rm b && - o5=$(git hash-object a) && - - test_tick && - git commit -m "remove removes b and modifies a" && - c5=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o5 a" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 $o5 0 a" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" - ) >expected && - test_cmp expected actual - -' - -test_expect_success 'setup 6' ' - - rm -rf [abcd] && - git checkout df-3 && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o0 a" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 $o0 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" - ) >expected && - test_cmp expected actual && - - rm -fr d && echo df-3 >d && git add d && - o6=$(git hash-object d) && - - test_tick && - git commit -m "df-3 makes d" && - c6=$(git rev-parse --verify HEAD) && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o0 a" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o6 d" && - echo "100644 $o0 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o6 0 d" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'setup 7' ' - - git checkout submod && - git rm d/e && - test_tick && - git commit -m "remove d/e" && - git update-index --add --cacheinfo 160000 $c1 d && - test_tick && - git commit -m "make d/ a submodule" -' - -test_expect_success 'setup 8' ' - git checkout rename && - git mv a e && - git add e && - test_tick && - git commit -m "rename a->e" && - c7=$(git rev-parse --verify HEAD) && - git checkout rename-ln && - git mv a e && - test_ln_s_add e a && - test_tick && - git commit -m "rename a->e, symlink a->e" && - oln=$(printf e | git hash-object --stdin) -' - -test_expect_success 'setup 9' ' - git checkout copy && - cp a e && - git add e && - test_tick && - git commit -m "copy a->e" -' - -test_expect_success 'merge-recursive simple' ' - - rm -fr [abcd] && - git checkout -f "$c2" && - - test_expect_code 1 git merge-recursive "$c0" -- "$c2" "$c1" -' - -test_expect_success 'merge-recursive result' ' - - git ls-files -s >actual && - ( - echo "100644 $o0 1 a" && - echo "100644 $o2 2 a" && - echo "100644 $o1 3 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o1 0 d/e" - ) >expected && - test_cmp expected actual - -' - -test_expect_success 'fail if the index has unresolved entries' ' - - rm -fr [abcd] && - git checkout -f "$c1" && - - test_must_fail git merge "$c5" && - test_must_fail git merge "$c5" 2> out && - test_i18ngrep "not possible because you have unmerged files" out && - git add -u && - test_must_fail git merge "$c5" 2> out && - test_i18ngrep "You have not concluded your merge" out && - rm -f .git/MERGE_HEAD && - test_must_fail git merge "$c5" 2> out && - test_i18ngrep "Your local changes to the following files would be overwritten by merge:" out -' - -test_expect_success 'merge-recursive remove conflict' ' - - rm -fr [abcd] && - git checkout -f "$c1" && - - test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c5" -' - -test_expect_success 'merge-recursive remove conflict' ' - - git ls-files -s >actual && - ( - echo "100644 $o0 1 a" && - echo "100644 $o1 2 a" && - echo "100644 $o5 3 a" && - echo "100644 $o0 0 c" && - echo "100644 $o1 0 d/e" - ) >expected && - test_cmp expected actual - -' - -test_expect_success 'merge-recursive d/f simple' ' - rm -fr [abcd] && - git reset --hard && - git checkout -f "$c1" && - - git merge-recursive "$c0" -- "$c1" "$c3" -' - -test_expect_success 'merge-recursive result' ' - - git ls-files -s >actual && - ( - echo "100644 $o1 0 a" && - echo "100644 $o3 0 b/c" && - echo "100644 $o0 0 c" && - echo "100644 $o1 0 d/e" - ) >expected && - test_cmp expected actual - -' - -test_expect_success 'merge-recursive d/f conflict' ' - - rm -fr [abcd] && - git reset --hard && - git checkout -f "$c1" && - - test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c4" -' - -test_expect_success 'merge-recursive d/f conflict result' ' - - git ls-files -s >actual && - ( - echo "100644 $o0 1 a" && - echo "100644 $o1 2 a" && - echo "100644 $o4 0 a/c" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o1 0 d/e" - ) >expected && - test_cmp expected actual - -' - -test_expect_success 'merge-recursive d/f conflict the other way' ' - - rm -fr [abcd] && - git reset --hard && - git checkout -f "$c4" && - - test_expect_code 1 git merge-recursive "$c0" -- "$c4" "$c1" -' - -test_expect_success 'merge-recursive d/f conflict result the other way' ' - - git ls-files -s >actual && - ( - echo "100644 $o0 1 a" && - echo "100644 $o1 3 a" && - echo "100644 $o4 0 a/c" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o1 0 d/e" - ) >expected && - test_cmp expected actual - -' - -test_expect_success 'merge-recursive d/f conflict' ' - - rm -fr [abcd] && - git reset --hard && - git checkout -f "$c1" && - - test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c6" -' - -test_expect_success 'merge-recursive d/f conflict result' ' - - git ls-files -s >actual && - ( - echo "100644 $o1 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o6 3 d" && - echo "100644 $o0 1 d/e" && - echo "100644 $o1 2 d/e" - ) >expected && - test_cmp expected actual - -' - -test_expect_success 'merge-recursive d/f conflict' ' - - rm -fr [abcd] && - git reset --hard && - git checkout -f "$c6" && - - test_expect_code 1 git merge-recursive "$c0" -- "$c6" "$c1" -' - -test_expect_success 'merge-recursive d/f conflict result' ' - - git ls-files -s >actual && - ( - echo "100644 $o1 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o6 2 d" && - echo "100644 $o0 1 d/e" && - echo "100644 $o1 3 d/e" - ) >expected && - test_cmp expected actual - -' - -test_expect_success SYMLINKS 'dir in working tree with symlink ancestor does not produce d/f conflict' ' - git init sym && - ( - cd sym && - ln -s . foo && - mkdir bar && - >bar/file && - git add foo bar/file && - git commit -m "foo symlink" && - - git checkout -b branch1 && - git commit --allow-empty -m "empty commit" && - - git checkout master && - git rm foo && - mkdir foo && - >foo/bar && - git add foo/bar && - git commit -m "replace foo symlink with real foo dir and foo/bar file" && - - git checkout branch1 && - - git cherry-pick master && - test_path_is_dir foo && - test_path_is_file foo/bar - ) -' - -test_expect_success 'reset and 3-way merge' ' - - git reset --hard "$c2" && - git read-tree -m "$c0" "$c2" "$c1" - -' - -test_expect_success 'reset and bind merge' ' - - git reset --hard master && - git read-tree --prefix=M/ master && - git ls-files -s >actual && - ( - echo "100644 $o1 0 M/a" && - echo "100644 $o0 0 M/b" && - echo "100644 $o0 0 M/c" && - echo "100644 $o1 0 M/d/e" && - echo "100644 $o1 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o1 0 d/e" - ) >expected && - test_cmp expected actual && - - git read-tree --prefix=a1/ master && - git ls-files -s >actual && - ( - echo "100644 $o1 0 M/a" && - echo "100644 $o0 0 M/b" && - echo "100644 $o0 0 M/c" && - echo "100644 $o1 0 M/d/e" && - echo "100644 $o1 0 a" && - echo "100644 $o1 0 a1/a" && - echo "100644 $o0 0 a1/b" && - echo "100644 $o0 0 a1/c" && - echo "100644 $o1 0 a1/d/e" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o1 0 d/e" - ) >expected && - test_cmp expected actual && - - git read-tree --prefix=z/ master && - git ls-files -s >actual && - ( - echo "100644 $o1 0 M/a" && - echo "100644 $o0 0 M/b" && - echo "100644 $o0 0 M/c" && - echo "100644 $o1 0 M/d/e" && - echo "100644 $o1 0 a" && - echo "100644 $o1 0 a1/a" && - echo "100644 $o0 0 a1/b" && - echo "100644 $o0 0 a1/c" && - echo "100644 $o1 0 a1/d/e" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o1 0 d/e" && - echo "100644 $o1 0 z/a" && - echo "100644 $o0 0 z/b" && - echo "100644 $o0 0 z/c" && - echo "100644 $o1 0 z/d/e" - ) >expected && - test_cmp expected actual - -' - -test_expect_success 'merge-recursive w/ empty work tree - ours has rename' ' - ( - GIT_WORK_TREE="$PWD/ours-has-rename-work" && - export GIT_WORK_TREE && - GIT_INDEX_FILE="$PWD/ours-has-rename-index" && - export GIT_INDEX_FILE && - mkdir "$GIT_WORK_TREE" && - 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_expect_success 'merge-recursive w/ empty work tree - theirs has rename' ' - ( - GIT_WORK_TREE="$PWD/theirs-has-rename-work" && - export GIT_WORK_TREE && - GIT_INDEX_FILE="$PWD/theirs-has-rename-index" && - export GIT_INDEX_FILE && - mkdir "$GIT_WORK_TREE" && - 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_expect_success 'merge removes empty directories' ' - - git reset --hard master && - git checkout -b rm && - git rm d/e && - git commit -mremoved-d/e && - git checkout master && - git merge -s recursive rm && - test_path_is_missing d -' - -test_expect_success 'merge-recursive simple w/submodule' ' - - git checkout submod && - git merge remove -' - -test_expect_success 'merge-recursive simple w/submodule result' ' - - git ls-files -s >actual && - ( - echo "100644 $o5 0 a" && - echo "100644 $o0 0 c" && - echo "160000 $c1 0 d" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'merge-recursive copy vs. rename' ' - git checkout -f copy && - git merge rename && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 blob $o0 e" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" && - echo "100644 $o0 0 e" - ) >expected && - test_cmp expected actual -' - -test_expect_failure 'merge-recursive rename vs. rename/symlink' ' - - git checkout -f rename && - git merge rename-ln && - ( git ls-tree -r HEAD && git ls-files -s ) >actual && - ( - echo "120000 blob $oln a" && - echo "100644 blob $o0 b" && - echo "100644 blob $o0 c" && - echo "100644 blob $o0 d/e" && - echo "100644 blob $o0 e" && - echo "120000 $oln 0 a" && - echo "100644 $o0 0 b" && - echo "100644 $o0 0 c" && - echo "100644 $o0 0 d/e" && - echo "100644 $o0 0 e" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'merging with triple rename across D/F conflict' ' - git reset --hard HEAD && - git checkout -b main && - git rm -rf . && - - echo "just a file" >sub1 && - mkdir -p sub2 && - echo content1 >sub2/file1 && - echo content2 >sub2/file2 && - echo content3 >sub2/file3 && - mkdir simple && - echo base >simple/bar && - git add -A && - test_tick && - git commit -m base && - - git checkout -b other && - echo more >>simple/bar && - test_tick && - git commit -a -m changesimplefile && - - git checkout main && - git rm sub1 && - git mv sub2 sub1 && - test_tick && - git commit -m changefiletodir && - - test_tick && - git merge other -' - -test_expect_success 'merge-recursive remembers the names of all base trees' ' - git reset --hard HEAD && - - # make the index match $c1 so that merge-recursive below does not - # fail early - git diff --binary HEAD $c1 -- | git apply --cached && - - # more trees than static slots used by oid_to_hex() - for commit in $c0 $c2 $c4 $c5 $c6 $c7 - do - git rev-parse "$commit^{tree}" - done >trees && - - # ignore the return code; it only fails because the input is weird... - test_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out && - - # ...but make sure it fails in the expected way - test_i18ngrep CONFLICT.*rename/rename out && - - # merge-recursive prints in reverse order, but we do not care - sort expect && - sed -n "s/^virtual //p" out | sort >actual && - test_cmp expect actual -' - -test_expect_success 'merge-recursive internal merge resolves to the sameness' ' - git reset --hard HEAD && - - # We are going to create a history leading to two criss-cross - # branches A and B. The common ancestor at the bottom, O0, - # has two child commits O1 and O2, both of which will be merge - # base between A and B, like so: - # - # O1---A - # / \ / - # O0 . - # \ / \ - # O2---B - # - # The recently added "check to see if the index is different from - # the tree into which something else is getting merged" check must - # NOT kick in when an inner merge between O1 and O2 is made. Both - # O1 and O2 happen to have the same tree as O0 in this test to - # trigger the bug---whether the inner merge is made by merging O2 - # into O1 or O1 into O2, their common ancestor O0 and the branch - # being merged have the same tree. We should not trigger the "is - # the index dirty?" check in this case. - - echo "zero" >file && - git add file && - test_tick && - git commit -m "O0" && - O0=$(git rev-parse HEAD) && - - test_tick && - git commit --allow-empty -m "O1" && - O1=$(git rev-parse HEAD) && - - git reset --hard $O0 && - test_tick && - git commit --allow-empty -m "O2" && - O2=$(git rev-parse HEAD) && - - test_tick && - git merge -s ours $O1 && - B=$(git rev-parse HEAD) && - - git reset --hard $O1 && - test_tick && - git merge -s ours $O2 && - A=$(git rev-parse HEAD) && - - git merge $B -' - -test_done diff --git a/t/t3031-merge-criscross.sh b/t/t3031-merge-criscross.sh deleted file mode 100755 index 3824756..0000000 --- a/t/t3031-merge-criscross.sh +++ /dev/null @@ -1,95 +0,0 @@ -#!/bin/sh - -test_description='merge-recursive backend test' - -. ./test-lib.sh - -# A <- create some files -# / \ -# B C <- cause rename/delete conflicts between B and C -# / \ -# |\ /| -# | D E | -# | \ / | -# | X | -# | / \ | -# | / \ | -# |/ \| -# F G <- merge E into B, D into C -# \ / -# \ / -# \ / -# H <- recursive merge crashes -# - -# initialize -test_expect_success 'setup repo with criss-cross history' ' - mkdir data && - - # create a bunch of files - n=1 && - while test $n -le 10 - do - echo $n > data/$n && - n=$(($n+1)) || - return 1 - done && - - # check them in - git add data && - git commit -m A && - git branch A && - - # a file in one branch - git checkout -b B A && - git rm data/9 && - git add data && - git commit -m B && - - # with a branch off of it - git branch D && - - # put some commits on D - git checkout D && - echo testD > data/testD && - git add data && - git commit -m D && - - # back up to the top, create another branch and cause - # a rename conflict with the file we deleted earlier - git checkout -b C A && - git mv data/9 data/new-9 && - git add data && - git commit -m C && - - # with a branch off of it - git branch E && - - # put a commit on E - git checkout E && - echo testE > data/testE && - git add data && - git commit -m E && - - # now, merge E into B - git checkout B && - test_must_fail git merge E && - # force-resolve - git add data && - git commit -m F && - git branch F && - - # and merge D into C - git checkout C && - test_must_fail git merge D && - # force-resolve - git add data && - git commit -m G && - git branch G -' - -test_expect_success 'recursive merge between F and G does not cause segfault' ' - git merge F -' - -test_done diff --git a/t/t3032-merge-recursive-space-options.sh b/t/t3032-merge-recursive-space-options.sh deleted file mode 100755 index b56180e..0000000 --- a/t/t3032-merge-recursive-space-options.sh +++ /dev/null @@ -1,207 +0,0 @@ -#!/bin/sh - -test_description='merge-recursive space options - -* [master] Clarify - ! [remote] Remove cruft --- - + [remote] Remove cruft -* [master] Clarify -*+ [remote^] Initial revision -* ok 1: setup -' - -. ./test-lib.sh - -test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b -if test_have_prereq GREP_STRIPS_CR -then - GREP_OPTIONS=-U - export GREP_OPTIONS -fi - -test_expect_success 'setup' ' - conflict_hunks () { - sed $SED_OPTIONS -n -e " - /^<<<>>>/ b - n - b conflict - " "$@" - } && - - cat <<-\EOF >text.txt && - Hope, he says, cherishes the soul of him who lives in - justice and holiness and is the nurse of his age and the - companion of his journey;--hope which is mightiest to sway - the restless soul of man. - - How admirable are his words! And the great blessing of riches, I do - not say to every man, but to a good man, is, that he has had no - occasion to deceive or to defraud others, either intentionally or - unintentionally; and when he departs to the world below he is not in - any apprehension about offerings due to the gods or debts which he owes - to men. Now to this peace of mind the possession of wealth greatly - contributes; and therefore I say, that, setting one thing against - another, of the many advantages which wealth has to give, to a man of - sense this is in my opinion the greatest. - - Well said, Cephalus, I replied; but as concerning justice, what is - it?--to speak the truth and to pay your debts--no more than this? And - even to this are there not exceptions? Suppose that a friend when in - his right mind has deposited arms with me and he asks for them when he - is not in his right mind, ought I to give them back to him? No one - would say that I ought or that I should be right in doing so, any more - than they would say that I ought always to speak the truth to one who - is in his condition. - - You are quite right, he replied. - - But then, I said, speaking the truth and paying your debts is not a - correct definition of justice. - - CEPHALUS - SOCRATES - POLEMARCHUS - - Quite correct, Socrates, if Simonides is to be believed, said - Polemarchus interposing. - - I fear, said Cephalus, that I must go now, for I have to look after the - sacrifices, and I hand over the argument to Polemarchus and the company. - EOF - git add text.txt && - test_tick && - git commit -m "Initial revision" && - - git checkout -b remote && - sed -e " - s/\. /\. /g - s/[?] /? /g - s/ / /g - s/--/---/g - s/but as concerning/but as con cerning/ - /CEPHALUS - SOCRATES - POLEMARCHUS/ d - " text.txt >text.txt+ && - mv text.txt+ text.txt && - git commit -a -m "Remove cruft" && - - git checkout master && - sed -e " - s/\(not in his right mind\),\(.*\)/\1;\2Q/ - s/Quite correct\(.*\)/It is too correct\1Q/ - s/unintentionally/un intentionally/ - /un intentionally/ s/$/Q/ - s/Polemarchus interposing./Polemarchus, interposing.Q/ - /justice and holiness/ s/$/Q/ - /pay your debts/ s/$/Q/ - " text.txt | q_to_cr >text.txt+ && - mv text.txt+ text.txt && - git commit -a -m "Clarify" && - git show-branch --all -' - -test_expect_success 'naive merge fails' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive HEAD^ -- HEAD remote && - test_must_fail git update-index --refresh && - grep "<<<<<<" text.txt -' - -test_expect_success '--ignore-space-change makes merge succeed' ' - git read-tree --reset -u HEAD && - git merge-recursive --ignore-space-change HEAD^ -- HEAD remote -' - -test_expect_success 'naive cherry-pick fails' ' - git read-tree --reset -u HEAD && - test_must_fail git cherry-pick --no-commit remote && - git read-tree --reset -u HEAD && - test_must_fail git cherry-pick remote && - test_must_fail git update-index --refresh && - grep "<<<<<<" text.txt -' - -test_expect_success '-Xignore-space-change makes cherry-pick succeed' ' - git read-tree --reset -u HEAD && - git cherry-pick --no-commit -Xignore-space-change remote -' - -test_expect_success '--ignore-space-change: our w/s-only change wins' ' - q_to_cr <<-\EOF >expected && - justice and holiness and is the nurse of his age and theQ - EOF - - git read-tree --reset -u HEAD && - git merge-recursive --ignore-space-change HEAD^ -- HEAD remote && - grep "justice and holiness" text.txt >actual && - test_cmp expected actual -' - -test_expect_success '--ignore-space-change: their real change wins over w/s' ' - cat <<-\EOF >expected && - it?---to speak the truth and to pay your debts---no more than this? And - EOF - - git read-tree --reset -u HEAD && - git merge-recursive --ignore-space-change HEAD^ -- HEAD remote && - grep "pay your debts" text.txt >actual && - test_cmp expected actual -' - -test_expect_success '--ignore-space-change: does not ignore new spaces' ' - cat <<-\EOF >expected1 && - Well said, Cephalus, I replied; but as con cerning justice, what is - EOF - q_to_cr <<-\EOF >expected2 && - un intentionally; and when he departs to the world below he is not inQ - EOF - - git read-tree --reset -u HEAD && - git merge-recursive --ignore-space-change HEAD^ -- HEAD remote && - grep "Well said" text.txt >actual1 && - grep "when he departs" text.txt >actual2 && - test_cmp expected1 actual1 && - test_cmp expected2 actual2 -' - -test_expect_success '--ignore-all-space drops their new spaces' ' - cat <<-\EOF >expected && - Well said, Cephalus, I replied; but as concerning justice, what is - EOF - - git read-tree --reset -u HEAD && - git merge-recursive --ignore-all-space HEAD^ -- HEAD remote && - grep "Well said" text.txt >actual && - test_cmp expected actual -' - -test_expect_success '--ignore-all-space keeps our new spaces' ' - q_to_cr <<-\EOF >expected && - un intentionally; and when he departs to the world below he is not inQ - EOF - - git read-tree --reset -u HEAD && - git merge-recursive --ignore-all-space HEAD^ -- HEAD remote && - grep "when he departs" text.txt >actual && - test_cmp expected actual -' - -test_expect_success '--ignore-space-at-eol' ' - q_to_cr <<-\EOF >expected && - <<<<<<< HEAD - is not in his right mind; ought I to give them back to him? No oneQ - ======= - is not in his right mind, ought I to give them back to him? No one - >>>>>>> remote - EOF - - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --ignore-space-at-eol \ - HEAD^ -- HEAD remote && - conflict_hunks text.txt >actual && - test_cmp expected actual -' - -test_done diff --git a/t/t3033-merge-toplevel.sh b/t/t3033-merge-toplevel.sh deleted file mode 100755 index e29c284..0000000 --- a/t/t3033-merge-toplevel.sh +++ /dev/null @@ -1,174 +0,0 @@ -#!/bin/sh - -test_description='"git merge" top-level frontend' - -. ./test-lib.sh - -t3033_reset () { - git checkout -B master two && - git branch -f left three && - git branch -f right four -} - -test_expect_success setup ' - test_commit one && - git branch left && - git branch right && - test_commit two && - git checkout left && - test_commit three && - git checkout right && - test_commit four && - git checkout --orphan newroot && - test_commit five && - git checkout master -' - -# Local branches - -test_expect_success 'merge an octopus into void' ' - t3033_reset && - git checkout --orphan test && - git rm -fr . && - test_must_fail git merge left right && - test_must_fail git rev-parse --verify HEAD && - git diff --quiet && - test_must_fail git rev-parse HEAD -' - -test_expect_success 'merge an octopus, fast-forward (ff)' ' - t3033_reset && - git reset --hard one && - git merge left right && - # one is ancestor of three (left) and four (right) - test_must_fail git rev-parse --verify HEAD^3 && - git rev-parse HEAD^1 HEAD^2 | sort >actual && - git rev-parse three four | sort >expect && - test_cmp expect actual -' - -test_expect_success 'merge octopus, non-fast-forward (ff)' ' - t3033_reset && - git reset --hard one && - git merge --no-ff left right && - # one is ancestor of three (left) and four (right) - test_must_fail git rev-parse --verify HEAD^4 && - git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && - git rev-parse one three four | sort >expect && - test_cmp expect actual -' - -test_expect_success 'merge octopus, fast-forward (does not ff)' ' - t3033_reset && - git merge left right && - # two (master) is not an ancestor of three (left) and four (right) - test_must_fail git rev-parse --verify HEAD^4 && - git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && - git rev-parse two three four | sort >expect && - test_cmp expect actual -' - -test_expect_success 'merge octopus, non-fast-forward' ' - t3033_reset && - git merge --no-ff left right && - test_must_fail git rev-parse --verify HEAD^4 && - git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && - git rev-parse two three four | sort >expect && - test_cmp expect actual -' - -# The same set with FETCH_HEAD - -test_expect_success 'merge FETCH_HEAD octopus into void' ' - t3033_reset && - git checkout --orphan test && - git rm -fr . && - git fetch . left right && - test_must_fail git merge FETCH_HEAD && - test_must_fail git rev-parse --verify HEAD && - git diff --quiet && - test_must_fail git rev-parse HEAD -' - -test_expect_success 'merge FETCH_HEAD octopus fast-forward (ff)' ' - t3033_reset && - git reset --hard one && - git fetch . left right && - git merge FETCH_HEAD && - # one is ancestor of three (left) and four (right) - test_must_fail git rev-parse --verify HEAD^3 && - git rev-parse HEAD^1 HEAD^2 | sort >actual && - git rev-parse three four | sort >expect && - test_cmp expect actual -' - -test_expect_success 'merge FETCH_HEAD octopus non-fast-forward (ff)' ' - t3033_reset && - git reset --hard one && - git fetch . left right && - git merge --no-ff FETCH_HEAD && - # one is ancestor of three (left) and four (right) - test_must_fail git rev-parse --verify HEAD^4 && - git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && - git rev-parse one three four | sort >expect && - test_cmp expect actual -' - -test_expect_success 'merge FETCH_HEAD octopus fast-forward (does not ff)' ' - t3033_reset && - git fetch . left right && - git merge FETCH_HEAD && - # two (master) is not an ancestor of three (left) and four (right) - test_must_fail git rev-parse --verify HEAD^4 && - git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && - git rev-parse two three four | sort >expect && - test_cmp expect actual -' - -test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' ' - t3033_reset && - git fetch . left right && - git merge --no-ff FETCH_HEAD && - test_must_fail git rev-parse --verify HEAD^4 && - git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && - git rev-parse two three four | sort >expect && - test_cmp expect actual -' - -# two-project merge -test_expect_success 'refuse two-project merge by default' ' - t3033_reset && - git reset --hard four && - test_must_fail git merge five -' - -test_expect_success 'refuse two-project merge by default, quit before --autostash happens' ' - t3033_reset && - git reset --hard four && - echo change >>one.t && - git diff >expect && - test_must_fail git merge --autostash five 2>err && - test_i18ngrep ! "stash" err && - git diff >actual && - test_cmp expect actual -' - -test_expect_success 'two-project merge with --allow-unrelated-histories' ' - t3033_reset && - git reset --hard four && - git merge --allow-unrelated-histories five && - git diff --exit-code five -' - -test_expect_success 'two-project merge with --allow-unrelated-histories with --autostash' ' - t3033_reset && - git reset --hard four && - echo change >>one.t && - git diff one.t >expect && - git merge --allow-unrelated-histories --autostash five 2>err && - test_i18ngrep "Applied autostash." err && - git diff one.t >actual && - test_cmp expect actual -' - -test_done diff --git a/t/t3034-merge-recursive-rename-options.sh b/t/t3034-merge-recursive-rename-options.sh deleted file mode 100755 index 3d9fae6..0000000 --- a/t/t3034-merge-recursive-rename-options.sh +++ /dev/null @@ -1,330 +0,0 @@ -#!/bin/sh - -test_description='merge-recursive rename options - -Test rename detection by examining rename/delete conflicts. - -* (HEAD -> rename) rename -| * (master) delete -|/ -* base - -git diff --name-status base master -D 0-old -D 1-old -D 2-old -D 3-old - -git diff --name-status -M01 base rename -R025 0-old 0-new -R050 1-old 1-new -R075 2-old 2-new -R100 3-old 3-new - -Actual similarity indices are parsed from diff output. We rely on the fact that -they are rounded down (see, e.g., Documentation/diff-generate-patch.txt, which -mentions this in a different context). -' - -. ./test-lib.sh - -get_expected_stages () { - git checkout rename -- $1-new && - git ls-files --stage $1-new >expected-stages-undetected-$1 && - sed "s/ 0 / 2 /" expected-stages-detected-$1 && - git read-tree -u --reset HEAD -} - -rename_detected () { - git ls-files --stage $1-old $1-new >stages-actual-$1 && - test_cmp expected-stages-detected-$1 stages-actual-$1 -} - -rename_undetected () { - git ls-files --stage $1-old $1-new >stages-actual-$1 && - test_cmp expected-stages-undetected-$1 stages-actual-$1 -} - -check_common () { - git ls-files --stage >stages-actual && - test_line_count = 4 stages-actual -} - -check_threshold_0 () { - check_common && - rename_detected 0 && - rename_detected 1 && - rename_detected 2 && - rename_detected 3 -} - -check_threshold_1 () { - check_common && - rename_undetected 0 && - rename_detected 1 && - rename_detected 2 && - rename_detected 3 -} - -check_threshold_2 () { - check_common && - rename_undetected 0 && - rename_undetected 1 && - rename_detected 2 && - rename_detected 3 -} - -check_exact_renames () { - check_common && - rename_undetected 0 && - rename_undetected 1 && - rename_undetected 2 && - rename_detected 3 -} - -check_no_renames () { - check_common && - rename_undetected 0 && - rename_undetected 1 && - rename_undetected 2 && - rename_undetected 3 -} - -test_expect_success 'setup repo' ' - cat <<-\EOF >3-old && - 33a - 33b - 33c - 33d - EOF - sed s/33/22/ <3-old >2-old && - sed s/33/11/ <3-old >1-old && - sed s/33/00/ <3-old >0-old && - git add [0-3]-old && - git commit -m base && - git rm [0-3]-old && - git commit -m delete && - git checkout -b rename HEAD^ && - cp 3-old 3-new && - sed 1,1s/./x/ <2-old >2-new && - sed 1,2s/./x/ <1-old >1-new && - sed 1,3s/./x/ <0-old >0-new && - git add [0-3]-new && - git rm [0-3]-old && - git commit -m rename && - get_expected_stages 0 && - get_expected_stages 1 && - get_expected_stages 2 && - get_expected_stages 3 && - check_50="false" && - tail="HEAD^ -- HEAD master" -' - -test_expect_success 'setup thresholds' ' - git diff --name-status -M01 HEAD^ HEAD >diff-output && - test_debug "cat diff-output" && - test_line_count = 4 diff-output && - grep "R[0-9][0-9][0-9] \([0-3]\)-old \1-new" diff-output \ - >grep-output && - test_cmp diff-output grep-output && - th0=$(sed -n "s/R\(...\) 0-old 0-new/\1/p" diff-output-0 && - git diff --name-status -M$th1 --diff-filter=R HEAD^ HEAD \ - >diff-output-1 && - git diff --name-status -M$th2 --diff-filter=R HEAD^ HEAD \ - >diff-output-2 && - git diff --name-status -M100% --diff-filter=R HEAD^ HEAD \ - >diff-output-3 && - test_line_count = 4 diff-output-0 && - test_line_count = 3 diff-output-1 && - test_line_count = 2 diff-output-2 && - test_line_count = 1 diff-output-3 -' - -test_expect_success 'default similarity threshold is 50%' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive $tail && - $check_50 -' - -test_expect_success 'low rename threshold' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --find-renames=$th0 $tail && - check_threshold_0 -' - -test_expect_success 'medium rename threshold' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --find-renames=$th1 $tail && - check_threshold_1 -' - -test_expect_success 'high rename threshold' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --find-renames=$th2 $tail && - check_threshold_2 -' - -test_expect_success 'exact renames only' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --find-renames=100% $tail && - check_exact_renames -' - -test_expect_success 'rename threshold is truncated' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --find-renames=200% $tail && - check_exact_renames -' - -test_expect_success 'disabled rename detection' ' - git read-tree --reset -u HEAD && - git merge-recursive --no-renames $tail && - check_no_renames -' - -test_expect_success 'last wins in --find-renames= --find-renames=' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive \ - --find-renames=$th0 --find-renames=$th2 $tail && - check_threshold_2 -' - -test_expect_success '--find-renames resets threshold' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive \ - --find-renames=$th0 --find-renames $tail && - $check_50 -' - -test_expect_success 'last wins in --no-renames --find-renames' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --no-renames --find-renames $tail && - $check_50 -' - -test_expect_success 'last wins in --find-renames --no-renames' ' - git read-tree --reset -u HEAD && - git merge-recursive --find-renames --no-renames $tail && - check_no_renames -' - -test_expect_success 'assumption for further tests: trivial merge succeeds' ' - git read-tree --reset -u HEAD && - git merge-recursive HEAD -- HEAD HEAD && - git diff --quiet --cached && - git merge-recursive --find-renames=$th0 HEAD -- HEAD HEAD && - git diff --quiet --cached && - git merge-recursive --find-renames=$th2 HEAD -- HEAD HEAD && - git diff --quiet --cached && - git merge-recursive --find-renames=100% HEAD -- HEAD HEAD && - git diff --quiet --cached && - git merge-recursive --no-renames HEAD -- HEAD HEAD && - git diff --quiet --cached -' - -test_expect_success '--find-renames rejects negative argument' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --find-renames=-25 \ - HEAD -- HEAD HEAD && - git diff --quiet --cached -' - -test_expect_success '--find-renames rejects non-numbers' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --find-renames=0xf \ - HEAD -- HEAD HEAD && - git diff --quiet --cached -' - -test_expect_success 'rename-threshold= is a synonym for find-renames=' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --rename-threshold=$th0 $tail && - check_threshold_0 -' - -test_expect_success 'last wins in --no-renames --rename-threshold=' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --no-renames --rename-threshold=$th0 $tail && - check_threshold_0 -' - -test_expect_success 'last wins in --rename-threshold= --no-renames' ' - git read-tree --reset -u HEAD && - git merge-recursive --rename-threshold=$th0 --no-renames $tail && - check_no_renames -' - -test_expect_success '--rename-threshold= rejects negative argument' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --rename-threshold=-25 \ - HEAD -- HEAD HEAD && - git diff --quiet --cached -' - -test_expect_success '--rename-threshold= rejects non-numbers' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive --rename-threshold=0xf \ - HEAD -- HEAD HEAD && - git diff --quiet --cached -' - -test_expect_success 'last wins in --rename-threshold= --find-renames=' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive \ - --rename-threshold=$th0 --find-renames=$th2 $tail && - check_threshold_2 -' - -test_expect_success 'last wins in --find-renames= --rename-threshold=' ' - git read-tree --reset -u HEAD && - test_must_fail git merge-recursive \ - --find-renames=$th2 --rename-threshold=$th0 $tail && - check_threshold_0 -' - -test_expect_success 'merge.renames disables rename detection' ' - git read-tree --reset -u HEAD && - git -c merge.renames=false merge-recursive $tail && - check_no_renames -' - -test_expect_success 'merge.renames defaults to diff.renames' ' - git read-tree --reset -u HEAD && - git -c diff.renames=false merge-recursive $tail && - check_no_renames -' - -test_expect_success 'merge.renames overrides diff.renames' ' - git read-tree --reset -u HEAD && - test_must_fail git -c diff.renames=false -c merge.renames=true merge-recursive $tail && - $check_50 -' - -test_done diff --git a/t/t3035-merge-sparse.sh b/t/t3035-merge-sparse.sh deleted file mode 100755 index 74562e1..0000000 --- a/t/t3035-merge-sparse.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/sh - -test_description='merge with sparse files' - -. ./test-lib.sh - -# test_file $filename $content -test_file () { - echo "$2" > "$1" && - git add "$1" -} - -# test_commit_this $message_and_tag -test_commit_this () { - git commit -m "$1" && - git tag "$1" -} - -test_expect_success 'setup' ' - test_file checked-out init && - test_file modify_delete modify_delete_init && - test_commit_this init && - test_file modify_delete modify_delete_theirs && - test_commit_this theirs && - git reset --hard init && - git rm modify_delete && - test_commit_this ours && - git config core.sparseCheckout true && - echo "/checked-out" >.git/info/sparse-checkout && - git reset --hard && - test_must_fail git merge theirs -' - -test_expect_success 'reset --hard works after the conflict' ' - git reset --hard -' - -test_expect_success 'is reset properly' ' - git status --porcelain -- modify_delete >out && - test_must_be_empty out && - test_path_is_missing modify_delete -' - -test_expect_success 'setup: conflict back' ' - test_must_fail git merge theirs -' - -test_expect_success 'Merge abort works after the conflict' ' - git merge --abort -' - -test_expect_success 'is aborted properly' ' - git status --porcelain -- modify_delete >out && - test_must_be_empty out && - test_path_is_missing modify_delete -' - -test_done diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh deleted file mode 100755 index 400a4cd..0000000 --- a/t/t6020-merge-df.sh +++ /dev/null @@ -1,150 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Fredrik Kuivinen -# - -test_description='Test merge with directory/file conflicts' -. ./test-lib.sh - -test_expect_success 'prepare repository' ' - echo Hello >init && - git add init && - git commit -m initial && - - git branch B && - mkdir dir && - echo foo >dir/foo && - git add dir/foo && - git commit -m "File: dir/foo" && - - git checkout B && - echo file dir >dir && - git add dir && - git commit -m "File: dir" -' - -test_expect_success 'Merge with d/f conflicts' ' - test_expect_code 1 git merge -m "merge msg" master -' - -test_expect_success 'F/D conflict' ' - git reset --hard && - git checkout master && - rm .git/index && - - mkdir before && - echo FILE >before/one && - echo FILE >after && - git add . && - git commit -m first && - - rm -f after && - git mv before after && - git commit -m move && - - git checkout -b para HEAD^ && - echo COMPLETELY ANOTHER FILE >another && - git add . && - git commit -m para && - - git merge master -' - -test_expect_success 'setup modify/delete + directory/file conflict' ' - git checkout --orphan modify && - git rm -rf . && - git clean -fdqx && - - printf "a\nb\nc\nd\ne\nf\ng\nh\n" >letters && - git add letters && - git commit -m initial && - - # Throw in letters.txt for sorting order fun - # ("letters.txt" sorts between "letters" and "letters/file") - echo i >>letters && - echo "version 2" >letters.txt && - git add letters letters.txt && - git commit -m modified && - - git checkout -b delete HEAD^ && - git rm letters && - mkdir letters && - >letters/file && - echo "version 1" >letters.txt && - git add letters letters.txt && - git commit -m deleted -' - -test_expect_success 'modify/delete + directory/file conflict' ' - git checkout delete^0 && - test_must_fail git merge modify && - - test 5 -eq $(git ls-files -s | wc -l) && - test 4 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && - - test_path_is_file letters/file && - test_path_is_file letters.txt && - test_path_is_file letters~modify -' - -test_expect_success 'modify/delete + directory/file conflict; other way' ' - git reset --hard && - git clean -f && - git checkout modify^0 && - - test_must_fail git merge delete && - - test 5 -eq $(git ls-files -s | wc -l) && - test 4 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && - - test_path_is_file letters/file && - test_path_is_file letters.txt && - test_path_is_file letters~HEAD -' - -test_expect_success 'Simple merge in repo with interesting pathnames' ' - # Simple lexicographic ordering of files and directories would be: - # foo - # foo/bar - # foo/bar-2 - # foo/bar/baz - # foo/bar-2/baz - # The fact that foo/bar-2 appears between foo/bar and foo/bar/baz - # can trip up some codepaths, and is the point of this test. - test_create_repo name-ordering && - ( - cd name-ordering && - - mkdir -p foo/bar && - mkdir -p foo/bar-2 && - >foo/bar/baz && - >foo/bar-2/baz && - git add . && - git commit -m initial && - - git branch main && - git branch other && - - git checkout other && - echo other >foo/bar-2/baz && - git add -u && - git commit -m other && - - git checkout main && - echo main >foo/bar/baz && - git add -u && - git commit -m main && - - git merge other && - git ls-files -s >out && - test_line_count = 2 out && - git rev-parse :0:foo/bar/baz :0:foo/bar-2/baz >actual && - git rev-parse HEAD~1:foo/bar/baz other:foo/bar-2/baz >expect && - test_cmp expect actual - ) - -' - -test_done diff --git a/t/t6021-merge-criss-cross.sh b/t/t6021-merge-criss-cross.sh deleted file mode 100755 index 9d5e992..0000000 --- a/t/t6021-merge-criss-cross.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Fredrik Kuivinen -# - -# See https://lore.kernel.org/git/Pine.LNX.4.44.0504271254120.4678-100000@wax.eds.org/ for a -# nice description of what this is about. - - -test_description='Test criss-cross merge' -. ./test-lib.sh - -test_expect_success 'prepare repository' ' - test_write_lines 1 2 3 4 5 6 7 8 9 >file && - git add file && - git commit -m "Initial commit" file && - - git branch A && - git branch B && - git checkout A && - - test_write_lines 1 2 3 4 5 6 7 "8 changed in B8, branch A" 9 >file && - git commit -m "B8" file && - git checkout B && - - test_write_lines 1 2 "3 changed in C3, branch B" 4 5 6 7 8 9 >file && - git commit -m "C3" file && - git branch C3 && - - git merge -m "pre E3 merge" A && - - test_write_lines 1 2 "3 changed in E3, branch B. New file size" 4 5 6 7 "8 changed in B8, branch A" 9 >file && - git commit -m "E3" file && - - git checkout A && - git merge -m "pre D8 merge" C3 && - test_write_lines 1 2 "3 changed in C3, branch B" 4 5 6 7 "8 changed in D8, branch A. New file size 2" 9 >file && - - git commit -m D8 file -' - -test_expect_success 'Criss-cross merge' ' - git merge -m "final merge" B -' - -test_expect_success 'Criss-cross merge result' ' - cat <<-\EOF >file-expect && - 1 - 2 - 3 changed in E3, branch B. New file size - 4 - 5 - 6 - 7 - 8 changed in D8, branch A. New file size 2 - 9 - EOF - - test_cmp file-expect file -' - -test_expect_success 'Criss-cross merge fails (-s resolve)' ' - git reset --hard A^ && - test_must_fail git merge -s resolve -m "final merge" B -' - -test_done diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh deleted file mode 100755 index bbbba3d..0000000 --- a/t/t6022-merge-rename.sh +++ /dev/null @@ -1,910 +0,0 @@ -#!/bin/sh - -test_description='Merge-recursive merging renames' -. ./test-lib.sh - -modify () { - sed -e "$1" <"$2" >"$2.x" && - mv "$2.x" "$2" -} - -test_expect_success 'setup' ' - cat >A <<-\EOF && - a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - c cccccccccccccccccccccccccccccccccccccccccccccccc - d dddddddddddddddddddddddddddddddddddddddddddddddd - e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee - f ffffffffffffffffffffffffffffffffffffffffffffffff - g gggggggggggggggggggggggggggggggggggggggggggggggg - h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh - i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii - j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj - k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk - l llllllllllllllllllllllllllllllllllllllllllllllll - m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm - n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn - o oooooooooooooooooooooooooooooooooooooooooooooooo - EOF - - cat >M <<-\EOF && - A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB - C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC - D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD - E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE - F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG - H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH - I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII - J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ - K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK - L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL - M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM - N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN - O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO - EOF - - git add A M && - git commit -m "initial has A and M" && - git branch white && - git branch red && - git branch blue && - git branch yellow && - git branch change && - git branch change+rename && - - sed -e "/^g /s/.*/g : master changes a line/" A+ && - mv A+ A && - git commit -a -m "master updates A" && - - git checkout yellow && - rm -f M && - git commit -a -m "yellow removes M" && - - git checkout white && - sed -e "/^g /s/.*/g : white changes a line/" B && - sed -e "/^G /s/.*/G : colored branch changes a line/" N && - rm -f A M && - git update-index --add --remove A B M N && - git commit -m "white renames A->B, M->N" && - - git checkout red && - sed -e "/^g /s/.*/g : red changes a line/" B && - sed -e "/^G /s/.*/G : colored branch changes a line/" N && - rm -f A M && - git update-index --add --remove A B M N && - git commit -m "red renames A->B, M->N" && - - git checkout blue && - sed -e "/^g /s/.*/g : blue changes a line/" C && - sed -e "/^G /s/.*/G : colored branch changes a line/" N && - rm -f A M && - git update-index --add --remove A C M N && - git commit -m "blue renames A->C, M->N" && - - git checkout change && - sed -e "/^g /s/.*/g : changed line/" A+ && - mv A+ A && - git commit -q -a -m "changed" && - - git checkout change+rename && - sed -e "/^g /s/.*/g : changed line/" B && - rm A && - git update-index --add B && - git commit -q -a -m "changed and renamed" && - - git checkout master -' - -test_expect_success 'pull renaming branch into unrenaming one' \ -' - git show-branch && - test_expect_code 1 git pull . white && - git ls-files -s && - git ls-files -u B >b.stages && - test_line_count = 3 b.stages && - git ls-files -s N >n.stages && - test_line_count = 1 n.stages && - sed -ne "/^g/{ - p - q - }" B | grep master && - git diff --exit-code white N -' - -test_expect_success 'pull renaming branch into another renaming one' \ -' - rm -f B && - git reset --hard && - git checkout red && - test_expect_code 1 git pull . white && - git ls-files -u B >b.stages && - test_line_count = 3 b.stages && - git ls-files -s N >n.stages && - test_line_count = 1 n.stages && - sed -ne "/^g/{ - p - q - }" B | grep red && - git diff --exit-code white N -' - -test_expect_success 'pull unrenaming branch into renaming one' \ -' - git reset --hard && - git show-branch && - test_expect_code 1 git pull . master && - git ls-files -u B >b.stages && - test_line_count = 3 b.stages && - git ls-files -s N >n.stages && - test_line_count = 1 n.stages && - sed -ne "/^g/{ - p - q - }" B | grep red && - git diff --exit-code white N -' - -test_expect_success 'pull conflicting renames' \ -' - git reset --hard && - git show-branch && - test_expect_code 1 git pull . blue && - git ls-files -u A >a.stages && - test_line_count = 1 a.stages && - git ls-files -u B >b.stages && - test_line_count = 1 b.stages && - git ls-files -u C >c.stages && - test_line_count = 1 c.stages && - git ls-files -s N >n.stages && - test_line_count = 1 n.stages && - sed -ne "/^g/{ - p - q - }" B | grep red && - git diff --exit-code white N -' - -test_expect_success 'interference with untracked working tree file' ' - git reset --hard && - git show-branch && - echo >A this file should not matter && - test_expect_code 1 git pull . white && - test_path_is_file A -' - -test_expect_success 'interference with untracked working tree file' ' - git reset --hard && - git checkout white && - git show-branch && - rm -f A && - echo >A this file should not matter && - test_expect_code 1 git pull . red && - test_path_is_file A -' - -test_expect_success 'interference with untracked working tree file' ' - git reset --hard && - rm -f A M && - git checkout -f master && - git tag -f anchor && - git show-branch && - git pull . yellow && - test_path_is_missing M && - git reset --hard anchor -' - -test_expect_success 'updated working tree file should prevent the merge' ' - git reset --hard && - rm -f A M && - git checkout -f master && - git tag -f anchor && - git show-branch && - echo >>M one line addition && - cat M >M.saved && - test_expect_code 128 git pull . yellow && - test_cmp M M.saved && - rm -f M.saved -' - -test_expect_success 'updated working tree file should prevent the merge' ' - git reset --hard && - rm -f A M && - git checkout -f master && - git tag -f anchor && - git show-branch && - echo >>M one line addition && - cat M >M.saved && - git update-index M && - test_expect_code 128 git pull . yellow && - test_cmp M M.saved && - rm -f M.saved -' - -test_expect_success 'interference with untracked working tree file' ' - git reset --hard && - rm -f A M && - git checkout -f yellow && - git tag -f anchor && - git show-branch && - echo >M this file should not matter && - git pull . master && - test_path_is_file M && - ! { - git ls-files -s | - grep M - } && - git reset --hard anchor -' - -test_expect_success 'merge of identical changes in a renamed file' ' - rm -f A M N && - git reset --hard && - git checkout change+rename && - - test-tool chmtime --get -3600 B >old-mtime && - GIT_MERGE_VERBOSITY=3 git merge change >out && - - test-tool chmtime --get B >new-mtime && - test_cmp old-mtime new-mtime && - - git reset --hard HEAD^ && - git checkout change && - - # A will be renamed to B; we check mtimes and file presence - test_path_is_missing B && - test-tool chmtime --get -3600 A >old-mtime && - GIT_MERGE_VERBOSITY=3 git merge change+rename >out && - - test_path_is_missing A && - test-tool chmtime --get B >new-mtime && - test $(cat old-mtime) -lt $(cat new-mtime) -' - -test_expect_success 'setup for rename + d/f conflicts' ' - git reset --hard && - git checkout --orphan dir-in-way && - git rm -rf . && - git clean -fdqx && - - mkdir sub && - mkdir dir && - printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >sub/file && - echo foo >dir/file-in-the-way && - git add -A && - git commit -m "Common commit" && - - echo 11 >>sub/file && - echo more >>dir/file-in-the-way && - git add -u && - git commit -m "Commit to merge, with dir in the way" && - - git checkout -b dir-not-in-way && - git reset --soft HEAD^ && - git rm -rf dir && - git commit -m "Commit to merge, with dir removed" -- dir sub/file && - - git checkout -b renamed-file-has-no-conflicts dir-in-way~1 && - git rm -rf dir && - git rm sub/file && - printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n" >dir && - git add dir && - git commit -m "Independent change" && - - git checkout -b renamed-file-has-conflicts dir-in-way~1 && - git rm -rf dir && - git mv sub/file dir && - echo 12 >>dir && - git add dir && - git commit -m "Conflicting change" -' - -test_expect_success 'Rename+D/F conflict; renamed file merges + dir not in way' ' - git reset --hard && - git checkout -q renamed-file-has-no-conflicts^0 && - - git merge --strategy=recursive dir-not-in-way && - - git diff --quiet && - test_path_is_file dir && - test_write_lines 1 2 3 4 5555 6 7 8 9 10 11 >expected && - test_cmp expected dir -' - -test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' ' - git reset --hard && - rm -rf dir~* && - git checkout -q renamed-file-has-no-conflicts^0 && - test_must_fail git merge --strategy=recursive dir-in-way >output && - - test_i18ngrep "CONFLICT (modify/delete): dir/file-in-the-way" output && - test_i18ngrep "Auto-merging dir" output && - test_i18ngrep "Adding as dir~HEAD instead" output && - - test 3 -eq "$(git ls-files -u | wc -l)" && - test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && - - test_must_fail git diff --quiet && - test_must_fail git diff --cached --quiet && - - test_path_is_file dir/file-in-the-way && - test_path_is_file dir~HEAD && - test_cmp expected dir~HEAD -' - -test_expect_success 'Same as previous, but merged other way' ' - git reset --hard && - rm -rf dir~* && - git checkout -q dir-in-way^0 && - test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors && - - ! grep "error: refusing to lose untracked file at" errors && - test_i18ngrep "CONFLICT (modify/delete): dir/file-in-the-way" output && - test_i18ngrep "Auto-merging dir" output && - test_i18ngrep "Adding as dir~renamed-file-has-no-conflicts instead" output && - - test 3 -eq "$(git ls-files -u | wc -l)" && - test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && - - test_must_fail git diff --quiet && - test_must_fail git diff --cached --quiet && - - test_path_is_file dir/file-in-the-way && - test_path_is_file dir~renamed-file-has-no-conflicts && - test_cmp expected dir~renamed-file-has-no-conflicts -' - -test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' ' - git reset --hard && - rm -rf dir~* && - git checkout -q renamed-file-has-conflicts^0 && - test_must_fail git merge --strategy=recursive dir-not-in-way && - - test 3 -eq "$(git ls-files -u | wc -l)" && - test 3 -eq "$(git ls-files -u dir | wc -l)" && - - test_must_fail git diff --quiet && - test_must_fail git diff --cached --quiet && - - test_path_is_file dir && - cat >expected <<-\EOF && - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - <<<<<<< HEAD:dir - 12 - ======= - 11 - >>>>>>> dir-not-in-way:sub/file - EOF - test_cmp expected dir -' - -test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in the way' ' - modify s/dir-not-in-way/dir-in-way/ expected && - - git reset --hard && - rm -rf dir~* && - git checkout -q renamed-file-has-conflicts^0 && - test_must_fail git merge --strategy=recursive dir-in-way && - - test 5 -eq "$(git ls-files -u | wc -l)" && - test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" && - test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && - - test_must_fail git diff --quiet && - test_must_fail git diff --cached --quiet && - - test_path_is_file dir/file-in-the-way && - test_path_is_file dir~HEAD && - test_cmp expected dir~HEAD -' - -test_expect_success 'Same as previous, but merged other way' ' - git reset --hard && - rm -rf dir~* && - git checkout -q dir-in-way^0 && - test_must_fail git merge --strategy=recursive renamed-file-has-conflicts && - - test 5 -eq "$(git ls-files -u | wc -l)" && - test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" && - test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && - - test_must_fail git diff --quiet && - test_must_fail git diff --cached --quiet && - - test_path_is_file dir/file-in-the-way && - test_path_is_file dir~renamed-file-has-conflicts && - cat >expected <<-\EOF && - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - <<<<<<< HEAD:sub/file - 11 - ======= - 12 - >>>>>>> renamed-file-has-conflicts:dir - EOF - test_cmp expected dir~renamed-file-has-conflicts -' - -test_expect_success 'setup both rename source and destination involved in D/F conflict' ' - git reset --hard && - git checkout --orphan rename-dest && - git rm -rf . && - git clean -fdqx && - - mkdir one && - echo stuff >one/file && - git add -A && - git commit -m "Common commit" && - - git mv one/file destdir && - git commit -m "Renamed to destdir" && - - git checkout -b source-conflict HEAD~1 && - git rm -rf one && - mkdir destdir && - touch one destdir/foo && - git add -A && - git commit -m "Conflicts in the way" -' - -test_expect_success 'both rename source and destination involved in D/F conflict' ' - git reset --hard && - rm -rf dir~* && - git checkout -q rename-dest^0 && - test_must_fail git merge --strategy=recursive source-conflict && - - test 1 -eq "$(git ls-files -u | wc -l)" && - - test_must_fail git diff --quiet && - - test_path_is_file destdir/foo && - test_path_is_file one && - test_path_is_file destdir~HEAD && - test "stuff" = "$(cat destdir~HEAD)" -' - -test_expect_success 'setup pair rename to parent of other (D/F conflicts)' ' - git reset --hard && - git checkout --orphan rename-two && - git rm -rf . && - git clean -fdqx && - - mkdir one && - mkdir two && - echo stuff >one/file && - echo other >two/file && - git add -A && - git commit -m "Common commit" && - - git rm -rf one && - git mv two/file one && - git commit -m "Rename two/file -> one" && - - git checkout -b rename-one HEAD~1 && - git rm -rf two && - git mv one/file two && - rm -r one && - git commit -m "Rename one/file -> two" -' - -test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked dir' ' - git checkout -q rename-one^0 && - mkdir one && - test_must_fail git merge --strategy=recursive rename-two && - - test 2 -eq "$(git ls-files -u | wc -l)" && - test 1 -eq "$(git ls-files -u one | wc -l)" && - test 1 -eq "$(git ls-files -u two | wc -l)" && - - test_must_fail git diff --quiet && - - test 4 -eq $(find . | grep -v .git | wc -l) && - - test_path_is_dir one && - test_path_is_file one~rename-two && - test_path_is_file two && - test "other" = $(cat one~rename-two) && - test "stuff" = $(cat two) -' - -test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean start' ' - git reset --hard && - git clean -fdqx && - test_must_fail git merge --strategy=recursive rename-two && - - test 2 -eq "$(git ls-files -u | wc -l)" && - test 1 -eq "$(git ls-files -u one | wc -l)" && - test 1 -eq "$(git ls-files -u two | wc -l)" && - - test_must_fail git diff --quiet && - - test 3 -eq $(find . | grep -v .git | wc -l) && - - test_path_is_file one && - test_path_is_file two && - test "other" = $(cat one) && - test "stuff" = $(cat two) -' - -test_expect_success 'setup rename of one file to two, with directories in the way' ' - git reset --hard && - git checkout --orphan first-rename && - git rm -rf . && - git clean -fdqx && - - echo stuff >original && - git add -A && - git commit -m "Common commit" && - - mkdir two && - >two/file && - git add two/file && - git mv original one && - git commit -m "Put two/file in the way, rename to one" && - - git checkout -b second-rename HEAD~1 && - mkdir one && - >one/file && - git add one/file && - git mv original two && - git commit -m "Put one/file in the way, rename to two" -' - -test_expect_success 'check handling of differently renamed file with D/F conflicts' ' - git checkout -q first-rename^0 && - test_must_fail git merge --strategy=recursive second-rename && - - test 5 -eq "$(git ls-files -s | wc -l)" && - test 3 -eq "$(git ls-files -u | wc -l)" && - test 1 -eq "$(git ls-files -u one | wc -l)" && - test 1 -eq "$(git ls-files -u two | wc -l)" && - test 1 -eq "$(git ls-files -u original | wc -l)" && - test 2 -eq "$(git ls-files -o | wc -l)" && - - test_path_is_file one/file && - test_path_is_file two/file && - test_path_is_file one~HEAD && - test_path_is_file two~second-rename && - test_path_is_missing original -' - -test_expect_success 'setup rename one file to two; directories moving out of the way' ' - git reset --hard && - git checkout --orphan first-rename-redo && - git rm -rf . && - git clean -fdqx && - - echo stuff >original && - mkdir one two && - touch one/file two/file && - git add -A && - git commit -m "Common commit" && - - git rm -rf one && - git mv original one && - git commit -m "Rename to one" && - - git checkout -b second-rename-redo HEAD~1 && - git rm -rf two && - git mv original two && - git commit -m "Rename to two" -' - -test_expect_success 'check handling of differently renamed file with D/F conflicts' ' - git checkout -q first-rename-redo^0 && - test_must_fail git merge --strategy=recursive second-rename-redo && - - test 3 -eq "$(git ls-files -u | wc -l)" && - test 1 -eq "$(git ls-files -u one | wc -l)" && - test 1 -eq "$(git ls-files -u two | wc -l)" && - test 1 -eq "$(git ls-files -u original | wc -l)" && - test 0 -eq "$(git ls-files -o | wc -l)" && - - test_path_is_file one && - test_path_is_file two && - test_path_is_missing original -' - -test_expect_success 'setup avoid unnecessary update, normal rename' ' - git reset --hard && - git checkout --orphan avoid-unnecessary-update-1 && - git rm -rf . && - git clean -fdqx && - - printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original && - git add -A && - git commit -m "Common commit" && - - git mv original rename && - echo 11 >>rename && - git add -u && - git commit -m "Renamed and modified" && - - git checkout -b merge-branch-1 HEAD~1 && - echo "random content" >random-file && - git add -A && - git commit -m "Random, unrelated changes" -' - -test_expect_success 'avoid unnecessary update, normal rename' ' - git checkout -q avoid-unnecessary-update-1^0 && - test-tool chmtime --get -3600 rename >expect && - git merge merge-branch-1 && - test-tool chmtime --get rename >actual && - test_cmp expect actual # "rename" should have stayed intact -' - -test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' ' - git reset --hard && - git checkout --orphan avoid-unnecessary-update-2 && - git rm -rf . && - git clean -fdqx && - - mkdir df && - printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file && - git add -A && - git commit -m "Common commit" && - - git mv df/file temp && - rm -rf df && - git mv temp df && - echo 11 >>df && - git add -u && - git commit -m "Renamed and modified" && - - git checkout -b merge-branch-2 HEAD~1 && - >unrelated-change && - git add unrelated-change && - git commit -m "Only unrelated changes" -' - -test_expect_success 'avoid unnecessary update, with D/F conflict' ' - git checkout -q avoid-unnecessary-update-2^0 && - test-tool chmtime --get -3600 df >expect && - git merge merge-branch-2 && - test-tool chmtime --get df >actual && - test_cmp expect actual # "df" should have stayed intact -' - -test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >irrelevant && - mkdir df && - >df/file && - git add -A && - git commit -mA && - - git checkout -b side && - git rm -rf df && - git commit -mB && - - git checkout master && - git rm -rf df && - echo bla >df && - git add -A && - git commit -m "Add a newfile" -' - -test_expect_success 'avoid unnecessary update, dir->(file,nothing)' ' - git checkout -q master^0 && - test-tool chmtime --get -3600 df >expect && - git merge side && - test-tool chmtime --get df >actual && - test_cmp expect actual # "df" should have stayed intact -' - -test_expect_success 'setup avoid unnecessary update, modify/delete' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - >irrelevant && - >file && - git add -A && - git commit -mA && - - git checkout -b side && - git rm -f file && - git commit -m "Delete file" && - - git checkout master && - echo bla >file && - git add -A && - git commit -m "Modify file" -' - -test_expect_success 'avoid unnecessary update, modify/delete' ' - git checkout -q master^0 && - test-tool chmtime --get -3600 file >expect && - test_must_fail git merge side && - test-tool chmtime --get file >actual && - test_cmp expect actual # "file" should have stayed intact -' - -test_expect_success 'setup avoid unnecessary update, rename/add-dest' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n7\n8\n" >file && - git add -A && - git commit -mA && - - git checkout -b side && - cp file newfile && - git add -A && - git commit -m "Add file copy" && - - git checkout master && - git mv file newfile && - git commit -m "Rename file" -' - -test_expect_success 'avoid unnecessary update, rename/add-dest' ' - git checkout -q master^0 && - test-tool chmtime --get -3600 newfile >expect && - git merge side && - test-tool chmtime --get newfile >actual && - test_cmp expect actual # "file" should have stayed intact -' - -test_expect_success 'setup merge of rename + small change' ' - git reset --hard && - git checkout --orphan rename-plus-small-change && - git rm -rf . && - git clean -fdqx && - - echo ORIGINAL >file && - git add file && - - test_tick && - git commit -m Initial && - git checkout -b rename_branch && - git mv file renamed_file && - git commit -m Rename && - git checkout rename-plus-small-change && - echo NEW-VERSION >file && - git commit -a -m Reformat -' - -test_expect_success 'merge rename + small change' ' - git merge rename_branch && - - test 1 -eq $(git ls-files -s | wc -l) && - test 0 -eq $(git ls-files -o | wc -l) && - test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file) -' - -test_expect_success 'setup for use of extended merge markers' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file && - git add original_file && - git commit -mA && - - git checkout -b rename && - echo 9 >>original_file && - git add original_file && - git mv original_file renamed_file && - git commit -mB && - - git checkout master && - echo 8.5 >>original_file && - git add original_file && - git commit -mC -' - -test_expect_success 'merge master into rename has correct extended markers' ' - git checkout rename^0 && - test_must_fail git merge -s recursive master^0 && - - cat >expected <<-\EOF && - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - <<<<<<< HEAD:renamed_file - 9 - ======= - 8.5 - >>>>>>> master^0:original_file - EOF - test_cmp expected renamed_file -' - -test_expect_success 'merge rename into master has correct extended markers' ' - git reset --hard && - git checkout master^0 && - test_must_fail git merge -s recursive rename^0 && - - cat >expected <<-\EOF && - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - <<<<<<< HEAD:original_file - 8.5 - ======= - 9 - >>>>>>> rename^0:renamed_file - EOF - test_cmp expected renamed_file -' - -test_expect_success 'setup spurious "refusing to lose untracked" message' ' - git rm -rf . && - git clean -fdqx && - rm -rf .git && - git init && - - > irrelevant_file && - printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file && - git add irrelevant_file original_file && - git commit -mA && - - git checkout -b rename && - git mv original_file renamed_file && - git commit -mB && - - git checkout master && - git rm original_file && - git commit -mC -' - -test_expect_success 'no spurious "refusing to lose untracked" message' ' - git checkout master^0 && - test_must_fail git merge rename^0 2>errors.txt && - ! grep "refusing to lose untracked file" errors.txt -' - -test_expect_success 'do not follow renames for empty files' ' - git checkout -f -b empty-base && - >empty1 && - git add empty1 && - git commit -m base && - echo content >empty1 && - git add empty1 && - git commit -m fill && - git checkout -b empty-topic HEAD^ && - git mv empty1 empty2 && - git commit -m rename && - test_must_fail git merge empty-base && - test_must_be_empty empty2 -' - -test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh deleted file mode 100755 index 2f421d9..0000000 --- a/t/t6023-merge-file.sh +++ /dev/null @@ -1,390 +0,0 @@ -#!/bin/sh - -test_description='RCS merge replacement: merge-file' -. ./test-lib.sh - -test_expect_success 'setup' ' - cat >orig.txt <<-\EOF && - Dominus regit me, - et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - EOF - - cat >new1.txt <<-\EOF && - Dominus regit me, - et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - Nam et si ambulavero in medio umbrae mortis, - non timebo mala, quoniam tu mecum es: - virga tua et baculus tuus ipsa me consolata sunt. - EOF - - cat >new2.txt <<-\EOF && - Dominus regit me, et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - EOF - - cat >new3.txt <<-\EOF && - DOMINUS regit me, - et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - EOF - - cat >new4.txt <<-\EOF && - Dominus regit me, et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - EOF - - printf "propter nomen suum." >>new4.txt -' - -test_expect_success 'merge with no changes' ' - cp orig.txt test.txt && - git merge-file test.txt orig.txt orig.txt && - test_cmp test.txt orig.txt -' - -test_expect_success "merge without conflict" ' - cp new1.txt test.txt && - git merge-file test.txt orig.txt new2.txt -' - -test_expect_success 'works in subdirectory' ' - mkdir dir && - cp new1.txt dir/a.txt && - cp orig.txt dir/o.txt && - cp new2.txt dir/b.txt && - ( cd dir && git merge-file a.txt o.txt b.txt ) && - test_path_is_missing a.txt -' - -test_expect_success "merge without conflict (--quiet)" ' - cp new1.txt test.txt && - git merge-file --quiet test.txt orig.txt new2.txt -' - -test_expect_failure "merge without conflict (missing LF at EOF)" ' - cp new1.txt test2.txt && - git merge-file test2.txt orig.txt new4.txt -' - -test_expect_failure "merge result added missing LF" ' - test_cmp test.txt test2.txt -' - -test_expect_success "merge without conflict (missing LF at EOF, away from change in the other file)" ' - cp new4.txt test3.txt && - git merge-file --quiet test3.txt new2.txt new3.txt -' - -test_expect_success "merge does not add LF away of change" ' - cat >expect.txt <<-\EOF && - DOMINUS regit me, - et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - EOF - printf "propter nomen suum." >>expect.txt && - - test_cmp expect.txt test3.txt -' - -test_expect_success "merge with conflicts" ' - cp test.txt backup.txt && - test_must_fail git merge-file test.txt orig.txt new3.txt -' - -test_expect_success "expected conflict markers" ' - cat >expect.txt <<-\EOF && - <<<<<<< test.txt - Dominus regit me, et nihil mihi deerit. - ======= - DOMINUS regit me, - et nihil mihi deerit. - >>>>>>> new3.txt - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - Nam et si ambulavero in medio umbrae mortis, - non timebo mala, quoniam tu mecum es: - virga tua et baculus tuus ipsa me consolata sunt. - EOF - - test_cmp expect.txt test.txt -' - -test_expect_success "merge conflicting with --ours" ' - cp backup.txt test.txt && - - cat >expect.txt <<-\EOF && - Dominus regit me, et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - Nam et si ambulavero in medio umbrae mortis, - non timebo mala, quoniam tu mecum es: - virga tua et baculus tuus ipsa me consolata sunt. - EOF - - git merge-file --ours test.txt orig.txt new3.txt && - test_cmp expect.txt test.txt -' - -test_expect_success "merge conflicting with --theirs" ' - cp backup.txt test.txt && - - cat >expect.txt <<-\EOF && - DOMINUS regit me, - et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - Nam et si ambulavero in medio umbrae mortis, - non timebo mala, quoniam tu mecum es: - virga tua et baculus tuus ipsa me consolata sunt. - EOF - - git merge-file --theirs test.txt orig.txt new3.txt && - test_cmp expect.txt test.txt -' - -test_expect_success "merge conflicting with --union" ' - cp backup.txt test.txt && - - cat >expect.txt <<-\EOF && - Dominus regit me, et nihil mihi deerit. - DOMINUS regit me, - et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - Nam et si ambulavero in medio umbrae mortis, - non timebo mala, quoniam tu mecum es: - virga tua et baculus tuus ipsa me consolata sunt. - EOF - - git merge-file --union test.txt orig.txt new3.txt && - test_cmp expect.txt test.txt -' - -test_expect_success "merge with conflicts, using -L" ' - cp backup.txt test.txt && - - test_must_fail git merge-file -L 1 -L 2 test.txt orig.txt new3.txt -' - -test_expect_success "expected conflict markers, with -L" ' - cat >expect.txt <<-\EOF && - <<<<<<< 1 - Dominus regit me, et nihil mihi deerit. - ======= - DOMINUS regit me, - et nihil mihi deerit. - >>>>>>> new3.txt - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - Nam et si ambulavero in medio umbrae mortis, - non timebo mala, quoniam tu mecum es: - virga tua et baculus tuus ipsa me consolata sunt. - EOF - - test_cmp expect.txt test.txt -' - -test_expect_success "conflict in removed tail" ' - sed "s/ tu / TU /" new5.txt && - test_must_fail git merge-file -p orig.txt new1.txt new5.txt >out -' - -test_expect_success "expected conflict markers" ' - cat >expect <<-\EOF && - Dominus regit me, - et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - <<<<<<< orig.txt - ======= - Nam et si ambulavero in medio umbrae mortis, - non timebo mala, quoniam TU mecum es: - virga tua et baculus tuus ipsa me consolata sunt. - >>>>>>> new5.txt - EOF - - test_cmp expect out -' - -test_expect_success 'binary files cannot be merged' ' - test_must_fail git merge-file -p \ - orig.txt "$TEST_DIRECTORY"/test-binary-1.png new1.txt 2> merge.err && - grep "Cannot merge binary files" merge.err -' - -test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' ' - sed -e "s/deerit.\$/deerit;/" -e "s/me;\$/me./" new6.txt && - sed -e "s/deerit.\$/deerit,/" -e "s/me;\$/me,/" new7.txt && - - test_must_fail git merge-file -p new6.txt new5.txt new7.txt > output && - test 1 = $(grep ======= new8.txt && - sed -e "s/deerit./&%%%%/" -e "s/locavit,/locavit --/" new9.txt && - - test_must_fail git merge-file -p \ - new8.txt new5.txt new9.txt >merge.out && - test 1 = $(grep ======= expect <<-\EOF && - Dominus regit me, - <<<<<<< new8.txt - et nihil mihi deerit; - - - - - In loco pascuae ibi me collocavit; - super aquam refectionis educavit me. - ||||||| new5.txt - et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - ======= - et nihil mihi deerit, - - - - - In loco pascuae ibi me collocavit -- - super aquam refectionis educavit me, - >>>>>>> new9.txt - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - Nam et si ambulavero in medio umbrae mortis, - non timebo mala, quoniam TU mecum es: - virga tua et baculus tuus ipsa me consolata sunt. - EOF - - test_must_fail git merge-file -p --diff3 \ - new8.txt new5.txt new9.txt >actual && - test_cmp expect actual -' - -test_expect_success '"diff3 -m" style output (2)' ' - git config merge.conflictstyle diff3 && - test_must_fail git merge-file -p \ - new8.txt new5.txt new9.txt >actual && - test_cmp expect actual -' - -test_expect_success 'marker size' ' - cat >expect <<-\EOF && - Dominus regit me, - <<<<<<<<<< new8.txt - et nihil mihi deerit; - - - - - In loco pascuae ibi me collocavit; - super aquam refectionis educavit me. - |||||||||| new5.txt - et nihil mihi deerit. - In loco pascuae ibi me collocavit, - super aquam refectionis educavit me; - ========== - et nihil mihi deerit, - - - - - In loco pascuae ibi me collocavit -- - super aquam refectionis educavit me, - >>>>>>>>>> new9.txt - animam meam convertit, - deduxit me super semitas jusitiae, - propter nomen suum. - Nam et si ambulavero in medio umbrae mortis, - non timebo mala, quoniam TU mecum es: - virga tua et baculus tuus ipsa me consolata sunt. - EOF - - test_must_fail git merge-file -p --marker-size=10 \ - new8.txt new5.txt new9.txt >actual && - test_cmp expect actual -' - -test_expect_success 'conflict at EOF without LF resolved by --ours' ' - printf "line1\nline2\nline3" >nolf-orig.txt && - printf "line1\nline2\nline3x" >nolf-diff1.txt && - printf "line1\nline2\nline3y" >nolf-diff2.txt && - - git merge-file -p --ours nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt && - printf "line1\nline2\nline3x" >expect.txt && - test_cmp expect.txt output.txt -' - -test_expect_success 'conflict at EOF without LF resolved by --theirs' ' - git merge-file -p --theirs nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt && - printf "line1\nline2\nline3y" >expect.txt && - test_cmp expect.txt output.txt -' - -test_expect_success 'conflict at EOF without LF resolved by --union' ' - git merge-file -p --union nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt && - printf "line1\nline2\nline3x\nline3y" >expect.txt && - test_cmp expect.txt output.txt -' - -test_expect_success 'conflict sections match existing line endings' ' - printf "1\\r\\n2\\r\\n3" >crlf-orig.txt && - printf "1\\r\\n2\\r\\n4" >crlf-diff1.txt && - printf "1\\r\\n2\\r\\n5" >crlf-diff2.txt && - test_must_fail git -c core.eol=crlf merge-file -p \ - crlf-diff1.txt crlf-orig.txt crlf-diff2.txt >crlf.txt && - test $(tr "\015" Q ].*Q$" | wc -l) = 3 && - test $(tr "\015" Q nolf.txt && - test $(tr "\015" Q ].*Q$" | wc -l) = 0 -' - -test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh deleted file mode 100755 index 332cfc5..0000000 --- a/t/t6024-recursive-merge.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/bin/sh - -test_description='Test merge without common ancestors' -. ./test-lib.sh - -# This scenario is based on a real-world repository of Shawn Pearce. - -# 1 - A - D - F -# \ X / -# B X -# X \ -# 2 - C - E - G - -GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" -export GIT_COMMITTER_DATE - -test_expect_success 'setup tests' ' - echo 1 >a1 && - git add a1 && - GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 && - - git checkout -b A master && - echo A >a1 && - GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 && - - git checkout -b B master && - echo B >a1 && - GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 && - - git checkout -b D A && - git rev-parse B >.git/MERGE_HEAD && - echo D >a1 && - git update-index a1 && - GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D && - - git symbolic-ref HEAD refs/heads/other && - echo 2 >a1 && - GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 && - - git checkout -b C && - echo C >a1 && - GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 && - - git checkout -b E C && - git rev-parse B >.git/MERGE_HEAD && - echo E >a1 && - git update-index a1 && - GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E && - - git checkout -b G E && - git rev-parse A >.git/MERGE_HEAD && - echo G >a1 && - git update-index a1 && - GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G && - - git checkout -b F D && - git rev-parse C >.git/MERGE_HEAD && - echo F >a1 && - git update-index a1 && - GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F && - - test_oid_cache <<-EOF - idxstage1 sha1:ec3fe2a791706733f2d8fa7ad45d9a9672031f5e - idxstage1 sha256:b3c8488929903aaebdeb22270cb6d36e5b8724b01ae0d4da24632f158c99676f - EOF -' - -test_expect_success 'combined merge conflicts' ' - test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G -' - -test_expect_success 'result contains a conflict' ' - cat >expect <<-\EOF && - <<<<<<< HEAD - F - ======= - G - >>>>>>> G - EOF - - test_cmp expect a1 -' - -test_expect_success 'virtual trees were processed' ' - git ls-files --stage >out && - - cat >expect <<-EOF && - 100644 $(test_oid idxstage1) 1 a1 - 100644 $(git rev-parse F:a1) 2 a1 - 100644 $(git rev-parse G:a1) 3 a1 - EOF - - test_cmp expect out -' - -test_expect_success 'refuse to merge binary files' ' - git reset --hard && - printf "\0" >binary-file && - git add binary-file && - git commit -m binary && - git checkout G && - printf "\0\0" >binary-file && - git add binary-file && - git commit -m binary2 && - test_must_fail git merge F >merge.out 2>merge.err && - grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err -' - -test_expect_success 'mark rename/delete as unmerged' ' - - git reset --hard && - git checkout -b delete && - git rm a1 && - test_tick && - git commit -m delete && - git checkout -b rename HEAD^ && - git mv a1 a2 && - test_tick && - git commit -m rename && - test_must_fail git merge delete && - test 1 = $(git ls-files --unmerged | wc -l) && - git rev-parse --verify :2:a2 && - test_must_fail git rev-parse --verify :3:a2 && - git checkout -f delete && - test_must_fail git merge rename && - test 1 = $(git ls-files --unmerged | wc -l) && - test_must_fail git rev-parse --verify :2:a2 && - git rev-parse --verify :3:a2 -' - -test_done diff --git a/t/t6025-merge-symlinks.sh b/t/t6025-merge-symlinks.sh deleted file mode 100755 index 6c0a90d..0000000 --- a/t/t6025-merge-symlinks.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2007 Johannes Sixt -# - -test_description='merging symlinks on filesystem w/o symlink support. - -This tests that git merge-recursive writes merge results as plain files -if core.symlinks is false.' - -. ./test-lib.sh - -test_expect_success 'setup' ' - git config core.symlinks false && - >file && - git add file && - git commit -m initial && - git branch b-symlink && - git branch b-file && - l=$(printf file | git hash-object -t blob -w --stdin) && - echo "120000 $l symlink" | git update-index --index-info && - git commit -m master && - git checkout b-symlink && - l=$(printf file-different | git hash-object -t blob -w --stdin) && - echo "120000 $l symlink" | git update-index --index-info && - git commit -m b-symlink && - git checkout b-file && - echo plain-file >symlink && - git add symlink && - git commit -m b-file -' - -test_expect_success 'merge master into b-symlink, which has a different symbolic link' ' - git checkout b-symlink && - test_must_fail git merge master -' - -test_expect_success 'the merge result must be a file' ' - test_path_is_file symlink -' - -test_expect_success 'merge master into b-file, which has a file instead of a symbolic link' ' - git reset --hard && - git checkout b-file && - test_must_fail git merge master -' - -test_expect_success 'the merge result must be a file' ' - test_path_is_file symlink -' - -test_expect_success 'merge b-file, which has a file instead of a symbolic link, into master' ' - git reset --hard && - git checkout master && - test_must_fail git merge b-file -' - -test_expect_success 'the merge result must be a file' ' - test_path_is_file symlink -' - -test_done diff --git a/t/t6026-merge-attr.sh b/t/t6026-merge-attr.sh deleted file mode 100755 index 5900358..0000000 --- a/t/t6026-merge-attr.sh +++ /dev/null @@ -1,207 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2007 Junio C Hamano -# - -test_description='per path merge controlled by merge attribute' - -. ./test-lib.sh - -test_expect_success setup ' - - for f in text binary union - do - echo Initial >$f && git add $f || return 1 - done && - test_tick && - git commit -m Initial && - - git branch side && - for f in text binary union - do - echo Master >>$f && git add $f || return 1 - done && - test_tick && - git commit -m Master && - - git checkout side && - for f in text binary union - do - echo Side >>$f && git add $f || return 1 - done && - test_tick && - git commit -m Side && - - git tag anchor && - - cat >./custom-merge <<-\EOF && - #!/bin/sh - - orig="$1" ours="$2" theirs="$3" exit="$4" path=$5 - ( - echo "orig is $orig" - echo "ours is $ours" - echo "theirs is $theirs" - echo "path is $path" - echo "=== orig ===" - cat "$orig" - echo "=== ours ===" - cat "$ours" - echo "=== theirs ===" - cat "$theirs" - ) >"$ours+" - cat "$ours+" >"$ours" - rm -f "$ours+" - exit "$exit" - EOF - chmod +x ./custom-merge -' - -test_expect_success merge ' - - { - echo "binary -merge" - echo "union merge=union" - } >.gitattributes && - - if git merge master - then - echo Gaah, should have conflicted - false - else - echo Ok, conflicted. - fi -' - -test_expect_success 'check merge result in index' ' - - git ls-files -u | grep binary && - git ls-files -u | grep text && - ! (git ls-files -u | grep union) - -' - -test_expect_success 'check merge result in working tree' ' - - git cat-file -p HEAD:binary >binary-orig && - grep "<<<<<<<" text && - cmp binary-orig binary && - ! grep "<<<<<<<" union && - grep Master union && - grep Side union - -' - -test_expect_success 'retry the merge with longer context' ' - echo text conflict-marker-size=32 >>.gitattributes && - git checkout -m text && - sed -ne "/^\([<=>]\)\1\1\1*/{ - s/ .*$// - p - }" >actual text && - grep ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" actual && - grep "================================" actual && - grep "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" actual -' - -test_expect_success 'custom merge backend' ' - - echo "* merge=union" >.gitattributes && - echo "text merge=custom" >>.gitattributes && - - git reset --hard anchor && - git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 0 %P" && - git config --replace-all \ - merge.custom.name "custom merge driver for testing" && - - git merge master && - - cmp binary union && - sed -e 1,3d text >check-1 && - o=$(git unpack-file master^:text) && - a=$(git unpack-file side^:text) && - b=$(git unpack-file master:text) && - sh -c "./custom-merge $o $a $b 0 'text'" && - sed -e 1,3d $a >check-2 && - cmp check-1 check-2 && - rm -f $o $a $b -' - -test_expect_success 'custom merge backend' ' - - git reset --hard anchor && - git config --replace-all \ - merge.custom.driver "./custom-merge %O %A %B 1 %P" && - git config --replace-all \ - merge.custom.name "custom merge driver for testing" && - - if git merge master - then - echo "Eh? should have conflicted" - false - else - echo "Ok, conflicted" - fi && - - cmp binary union && - sed -e 1,3d text >check-1 && - o=$(git unpack-file master^:text) && - a=$(git unpack-file anchor:text) && - b=$(git unpack-file master:text) && - sh -c "./custom-merge $o $a $b 0 'text'" && - sed -e 1,3d $a >check-2 && - cmp check-1 check-2 && - sed -e 1,3d -e 4q $a >check-3 && - echo "path is text" >expect && - cmp expect check-3 && - rm -f $o $a $b -' - -test_expect_success 'up-to-date merge without common ancestor' ' - test_create_repo repo1 && - test_create_repo repo2 && - test_tick && - ( - cd repo1 && - >a && - git add a && - git commit -m initial - ) && - test_tick && - ( - cd repo2 && - git commit --allow-empty -m initial - ) && - test_tick && - ( - cd repo1 && - git fetch ../repo2 master && - git merge --allow-unrelated-histories FETCH_HEAD - ) -' - -test_expect_success 'custom merge does not lock index' ' - git reset --hard anchor && - write_script sleep-an-hour.sh <<-\EOF && - sleep 3600 & - echo $! >sleep.pid - EOF - - test_write_lines >.gitattributes \ - "* merge=ours" "text merge=sleep-an-hour" && - test_config merge.ours.driver true && - test_config merge.sleep-an-hour.driver ./sleep-an-hour.sh && - - # We are testing that the custom merge driver does not block - # index.lock on Windows due to an inherited file handle. - # To ensure that the backgrounded process ran sufficiently - # long (and has been started in the first place), we do not - # ignore the result of the kill command. - # By packaging the command in test_when_finished, we get both - # the correctness check and the clean-up. - test_when_finished "kill \$(cat sleep.pid)" && - git merge master -' - -test_done diff --git a/t/t6027-merge-binary.sh b/t/t6027-merge-binary.sh deleted file mode 100755 index 4e6c7cb..0000000 --- a/t/t6027-merge-binary.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/bin/sh - -test_description='ask merge-recursive to merge binary files' - -. ./test-lib.sh - -test_expect_success setup ' - - cat "$TEST_DIRECTORY"/test-binary-1.png >m && - git add m && - git ls-files -s | sed -e "s/ 0 / 1 /" >E1 && - test_tick && - git commit -m "initial" && - - git branch side && - echo frotz >a && - git add a && - echo nitfol >>m && - git add a m && - git ls-files -s a >E0 && - git ls-files -s m | sed -e "s/ 0 / 3 /" >E3 && - test_tick && - git commit -m "master adds some" && - - git checkout side && - echo rezrov >>m && - git add m && - git ls-files -s m | sed -e "s/ 0 / 2 /" >E2 && - test_tick && - git commit -m "side modifies" && - - git tag anchor && - - cat E0 E1 E2 E3 >expect -' - -test_expect_success resolve ' - - rm -f a* m* && - git reset --hard anchor && - - if git merge -s resolve master - then - echo Oops, should not have succeeded - false - else - git ls-files -s >current - test_cmp expect current - fi -' - -test_expect_success recursive ' - - rm -f a* m* && - git reset --hard anchor && - - if git merge -s recursive master - then - echo Oops, should not have succeeded - false - else - git ls-files -s >current - test_cmp expect current - fi -' - -test_done diff --git a/t/t6028-merge-up-to-date.sh b/t/t6028-merge-up-to-date.sh deleted file mode 100755 index 7763c1b..0000000 --- a/t/t6028-merge-up-to-date.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/bin/sh - -test_description='merge fast-forward and up to date' - -. ./test-lib.sh - -test_expect_success setup ' - >file && - git add file && - test_tick && - git commit -m initial && - git tag c0 && - - echo second >file && - git add file && - test_tick && - git commit -m second && - git tag c1 && - git branch test && - echo third >file && - git add file && - test_tick && - git commit -m third && - git tag c2 -' - -test_expect_success 'merge -s recursive up-to-date' ' - - git reset --hard c1 && - test_tick && - git merge -s recursive c0 && - expect=$(git rev-parse c1) && - current=$(git rev-parse HEAD) && - test "$expect" = "$current" - -' - -test_expect_success 'merge -s recursive fast-forward' ' - - git reset --hard c0 && - test_tick && - git merge -s recursive c1 && - expect=$(git rev-parse c1) && - current=$(git rev-parse HEAD) && - test "$expect" = "$current" - -' - -test_expect_success 'merge -s ours up-to-date' ' - - git reset --hard c1 && - test_tick && - git merge -s ours c0 && - expect=$(git rev-parse c1) && - current=$(git rev-parse HEAD) && - test "$expect" = "$current" - -' - -test_expect_success 'merge -s ours fast-forward' ' - - git reset --hard c0 && - test_tick && - git merge -s ours c1 && - expect=$(git rev-parse c0^{tree}) && - current=$(git rev-parse HEAD^{tree}) && - test "$expect" = "$current" - -' - -test_expect_success 'merge -s subtree up-to-date' ' - - git reset --hard c1 && - test_tick && - git merge -s subtree c0 && - expect=$(git rev-parse c1) && - current=$(git rev-parse HEAD) && - test "$expect" = "$current" - -' - -test_expect_success 'merge fast-forward octopus' ' - - git reset --hard c0 && - test_tick && - git merge c1 c2 && - expect=$(git rev-parse c2) && - current=$(git rev-parse HEAD) && - test "$expect" = "$current" -' - -test_done diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh deleted file mode 100755 index 793f0c8..0000000 --- a/t/t6029-merge-subtree.sh +++ /dev/null @@ -1,152 +0,0 @@ -#!/bin/sh - -test_description='subtree merge strategy' - -. ./test-lib.sh - -test_expect_success setup ' - - s="1 2 3 4 5 6 7 8" && - for i in $s; do echo $i; done >hello && - git add hello && - git commit -m initial && - git checkout -b side && - echo >>hello world && - git add hello && - git commit -m second && - git checkout master && - for i in mundo $s; do echo $i; done >hello && - git add hello && - git commit -m master - -' - -test_expect_success 'subtree available and works like recursive' ' - - git merge -s subtree side && - for i in mundo $s world; do echo $i; done >expect && - test_cmp expect hello - -' - -test_expect_success 'setup branch sub' ' - git checkout --orphan sub && - git rm -rf . && - test_commit foo -' - -test_expect_success 'setup branch main' ' - git checkout -b main master && - git merge -s ours --no-commit --allow-unrelated-histories sub && - git read-tree --prefix=dir/ -u sub && - git commit -m "initial merge of sub into main" && - test_path_is_file dir/foo.t && - test_path_is_file hello -' - -test_expect_success 'update branch sub' ' - git checkout sub && - test_commit bar -' - -test_expect_success 'update branch main' ' - git checkout main && - git merge -s subtree sub -m "second merge of sub into main" && - test_path_is_file dir/bar.t && - test_path_is_file dir/foo.t && - test_path_is_file hello -' - -test_expect_success 'setup' ' - mkdir git-gui && - cd git-gui && - git init && - echo git-gui > git-gui.sh && - o1=$(git hash-object git-gui.sh) && - git add git-gui.sh && - git commit -m "initial git-gui" && - cd .. && - mkdir git && - cd git && - git init && - echo git >git.c && - o2=$(git hash-object git.c) && - git add git.c && - git commit -m "initial git" -' - -test_expect_success 'initial merge' ' - git remote add -f gui ../git-gui && - git merge -s ours --no-commit --allow-unrelated-histories gui/master && - git read-tree --prefix=git-gui/ -u gui/master && - git commit -m "Merge git-gui as our subdirectory" && - git checkout -b work && - git ls-files -s >actual && - ( - echo "100644 $o1 0 git-gui/git-gui.sh" && - echo "100644 $o2 0 git.c" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'merge update' ' - cd ../git-gui && - echo git-gui2 > git-gui.sh && - o3=$(git hash-object git-gui.sh) && - git add git-gui.sh && - git checkout -b master2 && - git commit -m "update git-gui" && - cd ../git && - git pull -s subtree gui master2 && - git ls-files -s >actual && - ( - echo "100644 $o3 0 git-gui/git-gui.sh" && - echo "100644 $o2 0 git.c" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'initial ambiguous subtree' ' - cd ../git && - git reset --hard master && - git checkout -b master2 && - git merge -s ours --no-commit gui/master && - git read-tree --prefix=git-gui2/ -u gui/master && - git commit -m "Merge git-gui2 as our subdirectory" && - git checkout -b work2 && - git ls-files -s >actual && - ( - echo "100644 $o1 0 git-gui/git-gui.sh" && - echo "100644 $o1 0 git-gui2/git-gui.sh" && - echo "100644 $o2 0 git.c" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'merge using explicit' ' - cd ../git && - git reset --hard master2 && - git pull -Xsubtree=git-gui gui master2 && - git ls-files -s >actual && - ( - echo "100644 $o3 0 git-gui/git-gui.sh" && - echo "100644 $o1 0 git-gui2/git-gui.sh" && - echo "100644 $o2 0 git.c" - ) >expected && - test_cmp expected actual -' - -test_expect_success 'merge2 using explicit' ' - cd ../git && - git reset --hard master2 && - git pull -Xsubtree=git-gui2 gui master2 && - git ls-files -s >actual && - ( - echo "100644 $o1 0 git-gui/git-gui.sh" && - echo "100644 $o3 0 git-gui2/git-gui.sh" && - echo "100644 $o2 0 git.c" - ) >expected && - test_cmp expected actual -' - -test_done diff --git a/t/t6031-merge-filemode.sh b/t/t6031-merge-filemode.sh deleted file mode 100755 index 87741ef..0000000 --- a/t/t6031-merge-filemode.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/sh - -test_description='merge: handle file mode' -. ./test-lib.sh - -test_expect_success 'set up mode change in one branch' ' - : >file1 && - git add file1 && - git commit -m initial && - git checkout -b a1 master && - : >dummy && - git add dummy && - git commit -m a && - git checkout -b b1 master && - test_chmod +x file1 && - git add file1 && - git commit -m b1 -' - -do_one_mode () { - strategy=$1 - us=$2 - them=$3 - test_expect_success "resolve single mode change ($strategy, $us)" ' - git checkout -f $us && - git merge -s $strategy $them && - git ls-files -s file1 | grep ^100755 - ' - - test_expect_success FILEMODE "verify executable bit on file ($strategy, $us)" ' - test -x file1 - ' -} - -do_one_mode recursive a1 b1 -do_one_mode recursive b1 a1 -do_one_mode resolve a1 b1 -do_one_mode resolve b1 a1 - -test_expect_success 'set up mode change in both branches' ' - git reset --hard HEAD && - git checkout -b a2 master && - : >file2 && - H=$(git hash-object file2) && - test_chmod +x file2 && - git commit -m a2 && - git checkout -b b2 master && - : >file2 && - git add file2 && - git commit -m b2 && - { - echo "100755 $H 2 file2" - echo "100644 $H 3 file2" - } >expect -' - -do_both_modes () { - strategy=$1 - test_expect_success "detect conflict on double mode change ($strategy)" ' - git reset --hard && - git checkout -f a2 && - test_must_fail git merge -s $strategy b2 && - git ls-files -u >actual && - test_cmp expect actual && - git ls-files -s file2 | grep ^100755 - ' - - test_expect_success FILEMODE "verify executable bit on file ($strategy)" ' - test -x file2 - ' -} - -# both sides are equivalent, so no need to run both ways -do_both_modes recursive -do_both_modes resolve - -test_expect_success 'set up delete/modechange scenario' ' - git reset --hard && - git checkout -b deletion master && - git rm file1 && - git commit -m deletion -' - -do_delete_modechange () { - strategy=$1 - us=$2 - them=$3 - test_expect_success "detect delete/modechange conflict ($strategy, $us)" ' - git reset --hard && - git checkout $us && - test_must_fail git merge -s $strategy $them - ' -} - -do_delete_modechange recursive b1 deletion -do_delete_modechange recursive deletion b1 -do_delete_modechange resolve b1 deletion -do_delete_modechange resolve deletion b1 - -test_done diff --git a/t/t6032-merge-large-rename.sh b/t/t6032-merge-large-rename.sh deleted file mode 100755 index 8077738..0000000 --- a/t/t6032-merge-large-rename.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/bin/sh - -test_description='merging with large rename matrix' -. ./test-lib.sh - -count() { - i=1 - while test $i -le $1; do - echo $i - i=$(($i + 1)) - done -} - -test_expect_success 'setup (initial)' ' - touch file && - git add . && - git commit -m initial && - git tag initial -' - -make_text() { - echo $1: $2 - for i in $(count 20); do - echo $1: $i - done - echo $1: $3 -} - -test_rename() { - test_expect_success "rename ($1, $2)" ' - n='$1' && - expect='$2' && - git checkout -f master && - test_might_fail git branch -D test$n && - git reset --hard initial && - for i in $(count $n); do - make_text $i initial initial >$i - done && - git add . && - git commit -m add=$n && - for i in $(count $n); do - make_text $i changed initial >$i - done && - git commit -a -m change=$n && - git checkout -b test$n HEAD^ && - for i in $(count $n); do - git rm $i - make_text $i initial changed >$i.moved - done && - git add . && - git commit -m change+rename=$n && - case "$expect" in - ok) git merge master ;; - *) test_must_fail git merge master ;; - esac - ' -} - -test_rename 5 ok - -test_expect_success 'set diff.renamelimit to 4' ' - git config diff.renamelimit 4 -' -test_rename 4 ok -test_rename 5 fail - -test_expect_success 'set merge.renamelimit to 5' ' - git config merge.renamelimit 5 -' -test_rename 5 ok -test_rename 6 fail - -test_expect_success 'setup large simple rename' ' - git config --unset merge.renamelimit && - git config --unset diff.renamelimit && - - git reset --hard initial && - for i in $(count 200); do - make_text foo bar baz >$i - done && - git add . && - git commit -m create-files && - - git branch simple-change && - git checkout -b simple-rename && - - mkdir builtin && - git mv [0-9]* builtin/ && - git commit -m renamed && - - git checkout simple-change && - >unrelated-change && - git add unrelated-change && - git commit -m unrelated-change -' - -test_expect_success 'massive simple rename does not spam added files' ' - sane_unset GIT_MERGE_VERBOSITY && - git merge --no-stat simple-rename | grep -v Removing >output && - test_line_count -lt 5 output -' - -test_done diff --git a/t/t6033-merge-crlf.sh b/t/t6033-merge-crlf.sh deleted file mode 100755 index e8d65ee..0000000 --- a/t/t6033-merge-crlf.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh - -test_description='merge conflict in crlf repo - - b---M - / / - initial---a - -' - -. ./test-lib.sh - -test_expect_success setup ' - git config core.autocrlf true && - echo foo | append_cr >file && - git add file && - git commit -m "Initial" && - git tag initial && - git branch side && - echo line from a | append_cr >file && - git commit -m "add line from a" file && - git tag a && - git checkout side && - echo line from b | append_cr >file && - git commit -m "add line from b" file && - git tag b && - git checkout master -' - -test_expect_success 'Check "ours" is CRLF' ' - git reset --hard initial && - git merge side -s ours && - cat file | remove_cr | append_cr >file.temp && - test_cmp file file.temp -' - -test_expect_success 'Check that conflict file is CRLF' ' - git reset --hard a && - test_must_fail git merge side && - cat file | remove_cr | append_cr >file.temp && - test_cmp file file.temp -' - -test_done diff --git a/t/t6034-merge-rename-nocruft.sh b/t/t6034-merge-rename-nocruft.sh deleted file mode 100755 index a25e730..0000000 --- a/t/t6034-merge-rename-nocruft.sh +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/sh - -test_description='Merge-recursive merging renames' -. ./test-lib.sh - -test_expect_success 'setup' ' - cat >A <<-\EOF && - a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa - b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb - c cccccccccccccccccccccccccccccccccccccccccccccccc - d dddddddddddddddddddddddddddddddddddddddddddddddd - e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee - f ffffffffffffffffffffffffffffffffffffffffffffffff - g gggggggggggggggggggggggggggggggggggggggggggggggg - h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh - i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii - j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj - k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk - l llllllllllllllllllllllllllllllllllllllllllllllll - m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm - n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn - o oooooooooooooooooooooooooooooooooooooooooooooooo - EOF - - cat >M <<-\EOF && - A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB - C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC - D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD - E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE - F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF - G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG - H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH - I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII - J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ - K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK - L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL - M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM - N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN - O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO - EOF - - git add A M && - git commit -m "initial has A and M" && - git branch white && - git branch red && - git branch blue && - - git checkout white && - sed -e "/^g /s/.*/g : white changes a line/" B && - sed -e "/^G /s/.*/G : colored branch changes a line/" N && - rm -f A M && - git update-index --add --remove A B M N && - git commit -m "white renames A->B, M->N" && - - git checkout red && - echo created by red >R && - git update-index --add R && - git commit -m "red creates R" && - - git checkout blue && - sed -e "/^o /s/.*/g : blue changes a line/" B && - rm -f A && - mv B A && - git update-index A && - git commit -m "blue modify A" && - - git checkout master -' - -# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae -test_expect_success 'merge white into red (A->B,M->N)' ' - git checkout -b red-white red && - git merge white && - git write-tree && - test_path_is_file B && - test_path_is_file N && - test_path_is_file R && - test_path_is_missing A && - test_path_is_missing M -' - -# This test broke in 8371234ecaaf6e14fe3f2082a855eff1bbd79ae9 -test_expect_success 'merge blue into white (A->B, mod A, A untracked)' ' - git checkout -b white-blue white && - echo dirty >A && - git merge blue && - git write-tree && - test_path_is_file A && - echo dirty >expect && - test_cmp expect A && - test_path_is_file B && - test_path_is_file N && - test_path_is_missing M -' - -test_done diff --git a/t/t6035-merge-dir-to-symlink.sh b/t/t6035-merge-dir-to-symlink.sh deleted file mode 100755 index 2eddcc7..0000000 --- a/t/t6035-merge-dir-to-symlink.sh +++ /dev/null @@ -1,172 +0,0 @@ -#!/bin/sh - -test_description='merging when a directory was replaced with a symlink' -. ./test-lib.sh - -test_expect_success 'create a commit where dir a/b changed to symlink' ' - mkdir -p a/b/c a/b-2/c && - > a/b/c/d && - > a/b-2/c/d && - > a/x && - git add -A && - git commit -m base && - git tag start && - rm -rf a/b && - git add -A && - test_ln_s_add b-2 a/b && - git commit -m "dir to symlink" -' - -test_expect_success 'checkout does not clobber untracked symlink' ' - git checkout HEAD^0 && - git reset --hard master && - git rm --cached a/b && - git commit -m "untracked symlink remains" && - test_must_fail git checkout start^0 -' - -test_expect_success 'a/b-2/c/d is kept when clobbering symlink b' ' - git checkout HEAD^0 && - git reset --hard master && - git rm --cached a/b && - git commit -m "untracked symlink remains" && - git checkout -f start^0 && - test_path_is_file a/b-2/c/d -' - -test_expect_success 'checkout should not have deleted a/b-2/c/d' ' - git checkout HEAD^0 && - git reset --hard master && - git checkout start^0 && - test_path_is_file a/b-2/c/d -' - -test_expect_success 'setup for merge test' ' - git reset --hard && - test_path_is_file a/b-2/c/d && - echo x > a/x && - git add a/x && - git commit -m x && - git tag baseline -' - -test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' ' - git reset --hard && - git checkout baseline^0 && - git merge -s resolve master && - test_path_is_file a/b-2/c/d -' - -test_expect_success SYMLINKS 'a/b was resolved as symlink' ' - test -h a/b -' - -test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' ' - git reset --hard && - git checkout baseline^0 && - git merge -s recursive master && - test_path_is_file a/b-2/c/d -' - -test_expect_success SYMLINKS 'a/b was resolved as symlink' ' - test -h a/b -' - -test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' ' - git reset --hard && - git checkout master^0 && - git merge -s resolve baseline^0 && - test_path_is_file a/b-2/c/d -' - -test_expect_success SYMLINKS 'a/b was resolved as symlink' ' - test -h a/b -' - -test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' ' - git reset --hard && - git checkout master^0 && - git merge -s recursive baseline^0 && - test_path_is_file a/b-2/c/d -' - -test_expect_success SYMLINKS 'a/b was resolved as symlink' ' - test -h a/b -' - -test_expect_failure 'do not lose untracked in merge (resolve)' ' - git reset --hard && - git checkout baseline^0 && - >a/b/c/e && - test_must_fail git merge -s resolve master && - test_path_is_file a/b/c/e && - test_path_is_file a/b-2/c/d -' - -test_expect_success 'do not lose untracked in merge (recursive)' ' - git reset --hard && - git checkout baseline^0 && - >a/b/c/e && - test_must_fail git merge -s recursive master && - test_path_is_file a/b/c/e && - test_path_is_file a/b-2/c/d -' - -test_expect_success 'do not lose modifications in merge (resolve)' ' - git reset --hard && - git checkout baseline^0 && - echo more content >>a/b/c/d && - test_must_fail git merge -s resolve master -' - -test_expect_success 'do not lose modifications in merge (recursive)' ' - git reset --hard && - git checkout baseline^0 && - echo more content >>a/b/c/d && - test_must_fail git merge -s recursive master -' - -test_expect_success 'setup a merge where dir a/b-2 changed to symlink' ' - git reset --hard && - git checkout start^0 && - rm -rf a/b-2 && - git add -A && - test_ln_s_add b a/b-2 && - git commit -m "dir a/b-2 to symlink" && - git tag test2 -' - -test_expect_success 'merge should not have D/F conflicts (resolve)' ' - git reset --hard && - git checkout baseline^0 && - git merge -s resolve test2 && - test_path_is_file a/b/c/d -' - -test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' ' - test -h a/b-2 -' - -test_expect_success 'merge should not have D/F conflicts (recursive)' ' - git reset --hard && - git checkout baseline^0 && - git merge -s recursive test2 && - test_path_is_file a/b/c/d -' - -test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' ' - test -h a/b-2 -' - -test_expect_success 'merge should not have F/D conflicts (recursive)' ' - git reset --hard && - git checkout -b foo test2 && - git merge -s recursive baseline^0 && - test_path_is_file a/b/c/d -' - -test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' ' - test -h a/b-2 -' - -test_done diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh deleted file mode 100755 index b3bf462..0000000 --- a/t/t6036-recursive-corner-cases.sh +++ /dev/null @@ -1,1820 +0,0 @@ -#!/bin/sh - -test_description='recursive merge corner cases involving criss-cross merges' - -. ./test-lib.sh - -# -# L1 L2 -# o---o -# / \ / \ -# o X ? -# \ / \ / -# o---o -# R1 R2 -# - -test_expect_success 'setup basic criss-cross + rename with no modifications' ' - test_create_repo basic-rename && - ( - cd basic-rename && - - ten="0 1 2 3 4 5 6 7 8 9" && - for i in $ten - do - echo line $i in a sample file - done >one && - for i in $ten - do - echo line $i in another sample file - done >two && - git add one two && - test_tick && git commit -m initial && - - git branch L1 && - git checkout -b R1 && - git mv one three && - test_tick && git commit -m R1 && - - git checkout L1 && - git mv two three && - test_tick && git commit -m L1 && - - git checkout L1^0 && - test_tick && git merge -s ours R1 && - git tag L2 && - - git checkout R1^0 && - test_tick && git merge -s ours L1 && - git tag R2 - ) -' - -test_expect_success 'merge simple rename+criss-cross with no modifications' ' - ( - cd basic-rename && - - git reset --hard && - git checkout L2^0 && - - test_must_fail git merge -s recursive R2^0 && - - git ls-files -s >out && - test_line_count = 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 >expect \ - L2:three R2:three && - git rev-parse >actual \ - :2:three :3:three && - test_cmp expect actual - ) -' - -# -# Same as before, but modify L1 slightly: -# -# L1m L2 -# o---o -# / \ / \ -# o X ? -# \ / \ / -# o---o -# R1 R2 -# - -test_expect_success 'setup criss-cross + rename merges with basic modification' ' - test_create_repo rename-modify && - ( - cd rename-modify && - - ten="0 1 2 3 4 5 6 7 8 9" && - for i in $ten - do - echo line $i in a sample file - done >one && - for i in $ten - do - echo line $i in another sample file - done >two && - git add one two && - test_tick && git commit -m initial && - - git branch L1 && - git checkout -b R1 && - git mv one three && - echo more >>two && - git add two && - test_tick && git commit -m R1 && - - git checkout L1 && - git mv two three && - test_tick && git commit -m L1 && - - git checkout L1^0 && - test_tick && git merge -s ours R1 && - git tag L2 && - - git checkout R1^0 && - test_tick && git merge -s ours L1 && - git tag R2 - ) -' - -test_expect_success 'merge criss-cross + rename merges with basic modification' ' - ( - cd rename-modify && - - git checkout L2^0 && - - test_must_fail git merge -s recursive R2^0 && - - 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 >expect \ - L2:three R2:three && - git rev-parse >actual \ - :2:three :3:three && - test_cmp expect actual - ) -' - -# -# For the next test, we start with three commits in two lines of development -# which setup a rename/add conflict: -# Commit A: File 'a' exists -# Commit B: Rename 'a' -> 'new_a' -# Commit C: Modify 'a', create different 'new_a' -# Later, two different people merge and resolve differently: -# Commit D: Merge B & C, ignoring separately created 'new_a' -# Commit E: Merge B & C making use of some piece of secondary 'new_a' -# Finally, someone goes to merge D & E. Does git detect the conflict? -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# - -test_expect_success 'setup differently handled merges of rename/add conflict' ' - test_create_repo rename-add && - ( - cd rename-add && - - printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a && - git add a && - test_tick && git commit -m A && - - git branch B && - git checkout -b C && - echo 10 >>a && - test_write_lines 0 1 2 3 4 5 6 7 foobar >new_a && - git add a new_a && - test_tick && git commit -m C && - - git checkout B && - git mv a new_a && - test_tick && git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - git show :2:new_a >new_a && - git add new_a && - test_tick && git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - test_write_lines 0 1 2 3 4 5 6 7 bad_merge >new_a && - git add -u && - test_tick && git commit -m E && - git tag E - ) -' - -test_expect_success 'git detects differently handled merges conflict' ' - ( - cd rename-add && - - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 1 out && - - git cat-file -p C:new_a >ours && - git cat-file -p C:a >theirs && - >empty && - test_must_fail git merge-file \ - -L "Temporary merge branch 1" \ - -L "" \ - -L "Temporary merge branch 2" \ - ours empty theirs && - sed -e "s/^\([<=>]\)/\1\1\1/" ours >ours-tweaked && - git hash-object ours-tweaked >expect && - git rev-parse >>expect \ - D:new_a E:new_a && - git rev-parse >actual \ - :1:new_a :2:new_a :3:new_a && - test_cmp expect actual && - - # Test that the two-way merge in new_a is as expected - git cat-file -p D:new_a >ours && - git cat-file -p E:new_a >theirs && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "E^0" \ - ours empty theirs && - sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && - git hash-object new_a >actual && - git hash-object ours >expect && - test_cmp expect actual - ) -' - -# Repeat the above testcase with precisely the same setup, other than with -# the two merge bases having different orderings of commit timestamps so -# that they are reversed in the order they are provided to merge-recursive, -# so that we can improve code coverage. -test_expect_success 'git detects differently handled merges conflict, swapped' ' - ( - cd rename-add && - - # Difference #1: Do cleanup from previous testrun - git reset --hard && - git clean -fdqx && - - # Difference #2: Change commit timestamps - btime=$(git log --no-walk --date=raw --format=%cd B | awk "{print \$1}") && - ctime=$(git log --no-walk --date=raw --format=%cd C | awk "{print \$1}") && - newctime=$(($btime+1)) && - git fast-export --no-data --all | sed -e s/$ctime/$newctime/ | git fast-import --force --quiet && - # End of most differences; rest is copy-paste of last test, - # other than swapping C:a and C:new_a due to order switch - - git checkout D^0 && - test_must_fail git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 1 out && - - git cat-file -p C:a >ours && - git cat-file -p C:new_a >theirs && - >empty && - test_must_fail git merge-file \ - -L "Temporary merge branch 1" \ - -L "" \ - -L "Temporary merge branch 2" \ - ours empty theirs && - sed -e "s/^\([<=>]\)/\1\1\1/" ours >ours-tweaked && - git hash-object ours-tweaked >expect && - git rev-parse >>expect \ - D:new_a E:new_a && - git rev-parse >actual \ - :1:new_a :2:new_a :3:new_a && - test_cmp expect actual && - - # Test that the two-way merge in new_a is as expected - git cat-file -p D:new_a >ours && - git cat-file -p E:new_a >theirs && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "E^0" \ - ours empty theirs && - sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && - git hash-object new_a >actual && - git hash-object ours >expect && - test_cmp expect actual - ) -' - -# -# criss-cross + modify/delete: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: file with contents 'A\n' -# Commit B: file with contents 'B\n' -# Commit C: file not present -# Commit D: file with contents 'B\n' -# Commit E: file not present -# -# Merging commits D & E should result in modify/delete conflict. - -test_expect_success 'setup criss-cross + modify/delete resolved differently' ' - test_create_repo modify-delete && - ( - cd modify-delete && - - echo A >file && - git add file && - test_tick && - git commit -m A && - - git branch B && - git checkout -b C && - git rm file && - test_tick && - git commit -m C && - - git checkout B && - echo B >file && - git add file && - test_tick && - git commit -m B && - - git checkout B^0 && - test_must_fail git merge C && - echo B >file && - git add file && - test_tick && - git commit -m D && - git tag D && - - git checkout C^0 && - test_must_fail git merge B && - git rm file && - test_tick && - git commit -m E && - git tag E - ) -' - -test_expect_success 'git detects conflict merging criss-cross+modify/delete' ' - ( - cd modify-delete && - - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - - git rev-parse >expect \ - master:file B:file && - git rev-parse >actual \ - :1:file :2:file && - test_cmp expect actual - ) -' - -test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' ' - ( - cd modify-delete && - - git reset --hard && - git checkout E^0 && - - test_must_fail git merge -s recursive D^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - - git rev-parse >expect \ - master:file B:file && - git rev-parse >actual \ - :1:file :3:file && - test_cmp expect actual - ) -' - -# SORRY FOR THE SUPER LONG DESCRIPTION, BUT THIS NEXT ONE IS HAIRY -# -# criss-cross + d/f conflict via add/add: -# Commit A: Neither file 'a' nor directory 'a/' exists. -# Commit B: Introduce 'a' -# Commit C: Introduce 'a/file' -# Commit D1: Merge B & C, keeping 'a' and deleting 'a/' -# Commit E1: Merge B & C, deleting 'a' but keeping 'a/file' -# -# B D1 or D2 -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E1 or E2 or E3 -# -# I'll describe D2, E2, & E3 (which are alternatives for D1 & E1) more below... -# -# Merging D1 & E1 requires we first create a virtual merge base X from -# merging A & B in memory. There are several possibilities for the merge-base: -# 1: Keep both 'a' and 'a/file' (assuming crazy filesystem allowing a tree -# with a directory and file at same path): results in merge of D1 & E1 -# being clean with both files deleted. Bad (no conflict detected). -# 2: Keep 'a' but not 'a/file': Merging D1 & E1 is clean and matches E1. Bad. -# 3: Keep 'a/file' but not 'a': Merging D1 & E1 is clean and matches D1. Bad. -# 4: Keep neither file: Merging D1 & E1 reports the D/F add/add conflict. -# -# So 4 sounds good for this case, but if we were to merge D1 & E3, where E3 -# is defined as: -# Commit E3: Merge B & C, keeping modified a, and deleting a/ -# then we'd get an add/add conflict for 'a', which seems suboptimal. A little -# creativity leads us to an alternate choice: -# 5: Keep 'a' as 'a~$UNIQUE' and a/file; results: -# Merge D1 & E1: rename/delete conflict for 'a'; a/file silently deleted -# Merge D1 & E3 is clean, as expected. -# -# So choice 5 at least provides some kind of conflict for the original case, -# and can merge cleanly as expected with D1 and E3. It also made things just -# slightly funny for merging D1 and e$, where E4 is defined as: -# Commit E4: Merge B & C, modifying 'a' and renaming to 'a2', and deleting 'a/' -# in this case, we'll get a rename/rename(1to2) conflict because a~$UNIQUE -# gets renamed to 'a' in D1 and to 'a2' in E4. But that's better than having -# two files (both 'a' and 'a2') sitting around without the user being notified -# that we could detect they were related and need to be merged. Also, choice -# 5 makes the handling of 'a/file' seem suboptimal. What if we were to merge -# D2 and E4, where D2 is: -# Commit D2: Merge B & C, renaming 'a'->'a2', keeping 'a/file' -# This would result in a clean merge with 'a2' having three-way merged -# contents (good), and deleting 'a/' (bad) -- it doesn't detect the -# conflict in how the different sides treated a/file differently. -# Continuing down the creative route: -# 6: Keep 'a' as 'a~$UNIQUE1' and keep 'a/' as 'a~$UNIQUE2/'; results: -# Merge D1 & E1: rename/delete conflict for 'a' and each path under 'a/'. -# Merge D1 & E3: clean, as expected. -# Merge D1 & E4: rename/rename(1to2) conflict on 'a' vs 'a2'. -# Merge D2 & E4: clean for 'a2', rename/delete for a/file -# -# Choice 6 could cause rename detection to take longer (providing more targets -# that need to be searched). Also, the conflict message for each path under -# 'a/' might be annoying unless we can detect it at the directory level, print -# it once, and then suppress it for individual filepaths underneath. -# -# -# As of time of writing, git uses choice 5. Directory rename detection and -# rename detection performance improvements might make choice 6 a desirable -# improvement. But we can at least document where we fall short for now... -# -# -# Historically, this testcase also used: -# Commit E2: Merge B & C, deleting 'a' but keeping slightly modified 'a/file' -# The merge of D1 & E2 is very similar to D1 & E1 -- it has similar issues for -# path 'a', but should always result in a modify/delete conflict for path -# 'a/file'. These tests ran the two merges -# D1 & E1 -# D1 & E2 -# in both directions, to check for directional issues with D/F conflict -# handling. Later we added -# D1 & E3 -# D1 & E4 -# D2 & E4 -# for good measure, though we only ran those one way because we had pretty -# good confidence in merge-recursive's directional handling of D/F issues. -# -# Just to summarize all the intermediate merge commits: -# Commit D1: Merge B & C, keeping a and deleting a/ -# Commit D2: Merge B & C, renaming a->a2, keeping a/file -# Commit E1: Merge B & C, deleting a but keeping a/file -# Commit E2: Merge B & C, deleting a but keeping slightly modified a/file -# Commit E3: Merge B & C, keeping modified a, and deleting a/ -# Commit E4: Merge B & C, modifying 'a' and renaming to 'a2', and deleting 'a/' -# - -test_expect_success 'setup differently handled merges of directory/file conflict' ' - test_create_repo directory-file && - ( - cd directory-file && - - >ignore-me && - git add ignore-me && - test_tick && - git commit -m A && - git tag A && - - git branch B && - git checkout -b C && - mkdir a && - test_write_lines a b c d e f g >a/file && - git add a/file && - test_tick && - git commit -m C && - - git checkout B && - test_write_lines 1 2 3 4 5 6 7 >a && - git add a && - test_tick && - git commit -m B && - - git checkout B^0 && - git merge -s ours -m D1 C^0 && - git tag D1 && - - git checkout B^0 && - test_must_fail git merge C^0 && - git clean -fd && - git rm -rf a/ && - git rm a && - git cat-file -p B:a >a2 && - git add a2 && - git commit -m D2 && - git tag D2 && - - git checkout C^0 && - git merge -s ours -m E1 B^0 && - git tag E1 && - - git checkout C^0 && - git merge -s ours -m E2 B^0 && - test_write_lines a b c d e f g h >a/file && - git add a/file && - git commit --amend -C HEAD && - git tag E2 && - - git checkout C^0 && - test_must_fail git merge B^0 && - git clean -fd && - git rm -rf a/ && - test_write_lines 1 2 3 4 5 6 7 8 >a && - git add a && - git commit -m E3 && - git tag E3 && - - git checkout C^0 && - test_must_fail git merge B^0 && - git clean -fd && - git rm -rf a/ && - git rm a && - test_write_lines 1 2 3 4 5 6 7 8 >a2 && - git add a2 && - git commit -m E4 && - git tag E4 - ) -' - -test_expect_success 'merge of D1 & E1 fails but has appropriate contents' ' - test_when_finished "git -C directory-file reset --hard" && - test_when_finished "git -C directory-file clean -fdqx" && - ( - cd directory-file && - - git checkout D1^0 && - - test_must_fail git merge -s recursive E1^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:ignore-me B:a && - git rev-parse >actual \ - :0:ignore-me :2:a && - test_cmp expect actual - ) -' - -test_expect_success 'merge of E1 & D1 fails but has appropriate contents' ' - test_when_finished "git -C directory-file reset --hard" && - test_when_finished "git -C directory-file clean -fdqx" && - ( - cd directory-file && - - git checkout E1^0 && - - test_must_fail git merge -s recursive D1^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:ignore-me B:a && - git rev-parse >actual \ - :0:ignore-me :3:a && - test_cmp expect actual - ) -' - -test_expect_success 'merge of D1 & E2 fails but has appropriate contents' ' - test_when_finished "git -C directory-file reset --hard" && - test_when_finished "git -C directory-file clean -fdqx" && - ( - cd directory-file && - - git checkout D1^0 && - - test_must_fail git merge -s recursive E2^0 && - - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 2 out && - - git rev-parse >expect \ - B:a E2:a/file C:a/file A:ignore-me && - git rev-parse >actual \ - :2:a :3:a/file :1:a/file :0:ignore-me && - test_cmp expect actual && - - test_path_is_file a~HEAD - ) -' - -test_expect_success 'merge of E2 & D1 fails but has appropriate contents' ' - test_when_finished "git -C directory-file reset --hard" && - test_when_finished "git -C directory-file clean -fdqx" && - ( - cd directory-file && - - git checkout E2^0 && - - test_must_fail git merge -s recursive D1^0 && - - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 2 out && - - git rev-parse >expect \ - B:a E2:a/file C:a/file A:ignore-me && - git rev-parse >actual \ - :3:a :2:a/file :1:a/file :0:ignore-me && - test_cmp expect actual && - - test_path_is_file a~D1^0 - ) -' - -test_expect_success 'merge of D1 & E3 succeeds' ' - test_when_finished "git -C directory-file reset --hard" && - test_when_finished "git -C directory-file clean -fdqx" && - ( - cd directory-file && - - git checkout D1^0 && - - git merge -s recursive E3^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:ignore-me E3:a && - git rev-parse >actual \ - :0:ignore-me :0:a && - test_cmp expect actual - ) -' - -test_expect_success 'merge of D1 & E4 notifies user a and a2 are related' ' - test_when_finished "git -C directory-file reset --hard" && - test_when_finished "git -C directory-file clean -fdqx" && - ( - cd directory-file && - - git checkout D1^0 && - - test_must_fail git merge -s recursive E4^0 && - - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:ignore-me B:a D1:a E4:a2 && - git rev-parse >actual \ - :0:ignore-me :1:a~Temporary\ merge\ branch\ 2 :2:a :3:a2 && - test_cmp expect actual - ) -' - -test_expect_failure 'merge of D2 & E4 merges a2s & reports conflict for a/file' ' - test_when_finished "git -C directory-file reset --hard" && - test_when_finished "git -C directory-file clean -fdqx" && - ( - cd directory-file && - - git checkout D2^0 && - - test_must_fail git merge -s recursive E4^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:ignore-me E4:a2 D2:a/file && - git rev-parse >actual \ - :0:ignore-me :0:a2 :2:a/file && - test_cmp expect actual - ) -' - -# -# criss-cross with rename/rename(1to2)/modify followed by -# rename/rename(2to1)/modify: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: new file: a -# Commit B: rename a->b, modifying by adding a line -# Commit C: rename a->c -# Commit D: merge B&C, resolving conflict by keeping contents in newname -# Commit E: merge B&C, resolving conflict similar to D but adding another line -# -# There is a conflict merging B & C, but one of filename not of file -# content. Whoever created D and E chose specific resolutions for that -# conflict resolution. Now, since: (1) there is no content conflict -# merging B & C, (2) D does not modify that merged content further, and (3) -# both D & E resolve the name conflict in the same way, the modification to -# newname in E should not cause any conflicts when it is merged with D. -# (Note that this can be accomplished by having the virtual merge base have -# the merged contents of b and c stored in a file named a, which seems like -# the most logical choice anyway.) -# -# Comment from Junio: I do not necessarily agree with the choice "a", but -# it feels sound to say "B and C do not agree what the final pathname -# should be, but we know this content was derived from the common A:a so we -# use one path whose name is arbitrary in the virtual merge base X between -# D and E" and then further let the rename detection to notice that that -# arbitrary path gets renamed between X-D to "newname" and X-E also to -# "newname" to resolve it as both sides renaming it to the same new -# name. It is akin to what we do at the content level, i.e. "B and C do not -# agree what the final contents should be, so we leave the conflict marker -# but that may cancel out at the final merge stage". - -test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' - test_create_repo rename-squared-squared && - ( - cd rename-squared-squared && - - printf "1\n2\n3\n4\n5\n6\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - echo 7 >>b && - git add -u && - git commit -m B && - - git checkout -b C A && - git mv a c && - git commit -m C && - - git checkout -q B^0 && - git merge --no-commit -s ours C^0 && - git mv b newname && - git commit -m "Merge commit C^0 into HEAD" && - git tag D && - - git checkout -q C^0 && - git merge --no-commit -s ours B^0 && - git mv c newname && - printf "7\n8\n" >>newname && - git add -u && - git commit -m "Merge commit B^0 into HEAD" && - git tag E - ) -' - -test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' - ( - cd rename-squared-squared && - - git checkout D^0 && - - git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 1 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 1 out && - - test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname) - ) -' - -# -# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: new file: a -# Commit B: rename a->b -# Commit C: rename a->c, add different a -# Commit D: merge B&C, keeping b&c and (new) a modified at beginning -# Commit E: merge B&C, keeping b&c and (new) a modified at end -# -# Merging commits D & E should result in no conflict; doing so correctly -# requires getting the virtual merge base (from merging B&C) right, handling -# renaming carefully (both in the virtual merge base and later), and getting -# content merge handled. - -test_expect_success 'setup criss-cross + rename/rename/add-source + modify/modify' ' - test_create_repo rename-rename-add-source && - ( - cd rename-rename-add-source && - - printf "lots\nof\nwords\nand\ncontent\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a c && - printf "2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m C && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git checkout C -- a c && - mv a old_a && - echo 1 >a && - cat old_a >>a && - rm old_a && - git add -u && - git commit -m "Merge commit C^0 into HEAD" && - git tag D && - - git checkout C^0 && - git merge --no-commit -s ours B^0 && - git checkout B -- b && - echo 8 >>a && - git add -u && - git commit -m "Merge commit B^0 into HEAD" && - git tag E - ) -' - -test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' - ( - cd rename-rename-add-source && - - git checkout D^0 && - - git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 1 out && - - printf "1\n2\n3\n4\n5\n6\n7\n8\n" >correct && - git rev-parse >expect \ - A:a A:a \ - correct && - git rev-parse >actual \ - :0:b :0:c && - git hash-object >>actual \ - a && - test_cmp expect actual - ) -' - -# -# criss-cross with rename/rename(1to2)/add-dest + simple modify: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: new file: a -# Commit B: rename a->b, add c -# Commit C: rename a->c -# Commit D: merge B&C, keeping A:a and B:c -# Commit E: merge B&C, keeping A:a and slightly modified c from B -# -# Merging commits D & E should result in no conflict. The virtual merge -# base of B & C needs to not delete B:c for that to work, though... - -test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' ' - test_create_repo rename-rename-add-dest && - ( - cd rename-rename-add-dest && - - >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - printf "1\n2\n3\n4\n5\n6\n7\n" >c && - git add c && - git commit -m B && - - git checkout -b C A && - git mv a c && - git commit -m C && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git mv b a && - git commit -m "D is like B but renames b back to a" && - git tag D && - - git checkout B^0 && - git merge --no-commit -s ours C^0 && - git mv b a && - echo 8 >>c && - git add c && - git commit -m "E like D but has mod in c" && - git tag E - ) -' - -test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' ' - ( - cd rename-rename-add-dest && - - git checkout D^0 && - - git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:a E:c && - git rev-parse >actual \ - :0:a :0:c && - test_cmp expect actual - ) -' - -# -# criss-cross with modify/modify on a symlink: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: simple simlink fickle->lagoon -# Commit B: redirect fickle->disneyland -# Commit C: redirect fickle->home -# Commit D: merge B&C, resolving in favor of B -# Commit E: merge B&C, resolving in favor of C -# -# This is an obvious modify/modify conflict for the symlink 'fickle'. Can -# git detect it? - -test_expect_success 'setup symlink modify/modify' ' - test_create_repo symlink-modify-modify && - ( - cd symlink-modify-modify && - - test_ln_s_add lagoon fickle && - git commit -m A && - git tag A && - - git checkout -b B A && - git rm fickle && - test_ln_s_add disneyland fickle && - git commit -m B && - - git checkout -b C A && - git rm fickle && - test_ln_s_add home fickle && - git add fickle && - git commit -m C && - - git checkout -q B^0 && - git merge -s ours -m D C^0 && - git tag D && - - git checkout -q C^0 && - git merge -s ours -m E B^0 && - git tag E - ) -' - -test_expect_failure 'check symlink modify/modify' ' - ( - cd symlink-modify-modify && - - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 1 out - ) -' - -# -# criss-cross with add/add of a symlink: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: No symlink or path exists yet -# Commit B: set up symlink: fickle->disneyland -# Commit C: set up symlink: fickle->home -# Commit D: merge B&C, resolving in favor of B -# Commit E: merge B&C, resolving in favor of C -# -# This is an obvious add/add conflict for the symlink 'fickle'. Can -# git detect it? - -test_expect_success 'setup symlink add/add' ' - test_create_repo symlink-add-add && - ( - cd symlink-add-add && - - touch ignoreme && - git add ignoreme && - git commit -m A && - git tag A && - - git checkout -b B A && - test_ln_s_add disneyland fickle && - git commit -m B && - - git checkout -b C A && - test_ln_s_add home fickle && - git add fickle && - git commit -m C && - - git checkout -q B^0 && - git merge -s ours -m D C^0 && - git tag D && - - git checkout -q C^0 && - git merge -s ours -m E B^0 && - git tag E - ) -' - -test_expect_failure 'check symlink add/add' ' - ( - cd symlink-add-add && - - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out - ) -' - -# -# criss-cross with modify/modify on a submodule: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: simple submodule repo -# Commit B: update repo -# Commit C: update repo differently -# Commit D: merge B&C, resolving in favor of B -# Commit E: merge B&C, resolving in favor of C -# -# This is an obvious modify/modify conflict for the submodule 'repo'. Can -# git detect it? - -test_expect_success 'setup submodule modify/modify' ' - test_create_repo submodule-modify-modify && - ( - cd submodule-modify-modify && - - test_create_repo submod && - ( - cd submod && - touch file-A && - git add file-A && - git commit -m A && - git tag A && - - git checkout -b B A && - touch file-B && - git add file-B && - git commit -m B && - git tag B && - - git checkout -b C A && - touch file-C && - git add file-C && - git commit -m C && - git tag C - ) && - - git -C submod reset --hard A && - git add submod && - git commit -m A && - git tag A && - - git checkout -b B A && - git -C submod reset --hard B && - git add submod && - git commit -m B && - - git checkout -b C A && - git -C submod reset --hard C && - git add submod && - git commit -m C && - - git checkout -q B^0 && - git merge -s ours -m D C^0 && - git tag D && - - git checkout -q C^0 && - git merge -s ours -m E B^0 && - git tag E - ) -' - -test_expect_failure 'check submodule modify/modify' ' - ( - cd submodule-modify-modify && - - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 1 out - ) -' - -# -# criss-cross with add/add on a submodule: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: nothing of note -# Commit B: introduce submodule repo -# Commit C: introduce submodule repo at different commit -# Commit D: merge B&C, resolving in favor of B -# Commit E: merge B&C, resolving in favor of C -# -# This is an obvious add/add conflict for the submodule 'repo'. Can -# git detect it? - -test_expect_success 'setup submodule add/add' ' - test_create_repo submodule-add-add && - ( - cd submodule-add-add && - - test_create_repo submod && - ( - cd submod && - touch file-A && - git add file-A && - git commit -m A && - git tag A && - - git checkout -b B A && - touch file-B && - git add file-B && - git commit -m B && - git tag B && - - git checkout -b C A && - touch file-C && - git add file-C && - git commit -m C && - git tag C - ) && - - touch irrelevant-file && - git add irrelevant-file && - git commit -m A && - git tag A && - - git checkout -b B A && - git -C submod reset --hard B && - git add submod && - git commit -m B && - - git checkout -b C A && - git -C submod reset --hard C && - git add submod && - git commit -m C && - - git checkout -q B^0 && - git merge -s ours -m D C^0 && - git tag D && - - git checkout -q C^0 && - git merge -s ours -m E B^0 && - git tag E - ) -' - -test_expect_failure 'check submodule add/add' ' - ( - cd submodule-add-add && - - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out - ) -' - -# -# criss-cross with conflicting entry types: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: nothing of note -# Commit B: introduce submodule 'path' -# Commit C: introduce symlink 'path' -# Commit D: merge B&C, resolving in favor of B -# Commit E: merge B&C, resolving in favor of C -# -# This is an obvious add/add conflict for 'path'. Can git detect it? - -test_expect_success 'setup conflicting entry types (submodule vs symlink)' ' - test_create_repo submodule-symlink-add-add && - ( - cd submodule-symlink-add-add && - - test_create_repo path && - ( - cd path && - touch file-B && - git add file-B && - git commit -m B && - git tag B - ) && - - touch irrelevant-file && - git add irrelevant-file && - git commit -m A && - git tag A && - - git checkout -b B A && - git -C path reset --hard B && - git add path && - git commit -m B && - - git checkout -b C A && - rm -rf path/ && - test_ln_s_add irrelevant-file path && - git commit -m C && - - git checkout -q B^0 && - git merge -s ours -m D C^0 && - git tag D && - - git checkout -q C^0 && - git merge -s ours -m E B^0 && - git tag E - ) -' - -test_expect_failure 'check conflicting entry types (submodule vs symlink)' ' - ( - cd submodule-symlink-add-add && - - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out - ) -' - -# -# criss-cross with regular files that have conflicting modes: -# -# B D -# o---o -# / \ / \ -# A o X ? F -# \ / \ / -# o---o -# C E -# -# Commit A: nothing of note -# Commit B: introduce file source_me.bash, not executable -# Commit C: introduce file source_me.bash, executable -# Commit D: merge B&C, resolving in favor of B -# Commit E: merge B&C, resolving in favor of C -# -# This is an obvious add/add mode conflict. Can git detect it? - -test_expect_success 'setup conflicting modes for regular file' ' - test_create_repo regular-file-mode-conflict && - ( - cd regular-file-mode-conflict && - - touch irrelevant-file && - git add irrelevant-file && - git commit -m A && - git tag A && - - git checkout -b B A && - echo "command_to_run" >source_me.bash && - git add source_me.bash && - git commit -m B && - - git checkout -b C A && - echo "command_to_run" >source_me.bash && - git add source_me.bash && - test_chmod +x source_me.bash && - git commit -m C && - - git checkout -q B^0 && - git merge -s ours -m D C^0 && - git tag D && - - git checkout -q C^0 && - git merge -s ours -m E B^0 && - git tag E - ) -' - -test_expect_failure 'check conflicting modes for regular file' ' - ( - cd regular-file-mode-conflict && - - git checkout D^0 && - - test_must_fail git merge -s recursive E^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out - ) -' - -# Setup: -# L1---L2 -# / \ / \ -# master X ? -# \ / \ / -# R1---R2 -# -# Where: -# master has two files, named 'b' and 'a' -# branches L1 and R1 both modify each of the two files in conflicting ways -# -# L2 is a merge of R1 into L1; more on it later. -# R2 is a merge of L1 into R1; more on it later. -# -# X is an auto-generated merge-base used when merging L2 and R2. -# since X is a merge of L1 and R1, it has conflicting versions of each file -# -# More about L2 and R2: -# - both resolve the conflicts in 'b' and 'a' differently -# - L2 renames 'b' to 'm' -# - R2 renames 'a' to 'm' -# -# In the end, in file 'm' we have four different conflicting files (from -# two versions of 'b' and two of 'a'). In addition, if -# merge.conflictstyle is diff3, then the base version also has -# conflict markers of its own, leading to a total of three levels of -# conflict markers. This is a pretty weird corner case, but we just want -# to ensure that we handle it as well as practical. - -test_expect_success 'setup nested conflicts' ' - test_create_repo nested_conflicts && - ( - cd nested_conflicts && - - # Create some related files now - for i in $(test_seq 1 10) - do - echo Random base content line $i - done >initial && - - cp initial b_L1 && - cp initial b_R1 && - cp initial b_L2 && - cp initial b_R2 && - cp initial a_L1 && - cp initial a_R1 && - cp initial a_L2 && - cp initial a_R2 && - - test_write_lines b b_L1 >>b_L1 && - test_write_lines b b_R1 >>b_R1 && - test_write_lines b b_L2 >>b_L2 && - test_write_lines b b_R2 >>b_R2 && - test_write_lines a a_L1 >>a_L1 && - test_write_lines a a_R1 >>a_R1 && - test_write_lines a a_L2 >>a_L2 && - test_write_lines a a_R2 >>a_R2 && - - # Setup original commit (or merge-base), consisting of - # files named "b" and "a" - cp initial b && - cp initial a && - echo b >>b && - echo a >>a && - git add b a && - test_tick && git commit -m initial && - - git branch L && - git branch R && - - # Handle the left side - git checkout L && - mv -f b_L1 b && - mv -f a_L1 a && - git add b a && - test_tick && git commit -m "version L1 of files" && - git tag L1 && - - # Handle the right side - git checkout R && - mv -f b_R1 b && - mv -f a_R1 a && - git add b a && - test_tick && git commit -m "version R1 of files" && - git tag R1 && - - # Create first merge on left side - git checkout L && - test_must_fail git merge R1 && - mv -f b_L2 b && - mv -f a_L2 a && - git add b a && - git mv b m && - test_tick && git commit -m "left merge, rename b->m" && - git tag L2 && - - # Create first merge on right side - git checkout R && - test_must_fail git merge L1 && - mv -f b_R2 b && - mv -f a_R2 a && - git add b a && - git mv a m && - test_tick && git commit -m "right merge, rename a->m" && - git tag R2 - ) -' - -test_expect_success 'check nested conflicts' ' - ( - cd nested_conflicts && - - git clean -f && - MASTER=$(git rev-parse --short master) && - git checkout L2^0 && - - # Merge must fail; there is a conflict - test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R2^0 && - - # Make sure the index has the right number of entries - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - # Ensure we have the correct number of untracked files - git ls-files -o >out && - test_line_count = 1 out && - - # Create a and b from virtual merge base X - git cat-file -p master:a >base && - git cat-file -p L1:a >ours && - git cat-file -p R1:a >theirs && - test_must_fail git merge-file --diff3 \ - -L "Temporary merge branch 1" \ - -L "$MASTER" \ - -L "Temporary merge branch 2" \ - ours \ - base \ - theirs && - sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_a && - - git cat-file -p master:b >base && - git cat-file -p L1:b >ours && - git cat-file -p R1:b >theirs && - test_must_fail git merge-file --diff3 \ - -L "Temporary merge branch 1" \ - -L "$MASTER" \ - -L "Temporary merge branch 2" \ - ours \ - base \ - theirs && - sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_b && - - # Compare :2:m to expected values - git cat-file -p L2:m >ours && - git cat-file -p R2:b >theirs && - test_must_fail git merge-file --diff3 \ - -L "HEAD:m" \ - -L "merged common ancestors:b" \ - -L "R2^0:b" \ - ours \ - vmb_b \ - theirs && - sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_2 && - git cat-file -p :2:m >actual && - test_cmp m_stage_2 actual && - - # Compare :3:m to expected values - git cat-file -p L2:a >ours && - git cat-file -p R2:m >theirs && - test_must_fail git merge-file --diff3 \ - -L "HEAD:a" \ - -L "merged common ancestors:a" \ - -L "R2^0:m" \ - ours \ - vmb_a \ - theirs && - sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_3 && - git cat-file -p :3:m >actual && - test_cmp m_stage_3 actual && - - # Compare m to expected contents - >empty && - cp m_stage_2 expected_final_m && - test_must_fail git merge-file --diff3 \ - -L "HEAD" \ - -L "merged common ancestors" \ - -L "R2^0" \ - expected_final_m \ - empty \ - m_stage_3 && - test_cmp expected_final_m m - ) -' - -# Setup: -# L1---L2---L3 -# / \ / \ / \ -# master X1 X2 ? -# \ / \ / \ / -# R1---R2---R3 -# -# Where: -# master has one file named 'content' -# branches L1 and R1 both modify each of the two files in conflicting ways -# -# L (n>1) is a merge of R into L -# R (n>1) is a merge of L into R -# L and R resolve the conflicts differently. -# -# X is an auto-generated merge-base used when merging L and R. -# By construction, X1 has conflict markers due to conflicting versions. -# X2, due to using merge.conflictstyle=3, has nested conflict markers. -# -# So, merging R3 into L3 using merge.conflictstyle=3 should show the -# nested conflict markers from X2 in the base version -- that means we -# have three levels of conflict markers. Can we distinguish all three? - -test_expect_success 'setup virtual merge base with nested conflicts' ' - test_create_repo virtual_merge_base_has_nested_conflicts && - ( - cd virtual_merge_base_has_nested_conflicts && - - # Create some related files now - for i in $(test_seq 1 10) - do - echo Random base content line $i - done >content && - - # Setup original commit - git add content && - test_tick && git commit -m initial && - - git branch L && - git branch R && - - # Create L1 - git checkout L && - echo left >>content && - git add content && - test_tick && git commit -m "version L1 of content" && - git tag L1 && - - # Create R1 - git checkout R && - echo right >>content && - git add content && - test_tick && git commit -m "version R1 of content" && - git tag R1 && - - # Create L2 - git checkout L && - test_must_fail git -c merge.conflictstyle=diff3 merge R1 && - git checkout L1 content && - test_tick && git commit -m "version L2 of content" && - git tag L2 && - - # Create R2 - git checkout R && - test_must_fail git -c merge.conflictstyle=diff3 merge L1 && - git checkout R1 content && - test_tick && git commit -m "version R2 of content" && - git tag R2 && - - # Create L3 - git checkout L && - test_must_fail git -c merge.conflictstyle=diff3 merge R2 && - git checkout L1 content && - test_tick && git commit -m "version L3 of content" && - git tag L3 && - - # Create R3 - git checkout R && - test_must_fail git -c merge.conflictstyle=diff3 merge L2 && - git checkout R1 content && - test_tick && git commit -m "version R3 of content" && - git tag R3 - ) -' - -test_expect_success 'check virtual merge base with nested conflicts' ' - ( - cd virtual_merge_base_has_nested_conflicts && - - MASTER=$(git rev-parse --short master) && - git checkout L3^0 && - - # Merge must fail; there is a conflict - test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R3^0 && - - # Make sure the index has the right number of entries - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 3 out && - # Ensure we have the correct number of untracked files - git ls-files -o >out && - test_line_count = 1 out && - - # Compare :[23]:content to expected values - git rev-parse L1:content R1:content >expect && - git rev-parse :2:content :3:content >actual && - test_cmp expect actual && - - # Imitate X1 merge base, except without long enough conflict - # markers because a subsequent sed will modify them. Put - # result into vmb. - git cat-file -p master:content >base && - git cat-file -p L:content >left && - git cat-file -p R:content >right && - cp left merged-once && - test_must_fail git merge-file --diff3 \ - -L "Temporary merge branch 1" \ - -L "$MASTER" \ - -L "Temporary merge branch 2" \ - merged-once \ - base \ - right && - sed -e "s/^\([<|=>]\)/\1\1\1/" merged-once >vmb && - - # Imitate X2 merge base, overwriting vmb. Note that we - # extend both sets of conflict markers to make them longer - # with the sed command. - cp left merged-twice && - test_must_fail git merge-file --diff3 \ - -L "Temporary merge branch 1" \ - -L "merged common ancestors" \ - -L "Temporary merge branch 2" \ - merged-twice \ - vmb \ - right && - sed -e "s/^\([<|=>]\)/\1\1\1/" merged-twice >vmb && - - # Compare :1:content to expected value - git cat-file -p :1:content >actual && - test_cmp vmb actual && - - # Determine expected content in final outer merge, compare to - # what the merge generated. - cp -f left expect && - test_must_fail git merge-file --diff3 \ - -L "HEAD" -L "merged common ancestors" -L "R3^0" \ - expect vmb right && - test_cmp expect content - ) -' - -test_done diff --git a/t/t6037-merge-ours-theirs.sh b/t/t6037-merge-ours-theirs.sh deleted file mode 100755 index 0aebc6c..0000000 --- a/t/t6037-merge-ours-theirs.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/sh - -test_description='Merge-recursive ours and theirs variants' -. ./test-lib.sh - -test_expect_success setup ' - for i in 1 2 3 4 5 6 7 8 9 - do - echo "$i" - done >file && - git add file && - cp file elif && - git commit -m initial && - - sed -e "s/1/one/" -e "s/9/nine/" >file file .gitattributes && - - git reset --hard master && - git merge -s recursive -X theirs side && - git diff --exit-code side HEAD -- file && - - git reset --hard master && - git merge -s recursive -X ours side && - git diff --exit-code master HEAD -- file -' - -test_expect_success 'pull passes -X to underlying merge' ' - git reset --hard master && git pull -s recursive -Xours . side && - git reset --hard master && git pull -s recursive -X ours . side && - git reset --hard master && git pull -s recursive -Xtheirs . side && - git reset --hard master && git pull -s recursive -X theirs . side && - git reset --hard master && test_must_fail git pull -s recursive -X bork . side -' - -test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' ' - git reset --hard master && - git checkout -b two master && - ln -s target-zero link && - git add link && - git commit -m "add link pointing to zero" && - - ln -f -s target-two link && - git commit -m "add link pointing to two" link && - - git checkout -b one HEAD^ && - ln -f -s target-one link && - git commit -m "add link pointing to one" link && - - # we expect symbolic links not to resolve automatically, of course - git checkout one^0 && - test_must_fail git merge -s recursive two && - - # favor theirs to resolve to target-two? - git reset --hard && - git checkout one^0 && - git merge -s recursive -X theirs two && - git diff --exit-code two HEAD link && - - # favor ours to resolve to target-one? - git reset --hard && - git checkout one^0 && - git merge -s recursive -X ours two && - git diff --exit-code one HEAD link - -' - -test_done diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh deleted file mode 100755 index 89c86d4..0000000 --- a/t/t6038-merge-text-auto.sh +++ /dev/null @@ -1,203 +0,0 @@ -#!/bin/sh - -test_description='CRLF merge conflict across text=auto change - -* [master] remove .gitattributes - ! [side] add line from b --- - + [side] add line from b -* [master] remove .gitattributes -* [master^] add line from a -* [master~2] normalize file -*+ [side^] Initial -' - -. ./test-lib.sh - -test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b - -compare_files () { - tr '\015\000' QN <"$1" >"$1".expect && - tr '\015\000' QN <"$2" >"$2".actual && - test_cmp "$1".expect "$2".actual && - rm "$1".expect "$2".actual -} - -test_expect_success setup ' - git config core.autocrlf false && - - echo first line | append_cr >file && - echo first line >control_file && - echo only line >inert_file && - - git add file control_file inert_file && - test_tick && - git commit -m "Initial" && - git tag initial && - git branch side && - - echo "* text=auto" >.gitattributes && - echo first line >file && - git add .gitattributes file && - test_tick && - git commit -m "normalize file" && - - echo same line | append_cr >>file && - echo same line >>control_file && - git add file control_file && - test_tick && - git commit -m "add line from a" && - git tag a && - - git rm .gitattributes && - rm file && - git checkout file && - test_tick && - git commit -m "remove .gitattributes" && - git tag c && - - git checkout side && - echo same line | append_cr >>file && - echo same line >>control_file && - git add file control_file && - test_tick && - git commit -m "add line from b" && - git tag b && - - git checkout master -' - -test_expect_success 'set up fuzz_conflict() helper' ' - fuzz_conflict() { - sed $SED_OPTIONS -e "s/^\([<>=]......\) .*/\1/" "$@" - } -' - -test_expect_success 'Merge after setting text=auto' ' - cat <<-\EOF >expected && - first line - same line - EOF - - if test_have_prereq NATIVE_CRLF; then - append_cr expected.temp && - mv expected.temp expected - fi && - git config merge.renormalize true && - git rm -fr . && - rm -f .gitattributes && - git reset --hard a && - git merge b && - compare_files expected file -' - -test_expect_success 'Merge addition of text=auto eol=LF' ' - git config core.eol lf && - cat <<-\EOF >expected && - first line - same line - EOF - - git config merge.renormalize true && - git rm -fr . && - rm -f .gitattributes && - git reset --hard b && - git merge a && - compare_files expected file -' - -test_expect_success 'Merge addition of text=auto eol=CRLF' ' - git config core.eol crlf && - cat <<-\EOF >expected && - first line - same line - EOF - - append_cr expected.temp && - mv expected.temp expected && - git config merge.renormalize true && - git rm -fr . && - rm -f .gitattributes && - git reset --hard b && - echo >&2 "After git reset --hard b" && - git ls-files -s --eol >&2 && - git merge a && - compare_files expected file -' - -test_expect_success 'Detect CRLF/LF conflict after setting text=auto' ' - git config core.eol native && - echo "<<<<<<<" >expected && - echo first line >>expected && - echo same line >>expected && - echo ======= >>expected && - echo first line | append_cr >>expected && - echo same line | append_cr >>expected && - echo ">>>>>>>" >>expected && - git config merge.renormalize false && - rm -f .gitattributes && - git reset --hard a && - test_must_fail git merge b && - fuzz_conflict file >file.fuzzy && - compare_files expected file.fuzzy -' - -test_expect_success 'Detect LF/CRLF conflict from addition of text=auto' ' - echo "<<<<<<<" >expected && - echo first line | append_cr >>expected && - echo same line | append_cr >>expected && - echo ======= >>expected && - echo first line >>expected && - echo same line >>expected && - echo ">>>>>>>" >>expected && - git config merge.renormalize false && - rm -f .gitattributes && - git reset --hard b && - test_must_fail git merge a && - fuzz_conflict file >file.fuzzy && - compare_files expected file.fuzzy -' - -test_expect_success 'checkout -m after setting text=auto' ' - cat <<-\EOF >expected && - first line - same line - EOF - - git config merge.renormalize true && - git rm -fr . && - rm -f .gitattributes && - git reset --hard initial && - git restore --source=a -- . && - git checkout -m b && - git diff --no-index --ignore-cr-at-eol expected file -' - -test_expect_success 'checkout -m addition of text=auto' ' - cat <<-\EOF >expected && - first line - same line - EOF - - git config merge.renormalize true && - git rm -fr . && - rm -f .gitattributes file && - git reset --hard initial && - git restore --source=b -- . && - git checkout -m a && - git diff --no-index --ignore-cr-at-eol expected file -' - -test_expect_success 'Test delete/normalize conflict' ' - git checkout -f side && - git rm -fr . && - rm -f .gitattributes && - git reset --hard initial && - git rm file && - git commit -m "remove file" && - git checkout master && - git reset --hard a^ && - git merge side -' - -test_done diff --git a/t/t6039-merge-ignorecase.sh b/t/t6039-merge-ignorecase.sh deleted file mode 100755 index 531850d..0000000 --- a/t/t6039-merge-ignorecase.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -test_description='git-merge with case-changing rename on case-insensitive file system' - -. ./test-lib.sh - -if ! test_have_prereq CASE_INSENSITIVE_FS -then - skip_all='skipping case insensitive tests - case sensitive file system' - test_done -fi - -test_expect_success 'merge with case-changing rename' ' - test $(git config core.ignorecase) = true && - >TestCase && - git add TestCase && - git commit -m "add TestCase" && - git tag baseline && - git checkout -b with-camel && - >foo && - git add foo && - git commit -m "intervening commit" && - git checkout master && - git rm TestCase && - >testcase && - git add testcase && - git commit -m "rename to testcase" && - git checkout with-camel && - git merge master -m "merge" && - test_path_is_file testcase -' - -test_expect_success 'merge with case-changing rename on both sides' ' - git checkout master && - git reset --hard baseline && - git branch -D with-camel && - git checkout -b with-camel && - git mv TestCase testcase && - git commit -m "recase on branch" && - >foo && - git add foo && - git commit -m "intervening commit" && - git checkout master && - git rm TestCase && - >testcase && - git add testcase && - git commit -m "rename to testcase" && - git checkout with-camel && - git merge master -m "merge" && - test_path_is_file testcase -' - -test_done diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh deleted file mode 100755 index f163893..0000000 --- a/t/t6042-merge-rename-corner-cases.sh +++ /dev/null @@ -1,1437 +0,0 @@ -#!/bin/sh - -test_description="recursive merge corner cases w/ renames but not criss-crosses" -# t6036 has corner cases that involve both criss-cross merges and renames - -. ./test-lib.sh - -test_setup_rename_delete_untracked () { - test_create_repo rename-delete-untracked && - ( - cd rename-delete-untracked && - - echo "A pretty inscription" >ring && - git add ring && - test_tick && - git commit -m beginning && - - git branch people && - git checkout -b rename-the-ring && - git mv ring one-ring-to-rule-them-all && - test_tick && - git commit -m fullname && - - git checkout people && - git rm ring && - echo gollum >owner && - git add owner && - test_tick && - git commit -m track-people-instead-of-objects && - echo "Myyy PRECIOUSSS" >ring - ) -} - -test_expect_success "Does git preserve Gollum's precious artifact?" ' - test_setup_rename_delete_untracked && - ( - cd rename-delete-untracked && - - test_must_fail git merge -s recursive rename-the-ring && - - # Make sure git did not delete an untracked file - test_path_is_file ring - ) -' - -# Testcase setup for rename/modify/add-source: -# Commit A: new file: a -# Commit B: modify a slightly -# Commit C: rename a->b, add completely different a -# -# We should be able to merge B & C cleanly - -test_setup_rename_modify_add_source () { - test_create_repo rename-modify-add-source && - ( - cd rename-modify-add-source && - - printf "1\n2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - echo 8 >>a && - git add a && - git commit -m B && - - git checkout -b C A && - git mv a b && - echo something completely different >a && - git add a && - git commit -m C - ) -} - -test_expect_failure 'rename/modify/add-source conflict resolvable' ' - test_setup_rename_modify_add_source && - ( - cd rename-modify-add-source && - - git checkout B^0 && - - git merge -s recursive C^0 && - - git rev-parse >expect \ - B:a C:a && - git rev-parse >actual \ - b c && - test_cmp expect actual - ) -' - -test_setup_break_detection_1 () { - test_create_repo break-detection-1 && - ( - cd break-detection-1 && - - printf "1\n2\n3\n4\n5\n" >a && - echo foo >b && - git add a b && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a c && - echo "Completely different content" >a && - git add a && - git commit -m B && - - git checkout -b C A && - echo 6 >>a && - git add a && - git commit -m C - ) -} - -test_expect_failure 'conflict caused if rename not detected' ' - test_setup_break_detection_1 && - ( - cd break-detection-1 && - - git checkout -q C^0 && - git merge -s recursive B^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 1 out && - - test_line_count = 6 c && - git rev-parse >expect \ - B:a A:b && - git rev-parse >actual \ - :0:a :0:b && - test_cmp expect actual - ) -' - -test_setup_break_detection_2 () { - test_create_repo break-detection-2 && - ( - cd break-detection-2 && - - printf "1\n2\n3\n4\n5\n" >a && - echo foo >b && - git add a b && - git commit -m A && - git tag A && - - git checkout -b D A && - echo 7 >>a && - git add a && - git mv a c && - echo "Completely different content" >a && - git add a && - git commit -m D && - - git checkout -b E A && - git rm a && - echo "Completely different content" >>a && - git add a && - git commit -m E - ) -} - -test_expect_failure 'missed conflict if rename not detected' ' - test_setup_break_detection_2 && - ( - cd break-detection-2 && - - git checkout -q E^0 && - test_must_fail git merge -s recursive D^0 - ) -' - -# Tests for undetected rename/add-source causing a file to erroneously be -# deleted (and for mishandled rename/rename(1to1) causing the same issue). -# -# This test uses a rename/rename(1to1)+add-source conflict (1to1 means the -# same file is renamed on both sides to the same thing; it should trigger -# the 1to2 logic, which it would do if the add-source didn't cause issues -# for git's rename detection): -# Commit A: new file: a -# Commit B: rename a->b -# Commit C: rename a->b, add unrelated a - -test_setup_break_detection_3 () { - test_create_repo break-detection-3 && - ( - cd break-detection-3 && - - printf "1\n2\n3\n4\n5\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a b && - echo foobar >a && - git add a && - git commit -m C - ) -} - -test_expect_failure 'detect rename/add-source and preserve all data' ' - test_setup_break_detection_3 && - ( - cd break-detection-3 && - - git checkout B^0 && - - git merge -s recursive C^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - test_path_is_file a && - test_path_is_file b && - - git rev-parse >expect \ - A:a C:a && - git rev-parse >actual \ - :0:b :0:a && - test_cmp expect actual - ) -' - -test_expect_failure 'detect rename/add-source and preserve all data, merge other way' ' - test_setup_break_detection_3 && - ( - cd break-detection-3 && - - git checkout C^0 && - - git merge -s recursive B^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - test_path_is_file a && - test_path_is_file b && - - git rev-parse >expect \ - A:a C:a && - git rev-parse >actual \ - :0:b :0:a && - test_cmp expect actual - ) -' - -test_setup_rename_directory () { - test_create_repo rename-directory-$1 && - ( - cd rename-directory-$1 && - - printf "1\n2\n3\n4\n5\n6\n" >file && - git add file && - test_tick && - git commit -m base && - git tag base && - - git checkout -b right && - echo 7 >>file && - mkdir newfile && - echo junk >newfile/realfile && - git add file newfile/realfile && - test_tick && - git commit -m right && - - git checkout -b left-conflict base && - echo 8 >>file && - git add file && - git mv file newfile && - test_tick && - git commit -m left && - - git checkout -b left-clean base && - echo 0 >newfile && - cat file >>newfile && - git add newfile && - git rm file && - test_tick && - git commit -m left - ) -} - -test_expect_success 'rename/directory conflict + clean content merge' ' - test_setup_rename_directory 1a && - ( - cd rename-directory-1a && - - git checkout left-clean^0 && - - test_must_fail git merge -s recursive right^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 1 out && - git ls-files -o >out && - test_line_count = 2 out && - - echo 0 >expect && - git cat-file -p base:file >>expect && - echo 7 >>expect && - test_cmp expect newfile~HEAD && - - test $(git rev-parse :2:newfile) = $(git hash-object expect) && - - test_path_is_file newfile/realfile && - test_path_is_file newfile~HEAD - ) -' - -test_expect_success 'rename/directory conflict + content merge conflict' ' - test_setup_rename_directory 1b && - ( - cd rename-directory-1b && - - git reset --hard && - git clean -fdqx && - - git checkout left-conflict^0 && - - test_must_fail git merge -s recursive right^0 && - - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 2 out && - - git cat-file -p left-conflict:newfile >left && - git cat-file -p base:file >base && - git cat-file -p right:file >right && - test_must_fail git merge-file \ - -L "HEAD:newfile" \ - -L "" \ - -L "right^0:file" \ - left base right && - test_cmp left newfile~HEAD && - - git rev-parse >expect \ - base:file left-conflict:newfile right:file && - git rev-parse >actual \ - :1:newfile :2:newfile :3:newfile && - test_cmp expect actual && - - test_path_is_file newfile/realfile && - test_path_is_file newfile~HEAD - ) -' - -test_setup_rename_directory_2 () { - test_create_repo rename-directory-2 && - ( - cd rename-directory-2 && - - mkdir sub && - printf "1\n2\n3\n4\n5\n6\n" >sub/file && - git add sub/file && - test_tick && - git commit -m base && - git tag base && - - git checkout -b right && - echo 7 >>sub/file && - git add sub/file && - test_tick && - git commit -m right && - - git checkout -b left base && - echo 0 >newfile && - cat sub/file >>newfile && - git rm sub/file && - mv newfile sub && - git add sub && - test_tick && - git commit -m left - ) -} - -test_expect_success 'disappearing dir in rename/directory conflict handled' ' - test_setup_rename_directory_2 && - ( - cd rename-directory-2 && - - git checkout left^0 && - - git merge -s recursive right^0 && - - git ls-files -s >out && - test_line_count = 1 out && - git ls-files -u >out && - test_line_count = 0 out && - git ls-files -o >out && - test_line_count = 1 out && - - echo 0 >expect && - git cat-file -p base:sub/file >>expect && - echo 7 >>expect && - test_cmp expect sub && - - test_path_is_file sub - ) -' - -# Test for basic rename/add-dest conflict, with rename needing content merge: -# Commit O: a -# Commit A: rename a->b, modifying b too -# Commit B: modify a, add different b - -test_setup_rename_with_content_merge_and_add () { - test_create_repo rename-with-content-merge-and-add-$1 && - ( - cd rename-with-content-merge-and-add-$1 && - - test_seq 1 5 >a && - git add a && - git commit -m O && - git tag O && - - git checkout -b A O && - git mv a b && - test_seq 0 5 >b && - git add b && - git commit -m A && - - git checkout -b B O && - echo 6 >>a && - echo hello world >b && - git add a b && - git commit -m B - ) -} - -test_expect_success 'handle rename-with-content-merge vs. add' ' - test_setup_rename_with_content_merge_and_add AB && - ( - cd rename-with-content-merge-and-add-AB && - - git checkout A^0 && - - test_must_fail git merge -s recursive B^0 >out && - test_i18ngrep "CONFLICT (rename/add)" out && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - # Also, make sure both unmerged entries are for "b" - git ls-files -u b >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - test_path_is_missing a && - test_path_is_file b && - - test_seq 0 6 >tmp && - git hash-object tmp >expect && - git rev-parse B:b >>expect && - git rev-parse >actual \ - :2:b :3:b && - test_cmp expect actual && - - # Test that the two-way merge in b is as expected - git cat-file -p :2:b >>ours && - git cat-file -p :3:b >>theirs && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - ours empty theirs && - test_cmp ours b - ) -' - -test_expect_success 'handle rename-with-content-merge vs. add, merge other way' ' - test_setup_rename_with_content_merge_and_add BA && - ( - cd rename-with-content-merge-and-add-BA && - - git reset --hard && - git clean -fdx && - - git checkout B^0 && - - test_must_fail git merge -s recursive A^0 >out && - test_i18ngrep "CONFLICT (rename/add)" out && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - # Also, make sure both unmerged entries are for "b" - git ls-files -u b >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - test_path_is_missing a && - test_path_is_file b && - - test_seq 0 6 >tmp && - git rev-parse B:b >expect && - git hash-object tmp >>expect && - git rev-parse >actual \ - :2:b :3:b && - test_cmp expect actual && - - # Test that the two-way merge in b is as expected - git cat-file -p :2:b >>ours && - git cat-file -p :3:b >>theirs && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "A^0" \ - ours empty theirs && - test_cmp ours b - ) -' - -# Test for all kinds of things that can go wrong with rename/rename (2to1): -# Commit A: new files: a & b -# Commit B: rename a->c, modify b -# Commit C: rename b->c, modify a -# -# Merging of B & C should NOT be clean. Questions: -# * Both a & b should be removed by the merge; are they? -# * The two c's should contain modifications to a & b; do they? -# * The index should contain two files, both for c; does it? -# * The working copy should have two files, both of form c~; does it? -# * Nothing else should be present. Is anything? - -test_setup_rename_rename_2to1 () { - test_create_repo rename-rename-2to1 && - ( - cd rename-rename-2to1 && - - printf "1\n2\n3\n4\n5\n" >a && - printf "5\n4\n3\n2\n1\n" >b && - git add a b && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a c && - echo 0 >>b && - git add b && - git commit -m B && - - git checkout -b C A && - git mv b c && - echo 6 >>a && - git add a && - git commit -m C - ) -} - -test_expect_success 'handle rename/rename (2to1) conflict correctly' ' - test_setup_rename_rename_2to1 && - ( - cd rename-rename-2to1 && - - git checkout B^0 && - - test_must_fail git merge -s recursive C^0 >out && - test_i18ngrep "CONFLICT (rename/rename)" out && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - git ls-files -u c >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - test_path_is_missing a && - test_path_is_missing b && - - git rev-parse >expect \ - C:a B:b && - git rev-parse >actual \ - :2:c :3:c && - test_cmp expect actual && - - # Test that the two-way merge in new_a is as expected - git cat-file -p :2:c >>ours && - git cat-file -p :3:c >>theirs && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "C^0" \ - ours empty theirs && - git hash-object c >actual && - git hash-object ours >expect && - test_cmp expect actual - ) -' - -# Testcase setup for simple rename/rename (1to2) conflict: -# Commit A: new file: a -# Commit B: rename a->b -# Commit C: rename a->c -test_setup_rename_rename_1to2 () { - test_create_repo rename-rename-1to2 && - ( - cd rename-rename-1to2 && - - echo stuff >a && - git add a && - test_tick && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - test_tick && - git commit -m B && - - git checkout -b C A && - git mv a c && - test_tick && - git commit -m C - ) -} - -test_expect_success 'merge has correct working tree contents' ' - test_setup_rename_rename_1to2 && - ( - cd rename-rename-1to2 && - - git checkout C^0 && - - test_must_fail git merge -s recursive B^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 3 out && - git ls-files -o >out && - test_line_count = 1 out && - - test_path_is_missing a && - git rev-parse >expect \ - A:a A:a A:a \ - A:a A:a && - git rev-parse >actual \ - :1:a :3:b :2:c && - git hash-object >>actual \ - b c && - test_cmp expect actual - ) -' - -# Testcase setup for rename/rename(1to2)/add-source conflict: -# Commit A: new file: a -# Commit B: rename a->b -# Commit C: rename a->c, add completely different a -# -# Merging of B & C should NOT be clean; there's a rename/rename conflict - -test_setup_rename_rename_1to2_add_source_1 () { - test_create_repo rename-rename-1to2-add-source-1 && - ( - cd rename-rename-1to2-add-source-1 && - - printf "1\n2\n3\n4\n5\n6\n7\n" >a && - git add a && - git commit -m A && - git tag A && - - git checkout -b B A && - git mv a b && - git commit -m B && - - git checkout -b C A && - git mv a c && - echo something completely different >a && - git add a && - git commit -m C - ) -} - -test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' ' - test_setup_rename_rename_1to2_add_source_1 && - ( - cd rename-rename-1to2-add-source-1 && - - git checkout B^0 && - - test_must_fail git merge -s recursive C^0 && - - git ls-files -s >out && - test_line_count = 4 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - C:a A:a B:b C:C && - git rev-parse >actual \ - :3:a :1:a :2:b :3:c && - test_cmp expect actual && - - test_path_is_file a && - test_path_is_file b && - test_path_is_file c - ) -' - -test_setup_rename_rename_1to2_add_source_2 () { - test_create_repo rename-rename-1to2-add-source-2 && - ( - cd rename-rename-1to2-add-source-2 && - - >a && - git add a && - test_tick && - git commit -m base && - git tag A && - - git checkout -b B A && - git mv a b && - test_tick && - git commit -m one && - - git checkout -b C A && - git mv a b && - echo important-info >a && - git add a && - test_tick && - git commit -m two - ) -} - -test_expect_failure 'rename/rename/add-source still tracks new a file' ' - test_setup_rename_rename_1to2_add_source_2 && - ( - cd rename-rename-1to2-add-source-2 && - - git checkout C^0 && - git merge -s recursive B^0 && - - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - C:a A:a && - git rev-parse >actual \ - :0:a :0:b && - test_cmp expect actual - ) -' - -test_setup_rename_rename_1to2_add_dest () { - test_create_repo rename-rename-1to2-add-dest && - ( - cd rename-rename-1to2-add-dest && - - echo stuff >a && - git add a && - test_tick && - git commit -m base && - git tag A && - - git checkout -b B A && - git mv a b && - echo precious-data >c && - git add c && - test_tick && - git commit -m one && - - git checkout -b C A && - git mv a c && - echo important-info >b && - git add b && - test_tick && - git commit -m two - ) -} - -test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' ' - test_setup_rename_rename_1to2_add_dest && - ( - cd rename-rename-1to2-add-dest && - - git checkout C^0 && - test_must_fail git merge -s recursive B^0 && - - git ls-files -s >out && - test_line_count = 5 out && - git ls-files -u b >out && - test_line_count = 2 out && - git ls-files -u c >out && - test_line_count = 2 out && - git ls-files -o >out && - test_line_count = 1 out && - - git rev-parse >expect \ - A:a C:b B:b C:c B:c && - git rev-parse >actual \ - :1:a :2:b :3:b :2:c :3:c && - test_cmp expect actual && - - # Record some contents for re-doing merges - git cat-file -p A:a >stuff && - git cat-file -p C:b >important_info && - git cat-file -p B:c >precious_data && - >empty && - - # Test the merge in b - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - important_info empty stuff && - test_cmp important_info b && - - # Test the merge in c - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - stuff empty precious_data && - test_cmp stuff c - ) -' - -# Testcase rad, rename/add/delete -# Commit O: foo -# Commit A: rm foo, add different bar -# Commit B: rename foo->bar -# Expected: CONFLICT (rename/add/delete), two-way merged bar - -test_setup_rad () { - test_create_repo rad && - ( - cd rad && - echo "original file" >foo && - git add foo && - git commit -m "original" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - git rm foo && - echo "different file" >bar && - git add bar && - git commit -m "Remove foo, add bar" && - - git checkout B && - git mv foo bar && - git commit -m "rename foo to bar" - ) -} - -test_expect_failure 'rad-check: rename/add/delete conflict' ' - test_setup_rad && - ( - cd rad && - - git checkout B^0 && - test_must_fail git merge -s recursive A^0 >out 2>err && - - # Not sure whether the output should contain just one - # "CONFLICT (rename/add/delete)" line, or if it should break - # it into a pair of "CONFLICT (rename/delete)" and - # "CONFLICT (rename/add)"; allow for either. - test_i18ngrep "CONFLICT (rename.*add)" out && - test_i18ngrep "CONFLICT (rename.*delete)" out && - test_must_be_empty err && - - git ls-files -s >file_count && - test_line_count = 2 file_count && - git ls-files -u >file_count && - test_line_count = 2 file_count && - git ls-files -o >file_count && - test_line_count = 2 file_count && - - git rev-parse >actual \ - :2:bar :3:bar && - git rev-parse >expect \ - B:bar A:bar && - - test_cmp file_is_missing foo && - # bar should have two-way merged contents of the different - # versions of bar; check that content from both sides is - # present. - grep original bar && - grep different bar - ) -' - -# Testcase rrdd, rename/rename(2to1)/delete/delete -# Commit O: foo, bar -# Commit A: rename foo->baz, rm bar -# Commit B: rename bar->baz, rm foo -# Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz - -test_setup_rrdd () { - test_create_repo rrdd && - ( - cd rrdd && - echo foo >foo && - echo bar >bar && - git add foo bar && - git commit -m O && - - git branch O && - git branch A && - git branch B && - - git checkout A && - git mv foo baz && - git rm bar && - git commit -m "Rename foo, remove bar" && - - git checkout B && - git mv bar baz && - git rm foo && - git commit -m "Rename bar, remove foo" - ) -} - -test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' ' - test_setup_rrdd && - ( - cd rrdd && - - git checkout A^0 && - test_must_fail git merge -s recursive B^0 >out 2>err && - - # Not sure whether the output should contain just one - # "CONFLICT (rename/rename/delete/delete)" line, or if it - # should break it into three: "CONFLICT (rename/rename)" and - # two "CONFLICT (rename/delete)" lines; allow for either. - test_i18ngrep "CONFLICT (rename/rename)" out && - test_i18ngrep "CONFLICT (rename.*delete)" out && - test_must_be_empty err && - - git ls-files -s >file_count && - test_line_count = 2 file_count && - git ls-files -u >file_count && - test_line_count = 2 file_count && - git ls-files -o >file_count && - test_line_count = 2 file_count && - - git rev-parse >actual \ - :2:baz :3:baz && - git rev-parse >expect \ - O:foo O:bar && - - test_cmp file_is_missing foo && - test_cmp file_is_missing bar && - # baz should have two-way merged contents of the original - # contents of foo and bar; check that content from both sides - # is present. - grep foo baz && - grep bar baz - ) -' - -# Testcase mod6, chains of rename/rename(1to2) and rename/rename(2to1) -# Commit O: one, three, five -# Commit A: one->two, three->four, five->six -# Commit B: one->six, three->two, five->four -# Expected: six CONFLICT(rename/rename) messages, each path in two of the -# multi-way merged contents found in two, four, six - -test_setup_mod6 () { - test_create_repo mod6 && - ( - cd mod6 && - test_seq 11 19 >one && - test_seq 31 39 >three && - test_seq 51 59 >five && - git add . && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - test_seq 10 19 >one && - echo 40 >>three && - git add one three && - git mv one two && - git mv three four && - git mv five six && - test_tick && - git commit -m "A" && - - git checkout B && - echo 20 >>one && - echo forty >>three && - echo 60 >>five && - git add one three five && - git mv one six && - git mv three two && - git mv five four && - test_tick && - git commit -m "B" - ) -} - -test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' ' - test_setup_mod6 && - ( - cd mod6 && - - git checkout A^0 && - - test_must_fail git merge -s recursive B^0 >out 2>err && - - test_i18ngrep "CONFLICT (rename/rename)" out && - test_must_be_empty err && - - git ls-files -s >file_count && - test_line_count = 6 file_count && - git ls-files -u >file_count && - test_line_count = 6 file_count && - git ls-files -o >file_count && - test_line_count = 3 file_count && - - test_seq 10 20 >merged-one && - test_seq 51 60 >merged-five && - # Determine what the merge of three would give us. - test_seq 30 40 >three-side-A && - test_seq 31 39 >three-side-B && - echo forty >three-side-B && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - three-side-A empty three-side-B && - sed -e "s/^\([<=>]\)/\1\1\1/" three-side-A >merged-three && - - # Verify the index is as expected - git rev-parse >actual \ - :2:two :3:two \ - :2:four :3:four \ - :2:six :3:six && - git hash-object >expect \ - merged-one merged-three \ - merged-three merged-five \ - merged-five merged-one && - test_cmp expect actual && - - git cat-file -p :2:two >expect && - git cat-file -p :3:two >other && - test_must_fail git merge-file \ - -L "HEAD" -L "" -L "B^0" \ - expect empty other && - test_cmp expect two && - - git cat-file -p :2:four >expect && - git cat-file -p :3:four >other && - test_must_fail git merge-file \ - -L "HEAD" -L "" -L "B^0" \ - expect empty other && - test_cmp expect four && - - git cat-file -p :2:six >expect && - git cat-file -p :3:six >other && - test_must_fail git merge-file \ - -L "HEAD" -L "" -L "B^0" \ - expect empty other && - test_cmp expect six - ) -' - -test_conflicts_with_adds_and_renames() { - sideL=$1 - sideR=$2 - - # Setup: - # L - # / \ - # master ? - # \ / - # R - # - # Where: - # Both L and R have files named 'three' which collide. Each of - # the colliding files could have been involved in a rename, in - # which case there was a file named 'one' or 'two' that was - # modified on the opposite side of history and renamed into the - # collision on this side of history. - # - # Questions: - # 1) The index should contain both a stage 2 and stage 3 entry - # for the colliding file. Does it? - # 2) When renames are involved, the content merges are clean, so - # the index should reflect the content merges, not merely the - # version of the colliding file from the prior commit. Does - # it? - # 3) There should be a file in the worktree named 'three' - # containing the two-way merged contents of the content-merged - # versions of 'three' from each of the two colliding - # files. Is it present? - # 4) There should not be any three~* files in the working - # tree - test_setup_collision_conflict () { - #test_expect_success "setup simple $sideL/$sideR conflict" ' - test_create_repo simple_${sideL}_${sideR} && - ( - cd simple_${sideL}_${sideR} && - - # Create some related files now - for i in $(test_seq 1 10) - do - echo Random base content line $i - done >file_v1 && - cp file_v1 file_v2 && - echo modification >>file_v2 && - - cp file_v1 file_v3 && - echo more stuff >>file_v3 && - cp file_v3 file_v4 && - echo yet more stuff >>file_v4 && - - # Use a tag to record both these files for simple - # access, and clean out these untracked files - git tag file_v1 $(git hash-object -w file_v1) && - git tag file_v2 $(git hash-object -w file_v2) && - git tag file_v3 $(git hash-object -w file_v3) && - git tag file_v4 $(git hash-object -w file_v4) && - git clean -f && - - # Setup original commit (or merge-base), consisting of - # files named "one" and "two" if renames were involved. - touch irrelevant_file && - git add irrelevant_file && - if [ $sideL = "rename" ] - then - git show file_v1 >one && - git add one - fi && - if [ $sideR = "rename" ] - then - git show file_v3 >two && - git add two - fi && - test_tick && git commit -m initial && - - git branch L && - git branch R && - - # Handle the left side - git checkout L && - if [ $sideL = "rename" ] - then - git mv one three - else - git show file_v2 >three && - git add three - fi && - if [ $sideR = "rename" ] - then - git show file_v4 >two && - git add two - fi && - test_tick && git commit -m L && - - # Handle the right side - git checkout R && - if [ $sideL = "rename" ] - then - git show file_v2 >one && - git add one - fi && - if [ $sideR = "rename" ] - then - git mv two three - else - git show file_v4 >three && - git add three - fi && - test_tick && git commit -m R - ) - #' - } - - test_expect_success "check simple $sideL/$sideR conflict" ' - test_setup_collision_conflict && - ( - cd simple_${sideL}_${sideR} && - - git checkout L^0 && - - # Merge must fail; there is a conflict - test_must_fail git merge -s recursive R^0 && - - # Make sure the index has the right number of entries - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 2 out && - # Ensure we have the correct number of untracked files - git ls-files -o >out && - test_line_count = 1 out && - - # Nothing should have touched irrelevant_file - git rev-parse >actual \ - :0:irrelevant_file \ - :2:three \ - :3:three && - git rev-parse >expected \ - master:irrelevant_file \ - file_v2 \ - file_v4 && - test_cmp expected actual && - - # Make sure we have the correct merged contents for - # three - git show file_v1 >expected && - cat <<-\EOF >>expected && - <<<<<<< HEAD - modification - ======= - more stuff - yet more stuff - >>>>>>> R^0 - EOF - - test_cmp expected three - ) - ' -} - -test_conflicts_with_adds_and_renames rename rename -test_conflicts_with_adds_and_renames rename add -test_conflicts_with_adds_and_renames add rename -test_conflicts_with_adds_and_renames add add - -# Setup: -# L -# / \ -# master ? -# \ / -# R -# -# Where: -# master has two files, named 'one' and 'two'. -# branches L and R both modify 'one', in conflicting ways. -# branches L and R both modify 'two', in conflicting ways. -# branch L also renames 'one' to 'three'. -# branch R also renames 'two' to 'three'. -# -# So, we have four different conflicting files that all end up at path -# 'three'. -test_setup_nested_conflicts_from_rename_rename () { - test_create_repo nested_conflicts_from_rename_rename && - ( - cd nested_conflicts_from_rename_rename && - - # Create some related files now - for i in $(test_seq 1 10) - do - echo Random base content line $i - done >file_v1 && - - cp file_v1 file_v2 && - cp file_v1 file_v3 && - cp file_v1 file_v4 && - cp file_v1 file_v5 && - cp file_v1 file_v6 && - - echo one >>file_v1 && - echo uno >>file_v2 && - echo eins >>file_v3 && - - echo two >>file_v4 && - echo dos >>file_v5 && - echo zwei >>file_v6 && - - # Setup original commit (or merge-base), consisting of - # files named "one" and "two". - mv file_v1 one && - mv file_v4 two && - git add one two && - test_tick && git commit -m english && - - git branch L && - git branch R && - - # Handle the left side - git checkout L && - git rm one two && - mv -f file_v2 three && - mv -f file_v5 two && - git add two three && - test_tick && git commit -m spanish && - - # Handle the right side - git checkout R && - git rm one two && - mv -f file_v3 one && - mv -f file_v6 three && - git add one three && - test_tick && git commit -m german - ) -} - -test_expect_success 'check nested conflicts from rename/rename(2to1)' ' - test_setup_nested_conflicts_from_rename_rename && - ( - cd nested_conflicts_from_rename_rename && - - git checkout L^0 && - - # Merge must fail; there is a conflict - test_must_fail git merge -s recursive R^0 && - - # Make sure the index has the right number of entries - git ls-files -s >out && - test_line_count = 2 out && - git ls-files -u >out && - test_line_count = 2 out && - # Ensure we have the correct number of untracked files - git ls-files -o >out && - test_line_count = 1 out && - - # Compare :2:three to expected values - git cat-file -p master:one >base && - git cat-file -p L:three >ours && - git cat-file -p R:one >theirs && - test_must_fail git merge-file \ - -L "HEAD:three" -L "" -L "R^0:one" \ - ours base theirs && - sed -e "s/^\([<=>]\)/\1\1/" ours >L-three && - git cat-file -p :2:three >expect && - test_cmp expect L-three && - - # Compare :2:three to expected values - git cat-file -p master:two >base && - git cat-file -p L:two >ours && - git cat-file -p R:three >theirs && - test_must_fail git merge-file \ - -L "HEAD:two" -L "" -L "R^0:three" \ - ours base theirs && - sed -e "s/^\([<=>]\)/\1\1/" ours >R-three && - git cat-file -p :3:three >expect && - test_cmp expect R-three && - - # Compare three to expected contents - >empty && - test_must_fail git merge-file \ - -L "HEAD" -L "" -L "R^0" \ - L-three empty R-three && - test_cmp three L-three - ) -' - -# Testcase rename/rename(1to2) of a binary file -# Commit O: orig -# Commit A: orig-A -# Commit B: orig-B -# Expected: CONFLICT(rename/rename) message, three unstaged entries in the -# index, and contents of orig-[AB] at path orig-[AB] -test_setup_rename_rename_1_to_2_binary () { - test_create_repo rename_rename_1_to_2_binary && - ( - cd rename_rename_1_to_2_binary && - - echo '* binary' >.gitattributes && - git add .gitattributes && - - test_seq 1 10 >orig && - git add orig && - git commit -m orig && - - git branch A && - git branch B && - - git checkout A && - git mv orig orig-A && - test_seq 1 11 >orig-A && - git add orig-A && - git commit -m orig-A && - - git checkout B && - git mv orig orig-B && - test_seq 0 10 >orig-B && - git add orig-B && - git commit -m orig-B - - ) -} - -test_expect_success 'rename/rename(1to2) with a binary file' ' - test_setup_rename_rename_1_to_2_binary && - ( - cd rename_rename_1_to_2_binary && - - git checkout A^0 && - - test_must_fail git merge -s recursive B^0 && - - # Make sure the index has the right number of entries - git ls-files -s >actual && - test_line_count = 4 actual && - - git rev-parse A:orig-A B:orig-B >expect && - git hash-object orig-A orig-B >actual && - test_cmp expect actual - ) -' - -test_done diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh deleted file mode 100755 index 83792c5..0000000 --- a/t/t6043-merge-rename-directories.sh +++ /dev/null @@ -1,4693 +0,0 @@ -#!/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_setup_1a () { - 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: Simple directory rename detection' ' - test_setup_1a && - ( - cd 1a && - - git checkout A^0 && - - git -c merge.directoryRenames=true merge -s recursive B^0 >out && - - 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_setup_1b () { - 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: Merge a directory with another' ' - test_setup_1b && - ( - cd 1b && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_1c () { - 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: Transitive renaming' ' - test_setup_1c && - ( - cd 1c && - - git checkout A^0 && - - git -c merge.directoryRenames=true merge -s recursive B^0 >out && - - 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_setup_1d () { - 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: Directory renames cause a rename/rename(2to1) conflict' ' - test_setup_1d && - ( - cd 1d && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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 = 1 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 that the two-way merge in x/wham is as expected - git cat-file -p :2:x/wham >expect && - git cat-file -p :3:x/wham >other && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - expect empty other && - test_cmp expect x/wham - ) -' - -# 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_setup_1e () { - 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: Renamed directory, with all files being renamed too' ' - test_setup_1e && - ( - cd 1e && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_1f () { - 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: Split a directory into two other directories' ' - test_setup_1f && - ( - cd 1f && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_2a () { - 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: Directory split into two on one side, with equal numbers of paths' ' - test_setup_2a && - ( - cd 2a && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_2b () { - 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: Directory split into two on one side, with equal numbers of paths' ' - test_setup_2b && - ( - cd 2b && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_3a () { - 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: Avoid implicit rename if involved as source on other side' ' - test_setup_3a && - ( - cd 3a && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_3b () { - 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: Avoid implicit rename if involved as source on current side' ' - test_setup_3b && - ( - cd 3b && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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 commit 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_setup_4a () { - 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: Directory split, with original directory still present' ' - test_setup_4a && - ( - cd 4a && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_5a () { - 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: Merge directories, other side adds files to original and target' ' - test_setup_5a && - ( - cd 5a && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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 normally 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_setup_5b () { - 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: Rename/delete in order to get add/add/add conflict' ' - test_setup_5b && - ( - cd 5b && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_5c () { - 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: Transitive rename would cause rename/rename/rename/add/add/add' ' - test_setup_5c && - ( - cd 5c && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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 = 1 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 \ - z/d && - git rev-parse >expect \ - 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_setup_5d () { - 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: Directory/file/file conflict due to directory rename' ' - test_setup_5d && - ( - cd 5d && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_6a () { - 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: Tricky rename/delete' ' - test_setup_6a && - ( - cd 6a && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_6b () { - 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: Same rename done on both sides' ' - test_setup_6b && - ( - cd 6b && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_6c () { - 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: Rename only done on same side' ' - test_setup_6c && - ( - cd 6c && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_6d () { - 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: We do not always want transitive renaming' ' - test_setup_6d && - ( - cd 6d && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_6e () { - 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: Add/add from one side' ' - test_setup_6e && - ( - cd 6e && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_7a () { - 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: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' ' - test_setup_7a && - ( - cd 7a && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_7b () { - 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: rename/rename(2to1), but only due to transitive rename' ' - test_setup_7b && - ( - cd 7b && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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 = 1 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 that the two-way merge in y/d is as expected - git cat-file -p :2:y/d >expect && - git cat-file -p :3:y/d >other && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - expect empty other && - test_cmp expect y/d - ) -' - -# 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_setup_7c () { - 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: rename/rename(1to...2or3); transitive rename may add complexity' ' - test_setup_7c && - ( - cd 7c && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_7d () { - 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: transitive rename involved in rename/delete; how is it reported?' ' - test_setup_7d && - ( - cd 7d && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_7e () { - 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: transitive rename in rename/delete AND dirs in the way' ' - test_setup_7e && - ( - cd 7e && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_8a () { - 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: Dual-directory rename, one into the others way' ' - test_setup_8a && - ( - cd 8a && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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: -# 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_setup_8b () { - 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: Dual-directory rename, one into the others way, with conflicting filenames' ' - test_setup_8b && - ( - cd 8b && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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 modified 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_setup_8c () { - 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: modify/delete or rename+modify/delete' ' - test_setup_8c && - ( - cd 8c && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_8d () { - 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: rename/delete...or not?' ' - test_setup_8d && - ( - cd 8d && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_8e () { - 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: Both sides rename, one side adds to original directory' ' - test_setup_8e && - ( - cd 8e && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_9a () { - 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: Inner renamed directory within outer renamed directory' ' - test_setup_9a && - ( - cd 9a && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_9b () { - 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: Transitive rename with content merge' ' - test_setup_9b && - ( - cd 9b && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_9c () { - 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: Doubly transitive rename?' ' - test_setup_9c && - ( - cd 9c && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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: -# -# 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_setup_9d () { - 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: N-way transitive rename?' ' - test_setup_9d && - ( - cd 9d && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_9e () { - 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: N-to-1 whammo' ' - test_setup_9e && - ( - cd 9e && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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_setup_9f () { - 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: Renamed directory that only contained immediate subdirs' ' - test_setup_9f && - ( - cd 9f && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_9g () { - 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: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' ' - ( - cd 9g && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_9h () { - 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: Avoid dir rename on merely modified path' ' - test_setup_9h && - ( - cd 9h && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_10a () { - 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: Overwrite untracked with normal rename/delete' ' - test_setup_10a && - ( - cd 10a && - - git checkout A^0 && - echo very >z/c && - echo important >z/d && - - test_must_fail git -c merge.directoryRenames=true 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_setup_10b () { - 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: Overwrite untracked with dir rename + delete' ' - test_setup_10b && - ( - cd 10b && - - git checkout A^0 && - echo very >y/c && - echo important >y/d && - echo contents >y/e && - - test_must_fail git -c merge.directoryRenames=true 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_setup_10c () { - test_create_repo 10c_$1 && - ( - cd 10c_$1 && - - 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 '10c1: Overwrite untracked with dir rename/rename(1to2)' ' - test_setup_10c 1 && - ( - cd 10c_1 && - - git checkout A^0 && - echo important >y/c && - - test_must_fail git -c merge.directoryRenames=true 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 - ) -' - -test_expect_success '10c2: Overwrite untracked with dir rename/rename(1to2), other direction' ' - test_setup_10c 2 && - ( - cd 10c_2 && - - git reset --hard && - git clean -fdqx && - - git checkout B^0 && - mkdir y && - echo important >y/c && - - test_must_fail git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err && - test_i18ngrep "CONFLICT (rename/rename)" out && - test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~HEAD 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 :3:w/c :2: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~HEAD >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~merged}+ -# CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham -# ERROR_MSG(Refusing to lose untracked file at y/wham) - -test_setup_10d () { - 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: Delete untracked with dir rename/rename(2to1)' ' - test_setup_10d && - ( - cd 10d && - - git checkout A^0 && - echo important >y/wham && - - test_must_fail git -c merge.directoryRenames=true 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 = 3 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 && - - # Test that the two-way merge in y/wham~merged is as expected - git cat-file -p :2:y/wham >expect && - git cat-file -p :3:y/wham >other && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - expect empty other && - test_cmp expect y/wham~merged - ) -' - -# 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_setup_10e () { - 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: 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 -c merge.directoryRenames=true 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_setup_11a () { - 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: Avoid losing dirty contents with simple rename' ' - test_setup_11a && - ( - cd 11a && - - git checkout A^0 && - echo stuff >>z/c && - - test_must_fail git -c merge.directoryRenames=true 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_setup_11b () { - 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: Avoid losing dirty file involved in directory rename' ' - test_setup_11b && - ( - cd 11b && - - git checkout A^0 && - echo stuff >>z/c && - - git -c merge.directoryRenames=true 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_setup_11c () { - 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: Avoid losing not-uptodate with rename + D/F conflict' ' - test_setup_11c && - ( - cd 11c && - - git checkout A^0 && - echo stuff >>y/c && - - test_must_fail git -c merge.directoryRenames=true 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_setup_11d () { - 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: Avoid losing not-uptodate with rename + D/F conflict' ' - test_setup_11d && - ( - cd 11d && - - git checkout A^0 && - echo stuff >>z/c && - - test_must_fail git -c merge.directoryRenames=true 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_setup_11e () { - 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: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' ' - test_setup_11e && - ( - cd 11e && - - git checkout A^0 && - echo mods >>y/c && - - test_must_fail git -c merge.directoryRenames=true 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 = 3 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 && - - # See if y/c~merged has expected contents; requires manually - # doing the expected file merge - git cat-file -p A:y/c >c1 && - git cat-file -p B:z/c >c2 && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - c1 empty c2 && - test_cmp c1 y/c~merged - ) -' - -# 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~merged} + -# 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_setup_11f () { - 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: Avoid deleting not-uptodate with dir rename/rename(2to1)' ' - test_setup_11f && - ( - cd 11f && - - git checkout A^0 && - echo important >>y/wham && - - test_must_fail git -c merge.directoryRenames=true 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 = 3 out && - - test_seq 1 10 >expected && - echo important >>expected && - test_cmp expected y/wham && - - test_must_fail git rev-parse :1:y/wham && - - 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 && - - # Test that the two-way merge in y/wham~merged is as expected - git cat-file -p :2:y/wham >expect && - git cat-file -p :3:y/wham >other && - >empty && - test_must_fail git merge-file \ - -L "HEAD" \ - -L "" \ - -L "B^0" \ - expect empty other && - test_cmp expect y/wham~merged - ) -' - -########################################################################### -# 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_setup_12a () { - 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: Moving one directory hierarchy into another' ' - test_setup_12a && - ( - cd 12a && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_12b () { - 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: Moving two directory hierarchies into each other' ' - test_setup_12b && - ( - cd 12b && - - git checkout A^0 && - - git -c merge.directoryRenames=true 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_setup_12c () { - 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: Moving one directory hierarchy into another w/ content merge' ' - test_setup_12c && - ( - cd 12c && - - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true 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 - ) -' - -# Testcase 12d, Rename/merge of subdirectory into the root -# Commit O: a/b/subdir/foo -# Commit A: subdir/foo -# Commit B: a/b/subdir/foo, a/b/bar -# Expected: subdir/foo, bar - -test_setup_12d () { - test_create_repo 12d && - ( - cd 12d && - - mkdir -p a/b/subdir && - test_commit a/b/subdir/foo && - - git branch O && - git branch A && - git branch B && - - git checkout A && - mkdir subdir && - git mv a/b/subdir/foo.t subdir/foo.t && - test_tick && - git commit -m "A" && - - git checkout B && - test_commit a/b/bar - ) -} - -test_expect_success '12d: Rename/merge subdir into the root, variant 1' ' - test_setup_12d && - ( - cd 12d && - - git checkout A^0 && - - git -c merge.directoryRenames=true merge -s recursive B^0 && - - git ls-files -s >out && - test_line_count = 2 out && - - git rev-parse >actual \ - HEAD:subdir/foo.t HEAD:bar.t && - git rev-parse >expect \ - O:a/b/subdir/foo.t B:a/b/bar.t && - test_cmp expect actual && - - git hash-object bar.t >actual && - git rev-parse B:a/b/bar.t >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:a/b/subdir/foo.t && - test_must_fail git rev-parse HEAD:a/b/bar.t && - test_path_is_missing a/ && - test_path_is_file bar.t - ) -' - -# Testcase 12e, Rename/merge of subdirectory into the root -# Commit O: a/b/foo -# Commit A: foo -# Commit B: a/b/foo, a/b/bar -# Expected: foo, bar - -test_setup_12e () { - test_create_repo 12e && - ( - cd 12e && - - mkdir -p a/b && - test_commit a/b/foo && - - git branch O && - git branch A && - git branch B && - - git checkout A && - mkdir subdir && - git mv a/b/foo.t foo.t && - test_tick && - git commit -m "A" && - - git checkout B && - test_commit a/b/bar - ) -} - -test_expect_success '12e: Rename/merge subdir into the root, variant 2' ' - test_setup_12e && - ( - cd 12e && - - git checkout A^0 && - - git -c merge.directoryRenames=true merge -s recursive B^0 && - - git ls-files -s >out && - test_line_count = 2 out && - - git rev-parse >actual \ - HEAD:foo.t HEAD:bar.t && - git rev-parse >expect \ - O:a/b/foo.t B:a/b/bar.t && - test_cmp expect actual && - - git hash-object bar.t >actual && - git rev-parse B:a/b/bar.t >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:a/b/foo.t && - test_must_fail git rev-parse HEAD:a/b/bar.t && - test_path_is_missing a/ && - test_path_is_file bar.t - ) -' - -########################################################################### -# SECTION 13: Checking informational and conflict messages -# -# A year after directory rename detection became the default, it was -# instead decided to report conflicts on the pathname on the basis that -# some users may expect the new files added or moved into a directory to -# be unrelated to all the other files in that directory, and thus that -# directory rename detection is unexpected. Test that the messages printed -# match our expectation. -########################################################################### - -# Testcase 13a, Basic directory rename with newly added files -# 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}, with notices/conflicts for both y/d and y/e/f - -test_setup_13a () { - test_create_repo 13a_$1 && - ( - cd 13a_$1 && - - 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 '13a(conflict): messages for newly added files' ' - test_setup_13a conflict && - ( - cd 13a_conflict && - - git checkout A^0 && - - test_must_fail git merge -s recursive B^0 >out 2>err && - - test_i18ngrep CONFLICT..file.location.*z/e/f.added.in.B^0.*y/e/f out && - test_i18ngrep CONFLICT..file.location.*z/d.added.in.B^0.*y/d out && - - git ls-files >paths && - ! grep z/ paths && - grep "y/[de]" paths && - - test_path_is_missing z/d && - test_path_is_file y/d && - test_path_is_missing z/e/f && - test_path_is_file y/e/f - ) -' - -test_expect_success '13a(info): messages for newly added files' ' - test_setup_13a info && - ( - cd 13a_info && - - git reset --hard && - git checkout A^0 && - - git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - - test_i18ngrep Path.updated:.*z/e/f.added.in.B^0.*y/e/f out && - test_i18ngrep Path.updated:.*z/d.added.in.B^0.*y/d out && - - git ls-files >paths && - ! grep z/ paths && - grep "y/[de]" paths && - - test_path_is_missing z/d && - test_path_is_file y/d && - test_path_is_missing z/e/f && - test_path_is_file y/e/f - ) -' - -# Testcase 13b, Transitive rename with conflicted content merge and default -# "conflict" setting -# (Related to testcase 1c, 9b) -# 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}, with two conflict messages for y/d, -# one about content, and one about file location - -test_setup_13b () { - test_create_repo 13b_$1 && - ( - cd 13b_$1 && - - mkdir x && - mkdir z && - test_seq 1 10 >x/d && - echo b >z/b && - echo c >z/c && - git add x z && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - git mv z y && - echo 11 >>x/d && - git add x/d && - test_tick && - git commit -m "A" && - - git checkout B && - echo eleven >>x/d && - git mv x/d z/d && - git add z/d && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '13b(conflict): messages for transitive rename with conflicted content' ' - test_setup_13b conflict && - ( - cd 13b_conflict && - - git checkout A^0 && - - test_must_fail git merge -s recursive B^0 >out 2>err && - - test_i18ngrep CONFLICT.*content.*Merge.conflict.in.y/d out && - test_i18ngrep CONFLICT..file.location.*x/d.renamed.to.z/d.*moved.to.y/d out && - - git ls-files >paths && - ! grep z/ paths && - grep "y/d" paths && - - test_path_is_missing z/d && - test_path_is_file y/d - ) -' - -test_expect_success '13b(info): messages for transitive rename with conflicted content' ' - test_setup_13b info && - ( - cd 13b_info && - - git reset --hard && - git checkout A^0 && - - test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - - test_i18ngrep CONFLICT.*content.*Merge.conflict.in.y/d out && - test_i18ngrep Path.updated:.*x/d.renamed.to.z/d.in.B^0.*moving.it.to.y/d out && - - git ls-files >paths && - ! grep z/ paths && - grep "y/d" paths && - - test_path_is_missing z/d && - test_path_is_file y/d - ) -' - -# Testcase 13c, Rename/rename(1to1) due to directory rename -# Commit O: z/{b,c}, x/{d,e} -# Commit A: y/{b,c,d}, x/e -# Commit B: z/{b,c,d}, x/e -# Expected: y/{b,c,d}, with info or conflict messages for d ( -# A: renamed x/d -> z/d; B: renamed z/ -> y/ AND renamed x/d to y/d -# One could argue A had partial knowledge of what was done with -# d and B had full knowledge, but that's a slippery slope as -# shown in testcase 13d. - -test_setup_13c () { - test_create_repo 13c_$1 && - ( - cd 13c_$1 && - - mkdir x && - mkdir z && - test_seq 1 10 >x/d && - echo e >x/e && - echo b >z/b && - echo c >z/c && - git add x z && - 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/d y/ && - test_tick && - git commit -m "A" && - - git checkout B && - git mv x/d z/d && - git add z/d && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '13c(conflict): messages for rename/rename(1to1) via transitive rename' ' - test_setup_13c conflict && - ( - cd 13c_conflict && - - git checkout A^0 && - - test_must_fail git merge -s recursive B^0 >out 2>err && - - test_i18ngrep CONFLICT..file.location.*x/d.renamed.to.z/d.*moved.to.y/d out && - - git ls-files >paths && - ! grep z/ paths && - grep "y/d" paths && - - test_path_is_missing z/d && - test_path_is_file y/d - ) -' - -test_expect_success '13c(info): messages for rename/rename(1to1) via transitive rename' ' - test_setup_13c info && - ( - cd 13c_info && - - git reset --hard && - git checkout A^0 && - - git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - - test_i18ngrep Path.updated:.*x/d.renamed.to.z/d.in.B^0.*moving.it.to.y/d out && - - git ls-files >paths && - ! grep z/ paths && - grep "y/d" paths && - - test_path_is_missing z/d && - test_path_is_file y/d - ) -' - -# Testcase 13d, Rename/rename(1to1) due to directory rename on both sides -# Commit O: a/{z,y}, b/x, c/w -# Commit A: a/z, b/{y,x}, d/w -# Commit B: a/z, d/x, c/{y,w} -# Expected: a/z, d/{y,x,w} with no file location conflict for x -# Easy cases: -# * z is always in a; so it stays in a. -# * x starts in b, only modified on one side to move into d/ -# * w starts in c, only modified on one side to move into d/ -# Hard case: -# * A renames a/y to b/y, and B renames b/->d/ => a/y -> d/y -# * B renames a/y to c/y, and A renames c/->d/ => a/y -> d/y -# No conflict in where a/y ends up, so put it in d/y. - -test_setup_13d () { - test_create_repo 13d_$1 && - ( - cd 13d_$1 && - - mkdir a && - mkdir b && - mkdir c && - echo z >a/z && - echo y >a/y && - echo x >b/x && - echo w >c/w && - git add a b c && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - git mv a/y b/ && - git mv c/ d/ && - test_tick && - git commit -m "A" && - - git checkout B && - git mv a/y c/ && - git mv b/ d/ && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '13d(conflict): messages for rename/rename(1to1) via dual transitive rename' ' - test_setup_13d conflict && - ( - cd 13d_conflict && - - git checkout A^0 && - - test_must_fail git merge -s recursive B^0 >out 2>err && - - test_i18ngrep CONFLICT..file.location.*a/y.renamed.to.b/y.*moved.to.d/y out && - test_i18ngrep CONFLICT..file.location.*a/y.renamed.to.c/y.*moved.to.d/y out && - - git ls-files >paths && - ! grep b/ paths && - ! grep c/ paths && - grep "d/y" paths && - - test_path_is_missing b/y && - test_path_is_missing c/y && - test_path_is_file d/y - ) -' - -test_expect_success '13d(info): messages for rename/rename(1to1) via dual transitive rename' ' - test_setup_13d info && - ( - cd 13d_info && - - git reset --hard && - git checkout A^0 && - - git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - - test_i18ngrep Path.updated.*a/y.renamed.to.b/y.*moving.it.to.d/y out && - test_i18ngrep Path.updated.*a/y.renamed.to.c/y.*moving.it.to.d/y out && - - git ls-files >paths && - ! grep b/ paths && - ! grep c/ paths && - grep "d/y" paths && - - test_path_is_missing b/y && - test_path_is_missing c/y && - test_path_is_file d/y - ) -' - -# Testcase 13e, directory rename in virtual merge base -# -# This testcase has a slightly different setup than all the above cases, in -# order to include a recursive case: -# -# A C -# o - o -# / \ / \ -# O o X ? -# \ / \ / -# o o -# B D -# -# Commit O: a/{z,y} -# Commit A: b/{z,y} -# Commit B: a/{z,y,x} -# Commit C: b/{z,y,x} -# Commit D: b/{z,y}, a/x -# Expected: b/{z,y,x} (sort of; see below for why this might not be expected) -# -# NOTES: 'X' represents a virtual merge base. With the default of -# directory rename detection yielding conflicts, merging A and B -# results in a conflict complaining about whether 'x' should be -# under 'a/' or 'b/'. However, when creating the virtual merge -# base 'X', since virtual merge bases need to be written out as a -# tree, we cannot have a conflict, so some resolution has to be -# picked. -# -# In choosing the right resolution, it's worth noting here that -# commits C & D are merges of A & B that choose different -# locations for 'x' (i.e. they resolve the conflict differently), -# and so it would be nice when merging C & D if git could detect -# this difference of opinion and report a conflict. But the only -# way to do so that I can think of would be to have the virtual -# merge base place 'x' in some directory other than either 'a/' or -# 'b/', which seems a little weird -- especially since it'd result -# in a rename/rename(1to2) conflict with a source path that never -# existed in any version. -# -# So, for now, when directory rename detection is set to -# 'conflict' just avoid doing directory rename detection at all in -# the recursive case. This will not allow us to detect a conflict -# in the outer merge for this special kind of setup, but it at -# least avoids hitting a BUG(). -# -test_setup_13e () { - test_create_repo 13e && - ( - cd 13e && - - mkdir a && - echo z >a/z && - echo y >a/y && - git add a && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - git mv a/ b/ && - test_tick && - git commit -m "A" && - - git checkout B && - echo x >a/x && - git add a && - test_tick && - git commit -m "B" && - - git branch C A && - git branch D B && - - git checkout C && - test_must_fail git -c merge.directoryRenames=conflict merge B && - git add b/x && - test_tick && - git commit -m "C" && - - - git checkout D && - test_must_fail git -c merge.directoryRenames=conflict merge A && - git add b/x && - mkdir a && - git mv b/x a/x && - test_tick && - git commit -m "D" - ) -} - -test_expect_success '13e: directory rename detection in recursive case' ' - test_setup_13e && - ( - cd 13e && - - git checkout --quiet D^0 && - - git -c merge.directoryRenames=conflict merge -s recursive C^0 >out 2>err && - - test_i18ngrep ! CONFLICT out && - test_i18ngrep ! BUG: err && - test_i18ngrep ! core.dumped err && - test_must_be_empty err && - - git ls-files >paths && - ! grep a/x paths && - grep b/x paths - ) -' - -test_done diff --git a/t/t6044-merge-unrelated-index-changes.sh b/t/t6044-merge-unrelated-index-changes.sh deleted file mode 100755 index 5e3779e..0000000 --- a/t/t6044-merge-unrelated-index-changes.sh +++ /dev/null @@ -1,216 +0,0 @@ -#!/bin/sh - -test_description="merges with unrelated index changes" - -. ./test-lib.sh - -# Testcase for some simple merges -# A -# o-------o B -# \ -# \-----o C -# \ -# \---o D -# \ -# \-o E -# \ -# o F -# Commit A: some file a -# Commit B: adds file b, modifies end of a -# Commit C: adds file c -# Commit D: adds file d, modifies beginning of a -# Commit E: renames a->subdir/a, adds subdir/e -# Commit F: empty commit - -test_expect_success 'setup trivial merges' ' - test_seq 1 10 >a && - git add a && - test_tick && git commit -m A && - - git branch A && - git branch B && - git branch C && - git branch D && - git branch E && - git branch F && - - git checkout B && - echo b >b && - echo 11 >>a && - git add a b && - test_tick && git commit -m B && - - git checkout C && - echo c >c && - git add c && - test_tick && git commit -m C && - - git checkout D && - test_seq 2 10 >a && - echo d >d && - git add a d && - test_tick && git commit -m D && - - git checkout E && - mkdir subdir && - git mv a subdir/a && - echo e >subdir/e && - git add subdir && - test_tick && git commit -m E && - - git checkout F && - test_tick && git commit --allow-empty -m F -' - -test_expect_success 'ff update' ' - git reset --hard && - git checkout A^0 && - - touch random_file && git add random_file && - - git merge E^0 && - - test_must_fail git rev-parse HEAD:random_file && - test "$(git diff --name-only --cached E)" = "random_file" -' - -test_expect_success 'ff update, important file modified' ' - git reset --hard && - git checkout A^0 && - - mkdir subdir && - touch subdir/e && - git add subdir/e && - - test_must_fail git merge E^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'resolve, trivial' ' - git reset --hard && - git checkout B^0 && - - touch random_file && git add random_file && - - test_must_fail git merge -s resolve C^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'resolve, non-trivial' ' - git reset --hard && - git checkout B^0 && - - touch random_file && git add random_file && - - test_must_fail git merge -s resolve D^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'recursive' ' - git reset --hard && - git checkout B^0 && - - touch random_file && git add random_file && - - test_must_fail git merge -s recursive C^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'recursive, when merge branch matches merge base' ' - git reset --hard && - git checkout B^0 && - - touch random_file && git add random_file && - - test_must_fail git merge -s recursive F^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'merge-recursive, when index==head but head!=HEAD' ' - git reset --hard && - git checkout C^0 && - - # Make index match B - git diff C B -- | git apply --cached && - # Merge B & F, with B as "head" - git merge-recursive A -- B F > out && - test_i18ngrep "Already up to date" out -' - -test_expect_success 'recursive, when file has staged changes not matching HEAD nor what a merge would give' ' - git reset --hard && - git checkout B^0 && - - mkdir subdir && - test_seq 1 10 >subdir/a && - git add subdir/a && - - # We have staged changes; merge should error out - test_must_fail git merge -s recursive E^0 2>err && - test_i18ngrep "changes to the following files would be overwritten" err -' - -test_expect_success 'recursive, when file has staged changes matching what a merge would give' ' - git reset --hard && - git checkout B^0 && - - mkdir subdir && - test_seq 1 11 >subdir/a && - git add subdir/a && - - # We have staged changes; merge should error out - test_must_fail git merge -s recursive E^0 2>err && - test_i18ngrep "changes to the following files would be overwritten" err -' - -test_expect_success 'octopus, unrelated file touched' ' - git reset --hard && - git checkout B^0 && - - touch random_file && git add random_file && - - test_must_fail git merge C^0 D^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'octopus, related file removed' ' - git reset --hard && - git checkout B^0 && - - git rm b && - - test_must_fail git merge C^0 D^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'octopus, related file modified' ' - git reset --hard && - git checkout B^0 && - - echo 12 >>a && git add a && - - test_must_fail git merge C^0 D^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'ours' ' - git reset --hard && - git checkout B^0 && - - touch random_file && git add random_file && - - test_must_fail git merge -s ours C^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'subtree' ' - git reset --hard && - git checkout B^0 && - - touch random_file && git add random_file && - - test_must_fail git merge -s subtree E^0 && - test_path_is_missing .git/MERGE_HEAD -' - -test_done diff --git a/t/t6045-merge-rename-delete.sh b/t/t6045-merge-rename-delete.sh deleted file mode 100755 index 5d33577..0000000 --- a/t/t6045-merge-rename-delete.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -test_description='Merge-recursive rename/delete conflict message' -. ./test-lib.sh - -test_expect_success 'rename/delete' ' - echo foo >A && - git add A && - git commit -m "initial" && - - git checkout -b rename && - git mv A B && - git commit -m "rename" && - - git checkout master && - git rm A && - git commit -m "delete" && - - test_must_fail git merge --strategy=recursive rename >output && - test_i18ngrep "CONFLICT (rename/delete): A deleted in HEAD and renamed to B in rename. Version rename of B left in tree." output -' - -test_done diff --git a/t/t6046-merge-skip-unneeded-updates.sh b/t/t6046-merge-skip-unneeded-updates.sh deleted file mode 100755 index 5a2d07e..0000000 --- a/t/t6046-merge-skip-unneeded-updates.sh +++ /dev/null @@ -1,770 +0,0 @@ -#!/bin/sh - -test_description="merge cases" - -# The setup for all of them, pictorially, is: -# -# A -# o -# / \ -# O o ? -# \ / -# o -# B -# -# To help make it easier to follow the flow of tests, they have been -# divided into sections and each test will start with a quick explanation -# of what commits O, A, and B contain. -# -# Notation: -# z/{b,c} means files z/b and z/c both exist -# x/d_1 means file x/d exists with content d1. (Purpose of the -# underscore notation is to differentiate different -# files that might be renamed into each other's paths.) - -. ./test-lib.sh - - -########################################################################### -# SECTION 1: Cases involving no renames (one side has subset of changes of -# the other side) -########################################################################### - -# Testcase 1a, Changes on A, subset of changes on B -# Commit O: b_1 -# Commit A: b_2 -# Commit B: b_3 -# Expected: b_2 - -test_setup_1a () { - test_create_repo 1a_$1 && - ( - cd 1a_$1 && - - test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && - git add b && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && - git add b && - test_tick && - git commit -m "A" && - - git checkout B && - test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && - git add b && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '1a-L: Modify(A)/Modify(B), change on B subset of A' ' - test_setup_1a L && - ( - cd 1a_L && - - git checkout A^0 && - - test-tool chmtime --get -3600 b >old-mtime && - - GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && - - test_must_be_empty err && - - # Make sure b was NOT updated - test-tool chmtime --get b >new-mtime && - test_cmp old-mtime new-mtime && - - git ls-files -s >index_files && - test_line_count = 1 index_files && - - git rev-parse >actual HEAD:b && - git rev-parse >expect A:b && - test_cmp expect actual && - - git hash-object b >actual && - git rev-parse A:b >expect && - test_cmp expect actual - ) -' - -test_expect_success '1a-R: Modify(A)/Modify(B), change on B subset of A' ' - test_setup_1a R && - ( - cd 1a_R && - - git checkout B^0 && - - test-tool chmtime --get -3600 b >old-mtime && - GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && - - # Make sure b WAS updated - test-tool chmtime --get b >new-mtime && - test $(cat old-mtime) -lt $(cat new-mtime) && - - test_must_be_empty err && - - git ls-files -s >index_files && - test_line_count = 1 index_files && - - git rev-parse >actual HEAD:b && - git rev-parse >expect A:b && - test_cmp expect actual && - - git hash-object b >actual && - git rev-parse A:b >expect && - test_cmp expect actual - ) -' - - -########################################################################### -# SECTION 2: Cases involving basic renames -########################################################################### - -# Testcase 2a, Changes on A, rename on B -# Commit O: b_1 -# Commit A: b_2 -# Commit B: c_1 -# Expected: c_2 - -test_setup_2a () { - test_create_repo 2a_$1 && - ( - cd 2a_$1 && - - test_seq 1 10 >b && - git add b && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - test_seq 1 11 >b && - git add b && - test_tick && - git commit -m "A" && - - git checkout B && - git mv b c && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '2a-L: Modify/rename, merge into modify side' ' - test_setup_2a L && - ( - cd 2a_L && - - git checkout A^0 && - - test_path_is_missing c && - GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && - - test_path_is_file c && - - git ls-files -s >index_files && - test_line_count = 1 index_files && - - git rev-parse >actual HEAD:c && - git rev-parse >expect A:b && - test_cmp expect actual && - - git hash-object c >actual && - git rev-parse A:b >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:b && - test_path_is_missing b - ) -' - -test_expect_success '2a-R: Modify/rename, merge into rename side' ' - test_setup_2a R && - ( - cd 2a_R && - - git checkout B^0 && - - test-tool chmtime --get -3600 c >old-mtime && - GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && - - # Make sure c WAS updated - test-tool chmtime --get c >new-mtime && - test $(cat old-mtime) -lt $(cat new-mtime) && - - test_must_be_empty err && - - git ls-files -s >index_files && - test_line_count = 1 index_files && - - git rev-parse >actual HEAD:c && - git rev-parse >expect A:b && - test_cmp expect actual && - - git hash-object c >actual && - git rev-parse A:b >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:b && - test_path_is_missing b - ) -' - -# Testcase 2b, Changed and renamed on A, subset of changes on B -# Commit O: b_1 -# Commit A: c_2 -# Commit B: b_3 -# Expected: c_2 - -test_setup_2b () { - test_create_repo 2b_$1 && - ( - cd 2b_$1 && - - test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && - git add b && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && - git add b && - git mv b c && - test_tick && - git commit -m "A" && - - git checkout B && - test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && - git add b && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '2b-L: Rename+Mod(A)/Mod(B), B mods subset of A' ' - test_setup_2b L && - ( - cd 2b_L && - - git checkout A^0 && - - test-tool chmtime --get -3600 c >old-mtime && - GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && - - test_must_be_empty err && - - # Make sure c WAS updated - test-tool chmtime --get c >new-mtime && - test_cmp old-mtime new-mtime && - - git ls-files -s >index_files && - test_line_count = 1 index_files && - - git rev-parse >actual HEAD:c && - git rev-parse >expect A:c && - test_cmp expect actual && - - git hash-object c >actual && - git rev-parse A:c >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:b && - test_path_is_missing b - ) -' - -test_expect_success '2b-R: Rename+Mod(A)/Mod(B), B mods subset of A' ' - test_setup_2b R && - ( - cd 2b_R && - - git checkout B^0 && - - test_path_is_missing c && - GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && - - # Make sure c now present (and thus was updated) - test_path_is_file c && - - test_must_be_empty err && - - git ls-files -s >index_files && - test_line_count = 1 index_files && - - git rev-parse >actual HEAD:c && - git rev-parse >expect A:c && - test_cmp expect actual && - - git hash-object c >actual && - git rev-parse A:c >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:b && - test_path_is_missing b - ) -' - -# Testcase 2c, Changes on A, rename on B -# Commit O: b_1 -# Commit A: b_2, c_3 -# Commit B: c_1 -# Expected: rename/add conflict c_2 vs c_3 -# -# NOTE: Since A modified b_1->b_2, and B renamed b_1->c_1, the threeway -# merge of those files should result in c_2. We then should have a -# rename/add conflict between c_2 and c_3. However, if we note in -# merge_content() that A had the right contents (b_2 has same -# contents as c_2, just at a different name), and that A had the -# right path present (c_3 existed) and thus decides that it can -# skip the update, then we're in trouble. This test verifies we do -# not make that particular mistake. - -test_setup_2c () { - test_create_repo 2c && - ( - cd 2c && - - test_seq 1 10 >b && - git add b && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - test_seq 1 11 >b && - echo whatever >c && - git add b c && - test_tick && - git commit -m "A" && - - git checkout B && - git mv b c && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '2c: Modify b & add c VS rename b->c' ' - test_setup_2c && - ( - cd 2c && - - git checkout A^0 && - - test-tool chmtime --get -3600 c >old-mtime && - GIT_MERGE_VERBOSITY=3 && - export GIT_MERGE_VERBOSITY && - test_must_fail git merge -s recursive B^0 >out 2>err && - - test_i18ngrep "CONFLICT (rename/add): Rename b->c" out && - test_must_be_empty err && - - # Make sure c WAS updated - test-tool chmtime --get c >new-mtime && - test $(cat old-mtime) -lt $(cat new-mtime) - - # FIXME: rename/add conflicts are horribly broken right now; - # when I get back to my patch series fixing it and - # rename/rename(2to1) conflicts to bring them in line with - # how add/add conflicts behave, then checks like the below - # could be added. But that patch series is waiting until - # the rename-directory-detection series lands, which this - # is part of. And in the mean time, I do not want to further - # enforce broken behavior. So for now, the main test is the - # one above that err is an empty file. - - #git ls-files -s >index_files && - #test_line_count = 2 index_files && - - #git rev-parse >actual :2:c :3:c && - #git rev-parse >expect A:b A:c && - #test_cmp expect actual && - - #git cat-file -p A:b >>merged && - #git cat-file -p A:c >>merge-me && - #>empty && - #test_must_fail git merge-file \ - # -L "Temporary merge branch 1" \ - # -L "" \ - # -L "Temporary merge branch 2" \ - # merged empty merge-me && - #sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && - - #git hash-object c >actual && - #git hash-object merged-internal >expect && - #test_cmp expect actual && - - #test_path_is_missing b - ) -' - - -########################################################################### -# SECTION 3: Cases involving directory renames -# -# NOTE: -# Directory renames only apply when one side renames a directory, and the -# other side adds or renames a path into that directory. Applying the -# directory rename to that new path creates a new pathname that didn't -# exist on either side of history. Thus, it is impossible for the -# merge contents to already be at the right path, so all of these checks -# exist just to make sure that updates are not skipped. -########################################################################### - -# Testcase 3a, Change + rename into dir foo on A, dir rename foo->bar on B -# Commit O: bq_1, foo/whatever -# Commit A: foo/{bq_2, whatever} -# Commit B: bq_1, bar/whatever -# Expected: bar/{bq_2, whatever} - -test_setup_3a () { - test_create_repo 3a_$1 && - ( - cd 3a_$1 && - - mkdir foo && - test_seq 1 10 >bq && - test_write_lines a b c d e f g h i j k >foo/whatever && - git add bq foo/whatever && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - test_seq 1 11 >bq && - git add bq && - git mv bq foo/ && - test_tick && - git commit -m "A" && - - git checkout B && - git mv foo/ bar/ && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '3a-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_setup_3a L && - ( - cd 3a_L && - - git checkout A^0 && - - test_path_is_missing bar/bq && - GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - - test_must_be_empty err && - - test_path_is_file bar/bq && - - git ls-files -s >index_files && - test_line_count = 2 index_files && - - git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && - git rev-parse >expect A:foo/bq A:foo/whatever && - test_cmp expect actual && - - git hash-object bar/bq bar/whatever >actual && - git rev-parse A:foo/bq A:foo/whatever >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && - test_path_is_missing bq foo/bq foo/whatever - ) -' - -test_expect_success '3a-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_setup_3a R && - ( - cd 3a_R && - - git checkout B^0 && - - test_path_is_missing bar/bq && - GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err && - - test_must_be_empty err && - - test_path_is_file bar/bq && - - git ls-files -s >index_files && - test_line_count = 2 index_files && - - git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && - git rev-parse >expect A:foo/bq A:foo/whatever && - test_cmp expect actual && - - git hash-object bar/bq bar/whatever >actual && - git rev-parse A:foo/bq A:foo/whatever >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && - test_path_is_missing bq foo/bq foo/whatever - ) -' - -# Testcase 3b, rename into dir foo on A, dir rename foo->bar + change on B -# Commit O: bq_1, foo/whatever -# Commit A: foo/{bq_1, whatever} -# Commit B: bq_2, bar/whatever -# Expected: bar/{bq_2, whatever} - -test_setup_3b () { - test_create_repo 3b_$1 && - ( - cd 3b_$1 && - - mkdir foo && - test_seq 1 10 >bq && - test_write_lines a b c d e f g h i j k >foo/whatever && - git add bq foo/whatever && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - git mv bq foo/ && - test_tick && - git commit -m "A" && - - git checkout B && - test_seq 1 11 >bq && - git add bq && - git mv foo/ bar/ && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '3b-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_setup_3b L && - ( - cd 3b_L && - - git checkout A^0 && - - test_path_is_missing bar/bq && - GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && - - test_must_be_empty err && - - test_path_is_file bar/bq && - - git ls-files -s >index_files && - test_line_count = 2 index_files && - - git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && - git rev-parse >expect B:bq A:foo/whatever && - test_cmp expect actual && - - git hash-object bar/bq bar/whatever >actual && - git rev-parse B:bq A:foo/whatever >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && - test_path_is_missing bq foo/bq foo/whatever - ) -' - -test_expect_success '3b-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' - test_setup_3b R && - ( - cd 3b_R && - - git checkout B^0 && - - test_path_is_missing bar/bq && - GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err && - - test_must_be_empty err && - - test_path_is_file bar/bq && - - git ls-files -s >index_files && - test_line_count = 2 index_files && - - git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && - git rev-parse >expect B:bq A:foo/whatever && - test_cmp expect actual && - - git hash-object bar/bq bar/whatever >actual && - git rev-parse B:bq A:foo/whatever >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && - test_path_is_missing bq foo/bq foo/whatever - ) -' - -########################################################################### -# SECTION 4: Cases involving dirty changes -########################################################################### - -# Testcase 4a, Changed on A, subset of changes on B, locally modified -# Commit O: b_1 -# Commit A: b_2 -# Commit B: b_3 -# Working copy: b_4 -# Expected: b_2 for merge, b_4 in working copy - -test_setup_4a () { - test_create_repo 4a && - ( - cd 4a && - - test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && - git add b && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && - git add b && - test_tick && - git commit -m "A" && - - git checkout B && - test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && - git add b && - test_tick && - git commit -m "B" - ) -} - -# NOTE: For as long as we continue using unpack_trees() without index_only -# set to true, it will error out on a case like this claiming that the locally -# modified file would be overwritten by the merge. Getting this testcase -# correct requires doing the merge in-memory first, then realizing that no -# updates to the file are necessary, and thus that we can just leave the path -# alone. -test_expect_failure '4a: Change on A, change on B subset of A, dirty mods present' ' - test_setup_4a && - ( - cd 4a && - - git checkout A^0 && - echo "File rewritten" >b && - - test-tool chmtime --get -3600 b >old-mtime && - - GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && - - test_must_be_empty err && - - # Make sure b was NOT updated - test-tool chmtime --get b >new-mtime && - test_cmp old-mtime new-mtime && - - git ls-files -s >index_files && - test_line_count = 1 index_files && - - git rev-parse >actual :0:b && - git rev-parse >expect A:b && - test_cmp expect actual && - - git hash-object b >actual && - echo "File rewritten" | git hash-object --stdin >expect && - test_cmp expect actual - ) -' - -# Testcase 4b, Changed+renamed on A, subset of changes on B, locally modified -# Commit O: b_1 -# Commit A: c_2 -# Commit B: b_3 -# Working copy: c_4 -# Expected: c_2 - -test_setup_4b () { - test_create_repo 4b && - ( - cd 4b && - - test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && - git add b && - test_tick && - git commit -m "O" && - - git branch O && - git branch A && - git branch B && - - git checkout A && - test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && - git add b && - git mv b c && - test_tick && - git commit -m "A" && - - git checkout B && - test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && - git add b && - test_tick && - git commit -m "B" - ) -} - -test_expect_success '4b: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' - test_setup_4b && - ( - cd 4b && - - git checkout A^0 && - echo "File rewritten" >c && - - test-tool chmtime --get -3600 c >old-mtime && - - GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && - - test_must_be_empty err && - - # Make sure c was NOT updated - test-tool chmtime --get c >new-mtime && - test_cmp old-mtime new-mtime && - - git ls-files -s >index_files && - test_line_count = 1 index_files && - - git rev-parse >actual :0:c && - git rev-parse >expect A:c && - test_cmp expect actual && - - git hash-object c >actual && - echo "File rewritten" | git hash-object --stdin >expect && - test_cmp expect actual && - - test_must_fail git rev-parse HEAD:b && - test_path_is_missing b - ) -' - -test_done diff --git a/t/t6047-diff3-conflict-markers.sh b/t/t6047-diff3-conflict-markers.sh deleted file mode 100755 index f4655bb..0000000 --- a/t/t6047-diff3-conflict-markers.sh +++ /dev/null @@ -1,211 +0,0 @@ -#!/bin/sh - -test_description='recursive merge diff3 style conflict markers' - -. ./test-lib.sh - -# Setup: -# L1 -# \ -# ? -# / -# R1 -# -# Where: -# L1 and R1 both have a file named 'content' but have no common history -# - -test_expect_success 'setup no merge base' ' - test_create_repo no_merge_base && - ( - cd no_merge_base && - - git checkout -b L && - test_commit A content A && - - git checkout --orphan R && - test_commit B content B - ) -' - -test_expect_success 'check no merge base' ' - ( - cd no_merge_base && - - git checkout L^0 && - - test_must_fail git -c merge.conflictstyle=diff3 merge --allow-unrelated-histories -s recursive R^0 && - - grep "|||||| empty tree" content - ) -' - -# Setup: -# L1 -# / \ -# master ? -# \ / -# R1 -# -# Where: -# L1 and R1 have modified the same file ('content') in conflicting ways -# - -test_expect_success 'setup unique merge base' ' - test_create_repo unique_merge_base && - ( - cd unique_merge_base && - - test_commit base content "1 -2 -3 -4 -5 -" && - - git branch L && - git branch R && - - git checkout L && - test_commit L content "1 -2 -3 -4 -5 -7" && - - git checkout R && - git rm content && - test_commit R renamed "1 -2 -3 -4 -5 -six" - ) -' - -test_expect_success 'check unique merge base' ' - ( - cd unique_merge_base && - - git checkout L^0 && - MASTER=$(git rev-parse --short master) && - - test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 && - - grep "|||||| $MASTER:content" renamed - ) -' - -# Setup: -# L1---L2--L3 -# / \ / \ -# master X1 ? -# \ / \ / -# R1---R2--R3 -# -# Where: -# commits L1 and R1 have modified the same file in non-conflicting ways -# X1 is an auto-generated merge-base used when merging L1 and R1 -# commits L2 and R2 are merges of R1 and L1 into L1 and R1, respectively -# commits L3 and R3 both modify 'content' in conflicting ways -# - -test_expect_success 'setup multiple merge bases' ' - test_create_repo multiple_merge_bases && - ( - cd multiple_merge_bases && - - test_commit initial content "1 -2 -3 -4 -5" && - - git branch L && - git branch R && - - # Create L1 - git checkout L && - test_commit L1 content "0 -1 -2 -3 -4 -5" && - - # Create R1 - git checkout R && - test_commit R1 content "1 -2 -3 -4 -5 -6" && - - # Create L2 - git checkout L && - git merge R1 && - - # Create R2 - git checkout R && - git merge L1 && - - # Create L3 - git checkout L && - test_commit L3 content "0 -1 -2 -3 -4 -5 -A" && - - # Create R3 - git checkout R && - git rm content && - test_commit R3 renamed "0 -2 -3 -4 -5 -six" - ) -' - -test_expect_success 'check multiple merge bases' ' - ( - cd multiple_merge_bases && - - git checkout L^0 && - - test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 && - - grep "|||||| merged common ancestors:content" renamed - ) -' - -test_expect_success 'rebase --merge describes parent of commit being picked' ' - test_create_repo rebase && - ( - cd rebase && - test_commit base file && - test_commit master file && - git checkout -b side HEAD^ && - test_commit side file && - test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master && - grep "||||||| parent of" file - ) -' - -test_expect_success 'rebase --apply describes fake ancestor base' ' - ( - cd rebase && - git rebase --abort && - test_must_fail git -c merge.conflictstyle=diff3 rebase --apply master && - grep "||||||| constructed merge base" file - ) -' - -test_done diff --git a/t/t6400-merge-df.sh b/t/t6400-merge-df.sh new file mode 100755 index 0000000..400a4cd --- /dev/null +++ b/t/t6400-merge-df.sh @@ -0,0 +1,150 @@ +#!/bin/sh +# +# Copyright (c) 2005 Fredrik Kuivinen +# + +test_description='Test merge with directory/file conflicts' +. ./test-lib.sh + +test_expect_success 'prepare repository' ' + echo Hello >init && + git add init && + git commit -m initial && + + git branch B && + mkdir dir && + echo foo >dir/foo && + git add dir/foo && + git commit -m "File: dir/foo" && + + git checkout B && + echo file dir >dir && + git add dir && + git commit -m "File: dir" +' + +test_expect_success 'Merge with d/f conflicts' ' + test_expect_code 1 git merge -m "merge msg" master +' + +test_expect_success 'F/D conflict' ' + git reset --hard && + git checkout master && + rm .git/index && + + mkdir before && + echo FILE >before/one && + echo FILE >after && + git add . && + git commit -m first && + + rm -f after && + git mv before after && + git commit -m move && + + git checkout -b para HEAD^ && + echo COMPLETELY ANOTHER FILE >another && + git add . && + git commit -m para && + + git merge master +' + +test_expect_success 'setup modify/delete + directory/file conflict' ' + git checkout --orphan modify && + git rm -rf . && + git clean -fdqx && + + printf "a\nb\nc\nd\ne\nf\ng\nh\n" >letters && + git add letters && + git commit -m initial && + + # Throw in letters.txt for sorting order fun + # ("letters.txt" sorts between "letters" and "letters/file") + echo i >>letters && + echo "version 2" >letters.txt && + git add letters letters.txt && + git commit -m modified && + + git checkout -b delete HEAD^ && + git rm letters && + mkdir letters && + >letters/file && + echo "version 1" >letters.txt && + git add letters letters.txt && + git commit -m deleted +' + +test_expect_success 'modify/delete + directory/file conflict' ' + git checkout delete^0 && + test_must_fail git merge modify && + + test 5 -eq $(git ls-files -s | wc -l) && + test 4 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && + + test_path_is_file letters/file && + test_path_is_file letters.txt && + test_path_is_file letters~modify +' + +test_expect_success 'modify/delete + directory/file conflict; other way' ' + git reset --hard && + git clean -f && + git checkout modify^0 && + + test_must_fail git merge delete && + + test 5 -eq $(git ls-files -s | wc -l) && + test 4 -eq $(git ls-files -u | wc -l) && + test 1 -eq $(git ls-files -o | wc -l) && + + test_path_is_file letters/file && + test_path_is_file letters.txt && + test_path_is_file letters~HEAD +' + +test_expect_success 'Simple merge in repo with interesting pathnames' ' + # Simple lexicographic ordering of files and directories would be: + # foo + # foo/bar + # foo/bar-2 + # foo/bar/baz + # foo/bar-2/baz + # The fact that foo/bar-2 appears between foo/bar and foo/bar/baz + # can trip up some codepaths, and is the point of this test. + test_create_repo name-ordering && + ( + cd name-ordering && + + mkdir -p foo/bar && + mkdir -p foo/bar-2 && + >foo/bar/baz && + >foo/bar-2/baz && + git add . && + git commit -m initial && + + git branch main && + git branch other && + + git checkout other && + echo other >foo/bar-2/baz && + git add -u && + git commit -m other && + + git checkout main && + echo main >foo/bar/baz && + git add -u && + git commit -m main && + + git merge other && + git ls-files -s >out && + test_line_count = 2 out && + git rev-parse :0:foo/bar/baz :0:foo/bar-2/baz >actual && + git rev-parse HEAD~1:foo/bar/baz other:foo/bar-2/baz >expect && + test_cmp expect actual + ) + +' + +test_done diff --git a/t/t6401-merge-criss-cross.sh b/t/t6401-merge-criss-cross.sh new file mode 100755 index 0000000..9d5e992 --- /dev/null +++ b/t/t6401-merge-criss-cross.sh @@ -0,0 +1,67 @@ +#!/bin/sh +# +# Copyright (c) 2005 Fredrik Kuivinen +# + +# See https://lore.kernel.org/git/Pine.LNX.4.44.0504271254120.4678-100000@wax.eds.org/ for a +# nice description of what this is about. + + +test_description='Test criss-cross merge' +. ./test-lib.sh + +test_expect_success 'prepare repository' ' + test_write_lines 1 2 3 4 5 6 7 8 9 >file && + git add file && + git commit -m "Initial commit" file && + + git branch A && + git branch B && + git checkout A && + + test_write_lines 1 2 3 4 5 6 7 "8 changed in B8, branch A" 9 >file && + git commit -m "B8" file && + git checkout B && + + test_write_lines 1 2 "3 changed in C3, branch B" 4 5 6 7 8 9 >file && + git commit -m "C3" file && + git branch C3 && + + git merge -m "pre E3 merge" A && + + test_write_lines 1 2 "3 changed in E3, branch B. New file size" 4 5 6 7 "8 changed in B8, branch A" 9 >file && + git commit -m "E3" file && + + git checkout A && + git merge -m "pre D8 merge" C3 && + test_write_lines 1 2 "3 changed in C3, branch B" 4 5 6 7 "8 changed in D8, branch A. New file size 2" 9 >file && + + git commit -m D8 file +' + +test_expect_success 'Criss-cross merge' ' + git merge -m "final merge" B +' + +test_expect_success 'Criss-cross merge result' ' + cat <<-\EOF >file-expect && + 1 + 2 + 3 changed in E3, branch B. New file size + 4 + 5 + 6 + 7 + 8 changed in D8, branch A. New file size 2 + 9 + EOF + + test_cmp file-expect file +' + +test_expect_success 'Criss-cross merge fails (-s resolve)' ' + git reset --hard A^ && + test_must_fail git merge -s resolve -m "final merge" B +' + +test_done diff --git a/t/t6402-merge-rename.sh b/t/t6402-merge-rename.sh new file mode 100755 index 0000000..bbbba3d --- /dev/null +++ b/t/t6402-merge-rename.sh @@ -0,0 +1,910 @@ +#!/bin/sh + +test_description='Merge-recursive merging renames' +. ./test-lib.sh + +modify () { + sed -e "$1" <"$2" >"$2.x" && + mv "$2.x" "$2" +} + +test_expect_success 'setup' ' + cat >A <<-\EOF && + a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + c cccccccccccccccccccccccccccccccccccccccccccccccc + d dddddddddddddddddddddddddddddddddddddddddddddddd + e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + f ffffffffffffffffffffffffffffffffffffffffffffffff + g gggggggggggggggggggggggggggggggggggggggggggggggg + h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh + i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii + j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj + k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk + l llllllllllllllllllllllllllllllllllllllllllllllll + m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm + n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn + o oooooooooooooooooooooooooooooooooooooooooooooooo + EOF + + cat >M <<-\EOF && + A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC + D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD + E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG + H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII + J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ + K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK + L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL + M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM + N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN + O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + EOF + + git add A M && + git commit -m "initial has A and M" && + git branch white && + git branch red && + git branch blue && + git branch yellow && + git branch change && + git branch change+rename && + + sed -e "/^g /s/.*/g : master changes a line/" A+ && + mv A+ A && + git commit -a -m "master updates A" && + + git checkout yellow && + rm -f M && + git commit -a -m "yellow removes M" && + + git checkout white && + sed -e "/^g /s/.*/g : white changes a line/" B && + sed -e "/^G /s/.*/G : colored branch changes a line/" N && + rm -f A M && + git update-index --add --remove A B M N && + git commit -m "white renames A->B, M->N" && + + git checkout red && + sed -e "/^g /s/.*/g : red changes a line/" B && + sed -e "/^G /s/.*/G : colored branch changes a line/" N && + rm -f A M && + git update-index --add --remove A B M N && + git commit -m "red renames A->B, M->N" && + + git checkout blue && + sed -e "/^g /s/.*/g : blue changes a line/" C && + sed -e "/^G /s/.*/G : colored branch changes a line/" N && + rm -f A M && + git update-index --add --remove A C M N && + git commit -m "blue renames A->C, M->N" && + + git checkout change && + sed -e "/^g /s/.*/g : changed line/" A+ && + mv A+ A && + git commit -q -a -m "changed" && + + git checkout change+rename && + sed -e "/^g /s/.*/g : changed line/" B && + rm A && + git update-index --add B && + git commit -q -a -m "changed and renamed" && + + git checkout master +' + +test_expect_success 'pull renaming branch into unrenaming one' \ +' + git show-branch && + test_expect_code 1 git pull . white && + git ls-files -s && + git ls-files -u B >b.stages && + test_line_count = 3 b.stages && + git ls-files -s N >n.stages && + test_line_count = 1 n.stages && + sed -ne "/^g/{ + p + q + }" B | grep master && + git diff --exit-code white N +' + +test_expect_success 'pull renaming branch into another renaming one' \ +' + rm -f B && + git reset --hard && + git checkout red && + test_expect_code 1 git pull . white && + git ls-files -u B >b.stages && + test_line_count = 3 b.stages && + git ls-files -s N >n.stages && + test_line_count = 1 n.stages && + sed -ne "/^g/{ + p + q + }" B | grep red && + git diff --exit-code white N +' + +test_expect_success 'pull unrenaming branch into renaming one' \ +' + git reset --hard && + git show-branch && + test_expect_code 1 git pull . master && + git ls-files -u B >b.stages && + test_line_count = 3 b.stages && + git ls-files -s N >n.stages && + test_line_count = 1 n.stages && + sed -ne "/^g/{ + p + q + }" B | grep red && + git diff --exit-code white N +' + +test_expect_success 'pull conflicting renames' \ +' + git reset --hard && + git show-branch && + test_expect_code 1 git pull . blue && + git ls-files -u A >a.stages && + test_line_count = 1 a.stages && + git ls-files -u B >b.stages && + test_line_count = 1 b.stages && + git ls-files -u C >c.stages && + test_line_count = 1 c.stages && + git ls-files -s N >n.stages && + test_line_count = 1 n.stages && + sed -ne "/^g/{ + p + q + }" B | grep red && + git diff --exit-code white N +' + +test_expect_success 'interference with untracked working tree file' ' + git reset --hard && + git show-branch && + echo >A this file should not matter && + test_expect_code 1 git pull . white && + test_path_is_file A +' + +test_expect_success 'interference with untracked working tree file' ' + git reset --hard && + git checkout white && + git show-branch && + rm -f A && + echo >A this file should not matter && + test_expect_code 1 git pull . red && + test_path_is_file A +' + +test_expect_success 'interference with untracked working tree file' ' + git reset --hard && + rm -f A M && + git checkout -f master && + git tag -f anchor && + git show-branch && + git pull . yellow && + test_path_is_missing M && + git reset --hard anchor +' + +test_expect_success 'updated working tree file should prevent the merge' ' + git reset --hard && + rm -f A M && + git checkout -f master && + git tag -f anchor && + git show-branch && + echo >>M one line addition && + cat M >M.saved && + test_expect_code 128 git pull . yellow && + test_cmp M M.saved && + rm -f M.saved +' + +test_expect_success 'updated working tree file should prevent the merge' ' + git reset --hard && + rm -f A M && + git checkout -f master && + git tag -f anchor && + git show-branch && + echo >>M one line addition && + cat M >M.saved && + git update-index M && + test_expect_code 128 git pull . yellow && + test_cmp M M.saved && + rm -f M.saved +' + +test_expect_success 'interference with untracked working tree file' ' + git reset --hard && + rm -f A M && + git checkout -f yellow && + git tag -f anchor && + git show-branch && + echo >M this file should not matter && + git pull . master && + test_path_is_file M && + ! { + git ls-files -s | + grep M + } && + git reset --hard anchor +' + +test_expect_success 'merge of identical changes in a renamed file' ' + rm -f A M N && + git reset --hard && + git checkout change+rename && + + test-tool chmtime --get -3600 B >old-mtime && + GIT_MERGE_VERBOSITY=3 git merge change >out && + + test-tool chmtime --get B >new-mtime && + test_cmp old-mtime new-mtime && + + git reset --hard HEAD^ && + git checkout change && + + # A will be renamed to B; we check mtimes and file presence + test_path_is_missing B && + test-tool chmtime --get -3600 A >old-mtime && + GIT_MERGE_VERBOSITY=3 git merge change+rename >out && + + test_path_is_missing A && + test-tool chmtime --get B >new-mtime && + test $(cat old-mtime) -lt $(cat new-mtime) +' + +test_expect_success 'setup for rename + d/f conflicts' ' + git reset --hard && + git checkout --orphan dir-in-way && + git rm -rf . && + git clean -fdqx && + + mkdir sub && + mkdir dir && + printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >sub/file && + echo foo >dir/file-in-the-way && + git add -A && + git commit -m "Common commit" && + + echo 11 >>sub/file && + echo more >>dir/file-in-the-way && + git add -u && + git commit -m "Commit to merge, with dir in the way" && + + git checkout -b dir-not-in-way && + git reset --soft HEAD^ && + git rm -rf dir && + git commit -m "Commit to merge, with dir removed" -- dir sub/file && + + git checkout -b renamed-file-has-no-conflicts dir-in-way~1 && + git rm -rf dir && + git rm sub/file && + printf "1\n2\n3\n4\n5555\n6\n7\n8\n9\n10\n" >dir && + git add dir && + git commit -m "Independent change" && + + git checkout -b renamed-file-has-conflicts dir-in-way~1 && + git rm -rf dir && + git mv sub/file dir && + echo 12 >>dir && + git add dir && + git commit -m "Conflicting change" +' + +test_expect_success 'Rename+D/F conflict; renamed file merges + dir not in way' ' + git reset --hard && + git checkout -q renamed-file-has-no-conflicts^0 && + + git merge --strategy=recursive dir-not-in-way && + + git diff --quiet && + test_path_is_file dir && + test_write_lines 1 2 3 4 5555 6 7 8 9 10 11 >expected && + test_cmp expected dir +' + +test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' ' + git reset --hard && + rm -rf dir~* && + git checkout -q renamed-file-has-no-conflicts^0 && + test_must_fail git merge --strategy=recursive dir-in-way >output && + + test_i18ngrep "CONFLICT (modify/delete): dir/file-in-the-way" output && + test_i18ngrep "Auto-merging dir" output && + test_i18ngrep "Adding as dir~HEAD instead" output && + + test 3 -eq "$(git ls-files -u | wc -l)" && + test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && + + test_must_fail git diff --quiet && + test_must_fail git diff --cached --quiet && + + test_path_is_file dir/file-in-the-way && + test_path_is_file dir~HEAD && + test_cmp expected dir~HEAD +' + +test_expect_success 'Same as previous, but merged other way' ' + git reset --hard && + rm -rf dir~* && + git checkout -q dir-in-way^0 && + test_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors && + + ! grep "error: refusing to lose untracked file at" errors && + test_i18ngrep "CONFLICT (modify/delete): dir/file-in-the-way" output && + test_i18ngrep "Auto-merging dir" output && + test_i18ngrep "Adding as dir~renamed-file-has-no-conflicts instead" output && + + test 3 -eq "$(git ls-files -u | wc -l)" && + test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && + + test_must_fail git diff --quiet && + test_must_fail git diff --cached --quiet && + + test_path_is_file dir/file-in-the-way && + test_path_is_file dir~renamed-file-has-no-conflicts && + test_cmp expected dir~renamed-file-has-no-conflicts +' + +test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' ' + git reset --hard && + rm -rf dir~* && + git checkout -q renamed-file-has-conflicts^0 && + test_must_fail git merge --strategy=recursive dir-not-in-way && + + test 3 -eq "$(git ls-files -u | wc -l)" && + test 3 -eq "$(git ls-files -u dir | wc -l)" && + + test_must_fail git diff --quiet && + test_must_fail git diff --cached --quiet && + + test_path_is_file dir && + cat >expected <<-\EOF && + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + <<<<<<< HEAD:dir + 12 + ======= + 11 + >>>>>>> dir-not-in-way:sub/file + EOF + test_cmp expected dir +' + +test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in the way' ' + modify s/dir-not-in-way/dir-in-way/ expected && + + git reset --hard && + rm -rf dir~* && + git checkout -q renamed-file-has-conflicts^0 && + test_must_fail git merge --strategy=recursive dir-in-way && + + test 5 -eq "$(git ls-files -u | wc -l)" && + test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" && + test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && + + test_must_fail git diff --quiet && + test_must_fail git diff --cached --quiet && + + test_path_is_file dir/file-in-the-way && + test_path_is_file dir~HEAD && + test_cmp expected dir~HEAD +' + +test_expect_success 'Same as previous, but merged other way' ' + git reset --hard && + rm -rf dir~* && + git checkout -q dir-in-way^0 && + test_must_fail git merge --strategy=recursive renamed-file-has-conflicts && + + test 5 -eq "$(git ls-files -u | wc -l)" && + test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" && + test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && + + test_must_fail git diff --quiet && + test_must_fail git diff --cached --quiet && + + test_path_is_file dir/file-in-the-way && + test_path_is_file dir~renamed-file-has-conflicts && + cat >expected <<-\EOF && + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + <<<<<<< HEAD:sub/file + 11 + ======= + 12 + >>>>>>> renamed-file-has-conflicts:dir + EOF + test_cmp expected dir~renamed-file-has-conflicts +' + +test_expect_success 'setup both rename source and destination involved in D/F conflict' ' + git reset --hard && + git checkout --orphan rename-dest && + git rm -rf . && + git clean -fdqx && + + mkdir one && + echo stuff >one/file && + git add -A && + git commit -m "Common commit" && + + git mv one/file destdir && + git commit -m "Renamed to destdir" && + + git checkout -b source-conflict HEAD~1 && + git rm -rf one && + mkdir destdir && + touch one destdir/foo && + git add -A && + git commit -m "Conflicts in the way" +' + +test_expect_success 'both rename source and destination involved in D/F conflict' ' + git reset --hard && + rm -rf dir~* && + git checkout -q rename-dest^0 && + test_must_fail git merge --strategy=recursive source-conflict && + + test 1 -eq "$(git ls-files -u | wc -l)" && + + test_must_fail git diff --quiet && + + test_path_is_file destdir/foo && + test_path_is_file one && + test_path_is_file destdir~HEAD && + test "stuff" = "$(cat destdir~HEAD)" +' + +test_expect_success 'setup pair rename to parent of other (D/F conflicts)' ' + git reset --hard && + git checkout --orphan rename-two && + git rm -rf . && + git clean -fdqx && + + mkdir one && + mkdir two && + echo stuff >one/file && + echo other >two/file && + git add -A && + git commit -m "Common commit" && + + git rm -rf one && + git mv two/file one && + git commit -m "Rename two/file -> one" && + + git checkout -b rename-one HEAD~1 && + git rm -rf two && + git mv one/file two && + rm -r one && + git commit -m "Rename one/file -> two" +' + +test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked dir' ' + git checkout -q rename-one^0 && + mkdir one && + test_must_fail git merge --strategy=recursive rename-two && + + test 2 -eq "$(git ls-files -u | wc -l)" && + test 1 -eq "$(git ls-files -u one | wc -l)" && + test 1 -eq "$(git ls-files -u two | wc -l)" && + + test_must_fail git diff --quiet && + + test 4 -eq $(find . | grep -v .git | wc -l) && + + test_path_is_dir one && + test_path_is_file one~rename-two && + test_path_is_file two && + test "other" = $(cat one~rename-two) && + test "stuff" = $(cat two) +' + +test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean start' ' + git reset --hard && + git clean -fdqx && + test_must_fail git merge --strategy=recursive rename-two && + + test 2 -eq "$(git ls-files -u | wc -l)" && + test 1 -eq "$(git ls-files -u one | wc -l)" && + test 1 -eq "$(git ls-files -u two | wc -l)" && + + test_must_fail git diff --quiet && + + test 3 -eq $(find . | grep -v .git | wc -l) && + + test_path_is_file one && + test_path_is_file two && + test "other" = $(cat one) && + test "stuff" = $(cat two) +' + +test_expect_success 'setup rename of one file to two, with directories in the way' ' + git reset --hard && + git checkout --orphan first-rename && + git rm -rf . && + git clean -fdqx && + + echo stuff >original && + git add -A && + git commit -m "Common commit" && + + mkdir two && + >two/file && + git add two/file && + git mv original one && + git commit -m "Put two/file in the way, rename to one" && + + git checkout -b second-rename HEAD~1 && + mkdir one && + >one/file && + git add one/file && + git mv original two && + git commit -m "Put one/file in the way, rename to two" +' + +test_expect_success 'check handling of differently renamed file with D/F conflicts' ' + git checkout -q first-rename^0 && + test_must_fail git merge --strategy=recursive second-rename && + + test 5 -eq "$(git ls-files -s | wc -l)" && + test 3 -eq "$(git ls-files -u | wc -l)" && + test 1 -eq "$(git ls-files -u one | wc -l)" && + test 1 -eq "$(git ls-files -u two | wc -l)" && + test 1 -eq "$(git ls-files -u original | wc -l)" && + test 2 -eq "$(git ls-files -o | wc -l)" && + + test_path_is_file one/file && + test_path_is_file two/file && + test_path_is_file one~HEAD && + test_path_is_file two~second-rename && + test_path_is_missing original +' + +test_expect_success 'setup rename one file to two; directories moving out of the way' ' + git reset --hard && + git checkout --orphan first-rename-redo && + git rm -rf . && + git clean -fdqx && + + echo stuff >original && + mkdir one two && + touch one/file two/file && + git add -A && + git commit -m "Common commit" && + + git rm -rf one && + git mv original one && + git commit -m "Rename to one" && + + git checkout -b second-rename-redo HEAD~1 && + git rm -rf two && + git mv original two && + git commit -m "Rename to two" +' + +test_expect_success 'check handling of differently renamed file with D/F conflicts' ' + git checkout -q first-rename-redo^0 && + test_must_fail git merge --strategy=recursive second-rename-redo && + + test 3 -eq "$(git ls-files -u | wc -l)" && + test 1 -eq "$(git ls-files -u one | wc -l)" && + test 1 -eq "$(git ls-files -u two | wc -l)" && + test 1 -eq "$(git ls-files -u original | wc -l)" && + test 0 -eq "$(git ls-files -o | wc -l)" && + + test_path_is_file one && + test_path_is_file two && + test_path_is_missing original +' + +test_expect_success 'setup avoid unnecessary update, normal rename' ' + git reset --hard && + git checkout --orphan avoid-unnecessary-update-1 && + git rm -rf . && + git clean -fdqx && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >original && + git add -A && + git commit -m "Common commit" && + + git mv original rename && + echo 11 >>rename && + git add -u && + git commit -m "Renamed and modified" && + + git checkout -b merge-branch-1 HEAD~1 && + echo "random content" >random-file && + git add -A && + git commit -m "Random, unrelated changes" +' + +test_expect_success 'avoid unnecessary update, normal rename' ' + git checkout -q avoid-unnecessary-update-1^0 && + test-tool chmtime --get -3600 rename >expect && + git merge merge-branch-1 && + test-tool chmtime --get rename >actual && + test_cmp expect actual # "rename" should have stayed intact +' + +test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' ' + git reset --hard && + git checkout --orphan avoid-unnecessary-update-2 && + git rm -rf . && + git clean -fdqx && + + mkdir df && + printf "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n" >df/file && + git add -A && + git commit -m "Common commit" && + + git mv df/file temp && + rm -rf df && + git mv temp df && + echo 11 >>df && + git add -u && + git commit -m "Renamed and modified" && + + git checkout -b merge-branch-2 HEAD~1 && + >unrelated-change && + git add unrelated-change && + git commit -m "Only unrelated changes" +' + +test_expect_success 'avoid unnecessary update, with D/F conflict' ' + git checkout -q avoid-unnecessary-update-2^0 && + test-tool chmtime --get -3600 df >expect && + git merge merge-branch-2 && + test-tool chmtime --get df >actual && + test_cmp expect actual # "df" should have stayed intact +' + +test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + >irrelevant && + mkdir df && + >df/file && + git add -A && + git commit -mA && + + git checkout -b side && + git rm -rf df && + git commit -mB && + + git checkout master && + git rm -rf df && + echo bla >df && + git add -A && + git commit -m "Add a newfile" +' + +test_expect_success 'avoid unnecessary update, dir->(file,nothing)' ' + git checkout -q master^0 && + test-tool chmtime --get -3600 df >expect && + git merge side && + test-tool chmtime --get df >actual && + test_cmp expect actual # "df" should have stayed intact +' + +test_expect_success 'setup avoid unnecessary update, modify/delete' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + >irrelevant && + >file && + git add -A && + git commit -mA && + + git checkout -b side && + git rm -f file && + git commit -m "Delete file" && + + git checkout master && + echo bla >file && + git add -A && + git commit -m "Modify file" +' + +test_expect_success 'avoid unnecessary update, modify/delete' ' + git checkout -q master^0 && + test-tool chmtime --get -3600 file >expect && + test_must_fail git merge side && + test-tool chmtime --get file >actual && + test_cmp expect actual # "file" should have stayed intact +' + +test_expect_success 'setup avoid unnecessary update, rename/add-dest' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >file && + git add -A && + git commit -mA && + + git checkout -b side && + cp file newfile && + git add -A && + git commit -m "Add file copy" && + + git checkout master && + git mv file newfile && + git commit -m "Rename file" +' + +test_expect_success 'avoid unnecessary update, rename/add-dest' ' + git checkout -q master^0 && + test-tool chmtime --get -3600 newfile >expect && + git merge side && + test-tool chmtime --get newfile >actual && + test_cmp expect actual # "file" should have stayed intact +' + +test_expect_success 'setup merge of rename + small change' ' + git reset --hard && + git checkout --orphan rename-plus-small-change && + git rm -rf . && + git clean -fdqx && + + echo ORIGINAL >file && + git add file && + + test_tick && + git commit -m Initial && + git checkout -b rename_branch && + git mv file renamed_file && + git commit -m Rename && + git checkout rename-plus-small-change && + echo NEW-VERSION >file && + git commit -a -m Reformat +' + +test_expect_success 'merge rename + small change' ' + git merge rename_branch && + + test 1 -eq $(git ls-files -s | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && + test $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file) +' + +test_expect_success 'setup for use of extended merge markers' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file && + git add original_file && + git commit -mA && + + git checkout -b rename && + echo 9 >>original_file && + git add original_file && + git mv original_file renamed_file && + git commit -mB && + + git checkout master && + echo 8.5 >>original_file && + git add original_file && + git commit -mC +' + +test_expect_success 'merge master into rename has correct extended markers' ' + git checkout rename^0 && + test_must_fail git merge -s recursive master^0 && + + cat >expected <<-\EOF && + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + <<<<<<< HEAD:renamed_file + 9 + ======= + 8.5 + >>>>>>> master^0:original_file + EOF + test_cmp expected renamed_file +' + +test_expect_success 'merge rename into master has correct extended markers' ' + git reset --hard && + git checkout master^0 && + test_must_fail git merge -s recursive rename^0 && + + cat >expected <<-\EOF && + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + <<<<<<< HEAD:original_file + 8.5 + ======= + 9 + >>>>>>> rename^0:renamed_file + EOF + test_cmp expected renamed_file +' + +test_expect_success 'setup spurious "refusing to lose untracked" message' ' + git rm -rf . && + git clean -fdqx && + rm -rf .git && + git init && + + > irrelevant_file && + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >original_file && + git add irrelevant_file original_file && + git commit -mA && + + git checkout -b rename && + git mv original_file renamed_file && + git commit -mB && + + git checkout master && + git rm original_file && + git commit -mC +' + +test_expect_success 'no spurious "refusing to lose untracked" message' ' + git checkout master^0 && + test_must_fail git merge rename^0 2>errors.txt && + ! grep "refusing to lose untracked file" errors.txt +' + +test_expect_success 'do not follow renames for empty files' ' + git checkout -f -b empty-base && + >empty1 && + git add empty1 && + git commit -m base && + echo content >empty1 && + git add empty1 && + git commit -m fill && + git checkout -b empty-topic HEAD^ && + git mv empty1 empty2 && + git commit -m rename && + test_must_fail git merge empty-base && + test_must_be_empty empty2 +' + +test_done diff --git a/t/t6403-merge-file.sh b/t/t6403-merge-file.sh new file mode 100755 index 0000000..2f421d9 --- /dev/null +++ b/t/t6403-merge-file.sh @@ -0,0 +1,390 @@ +#!/bin/sh + +test_description='RCS merge replacement: merge-file' +. ./test-lib.sh + +test_expect_success 'setup' ' + cat >orig.txt <<-\EOF && + Dominus regit me, + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + EOF + + cat >new1.txt <<-\EOF && + Dominus regit me, + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + Nam et si ambulavero in medio umbrae mortis, + non timebo mala, quoniam tu mecum es: + virga tua et baculus tuus ipsa me consolata sunt. + EOF + + cat >new2.txt <<-\EOF && + Dominus regit me, et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + EOF + + cat >new3.txt <<-\EOF && + DOMINUS regit me, + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + EOF + + cat >new4.txt <<-\EOF && + Dominus regit me, et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + EOF + + printf "propter nomen suum." >>new4.txt +' + +test_expect_success 'merge with no changes' ' + cp orig.txt test.txt && + git merge-file test.txt orig.txt orig.txt && + test_cmp test.txt orig.txt +' + +test_expect_success "merge without conflict" ' + cp new1.txt test.txt && + git merge-file test.txt orig.txt new2.txt +' + +test_expect_success 'works in subdirectory' ' + mkdir dir && + cp new1.txt dir/a.txt && + cp orig.txt dir/o.txt && + cp new2.txt dir/b.txt && + ( cd dir && git merge-file a.txt o.txt b.txt ) && + test_path_is_missing a.txt +' + +test_expect_success "merge without conflict (--quiet)" ' + cp new1.txt test.txt && + git merge-file --quiet test.txt orig.txt new2.txt +' + +test_expect_failure "merge without conflict (missing LF at EOF)" ' + cp new1.txt test2.txt && + git merge-file test2.txt orig.txt new4.txt +' + +test_expect_failure "merge result added missing LF" ' + test_cmp test.txt test2.txt +' + +test_expect_success "merge without conflict (missing LF at EOF, away from change in the other file)" ' + cp new4.txt test3.txt && + git merge-file --quiet test3.txt new2.txt new3.txt +' + +test_expect_success "merge does not add LF away of change" ' + cat >expect.txt <<-\EOF && + DOMINUS regit me, + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + EOF + printf "propter nomen suum." >>expect.txt && + + test_cmp expect.txt test3.txt +' + +test_expect_success "merge with conflicts" ' + cp test.txt backup.txt && + test_must_fail git merge-file test.txt orig.txt new3.txt +' + +test_expect_success "expected conflict markers" ' + cat >expect.txt <<-\EOF && + <<<<<<< test.txt + Dominus regit me, et nihil mihi deerit. + ======= + DOMINUS regit me, + et nihil mihi deerit. + >>>>>>> new3.txt + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + Nam et si ambulavero in medio umbrae mortis, + non timebo mala, quoniam tu mecum es: + virga tua et baculus tuus ipsa me consolata sunt. + EOF + + test_cmp expect.txt test.txt +' + +test_expect_success "merge conflicting with --ours" ' + cp backup.txt test.txt && + + cat >expect.txt <<-\EOF && + Dominus regit me, et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + Nam et si ambulavero in medio umbrae mortis, + non timebo mala, quoniam tu mecum es: + virga tua et baculus tuus ipsa me consolata sunt. + EOF + + git merge-file --ours test.txt orig.txt new3.txt && + test_cmp expect.txt test.txt +' + +test_expect_success "merge conflicting with --theirs" ' + cp backup.txt test.txt && + + cat >expect.txt <<-\EOF && + DOMINUS regit me, + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + Nam et si ambulavero in medio umbrae mortis, + non timebo mala, quoniam tu mecum es: + virga tua et baculus tuus ipsa me consolata sunt. + EOF + + git merge-file --theirs test.txt orig.txt new3.txt && + test_cmp expect.txt test.txt +' + +test_expect_success "merge conflicting with --union" ' + cp backup.txt test.txt && + + cat >expect.txt <<-\EOF && + Dominus regit me, et nihil mihi deerit. + DOMINUS regit me, + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + Nam et si ambulavero in medio umbrae mortis, + non timebo mala, quoniam tu mecum es: + virga tua et baculus tuus ipsa me consolata sunt. + EOF + + git merge-file --union test.txt orig.txt new3.txt && + test_cmp expect.txt test.txt +' + +test_expect_success "merge with conflicts, using -L" ' + cp backup.txt test.txt && + + test_must_fail git merge-file -L 1 -L 2 test.txt orig.txt new3.txt +' + +test_expect_success "expected conflict markers, with -L" ' + cat >expect.txt <<-\EOF && + <<<<<<< 1 + Dominus regit me, et nihil mihi deerit. + ======= + DOMINUS regit me, + et nihil mihi deerit. + >>>>>>> new3.txt + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + Nam et si ambulavero in medio umbrae mortis, + non timebo mala, quoniam tu mecum es: + virga tua et baculus tuus ipsa me consolata sunt. + EOF + + test_cmp expect.txt test.txt +' + +test_expect_success "conflict in removed tail" ' + sed "s/ tu / TU /" new5.txt && + test_must_fail git merge-file -p orig.txt new1.txt new5.txt >out +' + +test_expect_success "expected conflict markers" ' + cat >expect <<-\EOF && + Dominus regit me, + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + <<<<<<< orig.txt + ======= + Nam et si ambulavero in medio umbrae mortis, + non timebo mala, quoniam TU mecum es: + virga tua et baculus tuus ipsa me consolata sunt. + >>>>>>> new5.txt + EOF + + test_cmp expect out +' + +test_expect_success 'binary files cannot be merged' ' + test_must_fail git merge-file -p \ + orig.txt "$TEST_DIRECTORY"/test-binary-1.png new1.txt 2> merge.err && + grep "Cannot merge binary files" merge.err +' + +test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' ' + sed -e "s/deerit.\$/deerit;/" -e "s/me;\$/me./" new6.txt && + sed -e "s/deerit.\$/deerit,/" -e "s/me;\$/me,/" new7.txt && + + test_must_fail git merge-file -p new6.txt new5.txt new7.txt > output && + test 1 = $(grep ======= new8.txt && + sed -e "s/deerit./&%%%%/" -e "s/locavit,/locavit --/" new9.txt && + + test_must_fail git merge-file -p \ + new8.txt new5.txt new9.txt >merge.out && + test 1 = $(grep ======= expect <<-\EOF && + Dominus regit me, + <<<<<<< new8.txt + et nihil mihi deerit; + + + + + In loco pascuae ibi me collocavit; + super aquam refectionis educavit me. + ||||||| new5.txt + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + ======= + et nihil mihi deerit, + + + + + In loco pascuae ibi me collocavit -- + super aquam refectionis educavit me, + >>>>>>> new9.txt + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + Nam et si ambulavero in medio umbrae mortis, + non timebo mala, quoniam TU mecum es: + virga tua et baculus tuus ipsa me consolata sunt. + EOF + + test_must_fail git merge-file -p --diff3 \ + new8.txt new5.txt new9.txt >actual && + test_cmp expect actual +' + +test_expect_success '"diff3 -m" style output (2)' ' + git config merge.conflictstyle diff3 && + test_must_fail git merge-file -p \ + new8.txt new5.txt new9.txt >actual && + test_cmp expect actual +' + +test_expect_success 'marker size' ' + cat >expect <<-\EOF && + Dominus regit me, + <<<<<<<<<< new8.txt + et nihil mihi deerit; + + + + + In loco pascuae ibi me collocavit; + super aquam refectionis educavit me. + |||||||||| new5.txt + et nihil mihi deerit. + In loco pascuae ibi me collocavit, + super aquam refectionis educavit me; + ========== + et nihil mihi deerit, + + + + + In loco pascuae ibi me collocavit -- + super aquam refectionis educavit me, + >>>>>>>>>> new9.txt + animam meam convertit, + deduxit me super semitas jusitiae, + propter nomen suum. + Nam et si ambulavero in medio umbrae mortis, + non timebo mala, quoniam TU mecum es: + virga tua et baculus tuus ipsa me consolata sunt. + EOF + + test_must_fail git merge-file -p --marker-size=10 \ + new8.txt new5.txt new9.txt >actual && + test_cmp expect actual +' + +test_expect_success 'conflict at EOF without LF resolved by --ours' ' + printf "line1\nline2\nline3" >nolf-orig.txt && + printf "line1\nline2\nline3x" >nolf-diff1.txt && + printf "line1\nline2\nline3y" >nolf-diff2.txt && + + git merge-file -p --ours nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt && + printf "line1\nline2\nline3x" >expect.txt && + test_cmp expect.txt output.txt +' + +test_expect_success 'conflict at EOF without LF resolved by --theirs' ' + git merge-file -p --theirs nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt && + printf "line1\nline2\nline3y" >expect.txt && + test_cmp expect.txt output.txt +' + +test_expect_success 'conflict at EOF without LF resolved by --union' ' + git merge-file -p --union nolf-diff1.txt nolf-orig.txt nolf-diff2.txt >output.txt && + printf "line1\nline2\nline3x\nline3y" >expect.txt && + test_cmp expect.txt output.txt +' + +test_expect_success 'conflict sections match existing line endings' ' + printf "1\\r\\n2\\r\\n3" >crlf-orig.txt && + printf "1\\r\\n2\\r\\n4" >crlf-diff1.txt && + printf "1\\r\\n2\\r\\n5" >crlf-diff2.txt && + test_must_fail git -c core.eol=crlf merge-file -p \ + crlf-diff1.txt crlf-orig.txt crlf-diff2.txt >crlf.txt && + test $(tr "\015" Q ].*Q$" | wc -l) = 3 && + test $(tr "\015" Q nolf.txt && + test $(tr "\015" Q ].*Q$" | wc -l) = 0 +' + +test_done diff --git a/t/t6404-recursive-merge.sh b/t/t6404-recursive-merge.sh new file mode 100755 index 0000000..332cfc5 --- /dev/null +++ b/t/t6404-recursive-merge.sh @@ -0,0 +1,131 @@ +#!/bin/sh + +test_description='Test merge without common ancestors' +. ./test-lib.sh + +# This scenario is based on a real-world repository of Shawn Pearce. + +# 1 - A - D - F +# \ X / +# B X +# X \ +# 2 - C - E - G + +GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" +export GIT_COMMITTER_DATE + +test_expect_success 'setup tests' ' + echo 1 >a1 && + git add a1 && + GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 && + + git checkout -b A master && + echo A >a1 && + GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 && + + git checkout -b B master && + echo B >a1 && + GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 && + + git checkout -b D A && + git rev-parse B >.git/MERGE_HEAD && + echo D >a1 && + git update-index a1 && + GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D && + + git symbolic-ref HEAD refs/heads/other && + echo 2 >a1 && + GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 && + + git checkout -b C && + echo C >a1 && + GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 && + + git checkout -b E C && + git rev-parse B >.git/MERGE_HEAD && + echo E >a1 && + git update-index a1 && + GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E && + + git checkout -b G E && + git rev-parse A >.git/MERGE_HEAD && + echo G >a1 && + git update-index a1 && + GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G && + + git checkout -b F D && + git rev-parse C >.git/MERGE_HEAD && + echo F >a1 && + git update-index a1 && + GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F && + + test_oid_cache <<-EOF + idxstage1 sha1:ec3fe2a791706733f2d8fa7ad45d9a9672031f5e + idxstage1 sha256:b3c8488929903aaebdeb22270cb6d36e5b8724b01ae0d4da24632f158c99676f + EOF +' + +test_expect_success 'combined merge conflicts' ' + test_must_fail env GIT_TEST_COMMIT_GRAPH=0 git merge -m final G +' + +test_expect_success 'result contains a conflict' ' + cat >expect <<-\EOF && + <<<<<<< HEAD + F + ======= + G + >>>>>>> G + EOF + + test_cmp expect a1 +' + +test_expect_success 'virtual trees were processed' ' + git ls-files --stage >out && + + cat >expect <<-EOF && + 100644 $(test_oid idxstage1) 1 a1 + 100644 $(git rev-parse F:a1) 2 a1 + 100644 $(git rev-parse G:a1) 3 a1 + EOF + + test_cmp expect out +' + +test_expect_success 'refuse to merge binary files' ' + git reset --hard && + printf "\0" >binary-file && + git add binary-file && + git commit -m binary && + git checkout G && + printf "\0\0" >binary-file && + git add binary-file && + git commit -m binary2 && + test_must_fail git merge F >merge.out 2>merge.err && + grep "Cannot merge binary files: binary-file (HEAD vs. F)" merge.err +' + +test_expect_success 'mark rename/delete as unmerged' ' + + git reset --hard && + git checkout -b delete && + git rm a1 && + test_tick && + git commit -m delete && + git checkout -b rename HEAD^ && + git mv a1 a2 && + test_tick && + git commit -m rename && + test_must_fail git merge delete && + test 1 = $(git ls-files --unmerged | wc -l) && + git rev-parse --verify :2:a2 && + test_must_fail git rev-parse --verify :3:a2 && + git checkout -f delete && + test_must_fail git merge rename && + test 1 = $(git ls-files --unmerged | wc -l) && + test_must_fail git rev-parse --verify :2:a2 && + git rev-parse --verify :3:a2 +' + +test_done diff --git a/t/t6405-merge-symlinks.sh b/t/t6405-merge-symlinks.sh new file mode 100755 index 0000000..6c0a90d --- /dev/null +++ b/t/t6405-merge-symlinks.sh @@ -0,0 +1,62 @@ +#!/bin/sh +# +# Copyright (c) 2007 Johannes Sixt +# + +test_description='merging symlinks on filesystem w/o symlink support. + +This tests that git merge-recursive writes merge results as plain files +if core.symlinks is false.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + git config core.symlinks false && + >file && + git add file && + git commit -m initial && + git branch b-symlink && + git branch b-file && + l=$(printf file | git hash-object -t blob -w --stdin) && + echo "120000 $l symlink" | git update-index --index-info && + git commit -m master && + git checkout b-symlink && + l=$(printf file-different | git hash-object -t blob -w --stdin) && + echo "120000 $l symlink" | git update-index --index-info && + git commit -m b-symlink && + git checkout b-file && + echo plain-file >symlink && + git add symlink && + git commit -m b-file +' + +test_expect_success 'merge master into b-symlink, which has a different symbolic link' ' + git checkout b-symlink && + test_must_fail git merge master +' + +test_expect_success 'the merge result must be a file' ' + test_path_is_file symlink +' + +test_expect_success 'merge master into b-file, which has a file instead of a symbolic link' ' + git reset --hard && + git checkout b-file && + test_must_fail git merge master +' + +test_expect_success 'the merge result must be a file' ' + test_path_is_file symlink +' + +test_expect_success 'merge b-file, which has a file instead of a symbolic link, into master' ' + git reset --hard && + git checkout master && + test_must_fail git merge b-file +' + +test_expect_success 'the merge result must be a file' ' + test_path_is_file symlink +' + +test_done diff --git a/t/t6406-merge-attr.sh b/t/t6406-merge-attr.sh new file mode 100755 index 0000000..5900358 --- /dev/null +++ b/t/t6406-merge-attr.sh @@ -0,0 +1,207 @@ +#!/bin/sh +# +# Copyright (c) 2007 Junio C Hamano +# + +test_description='per path merge controlled by merge attribute' + +. ./test-lib.sh + +test_expect_success setup ' + + for f in text binary union + do + echo Initial >$f && git add $f || return 1 + done && + test_tick && + git commit -m Initial && + + git branch side && + for f in text binary union + do + echo Master >>$f && git add $f || return 1 + done && + test_tick && + git commit -m Master && + + git checkout side && + for f in text binary union + do + echo Side >>$f && git add $f || return 1 + done && + test_tick && + git commit -m Side && + + git tag anchor && + + cat >./custom-merge <<-\EOF && + #!/bin/sh + + orig="$1" ours="$2" theirs="$3" exit="$4" path=$5 + ( + echo "orig is $orig" + echo "ours is $ours" + echo "theirs is $theirs" + echo "path is $path" + echo "=== orig ===" + cat "$orig" + echo "=== ours ===" + cat "$ours" + echo "=== theirs ===" + cat "$theirs" + ) >"$ours+" + cat "$ours+" >"$ours" + rm -f "$ours+" + exit "$exit" + EOF + chmod +x ./custom-merge +' + +test_expect_success merge ' + + { + echo "binary -merge" + echo "union merge=union" + } >.gitattributes && + + if git merge master + then + echo Gaah, should have conflicted + false + else + echo Ok, conflicted. + fi +' + +test_expect_success 'check merge result in index' ' + + git ls-files -u | grep binary && + git ls-files -u | grep text && + ! (git ls-files -u | grep union) + +' + +test_expect_success 'check merge result in working tree' ' + + git cat-file -p HEAD:binary >binary-orig && + grep "<<<<<<<" text && + cmp binary-orig binary && + ! grep "<<<<<<<" union && + grep Master union && + grep Side union + +' + +test_expect_success 'retry the merge with longer context' ' + echo text conflict-marker-size=32 >>.gitattributes && + git checkout -m text && + sed -ne "/^\([<=>]\)\1\1\1*/{ + s/ .*$// + p + }" >actual text && + grep ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>" actual && + grep "================================" actual && + grep "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<" actual +' + +test_expect_success 'custom merge backend' ' + + echo "* merge=union" >.gitattributes && + echo "text merge=custom" >>.gitattributes && + + git reset --hard anchor && + git config --replace-all \ + merge.custom.driver "./custom-merge %O %A %B 0 %P" && + git config --replace-all \ + merge.custom.name "custom merge driver for testing" && + + git merge master && + + cmp binary union && + sed -e 1,3d text >check-1 && + o=$(git unpack-file master^:text) && + a=$(git unpack-file side^:text) && + b=$(git unpack-file master:text) && + sh -c "./custom-merge $o $a $b 0 'text'" && + sed -e 1,3d $a >check-2 && + cmp check-1 check-2 && + rm -f $o $a $b +' + +test_expect_success 'custom merge backend' ' + + git reset --hard anchor && + git config --replace-all \ + merge.custom.driver "./custom-merge %O %A %B 1 %P" && + git config --replace-all \ + merge.custom.name "custom merge driver for testing" && + + if git merge master + then + echo "Eh? should have conflicted" + false + else + echo "Ok, conflicted" + fi && + + cmp binary union && + sed -e 1,3d text >check-1 && + o=$(git unpack-file master^:text) && + a=$(git unpack-file anchor:text) && + b=$(git unpack-file master:text) && + sh -c "./custom-merge $o $a $b 0 'text'" && + sed -e 1,3d $a >check-2 && + cmp check-1 check-2 && + sed -e 1,3d -e 4q $a >check-3 && + echo "path is text" >expect && + cmp expect check-3 && + rm -f $o $a $b +' + +test_expect_success 'up-to-date merge without common ancestor' ' + test_create_repo repo1 && + test_create_repo repo2 && + test_tick && + ( + cd repo1 && + >a && + git add a && + git commit -m initial + ) && + test_tick && + ( + cd repo2 && + git commit --allow-empty -m initial + ) && + test_tick && + ( + cd repo1 && + git fetch ../repo2 master && + git merge --allow-unrelated-histories FETCH_HEAD + ) +' + +test_expect_success 'custom merge does not lock index' ' + git reset --hard anchor && + write_script sleep-an-hour.sh <<-\EOF && + sleep 3600 & + echo $! >sleep.pid + EOF + + test_write_lines >.gitattributes \ + "* merge=ours" "text merge=sleep-an-hour" && + test_config merge.ours.driver true && + test_config merge.sleep-an-hour.driver ./sleep-an-hour.sh && + + # We are testing that the custom merge driver does not block + # index.lock on Windows due to an inherited file handle. + # To ensure that the backgrounded process ran sufficiently + # long (and has been started in the first place), we do not + # ignore the result of the kill command. + # By packaging the command in test_when_finished, we get both + # the correctness check and the clean-up. + test_when_finished "kill \$(cat sleep.pid)" && + git merge master +' + +test_done diff --git a/t/t6407-merge-binary.sh b/t/t6407-merge-binary.sh new file mode 100755 index 0000000..4e6c7cb --- /dev/null +++ b/t/t6407-merge-binary.sh @@ -0,0 +1,67 @@ +#!/bin/sh + +test_description='ask merge-recursive to merge binary files' + +. ./test-lib.sh + +test_expect_success setup ' + + cat "$TEST_DIRECTORY"/test-binary-1.png >m && + git add m && + git ls-files -s | sed -e "s/ 0 / 1 /" >E1 && + test_tick && + git commit -m "initial" && + + git branch side && + echo frotz >a && + git add a && + echo nitfol >>m && + git add a m && + git ls-files -s a >E0 && + git ls-files -s m | sed -e "s/ 0 / 3 /" >E3 && + test_tick && + git commit -m "master adds some" && + + git checkout side && + echo rezrov >>m && + git add m && + git ls-files -s m | sed -e "s/ 0 / 2 /" >E2 && + test_tick && + git commit -m "side modifies" && + + git tag anchor && + + cat E0 E1 E2 E3 >expect +' + +test_expect_success resolve ' + + rm -f a* m* && + git reset --hard anchor && + + if git merge -s resolve master + then + echo Oops, should not have succeeded + false + else + git ls-files -s >current + test_cmp expect current + fi +' + +test_expect_success recursive ' + + rm -f a* m* && + git reset --hard anchor && + + if git merge -s recursive master + then + echo Oops, should not have succeeded + false + else + git ls-files -s >current + test_cmp expect current + fi +' + +test_done diff --git a/t/t6408-merge-up-to-date.sh b/t/t6408-merge-up-to-date.sh new file mode 100755 index 0000000..7763c1b --- /dev/null +++ b/t/t6408-merge-up-to-date.sh @@ -0,0 +1,92 @@ +#!/bin/sh + +test_description='merge fast-forward and up to date' + +. ./test-lib.sh + +test_expect_success setup ' + >file && + git add file && + test_tick && + git commit -m initial && + git tag c0 && + + echo second >file && + git add file && + test_tick && + git commit -m second && + git tag c1 && + git branch test && + echo third >file && + git add file && + test_tick && + git commit -m third && + git tag c2 +' + +test_expect_success 'merge -s recursive up-to-date' ' + + git reset --hard c1 && + test_tick && + git merge -s recursive c0 && + expect=$(git rev-parse c1) && + current=$(git rev-parse HEAD) && + test "$expect" = "$current" + +' + +test_expect_success 'merge -s recursive fast-forward' ' + + git reset --hard c0 && + test_tick && + git merge -s recursive c1 && + expect=$(git rev-parse c1) && + current=$(git rev-parse HEAD) && + test "$expect" = "$current" + +' + +test_expect_success 'merge -s ours up-to-date' ' + + git reset --hard c1 && + test_tick && + git merge -s ours c0 && + expect=$(git rev-parse c1) && + current=$(git rev-parse HEAD) && + test "$expect" = "$current" + +' + +test_expect_success 'merge -s ours fast-forward' ' + + git reset --hard c0 && + test_tick && + git merge -s ours c1 && + expect=$(git rev-parse c0^{tree}) && + current=$(git rev-parse HEAD^{tree}) && + test "$expect" = "$current" + +' + +test_expect_success 'merge -s subtree up-to-date' ' + + git reset --hard c1 && + test_tick && + git merge -s subtree c0 && + expect=$(git rev-parse c1) && + current=$(git rev-parse HEAD) && + test "$expect" = "$current" + +' + +test_expect_success 'merge fast-forward octopus' ' + + git reset --hard c0 && + test_tick && + git merge c1 c2 && + expect=$(git rev-parse c2) && + current=$(git rev-parse HEAD) && + test "$expect" = "$current" +' + +test_done diff --git a/t/t6409-merge-subtree.sh b/t/t6409-merge-subtree.sh new file mode 100755 index 0000000..793f0c8 --- /dev/null +++ b/t/t6409-merge-subtree.sh @@ -0,0 +1,152 @@ +#!/bin/sh + +test_description='subtree merge strategy' + +. ./test-lib.sh + +test_expect_success setup ' + + s="1 2 3 4 5 6 7 8" && + for i in $s; do echo $i; done >hello && + git add hello && + git commit -m initial && + git checkout -b side && + echo >>hello world && + git add hello && + git commit -m second && + git checkout master && + for i in mundo $s; do echo $i; done >hello && + git add hello && + git commit -m master + +' + +test_expect_success 'subtree available and works like recursive' ' + + git merge -s subtree side && + for i in mundo $s world; do echo $i; done >expect && + test_cmp expect hello + +' + +test_expect_success 'setup branch sub' ' + git checkout --orphan sub && + git rm -rf . && + test_commit foo +' + +test_expect_success 'setup branch main' ' + git checkout -b main master && + git merge -s ours --no-commit --allow-unrelated-histories sub && + git read-tree --prefix=dir/ -u sub && + git commit -m "initial merge of sub into main" && + test_path_is_file dir/foo.t && + test_path_is_file hello +' + +test_expect_success 'update branch sub' ' + git checkout sub && + test_commit bar +' + +test_expect_success 'update branch main' ' + git checkout main && + git merge -s subtree sub -m "second merge of sub into main" && + test_path_is_file dir/bar.t && + test_path_is_file dir/foo.t && + test_path_is_file hello +' + +test_expect_success 'setup' ' + mkdir git-gui && + cd git-gui && + git init && + echo git-gui > git-gui.sh && + o1=$(git hash-object git-gui.sh) && + git add git-gui.sh && + git commit -m "initial git-gui" && + cd .. && + mkdir git && + cd git && + git init && + echo git >git.c && + o2=$(git hash-object git.c) && + git add git.c && + git commit -m "initial git" +' + +test_expect_success 'initial merge' ' + git remote add -f gui ../git-gui && + git merge -s ours --no-commit --allow-unrelated-histories gui/master && + git read-tree --prefix=git-gui/ -u gui/master && + git commit -m "Merge git-gui as our subdirectory" && + git checkout -b work && + git ls-files -s >actual && + ( + echo "100644 $o1 0 git-gui/git-gui.sh" && + echo "100644 $o2 0 git.c" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'merge update' ' + cd ../git-gui && + echo git-gui2 > git-gui.sh && + o3=$(git hash-object git-gui.sh) && + git add git-gui.sh && + git checkout -b master2 && + git commit -m "update git-gui" && + cd ../git && + git pull -s subtree gui master2 && + git ls-files -s >actual && + ( + echo "100644 $o3 0 git-gui/git-gui.sh" && + echo "100644 $o2 0 git.c" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'initial ambiguous subtree' ' + cd ../git && + git reset --hard master && + git checkout -b master2 && + git merge -s ours --no-commit gui/master && + git read-tree --prefix=git-gui2/ -u gui/master && + git commit -m "Merge git-gui2 as our subdirectory" && + git checkout -b work2 && + git ls-files -s >actual && + ( + echo "100644 $o1 0 git-gui/git-gui.sh" && + echo "100644 $o1 0 git-gui2/git-gui.sh" && + echo "100644 $o2 0 git.c" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'merge using explicit' ' + cd ../git && + git reset --hard master2 && + git pull -Xsubtree=git-gui gui master2 && + git ls-files -s >actual && + ( + echo "100644 $o3 0 git-gui/git-gui.sh" && + echo "100644 $o1 0 git-gui2/git-gui.sh" && + echo "100644 $o2 0 git.c" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'merge2 using explicit' ' + cd ../git && + git reset --hard master2 && + git pull -Xsubtree=git-gui2 gui master2 && + git ls-files -s >actual && + ( + echo "100644 $o1 0 git-gui/git-gui.sh" && + echo "100644 $o3 0 git-gui2/git-gui.sh" && + echo "100644 $o2 0 git.c" + ) >expected && + test_cmp expected actual +' + +test_done diff --git a/t/t6411-merge-filemode.sh b/t/t6411-merge-filemode.sh new file mode 100755 index 0000000..87741ef --- /dev/null +++ b/t/t6411-merge-filemode.sh @@ -0,0 +1,100 @@ +#!/bin/sh + +test_description='merge: handle file mode' +. ./test-lib.sh + +test_expect_success 'set up mode change in one branch' ' + : >file1 && + git add file1 && + git commit -m initial && + git checkout -b a1 master && + : >dummy && + git add dummy && + git commit -m a && + git checkout -b b1 master && + test_chmod +x file1 && + git add file1 && + git commit -m b1 +' + +do_one_mode () { + strategy=$1 + us=$2 + them=$3 + test_expect_success "resolve single mode change ($strategy, $us)" ' + git checkout -f $us && + git merge -s $strategy $them && + git ls-files -s file1 | grep ^100755 + ' + + test_expect_success FILEMODE "verify executable bit on file ($strategy, $us)" ' + test -x file1 + ' +} + +do_one_mode recursive a1 b1 +do_one_mode recursive b1 a1 +do_one_mode resolve a1 b1 +do_one_mode resolve b1 a1 + +test_expect_success 'set up mode change in both branches' ' + git reset --hard HEAD && + git checkout -b a2 master && + : >file2 && + H=$(git hash-object file2) && + test_chmod +x file2 && + git commit -m a2 && + git checkout -b b2 master && + : >file2 && + git add file2 && + git commit -m b2 && + { + echo "100755 $H 2 file2" + echo "100644 $H 3 file2" + } >expect +' + +do_both_modes () { + strategy=$1 + test_expect_success "detect conflict on double mode change ($strategy)" ' + git reset --hard && + git checkout -f a2 && + test_must_fail git merge -s $strategy b2 && + git ls-files -u >actual && + test_cmp expect actual && + git ls-files -s file2 | grep ^100755 + ' + + test_expect_success FILEMODE "verify executable bit on file ($strategy)" ' + test -x file2 + ' +} + +# both sides are equivalent, so no need to run both ways +do_both_modes recursive +do_both_modes resolve + +test_expect_success 'set up delete/modechange scenario' ' + git reset --hard && + git checkout -b deletion master && + git rm file1 && + git commit -m deletion +' + +do_delete_modechange () { + strategy=$1 + us=$2 + them=$3 + test_expect_success "detect delete/modechange conflict ($strategy, $us)" ' + git reset --hard && + git checkout $us && + test_must_fail git merge -s $strategy $them + ' +} + +do_delete_modechange recursive b1 deletion +do_delete_modechange recursive deletion b1 +do_delete_modechange resolve b1 deletion +do_delete_modechange resolve deletion b1 + +test_done diff --git a/t/t6412-merge-large-rename.sh b/t/t6412-merge-large-rename.sh new file mode 100755 index 0000000..8077738 --- /dev/null +++ b/t/t6412-merge-large-rename.sh @@ -0,0 +1,103 @@ +#!/bin/sh + +test_description='merging with large rename matrix' +. ./test-lib.sh + +count() { + i=1 + while test $i -le $1; do + echo $i + i=$(($i + 1)) + done +} + +test_expect_success 'setup (initial)' ' + touch file && + git add . && + git commit -m initial && + git tag initial +' + +make_text() { + echo $1: $2 + for i in $(count 20); do + echo $1: $i + done + echo $1: $3 +} + +test_rename() { + test_expect_success "rename ($1, $2)" ' + n='$1' && + expect='$2' && + git checkout -f master && + test_might_fail git branch -D test$n && + git reset --hard initial && + for i in $(count $n); do + make_text $i initial initial >$i + done && + git add . && + git commit -m add=$n && + for i in $(count $n); do + make_text $i changed initial >$i + done && + git commit -a -m change=$n && + git checkout -b test$n HEAD^ && + for i in $(count $n); do + git rm $i + make_text $i initial changed >$i.moved + done && + git add . && + git commit -m change+rename=$n && + case "$expect" in + ok) git merge master ;; + *) test_must_fail git merge master ;; + esac + ' +} + +test_rename 5 ok + +test_expect_success 'set diff.renamelimit to 4' ' + git config diff.renamelimit 4 +' +test_rename 4 ok +test_rename 5 fail + +test_expect_success 'set merge.renamelimit to 5' ' + git config merge.renamelimit 5 +' +test_rename 5 ok +test_rename 6 fail + +test_expect_success 'setup large simple rename' ' + git config --unset merge.renamelimit && + git config --unset diff.renamelimit && + + git reset --hard initial && + for i in $(count 200); do + make_text foo bar baz >$i + done && + git add . && + git commit -m create-files && + + git branch simple-change && + git checkout -b simple-rename && + + mkdir builtin && + git mv [0-9]* builtin/ && + git commit -m renamed && + + git checkout simple-change && + >unrelated-change && + git add unrelated-change && + git commit -m unrelated-change +' + +test_expect_success 'massive simple rename does not spam added files' ' + sane_unset GIT_MERGE_VERBOSITY && + git merge --no-stat simple-rename | grep -v Removing >output && + test_line_count -lt 5 output +' + +test_done diff --git a/t/t6413-merge-crlf.sh b/t/t6413-merge-crlf.sh new file mode 100755 index 0000000..e8d65ee --- /dev/null +++ b/t/t6413-merge-crlf.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +test_description='merge conflict in crlf repo + + b---M + / / + initial---a + +' + +. ./test-lib.sh + +test_expect_success setup ' + git config core.autocrlf true && + echo foo | append_cr >file && + git add file && + git commit -m "Initial" && + git tag initial && + git branch side && + echo line from a | append_cr >file && + git commit -m "add line from a" file && + git tag a && + git checkout side && + echo line from b | append_cr >file && + git commit -m "add line from b" file && + git tag b && + git checkout master +' + +test_expect_success 'Check "ours" is CRLF' ' + git reset --hard initial && + git merge side -s ours && + cat file | remove_cr | append_cr >file.temp && + test_cmp file file.temp +' + +test_expect_success 'Check that conflict file is CRLF' ' + git reset --hard a && + test_must_fail git merge side && + cat file | remove_cr | append_cr >file.temp && + test_cmp file file.temp +' + +test_done diff --git a/t/t6414-merge-rename-nocruft.sh b/t/t6414-merge-rename-nocruft.sh new file mode 100755 index 0000000..a25e730 --- /dev/null +++ b/t/t6414-merge-rename-nocruft.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +test_description='Merge-recursive merging renames' +. ./test-lib.sh + +test_expect_success 'setup' ' + cat >A <<-\EOF && + a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb + c cccccccccccccccccccccccccccccccccccccccccccccccc + d dddddddddddddddddddddddddddddddddddddddddddddddd + e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee + f ffffffffffffffffffffffffffffffffffffffffffffffff + g gggggggggggggggggggggggggggggggggggggggggggggggg + h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh + i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii + j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj + k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk + l llllllllllllllllllllllllllllllllllllllllllllllll + m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm + n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn + o oooooooooooooooooooooooooooooooooooooooooooooooo + EOF + + cat >M <<-\EOF && + A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB + C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC + D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD + E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE + F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF + G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG + H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH + I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII + J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ + K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK + L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL + M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM + N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN + O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO + EOF + + git add A M && + git commit -m "initial has A and M" && + git branch white && + git branch red && + git branch blue && + + git checkout white && + sed -e "/^g /s/.*/g : white changes a line/" B && + sed -e "/^G /s/.*/G : colored branch changes a line/" N && + rm -f A M && + git update-index --add --remove A B M N && + git commit -m "white renames A->B, M->N" && + + git checkout red && + echo created by red >R && + git update-index --add R && + git commit -m "red creates R" && + + git checkout blue && + sed -e "/^o /s/.*/g : blue changes a line/" B && + rm -f A && + mv B A && + git update-index A && + git commit -m "blue modify A" && + + git checkout master +' + +# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae +test_expect_success 'merge white into red (A->B,M->N)' ' + git checkout -b red-white red && + git merge white && + git write-tree && + test_path_is_file B && + test_path_is_file N && + test_path_is_file R && + test_path_is_missing A && + test_path_is_missing M +' + +# This test broke in 8371234ecaaf6e14fe3f2082a855eff1bbd79ae9 +test_expect_success 'merge blue into white (A->B, mod A, A untracked)' ' + git checkout -b white-blue white && + echo dirty >A && + git merge blue && + git write-tree && + test_path_is_file A && + echo dirty >expect && + test_cmp expect A && + test_path_is_file B && + test_path_is_file N && + test_path_is_missing M +' + +test_done diff --git a/t/t6415-merge-dir-to-symlink.sh b/t/t6415-merge-dir-to-symlink.sh new file mode 100755 index 0000000..2eddcc7 --- /dev/null +++ b/t/t6415-merge-dir-to-symlink.sh @@ -0,0 +1,172 @@ +#!/bin/sh + +test_description='merging when a directory was replaced with a symlink' +. ./test-lib.sh + +test_expect_success 'create a commit where dir a/b changed to symlink' ' + mkdir -p a/b/c a/b-2/c && + > a/b/c/d && + > a/b-2/c/d && + > a/x && + git add -A && + git commit -m base && + git tag start && + rm -rf a/b && + git add -A && + test_ln_s_add b-2 a/b && + git commit -m "dir to symlink" +' + +test_expect_success 'checkout does not clobber untracked symlink' ' + git checkout HEAD^0 && + git reset --hard master && + git rm --cached a/b && + git commit -m "untracked symlink remains" && + test_must_fail git checkout start^0 +' + +test_expect_success 'a/b-2/c/d is kept when clobbering symlink b' ' + git checkout HEAD^0 && + git reset --hard master && + git rm --cached a/b && + git commit -m "untracked symlink remains" && + git checkout -f start^0 && + test_path_is_file a/b-2/c/d +' + +test_expect_success 'checkout should not have deleted a/b-2/c/d' ' + git checkout HEAD^0 && + git reset --hard master && + git checkout start^0 && + test_path_is_file a/b-2/c/d +' + +test_expect_success 'setup for merge test' ' + git reset --hard && + test_path_is_file a/b-2/c/d && + echo x > a/x && + git add a/x && + git commit -m x && + git tag baseline +' + +test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (resolve)' ' + git reset --hard && + git checkout baseline^0 && + git merge -s resolve master && + test_path_is_file a/b-2/c/d +' + +test_expect_success SYMLINKS 'a/b was resolved as symlink' ' + test -h a/b +' + +test_expect_success 'Handle D/F conflict, do not lose a/b-2/c/d in merge (recursive)' ' + git reset --hard && + git checkout baseline^0 && + git merge -s recursive master && + test_path_is_file a/b-2/c/d +' + +test_expect_success SYMLINKS 'a/b was resolved as symlink' ' + test -h a/b +' + +test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (resolve)' ' + git reset --hard && + git checkout master^0 && + git merge -s resolve baseline^0 && + test_path_is_file a/b-2/c/d +' + +test_expect_success SYMLINKS 'a/b was resolved as symlink' ' + test -h a/b +' + +test_expect_success 'Handle F/D conflict, do not lose a/b-2/c/d in merge (recursive)' ' + git reset --hard && + git checkout master^0 && + git merge -s recursive baseline^0 && + test_path_is_file a/b-2/c/d +' + +test_expect_success SYMLINKS 'a/b was resolved as symlink' ' + test -h a/b +' + +test_expect_failure 'do not lose untracked in merge (resolve)' ' + git reset --hard && + git checkout baseline^0 && + >a/b/c/e && + test_must_fail git merge -s resolve master && + test_path_is_file a/b/c/e && + test_path_is_file a/b-2/c/d +' + +test_expect_success 'do not lose untracked in merge (recursive)' ' + git reset --hard && + git checkout baseline^0 && + >a/b/c/e && + test_must_fail git merge -s recursive master && + test_path_is_file a/b/c/e && + test_path_is_file a/b-2/c/d +' + +test_expect_success 'do not lose modifications in merge (resolve)' ' + git reset --hard && + git checkout baseline^0 && + echo more content >>a/b/c/d && + test_must_fail git merge -s resolve master +' + +test_expect_success 'do not lose modifications in merge (recursive)' ' + git reset --hard && + git checkout baseline^0 && + echo more content >>a/b/c/d && + test_must_fail git merge -s recursive master +' + +test_expect_success 'setup a merge where dir a/b-2 changed to symlink' ' + git reset --hard && + git checkout start^0 && + rm -rf a/b-2 && + git add -A && + test_ln_s_add b a/b-2 && + git commit -m "dir a/b-2 to symlink" && + git tag test2 +' + +test_expect_success 'merge should not have D/F conflicts (resolve)' ' + git reset --hard && + git checkout baseline^0 && + git merge -s resolve test2 && + test_path_is_file a/b/c/d +' + +test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' ' + test -h a/b-2 +' + +test_expect_success 'merge should not have D/F conflicts (recursive)' ' + git reset --hard && + git checkout baseline^0 && + git merge -s recursive test2 && + test_path_is_file a/b/c/d +' + +test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' ' + test -h a/b-2 +' + +test_expect_success 'merge should not have F/D conflicts (recursive)' ' + git reset --hard && + git checkout -b foo test2 && + git merge -s recursive baseline^0 && + test_path_is_file a/b/c/d +' + +test_expect_success SYMLINKS 'a/b-2 was resolved as symlink' ' + test -h a/b-2 +' + +test_done diff --git a/t/t6416-recursive-corner-cases.sh b/t/t6416-recursive-corner-cases.sh new file mode 100755 index 0000000..b3bf462 --- /dev/null +++ b/t/t6416-recursive-corner-cases.sh @@ -0,0 +1,1820 @@ +#!/bin/sh + +test_description='recursive merge corner cases involving criss-cross merges' + +. ./test-lib.sh + +# +# L1 L2 +# o---o +# / \ / \ +# o X ? +# \ / \ / +# o---o +# R1 R2 +# + +test_expect_success 'setup basic criss-cross + rename with no modifications' ' + test_create_repo basic-rename && + ( + cd basic-rename && + + ten="0 1 2 3 4 5 6 7 8 9" && + for i in $ten + do + echo line $i in a sample file + done >one && + for i in $ten + do + echo line $i in another sample file + done >two && + git add one two && + test_tick && git commit -m initial && + + git branch L1 && + git checkout -b R1 && + git mv one three && + test_tick && git commit -m R1 && + + git checkout L1 && + git mv two three && + test_tick && git commit -m L1 && + + git checkout L1^0 && + test_tick && git merge -s ours R1 && + git tag L2 && + + git checkout R1^0 && + test_tick && git merge -s ours L1 && + git tag R2 + ) +' + +test_expect_success 'merge simple rename+criss-cross with no modifications' ' + ( + cd basic-rename && + + git reset --hard && + git checkout L2^0 && + + test_must_fail git merge -s recursive R2^0 && + + git ls-files -s >out && + test_line_count = 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 >expect \ + L2:three R2:three && + git rev-parse >actual \ + :2:three :3:three && + test_cmp expect actual + ) +' + +# +# Same as before, but modify L1 slightly: +# +# L1m L2 +# o---o +# / \ / \ +# o X ? +# \ / \ / +# o---o +# R1 R2 +# + +test_expect_success 'setup criss-cross + rename merges with basic modification' ' + test_create_repo rename-modify && + ( + cd rename-modify && + + ten="0 1 2 3 4 5 6 7 8 9" && + for i in $ten + do + echo line $i in a sample file + done >one && + for i in $ten + do + echo line $i in another sample file + done >two && + git add one two && + test_tick && git commit -m initial && + + git branch L1 && + git checkout -b R1 && + git mv one three && + echo more >>two && + git add two && + test_tick && git commit -m R1 && + + git checkout L1 && + git mv two three && + test_tick && git commit -m L1 && + + git checkout L1^0 && + test_tick && git merge -s ours R1 && + git tag L2 && + + git checkout R1^0 && + test_tick && git merge -s ours L1 && + git tag R2 + ) +' + +test_expect_success 'merge criss-cross + rename merges with basic modification' ' + ( + cd rename-modify && + + git checkout L2^0 && + + test_must_fail git merge -s recursive R2^0 && + + 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 >expect \ + L2:three R2:three && + git rev-parse >actual \ + :2:three :3:three && + test_cmp expect actual + ) +' + +# +# For the next test, we start with three commits in two lines of development +# which setup a rename/add conflict: +# Commit A: File 'a' exists +# Commit B: Rename 'a' -> 'new_a' +# Commit C: Modify 'a', create different 'new_a' +# Later, two different people merge and resolve differently: +# Commit D: Merge B & C, ignoring separately created 'new_a' +# Commit E: Merge B & C making use of some piece of secondary 'new_a' +# Finally, someone goes to merge D & E. Does git detect the conflict? +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# + +test_expect_success 'setup differently handled merges of rename/add conflict' ' + test_create_repo rename-add && + ( + cd rename-add && + + printf "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n" >a && + git add a && + test_tick && git commit -m A && + + git branch B && + git checkout -b C && + echo 10 >>a && + test_write_lines 0 1 2 3 4 5 6 7 foobar >new_a && + git add a new_a && + test_tick && git commit -m C && + + git checkout B && + git mv a new_a && + test_tick && git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + git show :2:new_a >new_a && + git add new_a && + test_tick && git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + test_write_lines 0 1 2 3 4 5 6 7 bad_merge >new_a && + git add -u && + test_tick && git commit -m E && + git tag E + ) +' + +test_expect_success 'git detects differently handled merges conflict' ' + ( + cd rename-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git cat-file -p C:new_a >ours && + git cat-file -p C:a >theirs && + >empty && + test_must_fail git merge-file \ + -L "Temporary merge branch 1" \ + -L "" \ + -L "Temporary merge branch 2" \ + ours empty theirs && + sed -e "s/^\([<=>]\)/\1\1\1/" ours >ours-tweaked && + git hash-object ours-tweaked >expect && + git rev-parse >>expect \ + D:new_a E:new_a && + git rev-parse >actual \ + :1:new_a :2:new_a :3:new_a && + test_cmp expect actual && + + # Test that the two-way merge in new_a is as expected + git cat-file -p D:new_a >ours && + git cat-file -p E:new_a >theirs && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "E^0" \ + ours empty theirs && + sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && + git hash-object new_a >actual && + git hash-object ours >expect && + test_cmp expect actual + ) +' + +# Repeat the above testcase with precisely the same setup, other than with +# the two merge bases having different orderings of commit timestamps so +# that they are reversed in the order they are provided to merge-recursive, +# so that we can improve code coverage. +test_expect_success 'git detects differently handled merges conflict, swapped' ' + ( + cd rename-add && + + # Difference #1: Do cleanup from previous testrun + git reset --hard && + git clean -fdqx && + + # Difference #2: Change commit timestamps + btime=$(git log --no-walk --date=raw --format=%cd B | awk "{print \$1}") && + ctime=$(git log --no-walk --date=raw --format=%cd C | awk "{print \$1}") && + newctime=$(($btime+1)) && + git fast-export --no-data --all | sed -e s/$ctime/$newctime/ | git fast-import --force --quiet && + # End of most differences; rest is copy-paste of last test, + # other than swapping C:a and C:new_a due to order switch + + git checkout D^0 && + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git cat-file -p C:a >ours && + git cat-file -p C:new_a >theirs && + >empty && + test_must_fail git merge-file \ + -L "Temporary merge branch 1" \ + -L "" \ + -L "Temporary merge branch 2" \ + ours empty theirs && + sed -e "s/^\([<=>]\)/\1\1\1/" ours >ours-tweaked && + git hash-object ours-tweaked >expect && + git rev-parse >>expect \ + D:new_a E:new_a && + git rev-parse >actual \ + :1:new_a :2:new_a :3:new_a && + test_cmp expect actual && + + # Test that the two-way merge in new_a is as expected + git cat-file -p D:new_a >ours && + git cat-file -p E:new_a >theirs && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "E^0" \ + ours empty theirs && + sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && + git hash-object new_a >actual && + git hash-object ours >expect && + test_cmp expect actual + ) +' + +# +# criss-cross + modify/delete: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: file with contents 'A\n' +# Commit B: file with contents 'B\n' +# Commit C: file not present +# Commit D: file with contents 'B\n' +# Commit E: file not present +# +# Merging commits D & E should result in modify/delete conflict. + +test_expect_success 'setup criss-cross + modify/delete resolved differently' ' + test_create_repo modify-delete && + ( + cd modify-delete && + + echo A >file && + git add file && + test_tick && + git commit -m A && + + git branch B && + git checkout -b C && + git rm file && + test_tick && + git commit -m C && + + git checkout B && + echo B >file && + git add file && + test_tick && + git commit -m B && + + git checkout B^0 && + test_must_fail git merge C && + echo B >file && + git add file && + test_tick && + git commit -m D && + git tag D && + + git checkout C^0 && + test_must_fail git merge B && + git rm file && + test_tick && + git commit -m E && + git tag E + ) +' + +test_expect_success 'git detects conflict merging criss-cross+modify/delete' ' + ( + cd modify-delete && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + + git rev-parse >expect \ + master:file B:file && + git rev-parse >actual \ + :1:file :2:file && + test_cmp expect actual + ) +' + +test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' ' + ( + cd modify-delete && + + git reset --hard && + git checkout E^0 && + + test_must_fail git merge -s recursive D^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + + git rev-parse >expect \ + master:file B:file && + git rev-parse >actual \ + :1:file :3:file && + test_cmp expect actual + ) +' + +# SORRY FOR THE SUPER LONG DESCRIPTION, BUT THIS NEXT ONE IS HAIRY +# +# criss-cross + d/f conflict via add/add: +# Commit A: Neither file 'a' nor directory 'a/' exists. +# Commit B: Introduce 'a' +# Commit C: Introduce 'a/file' +# Commit D1: Merge B & C, keeping 'a' and deleting 'a/' +# Commit E1: Merge B & C, deleting 'a' but keeping 'a/file' +# +# B D1 or D2 +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E1 or E2 or E3 +# +# I'll describe D2, E2, & E3 (which are alternatives for D1 & E1) more below... +# +# Merging D1 & E1 requires we first create a virtual merge base X from +# merging A & B in memory. There are several possibilities for the merge-base: +# 1: Keep both 'a' and 'a/file' (assuming crazy filesystem allowing a tree +# with a directory and file at same path): results in merge of D1 & E1 +# being clean with both files deleted. Bad (no conflict detected). +# 2: Keep 'a' but not 'a/file': Merging D1 & E1 is clean and matches E1. Bad. +# 3: Keep 'a/file' but not 'a': Merging D1 & E1 is clean and matches D1. Bad. +# 4: Keep neither file: Merging D1 & E1 reports the D/F add/add conflict. +# +# So 4 sounds good for this case, but if we were to merge D1 & E3, where E3 +# is defined as: +# Commit E3: Merge B & C, keeping modified a, and deleting a/ +# then we'd get an add/add conflict for 'a', which seems suboptimal. A little +# creativity leads us to an alternate choice: +# 5: Keep 'a' as 'a~$UNIQUE' and a/file; results: +# Merge D1 & E1: rename/delete conflict for 'a'; a/file silently deleted +# Merge D1 & E3 is clean, as expected. +# +# So choice 5 at least provides some kind of conflict for the original case, +# and can merge cleanly as expected with D1 and E3. It also made things just +# slightly funny for merging D1 and e$, where E4 is defined as: +# Commit E4: Merge B & C, modifying 'a' and renaming to 'a2', and deleting 'a/' +# in this case, we'll get a rename/rename(1to2) conflict because a~$UNIQUE +# gets renamed to 'a' in D1 and to 'a2' in E4. But that's better than having +# two files (both 'a' and 'a2') sitting around without the user being notified +# that we could detect they were related and need to be merged. Also, choice +# 5 makes the handling of 'a/file' seem suboptimal. What if we were to merge +# D2 and E4, where D2 is: +# Commit D2: Merge B & C, renaming 'a'->'a2', keeping 'a/file' +# This would result in a clean merge with 'a2' having three-way merged +# contents (good), and deleting 'a/' (bad) -- it doesn't detect the +# conflict in how the different sides treated a/file differently. +# Continuing down the creative route: +# 6: Keep 'a' as 'a~$UNIQUE1' and keep 'a/' as 'a~$UNIQUE2/'; results: +# Merge D1 & E1: rename/delete conflict for 'a' and each path under 'a/'. +# Merge D1 & E3: clean, as expected. +# Merge D1 & E4: rename/rename(1to2) conflict on 'a' vs 'a2'. +# Merge D2 & E4: clean for 'a2', rename/delete for a/file +# +# Choice 6 could cause rename detection to take longer (providing more targets +# that need to be searched). Also, the conflict message for each path under +# 'a/' might be annoying unless we can detect it at the directory level, print +# it once, and then suppress it for individual filepaths underneath. +# +# +# As of time of writing, git uses choice 5. Directory rename detection and +# rename detection performance improvements might make choice 6 a desirable +# improvement. But we can at least document where we fall short for now... +# +# +# Historically, this testcase also used: +# Commit E2: Merge B & C, deleting 'a' but keeping slightly modified 'a/file' +# The merge of D1 & E2 is very similar to D1 & E1 -- it has similar issues for +# path 'a', but should always result in a modify/delete conflict for path +# 'a/file'. These tests ran the two merges +# D1 & E1 +# D1 & E2 +# in both directions, to check for directional issues with D/F conflict +# handling. Later we added +# D1 & E3 +# D1 & E4 +# D2 & E4 +# for good measure, though we only ran those one way because we had pretty +# good confidence in merge-recursive's directional handling of D/F issues. +# +# Just to summarize all the intermediate merge commits: +# Commit D1: Merge B & C, keeping a and deleting a/ +# Commit D2: Merge B & C, renaming a->a2, keeping a/file +# Commit E1: Merge B & C, deleting a but keeping a/file +# Commit E2: Merge B & C, deleting a but keeping slightly modified a/file +# Commit E3: Merge B & C, keeping modified a, and deleting a/ +# Commit E4: Merge B & C, modifying 'a' and renaming to 'a2', and deleting 'a/' +# + +test_expect_success 'setup differently handled merges of directory/file conflict' ' + test_create_repo directory-file && + ( + cd directory-file && + + >ignore-me && + git add ignore-me && + test_tick && + git commit -m A && + git tag A && + + git branch B && + git checkout -b C && + mkdir a && + test_write_lines a b c d e f g >a/file && + git add a/file && + test_tick && + git commit -m C && + + git checkout B && + test_write_lines 1 2 3 4 5 6 7 >a && + git add a && + test_tick && + git commit -m B && + + git checkout B^0 && + git merge -s ours -m D1 C^0 && + git tag D1 && + + git checkout B^0 && + test_must_fail git merge C^0 && + git clean -fd && + git rm -rf a/ && + git rm a && + git cat-file -p B:a >a2 && + git add a2 && + git commit -m D2 && + git tag D2 && + + git checkout C^0 && + git merge -s ours -m E1 B^0 && + git tag E1 && + + git checkout C^0 && + git merge -s ours -m E2 B^0 && + test_write_lines a b c d e f g h >a/file && + git add a/file && + git commit --amend -C HEAD && + git tag E2 && + + git checkout C^0 && + test_must_fail git merge B^0 && + git clean -fd && + git rm -rf a/ && + test_write_lines 1 2 3 4 5 6 7 8 >a && + git add a && + git commit -m E3 && + git tag E3 && + + git checkout C^0 && + test_must_fail git merge B^0 && + git clean -fd && + git rm -rf a/ && + git rm a && + test_write_lines 1 2 3 4 5 6 7 8 >a2 && + git add a2 && + git commit -m E4 && + git tag E4 + ) +' + +test_expect_success 'merge of D1 & E1 fails but has appropriate contents' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D1^0 && + + test_must_fail git merge -s recursive E1^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a && + git rev-parse >actual \ + :0:ignore-me :2:a && + test_cmp expect actual + ) +' + +test_expect_success 'merge of E1 & D1 fails but has appropriate contents' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout E1^0 && + + test_must_fail git merge -s recursive D1^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a && + git rev-parse >actual \ + :0:ignore-me :3:a && + test_cmp expect actual + ) +' + +test_expect_success 'merge of D1 & E2 fails but has appropriate contents' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D1^0 && + + test_must_fail git merge -s recursive E2^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >expect \ + B:a E2:a/file C:a/file A:ignore-me && + git rev-parse >actual \ + :2:a :3:a/file :1:a/file :0:ignore-me && + test_cmp expect actual && + + test_path_is_file a~HEAD + ) +' + +test_expect_success 'merge of E2 & D1 fails but has appropriate contents' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout E2^0 && + + test_must_fail git merge -s recursive D1^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git rev-parse >expect \ + B:a E2:a/file C:a/file A:ignore-me && + git rev-parse >actual \ + :3:a :2:a/file :1:a/file :0:ignore-me && + test_cmp expect actual && + + test_path_is_file a~D1^0 + ) +' + +test_expect_success 'merge of D1 & E3 succeeds' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D1^0 && + + git merge -s recursive E3^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me E3:a && + git rev-parse >actual \ + :0:ignore-me :0:a && + test_cmp expect actual + ) +' + +test_expect_success 'merge of D1 & E4 notifies user a and a2 are related' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D1^0 && + + test_must_fail git merge -s recursive E4^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me B:a D1:a E4:a2 && + git rev-parse >actual \ + :0:ignore-me :1:a~Temporary\ merge\ branch\ 2 :2:a :3:a2 && + test_cmp expect actual + ) +' + +test_expect_failure 'merge of D2 & E4 merges a2s & reports conflict for a/file' ' + test_when_finished "git -C directory-file reset --hard" && + test_when_finished "git -C directory-file clean -fdqx" && + ( + cd directory-file && + + git checkout D2^0 && + + test_must_fail git merge -s recursive E4^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:ignore-me E4:a2 D2:a/file && + git rev-parse >actual \ + :0:ignore-me :0:a2 :2:a/file && + test_cmp expect actual + ) +' + +# +# criss-cross with rename/rename(1to2)/modify followed by +# rename/rename(2to1)/modify: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: new file: a +# Commit B: rename a->b, modifying by adding a line +# Commit C: rename a->c +# Commit D: merge B&C, resolving conflict by keeping contents in newname +# Commit E: merge B&C, resolving conflict similar to D but adding another line +# +# There is a conflict merging B & C, but one of filename not of file +# content. Whoever created D and E chose specific resolutions for that +# conflict resolution. Now, since: (1) there is no content conflict +# merging B & C, (2) D does not modify that merged content further, and (3) +# both D & E resolve the name conflict in the same way, the modification to +# newname in E should not cause any conflicts when it is merged with D. +# (Note that this can be accomplished by having the virtual merge base have +# the merged contents of b and c stored in a file named a, which seems like +# the most logical choice anyway.) +# +# Comment from Junio: I do not necessarily agree with the choice "a", but +# it feels sound to say "B and C do not agree what the final pathname +# should be, but we know this content was derived from the common A:a so we +# use one path whose name is arbitrary in the virtual merge base X between +# D and E" and then further let the rename detection to notice that that +# arbitrary path gets renamed between X-D to "newname" and X-E also to +# "newname" to resolve it as both sides renaming it to the same new +# name. It is akin to what we do at the content level, i.e. "B and C do not +# agree what the final contents should be, so we leave the conflict marker +# but that may cancel out at the final merge stage". + +test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' + test_create_repo rename-squared-squared && + ( + cd rename-squared-squared && + + printf "1\n2\n3\n4\n5\n6\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + echo 7 >>b && + git add -u && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout -q B^0 && + git merge --no-commit -s ours C^0 && + git mv b newname && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout -q C^0 && + git merge --no-commit -s ours B^0 && + git mv c newname && + printf "7\n8\n" >>newname && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E + ) +' + +test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' ' + ( + cd rename-squared-squared && + + git checkout D^0 && + + git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + test $(git rev-parse HEAD:newname) = $(git rev-parse E:newname) + ) +' + +# +# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: new file: a +# Commit B: rename a->b +# Commit C: rename a->c, add different a +# Commit D: merge B&C, keeping b&c and (new) a modified at beginning +# Commit E: merge B&C, keeping b&c and (new) a modified at end +# +# Merging commits D & E should result in no conflict; doing so correctly +# requires getting the virtual merge base (from merging B&C) right, handling +# renaming carefully (both in the virtual merge base and later), and getting +# content merge handled. + +test_expect_success 'setup criss-cross + rename/rename/add-source + modify/modify' ' + test_create_repo rename-rename-add-source && + ( + cd rename-rename-add-source && + + printf "lots\nof\nwords\nand\ncontent\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + printf "2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git checkout C -- a c && + mv a old_a && + echo 1 >a && + cat old_a >>a && + rm old_a && + git add -u && + git commit -m "Merge commit C^0 into HEAD" && + git tag D && + + git checkout C^0 && + git merge --no-commit -s ours B^0 && + git checkout B -- b && + echo 8 >>a && + git add -u && + git commit -m "Merge commit B^0 into HEAD" && + git tag E + ) +' + +test_expect_failure 'detect rename/rename/add-source for virtual merge-base' ' + ( + cd rename-rename-add-source && + + git checkout D^0 && + + git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + printf "1\n2\n3\n4\n5\n6\n7\n8\n" >correct && + git rev-parse >expect \ + A:a A:a \ + correct && + git rev-parse >actual \ + :0:b :0:c && + git hash-object >>actual \ + a && + test_cmp expect actual + ) +' + +# +# criss-cross with rename/rename(1to2)/add-dest + simple modify: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: new file: a +# Commit B: rename a->b, add c +# Commit C: rename a->c +# Commit D: merge B&C, keeping A:a and B:c +# Commit E: merge B&C, keeping A:a and slightly modified c from B +# +# Merging commits D & E should result in no conflict. The virtual merge +# base of B & C needs to not delete B:c for that to work, though... + +test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' ' + test_create_repo rename-rename-add-dest && + ( + cd rename-rename-add-dest && + + >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + printf "1\n2\n3\n4\n5\n6\n7\n" >c && + git add c && + git commit -m B && + + git checkout -b C A && + git mv a c && + git commit -m C && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + git commit -m "D is like B but renames b back to a" && + git tag D && + + git checkout B^0 && + git merge --no-commit -s ours C^0 && + git mv b a && + echo 8 >>c && + git add c && + git commit -m "E like D but has mod in c" && + git tag E + ) +' + +test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' ' + ( + cd rename-rename-add-dest && + + git checkout D^0 && + + git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:a E:c && + git rev-parse >actual \ + :0:a :0:c && + test_cmp expect actual + ) +' + +# +# criss-cross with modify/modify on a symlink: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: simple simlink fickle->lagoon +# Commit B: redirect fickle->disneyland +# Commit C: redirect fickle->home +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious modify/modify conflict for the symlink 'fickle'. Can +# git detect it? + +test_expect_success 'setup symlink modify/modify' ' + test_create_repo symlink-modify-modify && + ( + cd symlink-modify-modify && + + test_ln_s_add lagoon fickle && + git commit -m A && + git tag A && + + git checkout -b B A && + git rm fickle && + test_ln_s_add disneyland fickle && + git commit -m B && + + git checkout -b C A && + git rm fickle && + test_ln_s_add home fickle && + git add fickle && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check symlink modify/modify' ' + ( + cd symlink-modify-modify && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with add/add of a symlink: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: No symlink or path exists yet +# Commit B: set up symlink: fickle->disneyland +# Commit C: set up symlink: fickle->home +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious add/add conflict for the symlink 'fickle'. Can +# git detect it? + +test_expect_success 'setup symlink add/add' ' + test_create_repo symlink-add-add && + ( + cd symlink-add-add && + + touch ignoreme && + git add ignoreme && + git commit -m A && + git tag A && + + git checkout -b B A && + test_ln_s_add disneyland fickle && + git commit -m B && + + git checkout -b C A && + test_ln_s_add home fickle && + git add fickle && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check symlink add/add' ' + ( + cd symlink-add-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with modify/modify on a submodule: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: simple submodule repo +# Commit B: update repo +# Commit C: update repo differently +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious modify/modify conflict for the submodule 'repo'. Can +# git detect it? + +test_expect_success 'setup submodule modify/modify' ' + test_create_repo submodule-modify-modify && + ( + cd submodule-modify-modify && + + test_create_repo submod && + ( + cd submod && + touch file-A && + git add file-A && + git commit -m A && + git tag A && + + git checkout -b B A && + touch file-B && + git add file-B && + git commit -m B && + git tag B && + + git checkout -b C A && + touch file-C && + git add file-C && + git commit -m C && + git tag C + ) && + + git -C submod reset --hard A && + git add submod && + git commit -m A && + git tag A && + + git checkout -b B A && + git -C submod reset --hard B && + git add submod && + git commit -m B && + + git checkout -b C A && + git -C submod reset --hard C && + git add submod && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check submodule modify/modify' ' + ( + cd submodule-modify-modify && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with add/add on a submodule: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: nothing of note +# Commit B: introduce submodule repo +# Commit C: introduce submodule repo at different commit +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious add/add conflict for the submodule 'repo'. Can +# git detect it? + +test_expect_success 'setup submodule add/add' ' + test_create_repo submodule-add-add && + ( + cd submodule-add-add && + + test_create_repo submod && + ( + cd submod && + touch file-A && + git add file-A && + git commit -m A && + git tag A && + + git checkout -b B A && + touch file-B && + git add file-B && + git commit -m B && + git tag B && + + git checkout -b C A && + touch file-C && + git add file-C && + git commit -m C && + git tag C + ) && + + touch irrelevant-file && + git add irrelevant-file && + git commit -m A && + git tag A && + + git checkout -b B A && + git -C submod reset --hard B && + git add submod && + git commit -m B && + + git checkout -b C A && + git -C submod reset --hard C && + git add submod && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check submodule add/add' ' + ( + cd submodule-add-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with conflicting entry types: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: nothing of note +# Commit B: introduce submodule 'path' +# Commit C: introduce symlink 'path' +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious add/add conflict for 'path'. Can git detect it? + +test_expect_success 'setup conflicting entry types (submodule vs symlink)' ' + test_create_repo submodule-symlink-add-add && + ( + cd submodule-symlink-add-add && + + test_create_repo path && + ( + cd path && + touch file-B && + git add file-B && + git commit -m B && + git tag B + ) && + + touch irrelevant-file && + git add irrelevant-file && + git commit -m A && + git tag A && + + git checkout -b B A && + git -C path reset --hard B && + git add path && + git commit -m B && + + git checkout -b C A && + rm -rf path/ && + test_ln_s_add irrelevant-file path && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check conflicting entry types (submodule vs symlink)' ' + ( + cd submodule-symlink-add-add && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# +# criss-cross with regular files that have conflicting modes: +# +# B D +# o---o +# / \ / \ +# A o X ? F +# \ / \ / +# o---o +# C E +# +# Commit A: nothing of note +# Commit B: introduce file source_me.bash, not executable +# Commit C: introduce file source_me.bash, executable +# Commit D: merge B&C, resolving in favor of B +# Commit E: merge B&C, resolving in favor of C +# +# This is an obvious add/add mode conflict. Can git detect it? + +test_expect_success 'setup conflicting modes for regular file' ' + test_create_repo regular-file-mode-conflict && + ( + cd regular-file-mode-conflict && + + touch irrelevant-file && + git add irrelevant-file && + git commit -m A && + git tag A && + + git checkout -b B A && + echo "command_to_run" >source_me.bash && + git add source_me.bash && + git commit -m B && + + git checkout -b C A && + echo "command_to_run" >source_me.bash && + git add source_me.bash && + test_chmod +x source_me.bash && + git commit -m C && + + git checkout -q B^0 && + git merge -s ours -m D C^0 && + git tag D && + + git checkout -q C^0 && + git merge -s ours -m E B^0 && + git tag E + ) +' + +test_expect_failure 'check conflicting modes for regular file' ' + ( + cd regular-file-mode-conflict && + + git checkout D^0 && + + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out + ) +' + +# Setup: +# L1---L2 +# / \ / \ +# master X ? +# \ / \ / +# R1---R2 +# +# Where: +# master has two files, named 'b' and 'a' +# branches L1 and R1 both modify each of the two files in conflicting ways +# +# L2 is a merge of R1 into L1; more on it later. +# R2 is a merge of L1 into R1; more on it later. +# +# X is an auto-generated merge-base used when merging L2 and R2. +# since X is a merge of L1 and R1, it has conflicting versions of each file +# +# More about L2 and R2: +# - both resolve the conflicts in 'b' and 'a' differently +# - L2 renames 'b' to 'm' +# - R2 renames 'a' to 'm' +# +# In the end, in file 'm' we have four different conflicting files (from +# two versions of 'b' and two of 'a'). In addition, if +# merge.conflictstyle is diff3, then the base version also has +# conflict markers of its own, leading to a total of three levels of +# conflict markers. This is a pretty weird corner case, but we just want +# to ensure that we handle it as well as practical. + +test_expect_success 'setup nested conflicts' ' + test_create_repo nested_conflicts && + ( + cd nested_conflicts && + + # Create some related files now + for i in $(test_seq 1 10) + do + echo Random base content line $i + done >initial && + + cp initial b_L1 && + cp initial b_R1 && + cp initial b_L2 && + cp initial b_R2 && + cp initial a_L1 && + cp initial a_R1 && + cp initial a_L2 && + cp initial a_R2 && + + test_write_lines b b_L1 >>b_L1 && + test_write_lines b b_R1 >>b_R1 && + test_write_lines b b_L2 >>b_L2 && + test_write_lines b b_R2 >>b_R2 && + test_write_lines a a_L1 >>a_L1 && + test_write_lines a a_R1 >>a_R1 && + test_write_lines a a_L2 >>a_L2 && + test_write_lines a a_R2 >>a_R2 && + + # Setup original commit (or merge-base), consisting of + # files named "b" and "a" + cp initial b && + cp initial a && + echo b >>b && + echo a >>a && + git add b a && + test_tick && git commit -m initial && + + git branch L && + git branch R && + + # Handle the left side + git checkout L && + mv -f b_L1 b && + mv -f a_L1 a && + git add b a && + test_tick && git commit -m "version L1 of files" && + git tag L1 && + + # Handle the right side + git checkout R && + mv -f b_R1 b && + mv -f a_R1 a && + git add b a && + test_tick && git commit -m "version R1 of files" && + git tag R1 && + + # Create first merge on left side + git checkout L && + test_must_fail git merge R1 && + mv -f b_L2 b && + mv -f a_L2 a && + git add b a && + git mv b m && + test_tick && git commit -m "left merge, rename b->m" && + git tag L2 && + + # Create first merge on right side + git checkout R && + test_must_fail git merge L1 && + mv -f b_R2 b && + mv -f a_R2 a && + git add b a && + git mv a m && + test_tick && git commit -m "right merge, rename a->m" && + git tag R2 + ) +' + +test_expect_success 'check nested conflicts' ' + ( + cd nested_conflicts && + + git clean -f && + MASTER=$(git rev-parse --short master) && + git checkout L2^0 && + + # Merge must fail; there is a conflict + test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R2^0 && + + # Make sure the index has the right number of entries + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + # Ensure we have the correct number of untracked files + git ls-files -o >out && + test_line_count = 1 out && + + # Create a and b from virtual merge base X + git cat-file -p master:a >base && + git cat-file -p L1:a >ours && + git cat-file -p R1:a >theirs && + test_must_fail git merge-file --diff3 \ + -L "Temporary merge branch 1" \ + -L "$MASTER" \ + -L "Temporary merge branch 2" \ + ours \ + base \ + theirs && + sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_a && + + git cat-file -p master:b >base && + git cat-file -p L1:b >ours && + git cat-file -p R1:b >theirs && + test_must_fail git merge-file --diff3 \ + -L "Temporary merge branch 1" \ + -L "$MASTER" \ + -L "Temporary merge branch 2" \ + ours \ + base \ + theirs && + sed -e "s/^\([<|=>]\)/\1\1/" ours >vmb_b && + + # Compare :2:m to expected values + git cat-file -p L2:m >ours && + git cat-file -p R2:b >theirs && + test_must_fail git merge-file --diff3 \ + -L "HEAD:m" \ + -L "merged common ancestors:b" \ + -L "R2^0:b" \ + ours \ + vmb_b \ + theirs && + sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_2 && + git cat-file -p :2:m >actual && + test_cmp m_stage_2 actual && + + # Compare :3:m to expected values + git cat-file -p L2:a >ours && + git cat-file -p R2:m >theirs && + test_must_fail git merge-file --diff3 \ + -L "HEAD:a" \ + -L "merged common ancestors:a" \ + -L "R2^0:m" \ + ours \ + vmb_a \ + theirs && + sed -e "s/^\([<|=>]\)/\1\1/" ours >m_stage_3 && + git cat-file -p :3:m >actual && + test_cmp m_stage_3 actual && + + # Compare m to expected contents + >empty && + cp m_stage_2 expected_final_m && + test_must_fail git merge-file --diff3 \ + -L "HEAD" \ + -L "merged common ancestors" \ + -L "R2^0" \ + expected_final_m \ + empty \ + m_stage_3 && + test_cmp expected_final_m m + ) +' + +# Setup: +# L1---L2---L3 +# / \ / \ / \ +# master X1 X2 ? +# \ / \ / \ / +# R1---R2---R3 +# +# Where: +# master has one file named 'content' +# branches L1 and R1 both modify each of the two files in conflicting ways +# +# L (n>1) is a merge of R into L +# R (n>1) is a merge of L into R +# L and R resolve the conflicts differently. +# +# X is an auto-generated merge-base used when merging L and R. +# By construction, X1 has conflict markers due to conflicting versions. +# X2, due to using merge.conflictstyle=3, has nested conflict markers. +# +# So, merging R3 into L3 using merge.conflictstyle=3 should show the +# nested conflict markers from X2 in the base version -- that means we +# have three levels of conflict markers. Can we distinguish all three? + +test_expect_success 'setup virtual merge base with nested conflicts' ' + test_create_repo virtual_merge_base_has_nested_conflicts && + ( + cd virtual_merge_base_has_nested_conflicts && + + # Create some related files now + for i in $(test_seq 1 10) + do + echo Random base content line $i + done >content && + + # Setup original commit + git add content && + test_tick && git commit -m initial && + + git branch L && + git branch R && + + # Create L1 + git checkout L && + echo left >>content && + git add content && + test_tick && git commit -m "version L1 of content" && + git tag L1 && + + # Create R1 + git checkout R && + echo right >>content && + git add content && + test_tick && git commit -m "version R1 of content" && + git tag R1 && + + # Create L2 + git checkout L && + test_must_fail git -c merge.conflictstyle=diff3 merge R1 && + git checkout L1 content && + test_tick && git commit -m "version L2 of content" && + git tag L2 && + + # Create R2 + git checkout R && + test_must_fail git -c merge.conflictstyle=diff3 merge L1 && + git checkout R1 content && + test_tick && git commit -m "version R2 of content" && + git tag R2 && + + # Create L3 + git checkout L && + test_must_fail git -c merge.conflictstyle=diff3 merge R2 && + git checkout L1 content && + test_tick && git commit -m "version L3 of content" && + git tag L3 && + + # Create R3 + git checkout R && + test_must_fail git -c merge.conflictstyle=diff3 merge L2 && + git checkout R1 content && + test_tick && git commit -m "version R3 of content" && + git tag R3 + ) +' + +test_expect_success 'check virtual merge base with nested conflicts' ' + ( + cd virtual_merge_base_has_nested_conflicts && + + MASTER=$(git rev-parse --short master) && + git checkout L3^0 && + + # Merge must fail; there is a conflict + test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R3^0 && + + # Make sure the index has the right number of entries + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + # Ensure we have the correct number of untracked files + git ls-files -o >out && + test_line_count = 1 out && + + # Compare :[23]:content to expected values + git rev-parse L1:content R1:content >expect && + git rev-parse :2:content :3:content >actual && + test_cmp expect actual && + + # Imitate X1 merge base, except without long enough conflict + # markers because a subsequent sed will modify them. Put + # result into vmb. + git cat-file -p master:content >base && + git cat-file -p L:content >left && + git cat-file -p R:content >right && + cp left merged-once && + test_must_fail git merge-file --diff3 \ + -L "Temporary merge branch 1" \ + -L "$MASTER" \ + -L "Temporary merge branch 2" \ + merged-once \ + base \ + right && + sed -e "s/^\([<|=>]\)/\1\1\1/" merged-once >vmb && + + # Imitate X2 merge base, overwriting vmb. Note that we + # extend both sets of conflict markers to make them longer + # with the sed command. + cp left merged-twice && + test_must_fail git merge-file --diff3 \ + -L "Temporary merge branch 1" \ + -L "merged common ancestors" \ + -L "Temporary merge branch 2" \ + merged-twice \ + vmb \ + right && + sed -e "s/^\([<|=>]\)/\1\1\1/" merged-twice >vmb && + + # Compare :1:content to expected value + git cat-file -p :1:content >actual && + test_cmp vmb actual && + + # Determine expected content in final outer merge, compare to + # what the merge generated. + cp -f left expect && + test_must_fail git merge-file --diff3 \ + -L "HEAD" -L "merged common ancestors" -L "R3^0" \ + expect vmb right && + test_cmp expect content + ) +' + +test_done diff --git a/t/t6417-merge-ours-theirs.sh b/t/t6417-merge-ours-theirs.sh new file mode 100755 index 0000000..0aebc6c --- /dev/null +++ b/t/t6417-merge-ours-theirs.sh @@ -0,0 +1,108 @@ +#!/bin/sh + +test_description='Merge-recursive ours and theirs variants' +. ./test-lib.sh + +test_expect_success setup ' + for i in 1 2 3 4 5 6 7 8 9 + do + echo "$i" + done >file && + git add file && + cp file elif && + git commit -m initial && + + sed -e "s/1/one/" -e "s/9/nine/" >file file .gitattributes && + + git reset --hard master && + git merge -s recursive -X theirs side && + git diff --exit-code side HEAD -- file && + + git reset --hard master && + git merge -s recursive -X ours side && + git diff --exit-code master HEAD -- file +' + +test_expect_success 'pull passes -X to underlying merge' ' + git reset --hard master && git pull -s recursive -Xours . side && + git reset --hard master && git pull -s recursive -X ours . side && + git reset --hard master && git pull -s recursive -Xtheirs . side && + git reset --hard master && git pull -s recursive -X theirs . side && + git reset --hard master && test_must_fail git pull -s recursive -X bork . side +' + +test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' ' + git reset --hard master && + git checkout -b two master && + ln -s target-zero link && + git add link && + git commit -m "add link pointing to zero" && + + ln -f -s target-two link && + git commit -m "add link pointing to two" link && + + git checkout -b one HEAD^ && + ln -f -s target-one link && + git commit -m "add link pointing to one" link && + + # we expect symbolic links not to resolve automatically, of course + git checkout one^0 && + test_must_fail git merge -s recursive two && + + # favor theirs to resolve to target-two? + git reset --hard && + git checkout one^0 && + git merge -s recursive -X theirs two && + git diff --exit-code two HEAD link && + + # favor ours to resolve to target-one? + git reset --hard && + git checkout one^0 && + git merge -s recursive -X ours two && + git diff --exit-code one HEAD link + +' + +test_done diff --git a/t/t6418-merge-text-auto.sh b/t/t6418-merge-text-auto.sh new file mode 100755 index 0000000..89c86d4 --- /dev/null +++ b/t/t6418-merge-text-auto.sh @@ -0,0 +1,203 @@ +#!/bin/sh + +test_description='CRLF merge conflict across text=auto change + +* [master] remove .gitattributes + ! [side] add line from b +-- + + [side] add line from b +* [master] remove .gitattributes +* [master^] add line from a +* [master~2] normalize file +*+ [side^] Initial +' + +. ./test-lib.sh + +test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b + +compare_files () { + tr '\015\000' QN <"$1" >"$1".expect && + tr '\015\000' QN <"$2" >"$2".actual && + test_cmp "$1".expect "$2".actual && + rm "$1".expect "$2".actual +} + +test_expect_success setup ' + git config core.autocrlf false && + + echo first line | append_cr >file && + echo first line >control_file && + echo only line >inert_file && + + git add file control_file inert_file && + test_tick && + git commit -m "Initial" && + git tag initial && + git branch side && + + echo "* text=auto" >.gitattributes && + echo first line >file && + git add .gitattributes file && + test_tick && + git commit -m "normalize file" && + + echo same line | append_cr >>file && + echo same line >>control_file && + git add file control_file && + test_tick && + git commit -m "add line from a" && + git tag a && + + git rm .gitattributes && + rm file && + git checkout file && + test_tick && + git commit -m "remove .gitattributes" && + git tag c && + + git checkout side && + echo same line | append_cr >>file && + echo same line >>control_file && + git add file control_file && + test_tick && + git commit -m "add line from b" && + git tag b && + + git checkout master +' + +test_expect_success 'set up fuzz_conflict() helper' ' + fuzz_conflict() { + sed $SED_OPTIONS -e "s/^\([<>=]......\) .*/\1/" "$@" + } +' + +test_expect_success 'Merge after setting text=auto' ' + cat <<-\EOF >expected && + first line + same line + EOF + + if test_have_prereq NATIVE_CRLF; then + append_cr expected.temp && + mv expected.temp expected + fi && + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes && + git reset --hard a && + git merge b && + compare_files expected file +' + +test_expect_success 'Merge addition of text=auto eol=LF' ' + git config core.eol lf && + cat <<-\EOF >expected && + first line + same line + EOF + + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes && + git reset --hard b && + git merge a && + compare_files expected file +' + +test_expect_success 'Merge addition of text=auto eol=CRLF' ' + git config core.eol crlf && + cat <<-\EOF >expected && + first line + same line + EOF + + append_cr expected.temp && + mv expected.temp expected && + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes && + git reset --hard b && + echo >&2 "After git reset --hard b" && + git ls-files -s --eol >&2 && + git merge a && + compare_files expected file +' + +test_expect_success 'Detect CRLF/LF conflict after setting text=auto' ' + git config core.eol native && + echo "<<<<<<<" >expected && + echo first line >>expected && + echo same line >>expected && + echo ======= >>expected && + echo first line | append_cr >>expected && + echo same line | append_cr >>expected && + echo ">>>>>>>" >>expected && + git config merge.renormalize false && + rm -f .gitattributes && + git reset --hard a && + test_must_fail git merge b && + fuzz_conflict file >file.fuzzy && + compare_files expected file.fuzzy +' + +test_expect_success 'Detect LF/CRLF conflict from addition of text=auto' ' + echo "<<<<<<<" >expected && + echo first line | append_cr >>expected && + echo same line | append_cr >>expected && + echo ======= >>expected && + echo first line >>expected && + echo same line >>expected && + echo ">>>>>>>" >>expected && + git config merge.renormalize false && + rm -f .gitattributes && + git reset --hard b && + test_must_fail git merge a && + fuzz_conflict file >file.fuzzy && + compare_files expected file.fuzzy +' + +test_expect_success 'checkout -m after setting text=auto' ' + cat <<-\EOF >expected && + first line + same line + EOF + + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes && + git reset --hard initial && + git restore --source=a -- . && + git checkout -m b && + git diff --no-index --ignore-cr-at-eol expected file +' + +test_expect_success 'checkout -m addition of text=auto' ' + cat <<-\EOF >expected && + first line + same line + EOF + + git config merge.renormalize true && + git rm -fr . && + rm -f .gitattributes file && + git reset --hard initial && + git restore --source=b -- . && + git checkout -m a && + git diff --no-index --ignore-cr-at-eol expected file +' + +test_expect_success 'Test delete/normalize conflict' ' + git checkout -f side && + git rm -fr . && + rm -f .gitattributes && + git reset --hard initial && + git rm file && + git commit -m "remove file" && + git checkout master && + git reset --hard a^ && + git merge side +' + +test_done diff --git a/t/t6419-merge-ignorecase.sh b/t/t6419-merge-ignorecase.sh new file mode 100755 index 0000000..531850d --- /dev/null +++ b/t/t6419-merge-ignorecase.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +test_description='git-merge with case-changing rename on case-insensitive file system' + +. ./test-lib.sh + +if ! test_have_prereq CASE_INSENSITIVE_FS +then + skip_all='skipping case insensitive tests - case sensitive file system' + test_done +fi + +test_expect_success 'merge with case-changing rename' ' + test $(git config core.ignorecase) = true && + >TestCase && + git add TestCase && + git commit -m "add TestCase" && + git tag baseline && + git checkout -b with-camel && + >foo && + git add foo && + git commit -m "intervening commit" && + git checkout master && + git rm TestCase && + >testcase && + git add testcase && + git commit -m "rename to testcase" && + git checkout with-camel && + git merge master -m "merge" && + test_path_is_file testcase +' + +test_expect_success 'merge with case-changing rename on both sides' ' + git checkout master && + git reset --hard baseline && + git branch -D with-camel && + git checkout -b with-camel && + git mv TestCase testcase && + git commit -m "recase on branch" && + >foo && + git add foo && + git commit -m "intervening commit" && + git checkout master && + git rm TestCase && + >testcase && + git add testcase && + git commit -m "rename to testcase" && + git checkout with-camel && + git merge master -m "merge" && + test_path_is_file testcase +' + +test_done diff --git a/t/t6422-merge-rename-corner-cases.sh b/t/t6422-merge-rename-corner-cases.sh new file mode 100755 index 0000000..f163893 --- /dev/null +++ b/t/t6422-merge-rename-corner-cases.sh @@ -0,0 +1,1437 @@ +#!/bin/sh + +test_description="recursive merge corner cases w/ renames but not criss-crosses" +# t6036 has corner cases that involve both criss-cross merges and renames + +. ./test-lib.sh + +test_setup_rename_delete_untracked () { + test_create_repo rename-delete-untracked && + ( + cd rename-delete-untracked && + + echo "A pretty inscription" >ring && + git add ring && + test_tick && + git commit -m beginning && + + git branch people && + git checkout -b rename-the-ring && + git mv ring one-ring-to-rule-them-all && + test_tick && + git commit -m fullname && + + git checkout people && + git rm ring && + echo gollum >owner && + git add owner && + test_tick && + git commit -m track-people-instead-of-objects && + echo "Myyy PRECIOUSSS" >ring + ) +} + +test_expect_success "Does git preserve Gollum's precious artifact?" ' + test_setup_rename_delete_untracked && + ( + cd rename-delete-untracked && + + test_must_fail git merge -s recursive rename-the-ring && + + # Make sure git did not delete an untracked file + test_path_is_file ring + ) +' + +# Testcase setup for rename/modify/add-source: +# Commit A: new file: a +# Commit B: modify a slightly +# Commit C: rename a->b, add completely different a +# +# We should be able to merge B & C cleanly + +test_setup_rename_modify_add_source () { + test_create_repo rename-modify-add-source && + ( + cd rename-modify-add-source && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + echo 8 >>a && + git add a && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo something completely different >a && + git add a && + git commit -m C + ) +} + +test_expect_failure 'rename/modify/add-source conflict resolvable' ' + test_setup_rename_modify_add_source && + ( + cd rename-modify-add-source && + + git checkout B^0 && + + git merge -s recursive C^0 && + + git rev-parse >expect \ + B:a C:a && + git rev-parse >actual \ + b c && + test_cmp expect actual + ) +' + +test_setup_break_detection_1 () { + test_create_repo break-detection-1 && + ( + cd break-detection-1 && + + printf "1\n2\n3\n4\n5\n" >a && + echo foo >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m B && + + git checkout -b C A && + echo 6 >>a && + git add a && + git commit -m C + ) +} + +test_expect_failure 'conflict caused if rename not detected' ' + test_setup_break_detection_1 && + ( + cd break-detection-1 && + + git checkout -q C^0 && + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_line_count = 6 c && + git rev-parse >expect \ + B:a A:b && + git rev-parse >actual \ + :0:a :0:b && + test_cmp expect actual + ) +' + +test_setup_break_detection_2 () { + test_create_repo break-detection-2 && + ( + cd break-detection-2 && + + printf "1\n2\n3\n4\n5\n" >a && + echo foo >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b D A && + echo 7 >>a && + git add a && + git mv a c && + echo "Completely different content" >a && + git add a && + git commit -m D && + + git checkout -b E A && + git rm a && + echo "Completely different content" >>a && + git add a && + git commit -m E + ) +} + +test_expect_failure 'missed conflict if rename not detected' ' + test_setup_break_detection_2 && + ( + cd break-detection-2 && + + git checkout -q E^0 && + test_must_fail git merge -s recursive D^0 + ) +' + +# Tests for undetected rename/add-source causing a file to erroneously be +# deleted (and for mishandled rename/rename(1to1) causing the same issue). +# +# This test uses a rename/rename(1to1)+add-source conflict (1to1 means the +# same file is renamed on both sides to the same thing; it should trigger +# the 1to2 logic, which it would do if the add-source didn't cause issues +# for git's rename detection): +# Commit A: new file: a +# Commit B: rename a->b +# Commit C: rename a->b, add unrelated a + +test_setup_break_detection_3 () { + test_create_repo break-detection-3 && + ( + cd break-detection-3 && + + printf "1\n2\n3\n4\n5\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a b && + echo foobar >a && + git add a && + git commit -m C + ) +} + +test_expect_failure 'detect rename/add-source and preserve all data' ' + test_setup_break_detection_3 && + ( + cd break-detection-3 && + + git checkout B^0 && + + git merge -s recursive C^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_path_is_file a && + test_path_is_file b && + + git rev-parse >expect \ + A:a C:a && + git rev-parse >actual \ + :0:b :0:a && + test_cmp expect actual + ) +' + +test_expect_failure 'detect rename/add-source and preserve all data, merge other way' ' + test_setup_break_detection_3 && + ( + cd break-detection-3 && + + git checkout C^0 && + + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_path_is_file a && + test_path_is_file b && + + git rev-parse >expect \ + A:a C:a && + git rev-parse >actual \ + :0:b :0:a && + test_cmp expect actual + ) +' + +test_setup_rename_directory () { + test_create_repo rename-directory-$1 && + ( + cd rename-directory-$1 && + + printf "1\n2\n3\n4\n5\n6\n" >file && + git add file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>file && + mkdir newfile && + echo junk >newfile/realfile && + git add file newfile/realfile && + test_tick && + git commit -m right && + + git checkout -b left-conflict base && + echo 8 >>file && + git add file && + git mv file newfile && + test_tick && + git commit -m left && + + git checkout -b left-clean base && + echo 0 >newfile && + cat file >>newfile && + git add newfile && + git rm file && + test_tick && + git commit -m left + ) +} + +test_expect_success 'rename/directory conflict + clean content merge' ' + test_setup_rename_directory 1a && + ( + cd rename-directory-1a && + + git checkout left-clean^0 && + + test_must_fail git merge -s recursive right^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 1 out && + git ls-files -o >out && + test_line_count = 2 out && + + echo 0 >expect && + git cat-file -p base:file >>expect && + echo 7 >>expect && + test_cmp expect newfile~HEAD && + + test $(git rev-parse :2:newfile) = $(git hash-object expect) && + + test_path_is_file newfile/realfile && + test_path_is_file newfile~HEAD + ) +' + +test_expect_success 'rename/directory conflict + content merge conflict' ' + test_setup_rename_directory 1b && + ( + cd rename-directory-1b && + + git reset --hard && + git clean -fdqx && + + git checkout left-conflict^0 && + + test_must_fail git merge -s recursive right^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 2 out && + + git cat-file -p left-conflict:newfile >left && + git cat-file -p base:file >base && + git cat-file -p right:file >right && + test_must_fail git merge-file \ + -L "HEAD:newfile" \ + -L "" \ + -L "right^0:file" \ + left base right && + test_cmp left newfile~HEAD && + + git rev-parse >expect \ + base:file left-conflict:newfile right:file && + git rev-parse >actual \ + :1:newfile :2:newfile :3:newfile && + test_cmp expect actual && + + test_path_is_file newfile/realfile && + test_path_is_file newfile~HEAD + ) +' + +test_setup_rename_directory_2 () { + test_create_repo rename-directory-2 && + ( + cd rename-directory-2 && + + mkdir sub && + printf "1\n2\n3\n4\n5\n6\n" >sub/file && + git add sub/file && + test_tick && + git commit -m base && + git tag base && + + git checkout -b right && + echo 7 >>sub/file && + git add sub/file && + test_tick && + git commit -m right && + + git checkout -b left base && + echo 0 >newfile && + cat sub/file >>newfile && + git rm sub/file && + mv newfile sub && + git add sub && + test_tick && + git commit -m left + ) +} + +test_expect_success 'disappearing dir in rename/directory conflict handled' ' + test_setup_rename_directory_2 && + ( + cd rename-directory-2 && + + git checkout left^0 && + + git merge -s recursive right^0 && + + git ls-files -s >out && + test_line_count = 1 out && + git ls-files -u >out && + test_line_count = 0 out && + git ls-files -o >out && + test_line_count = 1 out && + + echo 0 >expect && + git cat-file -p base:sub/file >>expect && + echo 7 >>expect && + test_cmp expect sub && + + test_path_is_file sub + ) +' + +# Test for basic rename/add-dest conflict, with rename needing content merge: +# Commit O: a +# Commit A: rename a->b, modifying b too +# Commit B: modify a, add different b + +test_setup_rename_with_content_merge_and_add () { + test_create_repo rename-with-content-merge-and-add-$1 && + ( + cd rename-with-content-merge-and-add-$1 && + + test_seq 1 5 >a && + git add a && + git commit -m O && + git tag O && + + git checkout -b A O && + git mv a b && + test_seq 0 5 >b && + git add b && + git commit -m A && + + git checkout -b B O && + echo 6 >>a && + echo hello world >b && + git add a b && + git commit -m B + ) +} + +test_expect_success 'handle rename-with-content-merge vs. add' ' + test_setup_rename_with_content_merge_and_add AB && + ( + cd rename-with-content-merge-and-add-AB && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out && + test_i18ngrep "CONFLICT (rename/add)" out && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + # Also, make sure both unmerged entries are for "b" + git ls-files -u b >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_path_is_missing a && + test_path_is_file b && + + test_seq 0 6 >tmp && + git hash-object tmp >expect && + git rev-parse B:b >>expect && + git rev-parse >actual \ + :2:b :3:b && + test_cmp expect actual && + + # Test that the two-way merge in b is as expected + git cat-file -p :2:b >>ours && + git cat-file -p :3:b >>theirs && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + ours empty theirs && + test_cmp ours b + ) +' + +test_expect_success 'handle rename-with-content-merge vs. add, merge other way' ' + test_setup_rename_with_content_merge_and_add BA && + ( + cd rename-with-content-merge-and-add-BA && + + git reset --hard && + git clean -fdx && + + git checkout B^0 && + + test_must_fail git merge -s recursive A^0 >out && + test_i18ngrep "CONFLICT (rename/add)" out && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + # Also, make sure both unmerged entries are for "b" + git ls-files -u b >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_path_is_missing a && + test_path_is_file b && + + test_seq 0 6 >tmp && + git rev-parse B:b >expect && + git hash-object tmp >>expect && + git rev-parse >actual \ + :2:b :3:b && + test_cmp expect actual && + + # Test that the two-way merge in b is as expected + git cat-file -p :2:b >>ours && + git cat-file -p :3:b >>theirs && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "A^0" \ + ours empty theirs && + test_cmp ours b + ) +' + +# Test for all kinds of things that can go wrong with rename/rename (2to1): +# Commit A: new files: a & b +# Commit B: rename a->c, modify b +# Commit C: rename b->c, modify a +# +# Merging of B & C should NOT be clean. Questions: +# * Both a & b should be removed by the merge; are they? +# * The two c's should contain modifications to a & b; do they? +# * The index should contain two files, both for c; does it? +# * The working copy should have two files, both of form c~; does it? +# * Nothing else should be present. Is anything? + +test_setup_rename_rename_2to1 () { + test_create_repo rename-rename-2to1 && + ( + cd rename-rename-2to1 && + + printf "1\n2\n3\n4\n5\n" >a && + printf "5\n4\n3\n2\n1\n" >b && + git add a b && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a c && + echo 0 >>b && + git add b && + git commit -m B && + + git checkout -b C A && + git mv b c && + echo 6 >>a && + git add a && + git commit -m C + ) +} + +test_expect_success 'handle rename/rename (2to1) conflict correctly' ' + test_setup_rename_rename_2to1 && + ( + cd rename-rename-2to1 && + + git checkout B^0 && + + test_must_fail git merge -s recursive C^0 >out && + test_i18ngrep "CONFLICT (rename/rename)" out && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + git ls-files -u c >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_path_is_missing a && + test_path_is_missing b && + + git rev-parse >expect \ + C:a B:b && + git rev-parse >actual \ + :2:c :3:c && + test_cmp expect actual && + + # Test that the two-way merge in new_a is as expected + git cat-file -p :2:c >>ours && + git cat-file -p :3:c >>theirs && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "C^0" \ + ours empty theirs && + git hash-object c >actual && + git hash-object ours >expect && + test_cmp expect actual + ) +' + +# Testcase setup for simple rename/rename (1to2) conflict: +# Commit A: new file: a +# Commit B: rename a->b +# Commit C: rename a->c +test_setup_rename_rename_1to2 () { + test_create_repo rename-rename-1to2 && + ( + cd rename-rename-1to2 && + + echo stuff >a && + git add a && + test_tick && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m B && + + git checkout -b C A && + git mv a c && + test_tick && + git commit -m C + ) +} + +test_expect_success 'merge has correct working tree contents' ' + test_setup_rename_rename_1to2 && + ( + cd rename-rename-1to2 && + + git checkout C^0 && + + test_must_fail git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + test_path_is_missing a && + git rev-parse >expect \ + A:a A:a A:a \ + A:a A:a && + git rev-parse >actual \ + :1:a :3:b :2:c && + git hash-object >>actual \ + b c && + test_cmp expect actual + ) +' + +# Testcase setup for rename/rename(1to2)/add-source conflict: +# Commit A: new file: a +# Commit B: rename a->b +# Commit C: rename a->c, add completely different a +# +# Merging of B & C should NOT be clean; there's a rename/rename conflict + +test_setup_rename_rename_1to2_add_source_1 () { + test_create_repo rename-rename-1to2-add-source-1 && + ( + cd rename-rename-1to2-add-source-1 && + + printf "1\n2\n3\n4\n5\n6\n7\n" >a && + git add a && + git commit -m A && + git tag A && + + git checkout -b B A && + git mv a b && + git commit -m B && + + git checkout -b C A && + git mv a c && + echo something completely different >a && + git add a && + git commit -m C + ) +} + +test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' ' + test_setup_rename_rename_1to2_add_source_1 && + ( + cd rename-rename-1to2-add-source-1 && + + git checkout B^0 && + + test_must_fail git merge -s recursive C^0 && + + git ls-files -s >out && + test_line_count = 4 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + C:a A:a B:b C:C && + git rev-parse >actual \ + :3:a :1:a :2:b :3:c && + test_cmp expect actual && + + test_path_is_file a && + test_path_is_file b && + test_path_is_file c + ) +' + +test_setup_rename_rename_1to2_add_source_2 () { + test_create_repo rename-rename-1to2-add-source-2 && + ( + cd rename-rename-1to2-add-source-2 && + + >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a b && + echo important-info >a && + git add a && + test_tick && + git commit -m two + ) +} + +test_expect_failure 'rename/rename/add-source still tracks new a file' ' + test_setup_rename_rename_1to2_add_source_2 && + ( + cd rename-rename-1to2-add-source-2 && + + git checkout C^0 && + git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + C:a A:a && + git rev-parse >actual \ + :0:a :0:b && + test_cmp expect actual + ) +' + +test_setup_rename_rename_1to2_add_dest () { + test_create_repo rename-rename-1to2-add-dest && + ( + cd rename-rename-1to2-add-dest && + + echo stuff >a && + git add a && + test_tick && + git commit -m base && + git tag A && + + git checkout -b B A && + git mv a b && + echo precious-data >c && + git add c && + test_tick && + git commit -m one && + + git checkout -b C A && + git mv a c && + echo important-info >b && + git add b && + test_tick && + git commit -m two + ) +} + +test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' ' + test_setup_rename_rename_1to2_add_dest && + ( + cd rename-rename-1to2-add-dest && + + git checkout C^0 && + test_must_fail git merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 5 out && + git ls-files -u b >out && + test_line_count = 2 out && + git ls-files -u c >out && + test_line_count = 2 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + A:a C:b B:b C:c B:c && + git rev-parse >actual \ + :1:a :2:b :3:b :2:c :3:c && + test_cmp expect actual && + + # Record some contents for re-doing merges + git cat-file -p A:a >stuff && + git cat-file -p C:b >important_info && + git cat-file -p B:c >precious_data && + >empty && + + # Test the merge in b + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + important_info empty stuff && + test_cmp important_info b && + + # Test the merge in c + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + stuff empty precious_data && + test_cmp stuff c + ) +' + +# Testcase rad, rename/add/delete +# Commit O: foo +# Commit A: rm foo, add different bar +# Commit B: rename foo->bar +# Expected: CONFLICT (rename/add/delete), two-way merged bar + +test_setup_rad () { + test_create_repo rad && + ( + cd rad && + echo "original file" >foo && + git add foo && + git commit -m "original" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git rm foo && + echo "different file" >bar && + git add bar && + git commit -m "Remove foo, add bar" && + + git checkout B && + git mv foo bar && + git commit -m "rename foo to bar" + ) +} + +test_expect_failure 'rad-check: rename/add/delete conflict' ' + test_setup_rad && + ( + cd rad && + + git checkout B^0 && + test_must_fail git merge -s recursive A^0 >out 2>err && + + # Not sure whether the output should contain just one + # "CONFLICT (rename/add/delete)" line, or if it should break + # it into a pair of "CONFLICT (rename/delete)" and + # "CONFLICT (rename/add)"; allow for either. + test_i18ngrep "CONFLICT (rename.*add)" out && + test_i18ngrep "CONFLICT (rename.*delete)" out && + test_must_be_empty err && + + git ls-files -s >file_count && + test_line_count = 2 file_count && + git ls-files -u >file_count && + test_line_count = 2 file_count && + git ls-files -o >file_count && + test_line_count = 2 file_count && + + git rev-parse >actual \ + :2:bar :3:bar && + git rev-parse >expect \ + B:bar A:bar && + + test_cmp file_is_missing foo && + # bar should have two-way merged contents of the different + # versions of bar; check that content from both sides is + # present. + grep original bar && + grep different bar + ) +' + +# Testcase rrdd, rename/rename(2to1)/delete/delete +# Commit O: foo, bar +# Commit A: rename foo->baz, rm bar +# Commit B: rename bar->baz, rm foo +# Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz + +test_setup_rrdd () { + test_create_repo rrdd && + ( + cd rrdd && + echo foo >foo && + echo bar >bar && + git add foo bar && + git commit -m O && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv foo baz && + git rm bar && + git commit -m "Rename foo, remove bar" && + + git checkout B && + git mv bar baz && + git rm foo && + git commit -m "Rename bar, remove foo" + ) +} + +test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' ' + test_setup_rrdd && + ( + cd rrdd && + + git checkout A^0 && + test_must_fail git merge -s recursive B^0 >out 2>err && + + # Not sure whether the output should contain just one + # "CONFLICT (rename/rename/delete/delete)" line, or if it + # should break it into three: "CONFLICT (rename/rename)" and + # two "CONFLICT (rename/delete)" lines; allow for either. + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "CONFLICT (rename.*delete)" out && + test_must_be_empty err && + + git ls-files -s >file_count && + test_line_count = 2 file_count && + git ls-files -u >file_count && + test_line_count = 2 file_count && + git ls-files -o >file_count && + test_line_count = 2 file_count && + + git rev-parse >actual \ + :2:baz :3:baz && + git rev-parse >expect \ + O:foo O:bar && + + test_cmp file_is_missing foo && + test_cmp file_is_missing bar && + # baz should have two-way merged contents of the original + # contents of foo and bar; check that content from both sides + # is present. + grep foo baz && + grep bar baz + ) +' + +# Testcase mod6, chains of rename/rename(1to2) and rename/rename(2to1) +# Commit O: one, three, five +# Commit A: one->two, three->four, five->six +# Commit B: one->six, three->two, five->four +# Expected: six CONFLICT(rename/rename) messages, each path in two of the +# multi-way merged contents found in two, four, six + +test_setup_mod6 () { + test_create_repo mod6 && + ( + cd mod6 && + test_seq 11 19 >one && + test_seq 31 39 >three && + test_seq 51 59 >five && + git add . && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 10 19 >one && + echo 40 >>three && + git add one three && + git mv one two && + git mv three four && + git mv five six && + test_tick && + git commit -m "A" && + + git checkout B && + echo 20 >>one && + echo forty >>three && + echo 60 >>five && + git add one three five && + git mv one six && + git mv three two && + git mv five four && + test_tick && + git commit -m "B" + ) +} + +test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' ' + test_setup_mod6 && + ( + cd mod6 && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "CONFLICT (rename/rename)" out && + test_must_be_empty err && + + git ls-files -s >file_count && + test_line_count = 6 file_count && + git ls-files -u >file_count && + test_line_count = 6 file_count && + git ls-files -o >file_count && + test_line_count = 3 file_count && + + test_seq 10 20 >merged-one && + test_seq 51 60 >merged-five && + # Determine what the merge of three would give us. + test_seq 30 40 >three-side-A && + test_seq 31 39 >three-side-B && + echo forty >three-side-B && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + three-side-A empty three-side-B && + sed -e "s/^\([<=>]\)/\1\1\1/" three-side-A >merged-three && + + # Verify the index is as expected + git rev-parse >actual \ + :2:two :3:two \ + :2:four :3:four \ + :2:six :3:six && + git hash-object >expect \ + merged-one merged-three \ + merged-three merged-five \ + merged-five merged-one && + test_cmp expect actual && + + git cat-file -p :2:two >expect && + git cat-file -p :3:two >other && + test_must_fail git merge-file \ + -L "HEAD" -L "" -L "B^0" \ + expect empty other && + test_cmp expect two && + + git cat-file -p :2:four >expect && + git cat-file -p :3:four >other && + test_must_fail git merge-file \ + -L "HEAD" -L "" -L "B^0" \ + expect empty other && + test_cmp expect four && + + git cat-file -p :2:six >expect && + git cat-file -p :3:six >other && + test_must_fail git merge-file \ + -L "HEAD" -L "" -L "B^0" \ + expect empty other && + test_cmp expect six + ) +' + +test_conflicts_with_adds_and_renames() { + sideL=$1 + sideR=$2 + + # Setup: + # L + # / \ + # master ? + # \ / + # R + # + # Where: + # Both L and R have files named 'three' which collide. Each of + # the colliding files could have been involved in a rename, in + # which case there was a file named 'one' or 'two' that was + # modified on the opposite side of history and renamed into the + # collision on this side of history. + # + # Questions: + # 1) The index should contain both a stage 2 and stage 3 entry + # for the colliding file. Does it? + # 2) When renames are involved, the content merges are clean, so + # the index should reflect the content merges, not merely the + # version of the colliding file from the prior commit. Does + # it? + # 3) There should be a file in the worktree named 'three' + # containing the two-way merged contents of the content-merged + # versions of 'three' from each of the two colliding + # files. Is it present? + # 4) There should not be any three~* files in the working + # tree + test_setup_collision_conflict () { + #test_expect_success "setup simple $sideL/$sideR conflict" ' + test_create_repo simple_${sideL}_${sideR} && + ( + cd simple_${sideL}_${sideR} && + + # Create some related files now + for i in $(test_seq 1 10) + do + echo Random base content line $i + done >file_v1 && + cp file_v1 file_v2 && + echo modification >>file_v2 && + + cp file_v1 file_v3 && + echo more stuff >>file_v3 && + cp file_v3 file_v4 && + echo yet more stuff >>file_v4 && + + # Use a tag to record both these files for simple + # access, and clean out these untracked files + git tag file_v1 $(git hash-object -w file_v1) && + git tag file_v2 $(git hash-object -w file_v2) && + git tag file_v3 $(git hash-object -w file_v3) && + git tag file_v4 $(git hash-object -w file_v4) && + git clean -f && + + # Setup original commit (or merge-base), consisting of + # files named "one" and "two" if renames were involved. + touch irrelevant_file && + git add irrelevant_file && + if [ $sideL = "rename" ] + then + git show file_v1 >one && + git add one + fi && + if [ $sideR = "rename" ] + then + git show file_v3 >two && + git add two + fi && + test_tick && git commit -m initial && + + git branch L && + git branch R && + + # Handle the left side + git checkout L && + if [ $sideL = "rename" ] + then + git mv one three + else + git show file_v2 >three && + git add three + fi && + if [ $sideR = "rename" ] + then + git show file_v4 >two && + git add two + fi && + test_tick && git commit -m L && + + # Handle the right side + git checkout R && + if [ $sideL = "rename" ] + then + git show file_v2 >one && + git add one + fi && + if [ $sideR = "rename" ] + then + git mv two three + else + git show file_v4 >three && + git add three + fi && + test_tick && git commit -m R + ) + #' + } + + test_expect_success "check simple $sideL/$sideR conflict" ' + test_setup_collision_conflict && + ( + cd simple_${sideL}_${sideR} && + + git checkout L^0 && + + # Merge must fail; there is a conflict + test_must_fail git merge -s recursive R^0 && + + # Make sure the index has the right number of entries + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + # Ensure we have the correct number of untracked files + git ls-files -o >out && + test_line_count = 1 out && + + # Nothing should have touched irrelevant_file + git rev-parse >actual \ + :0:irrelevant_file \ + :2:three \ + :3:three && + git rev-parse >expected \ + master:irrelevant_file \ + file_v2 \ + file_v4 && + test_cmp expected actual && + + # Make sure we have the correct merged contents for + # three + git show file_v1 >expected && + cat <<-\EOF >>expected && + <<<<<<< HEAD + modification + ======= + more stuff + yet more stuff + >>>>>>> R^0 + EOF + + test_cmp expected three + ) + ' +} + +test_conflicts_with_adds_and_renames rename rename +test_conflicts_with_adds_and_renames rename add +test_conflicts_with_adds_and_renames add rename +test_conflicts_with_adds_and_renames add add + +# Setup: +# L +# / \ +# master ? +# \ / +# R +# +# Where: +# master has two files, named 'one' and 'two'. +# branches L and R both modify 'one', in conflicting ways. +# branches L and R both modify 'two', in conflicting ways. +# branch L also renames 'one' to 'three'. +# branch R also renames 'two' to 'three'. +# +# So, we have four different conflicting files that all end up at path +# 'three'. +test_setup_nested_conflicts_from_rename_rename () { + test_create_repo nested_conflicts_from_rename_rename && + ( + cd nested_conflicts_from_rename_rename && + + # Create some related files now + for i in $(test_seq 1 10) + do + echo Random base content line $i + done >file_v1 && + + cp file_v1 file_v2 && + cp file_v1 file_v3 && + cp file_v1 file_v4 && + cp file_v1 file_v5 && + cp file_v1 file_v6 && + + echo one >>file_v1 && + echo uno >>file_v2 && + echo eins >>file_v3 && + + echo two >>file_v4 && + echo dos >>file_v5 && + echo zwei >>file_v6 && + + # Setup original commit (or merge-base), consisting of + # files named "one" and "two". + mv file_v1 one && + mv file_v4 two && + git add one two && + test_tick && git commit -m english && + + git branch L && + git branch R && + + # Handle the left side + git checkout L && + git rm one two && + mv -f file_v2 three && + mv -f file_v5 two && + git add two three && + test_tick && git commit -m spanish && + + # Handle the right side + git checkout R && + git rm one two && + mv -f file_v3 one && + mv -f file_v6 three && + git add one three && + test_tick && git commit -m german + ) +} + +test_expect_success 'check nested conflicts from rename/rename(2to1)' ' + test_setup_nested_conflicts_from_rename_rename && + ( + cd nested_conflicts_from_rename_rename && + + git checkout L^0 && + + # Merge must fail; there is a conflict + test_must_fail git merge -s recursive R^0 && + + # Make sure the index has the right number of entries + git ls-files -s >out && + test_line_count = 2 out && + git ls-files -u >out && + test_line_count = 2 out && + # Ensure we have the correct number of untracked files + git ls-files -o >out && + test_line_count = 1 out && + + # Compare :2:three to expected values + git cat-file -p master:one >base && + git cat-file -p L:three >ours && + git cat-file -p R:one >theirs && + test_must_fail git merge-file \ + -L "HEAD:three" -L "" -L "R^0:one" \ + ours base theirs && + sed -e "s/^\([<=>]\)/\1\1/" ours >L-three && + git cat-file -p :2:three >expect && + test_cmp expect L-three && + + # Compare :2:three to expected values + git cat-file -p master:two >base && + git cat-file -p L:two >ours && + git cat-file -p R:three >theirs && + test_must_fail git merge-file \ + -L "HEAD:two" -L "" -L "R^0:three" \ + ours base theirs && + sed -e "s/^\([<=>]\)/\1\1/" ours >R-three && + git cat-file -p :3:three >expect && + test_cmp expect R-three && + + # Compare three to expected contents + >empty && + test_must_fail git merge-file \ + -L "HEAD" -L "" -L "R^0" \ + L-three empty R-three && + test_cmp three L-three + ) +' + +# Testcase rename/rename(1to2) of a binary file +# Commit O: orig +# Commit A: orig-A +# Commit B: orig-B +# Expected: CONFLICT(rename/rename) message, three unstaged entries in the +# index, and contents of orig-[AB] at path orig-[AB] +test_setup_rename_rename_1_to_2_binary () { + test_create_repo rename_rename_1_to_2_binary && + ( + cd rename_rename_1_to_2_binary && + + echo '* binary' >.gitattributes && + git add .gitattributes && + + test_seq 1 10 >orig && + git add orig && + git commit -m orig && + + git branch A && + git branch B && + + git checkout A && + git mv orig orig-A && + test_seq 1 11 >orig-A && + git add orig-A && + git commit -m orig-A && + + git checkout B && + git mv orig orig-B && + test_seq 0 10 >orig-B && + git add orig-B && + git commit -m orig-B + + ) +} + +test_expect_success 'rename/rename(1to2) with a binary file' ' + test_setup_rename_rename_1_to_2_binary && + ( + cd rename_rename_1_to_2_binary && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 && + + # Make sure the index has the right number of entries + git ls-files -s >actual && + test_line_count = 4 actual && + + git rev-parse A:orig-A B:orig-B >expect && + git hash-object orig-A orig-B >actual && + test_cmp expect actual + ) +' + +test_done diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh new file mode 100755 index 0000000..83792c5 --- /dev/null +++ b/t/t6423-merge-rename-directories.sh @@ -0,0 +1,4693 @@ +#!/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_setup_1a () { + 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: Simple directory rename detection' ' + test_setup_1a && + ( + cd 1a && + + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 >out && + + 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_setup_1b () { + 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: Merge a directory with another' ' + test_setup_1b && + ( + cd 1b && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_1c () { + 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: Transitive renaming' ' + test_setup_1c && + ( + cd 1c && + + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 >out && + + 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_setup_1d () { + 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: Directory renames cause a rename/rename(2to1) conflict' ' + test_setup_1d && + ( + cd 1d && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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 = 1 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 that the two-way merge in x/wham is as expected + git cat-file -p :2:x/wham >expect && + git cat-file -p :3:x/wham >other && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + expect empty other && + test_cmp expect x/wham + ) +' + +# 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_setup_1e () { + 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: Renamed directory, with all files being renamed too' ' + test_setup_1e && + ( + cd 1e && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_1f () { + 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: Split a directory into two other directories' ' + test_setup_1f && + ( + cd 1f && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_2a () { + 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: Directory split into two on one side, with equal numbers of paths' ' + test_setup_2a && + ( + cd 2a && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_2b () { + 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: Directory split into two on one side, with equal numbers of paths' ' + test_setup_2b && + ( + cd 2b && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_3a () { + 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: Avoid implicit rename if involved as source on other side' ' + test_setup_3a && + ( + cd 3a && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_3b () { + 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: Avoid implicit rename if involved as source on current side' ' + test_setup_3b && + ( + cd 3b && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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 commit 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_setup_4a () { + 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: Directory split, with original directory still present' ' + test_setup_4a && + ( + cd 4a && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_5a () { + 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: Merge directories, other side adds files to original and target' ' + test_setup_5a && + ( + cd 5a && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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 normally 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_setup_5b () { + 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: Rename/delete in order to get add/add/add conflict' ' + test_setup_5b && + ( + cd 5b && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_5c () { + 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: Transitive rename would cause rename/rename/rename/add/add/add' ' + test_setup_5c && + ( + cd 5c && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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 = 1 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 \ + z/d && + git rev-parse >expect \ + 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_setup_5d () { + 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: Directory/file/file conflict due to directory rename' ' + test_setup_5d && + ( + cd 5d && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_6a () { + 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: Tricky rename/delete' ' + test_setup_6a && + ( + cd 6a && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_6b () { + 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: Same rename done on both sides' ' + test_setup_6b && + ( + cd 6b && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_6c () { + 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: Rename only done on same side' ' + test_setup_6c && + ( + cd 6c && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_6d () { + 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: We do not always want transitive renaming' ' + test_setup_6d && + ( + cd 6d && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_6e () { + 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: Add/add from one side' ' + test_setup_6e && + ( + cd 6e && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_7a () { + 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: rename-dir vs. rename-dir (NOT split evenly) PLUS add-other-file' ' + test_setup_7a && + ( + cd 7a && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_7b () { + 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: rename/rename(2to1), but only due to transitive rename' ' + test_setup_7b && + ( + cd 7b && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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 = 1 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 that the two-way merge in y/d is as expected + git cat-file -p :2:y/d >expect && + git cat-file -p :3:y/d >other && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + expect empty other && + test_cmp expect y/d + ) +' + +# 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_setup_7c () { + 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: rename/rename(1to...2or3); transitive rename may add complexity' ' + test_setup_7c && + ( + cd 7c && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_7d () { + 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: transitive rename involved in rename/delete; how is it reported?' ' + test_setup_7d && + ( + cd 7d && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_7e () { + 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: transitive rename in rename/delete AND dirs in the way' ' + test_setup_7e && + ( + cd 7e && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_8a () { + 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: Dual-directory rename, one into the others way' ' + test_setup_8a && + ( + cd 8a && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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: +# 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_setup_8b () { + 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: Dual-directory rename, one into the others way, with conflicting filenames' ' + test_setup_8b && + ( + cd 8b && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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 modified 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_setup_8c () { + 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: modify/delete or rename+modify/delete' ' + test_setup_8c && + ( + cd 8c && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_8d () { + 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: rename/delete...or not?' ' + test_setup_8d && + ( + cd 8d && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_8e () { + 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: Both sides rename, one side adds to original directory' ' + test_setup_8e && + ( + cd 8e && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_9a () { + 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: Inner renamed directory within outer renamed directory' ' + test_setup_9a && + ( + cd 9a && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_9b () { + 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: Transitive rename with content merge' ' + test_setup_9b && + ( + cd 9b && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_9c () { + 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: Doubly transitive rename?' ' + test_setup_9c && + ( + cd 9c && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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: +# +# 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_setup_9d () { + 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: N-way transitive rename?' ' + test_setup_9d && + ( + cd 9d && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_9e () { + 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: N-to-1 whammo' ' + test_setup_9e && + ( + cd 9e && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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_setup_9f () { + 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: Renamed directory that only contained immediate subdirs' ' + test_setup_9f && + ( + cd 9f && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_9g () { + 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: Renamed directory that only contained immediate subdirs, immediate subdirs renamed' ' + ( + cd 9g && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_9h () { + 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: Avoid dir rename on merely modified path' ' + test_setup_9h && + ( + cd 9h && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_10a () { + 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: Overwrite untracked with normal rename/delete' ' + test_setup_10a && + ( + cd 10a && + + git checkout A^0 && + echo very >z/c && + echo important >z/d && + + test_must_fail git -c merge.directoryRenames=true 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_setup_10b () { + 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: Overwrite untracked with dir rename + delete' ' + test_setup_10b && + ( + cd 10b && + + git checkout A^0 && + echo very >y/c && + echo important >y/d && + echo contents >y/e && + + test_must_fail git -c merge.directoryRenames=true 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_setup_10c () { + test_create_repo 10c_$1 && + ( + cd 10c_$1 && + + 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 '10c1: Overwrite untracked with dir rename/rename(1to2)' ' + test_setup_10c 1 && + ( + cd 10c_1 && + + git checkout A^0 && + echo important >y/c && + + test_must_fail git -c merge.directoryRenames=true 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 + ) +' + +test_expect_success '10c2: Overwrite untracked with dir rename/rename(1to2), other direction' ' + test_setup_10c 2 && + ( + cd 10c_2 && + + git reset --hard && + git clean -fdqx && + + git checkout B^0 && + mkdir y && + echo important >y/c && + + test_must_fail git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err && + test_i18ngrep "CONFLICT (rename/rename)" out && + test_i18ngrep "Refusing to lose untracked file at y/c; adding as y/c~HEAD 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 :3:w/c :2: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~HEAD >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~merged}+ +# CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham +# ERROR_MSG(Refusing to lose untracked file at y/wham) + +test_setup_10d () { + 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: Delete untracked with dir rename/rename(2to1)' ' + test_setup_10d && + ( + cd 10d && + + git checkout A^0 && + echo important >y/wham && + + test_must_fail git -c merge.directoryRenames=true 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 = 3 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 && + + # Test that the two-way merge in y/wham~merged is as expected + git cat-file -p :2:y/wham >expect && + git cat-file -p :3:y/wham >other && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + expect empty other && + test_cmp expect y/wham~merged + ) +' + +# 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_setup_10e () { + 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: 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 -c merge.directoryRenames=true 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_setup_11a () { + 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: Avoid losing dirty contents with simple rename' ' + test_setup_11a && + ( + cd 11a && + + git checkout A^0 && + echo stuff >>z/c && + + test_must_fail git -c merge.directoryRenames=true 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_setup_11b () { + 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: Avoid losing dirty file involved in directory rename' ' + test_setup_11b && + ( + cd 11b && + + git checkout A^0 && + echo stuff >>z/c && + + git -c merge.directoryRenames=true 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_setup_11c () { + 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: Avoid losing not-uptodate with rename + D/F conflict' ' + test_setup_11c && + ( + cd 11c && + + git checkout A^0 && + echo stuff >>y/c && + + test_must_fail git -c merge.directoryRenames=true 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_setup_11d () { + 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: Avoid losing not-uptodate with rename + D/F conflict' ' + test_setup_11d && + ( + cd 11d && + + git checkout A^0 && + echo stuff >>z/c && + + test_must_fail git -c merge.directoryRenames=true 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_setup_11e () { + 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: Avoid deleting not-uptodate with dir rename/rename(1to2)/add' ' + test_setup_11e && + ( + cd 11e && + + git checkout A^0 && + echo mods >>y/c && + + test_must_fail git -c merge.directoryRenames=true 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 = 3 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 && + + # See if y/c~merged has expected contents; requires manually + # doing the expected file merge + git cat-file -p A:y/c >c1 && + git cat-file -p B:z/c >c2 && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + c1 empty c2 && + test_cmp c1 y/c~merged + ) +' + +# 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~merged} + +# 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_setup_11f () { + 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: Avoid deleting not-uptodate with dir rename/rename(2to1)' ' + test_setup_11f && + ( + cd 11f && + + git checkout A^0 && + echo important >>y/wham && + + test_must_fail git -c merge.directoryRenames=true 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 = 3 out && + + test_seq 1 10 >expected && + echo important >>expected && + test_cmp expected y/wham && + + test_must_fail git rev-parse :1:y/wham && + + 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 && + + # Test that the two-way merge in y/wham~merged is as expected + git cat-file -p :2:y/wham >expect && + git cat-file -p :3:y/wham >other && + >empty && + test_must_fail git merge-file \ + -L "HEAD" \ + -L "" \ + -L "B^0" \ + expect empty other && + test_cmp expect y/wham~merged + ) +' + +########################################################################### +# 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_setup_12a () { + 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: Moving one directory hierarchy into another' ' + test_setup_12a && + ( + cd 12a && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_12b () { + 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: Moving two directory hierarchies into each other' ' + test_setup_12b && + ( + cd 12b && + + git checkout A^0 && + + git -c merge.directoryRenames=true 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_setup_12c () { + 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: Moving one directory hierarchy into another w/ content merge' ' + test_setup_12c && + ( + cd 12c && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true 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 + ) +' + +# Testcase 12d, Rename/merge of subdirectory into the root +# Commit O: a/b/subdir/foo +# Commit A: subdir/foo +# Commit B: a/b/subdir/foo, a/b/bar +# Expected: subdir/foo, bar + +test_setup_12d () { + test_create_repo 12d && + ( + cd 12d && + + mkdir -p a/b/subdir && + test_commit a/b/subdir/foo && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir subdir && + git mv a/b/subdir/foo.t subdir/foo.t && + test_tick && + git commit -m "A" && + + git checkout B && + test_commit a/b/bar + ) +} + +test_expect_success '12d: Rename/merge subdir into the root, variant 1' ' + test_setup_12d && + ( + cd 12d && + + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 2 out && + + git rev-parse >actual \ + HEAD:subdir/foo.t HEAD:bar.t && + git rev-parse >expect \ + O:a/b/subdir/foo.t B:a/b/bar.t && + test_cmp expect actual && + + git hash-object bar.t >actual && + git rev-parse B:a/b/bar.t >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:a/b/subdir/foo.t && + test_must_fail git rev-parse HEAD:a/b/bar.t && + test_path_is_missing a/ && + test_path_is_file bar.t + ) +' + +# Testcase 12e, Rename/merge of subdirectory into the root +# Commit O: a/b/foo +# Commit A: foo +# Commit B: a/b/foo, a/b/bar +# Expected: foo, bar + +test_setup_12e () { + test_create_repo 12e && + ( + cd 12e && + + mkdir -p a/b && + test_commit a/b/foo && + + git branch O && + git branch A && + git branch B && + + git checkout A && + mkdir subdir && + git mv a/b/foo.t foo.t && + test_tick && + git commit -m "A" && + + git checkout B && + test_commit a/b/bar + ) +} + +test_expect_success '12e: Rename/merge subdir into the root, variant 2' ' + test_setup_12e && + ( + cd 12e && + + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 && + + git ls-files -s >out && + test_line_count = 2 out && + + git rev-parse >actual \ + HEAD:foo.t HEAD:bar.t && + git rev-parse >expect \ + O:a/b/foo.t B:a/b/bar.t && + test_cmp expect actual && + + git hash-object bar.t >actual && + git rev-parse B:a/b/bar.t >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:a/b/foo.t && + test_must_fail git rev-parse HEAD:a/b/bar.t && + test_path_is_missing a/ && + test_path_is_file bar.t + ) +' + +########################################################################### +# SECTION 13: Checking informational and conflict messages +# +# A year after directory rename detection became the default, it was +# instead decided to report conflicts on the pathname on the basis that +# some users may expect the new files added or moved into a directory to +# be unrelated to all the other files in that directory, and thus that +# directory rename detection is unexpected. Test that the messages printed +# match our expectation. +########################################################################### + +# Testcase 13a, Basic directory rename with newly added files +# 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}, with notices/conflicts for both y/d and y/e/f + +test_setup_13a () { + test_create_repo 13a_$1 && + ( + cd 13a_$1 && + + 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 '13a(conflict): messages for newly added files' ' + test_setup_13a conflict && + ( + cd 13a_conflict && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out 2>err && + + test_i18ngrep CONFLICT..file.location.*z/e/f.added.in.B^0.*y/e/f out && + test_i18ngrep CONFLICT..file.location.*z/d.added.in.B^0.*y/d out && + + git ls-files >paths && + ! grep z/ paths && + grep "y/[de]" paths && + + test_path_is_missing z/d && + test_path_is_file y/d && + test_path_is_missing z/e/f && + test_path_is_file y/e/f + ) +' + +test_expect_success '13a(info): messages for newly added files' ' + test_setup_13a info && + ( + cd 13a_info && + + git reset --hard && + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && + + test_i18ngrep Path.updated:.*z/e/f.added.in.B^0.*y/e/f out && + test_i18ngrep Path.updated:.*z/d.added.in.B^0.*y/d out && + + git ls-files >paths && + ! grep z/ paths && + grep "y/[de]" paths && + + test_path_is_missing z/d && + test_path_is_file y/d && + test_path_is_missing z/e/f && + test_path_is_file y/e/f + ) +' + +# Testcase 13b, Transitive rename with conflicted content merge and default +# "conflict" setting +# (Related to testcase 1c, 9b) +# 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}, with two conflict messages for y/d, +# one about content, and one about file location + +test_setup_13b () { + test_create_repo 13b_$1 && + ( + cd 13b_$1 && + + mkdir x && + mkdir z && + test_seq 1 10 >x/d && + echo b >z/b && + echo c >z/c && + git add x z && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv z y && + echo 11 >>x/d && + git add x/d && + test_tick && + git commit -m "A" && + + git checkout B && + echo eleven >>x/d && + git mv x/d z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '13b(conflict): messages for transitive rename with conflicted content' ' + test_setup_13b conflict && + ( + cd 13b_conflict && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out 2>err && + + test_i18ngrep CONFLICT.*content.*Merge.conflict.in.y/d out && + test_i18ngrep CONFLICT..file.location.*x/d.renamed.to.z/d.*moved.to.y/d out && + + git ls-files >paths && + ! grep z/ paths && + grep "y/d" paths && + + test_path_is_missing z/d && + test_path_is_file y/d + ) +' + +test_expect_success '13b(info): messages for transitive rename with conflicted content' ' + test_setup_13b info && + ( + cd 13b_info && + + git reset --hard && + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && + + test_i18ngrep CONFLICT.*content.*Merge.conflict.in.y/d out && + test_i18ngrep Path.updated:.*x/d.renamed.to.z/d.in.B^0.*moving.it.to.y/d out && + + git ls-files >paths && + ! grep z/ paths && + grep "y/d" paths && + + test_path_is_missing z/d && + test_path_is_file y/d + ) +' + +# Testcase 13c, Rename/rename(1to1) due to directory rename +# Commit O: z/{b,c}, x/{d,e} +# Commit A: y/{b,c,d}, x/e +# Commit B: z/{b,c,d}, x/e +# Expected: y/{b,c,d}, with info or conflict messages for d ( +# A: renamed x/d -> z/d; B: renamed z/ -> y/ AND renamed x/d to y/d +# One could argue A had partial knowledge of what was done with +# d and B had full knowledge, but that's a slippery slope as +# shown in testcase 13d. + +test_setup_13c () { + test_create_repo 13c_$1 && + ( + cd 13c_$1 && + + mkdir x && + mkdir z && + test_seq 1 10 >x/d && + echo e >x/e && + echo b >z/b && + echo c >z/c && + git add x z && + 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/d y/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv x/d z/d && + git add z/d && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '13c(conflict): messages for rename/rename(1to1) via transitive rename' ' + test_setup_13c conflict && + ( + cd 13c_conflict && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out 2>err && + + test_i18ngrep CONFLICT..file.location.*x/d.renamed.to.z/d.*moved.to.y/d out && + + git ls-files >paths && + ! grep z/ paths && + grep "y/d" paths && + + test_path_is_missing z/d && + test_path_is_file y/d + ) +' + +test_expect_success '13c(info): messages for rename/rename(1to1) via transitive rename' ' + test_setup_13c info && + ( + cd 13c_info && + + git reset --hard && + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && + + test_i18ngrep Path.updated:.*x/d.renamed.to.z/d.in.B^0.*moving.it.to.y/d out && + + git ls-files >paths && + ! grep z/ paths && + grep "y/d" paths && + + test_path_is_missing z/d && + test_path_is_file y/d + ) +' + +# Testcase 13d, Rename/rename(1to1) due to directory rename on both sides +# Commit O: a/{z,y}, b/x, c/w +# Commit A: a/z, b/{y,x}, d/w +# Commit B: a/z, d/x, c/{y,w} +# Expected: a/z, d/{y,x,w} with no file location conflict for x +# Easy cases: +# * z is always in a; so it stays in a. +# * x starts in b, only modified on one side to move into d/ +# * w starts in c, only modified on one side to move into d/ +# Hard case: +# * A renames a/y to b/y, and B renames b/->d/ => a/y -> d/y +# * B renames a/y to c/y, and A renames c/->d/ => a/y -> d/y +# No conflict in where a/y ends up, so put it in d/y. + +test_setup_13d () { + test_create_repo 13d_$1 && + ( + cd 13d_$1 && + + mkdir a && + mkdir b && + mkdir c && + echo z >a/z && + echo y >a/y && + echo x >b/x && + echo w >c/w && + git add a b c && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv a/y b/ && + git mv c/ d/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv a/y c/ && + git mv b/ d/ && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '13d(conflict): messages for rename/rename(1to1) via dual transitive rename' ' + test_setup_13d conflict && + ( + cd 13d_conflict && + + git checkout A^0 && + + test_must_fail git merge -s recursive B^0 >out 2>err && + + test_i18ngrep CONFLICT..file.location.*a/y.renamed.to.b/y.*moved.to.d/y out && + test_i18ngrep CONFLICT..file.location.*a/y.renamed.to.c/y.*moved.to.d/y out && + + git ls-files >paths && + ! grep b/ paths && + ! grep c/ paths && + grep "d/y" paths && + + test_path_is_missing b/y && + test_path_is_missing c/y && + test_path_is_file d/y + ) +' + +test_expect_success '13d(info): messages for rename/rename(1to1) via dual transitive rename' ' + test_setup_13d info && + ( + cd 13d_info && + + git reset --hard && + git checkout A^0 && + + git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && + + test_i18ngrep Path.updated.*a/y.renamed.to.b/y.*moving.it.to.d/y out && + test_i18ngrep Path.updated.*a/y.renamed.to.c/y.*moving.it.to.d/y out && + + git ls-files >paths && + ! grep b/ paths && + ! grep c/ paths && + grep "d/y" paths && + + test_path_is_missing b/y && + test_path_is_missing c/y && + test_path_is_file d/y + ) +' + +# Testcase 13e, directory rename in virtual merge base +# +# This testcase has a slightly different setup than all the above cases, in +# order to include a recursive case: +# +# A C +# o - o +# / \ / \ +# O o X ? +# \ / \ / +# o o +# B D +# +# Commit O: a/{z,y} +# Commit A: b/{z,y} +# Commit B: a/{z,y,x} +# Commit C: b/{z,y,x} +# Commit D: b/{z,y}, a/x +# Expected: b/{z,y,x} (sort of; see below for why this might not be expected) +# +# NOTES: 'X' represents a virtual merge base. With the default of +# directory rename detection yielding conflicts, merging A and B +# results in a conflict complaining about whether 'x' should be +# under 'a/' or 'b/'. However, when creating the virtual merge +# base 'X', since virtual merge bases need to be written out as a +# tree, we cannot have a conflict, so some resolution has to be +# picked. +# +# In choosing the right resolution, it's worth noting here that +# commits C & D are merges of A & B that choose different +# locations for 'x' (i.e. they resolve the conflict differently), +# and so it would be nice when merging C & D if git could detect +# this difference of opinion and report a conflict. But the only +# way to do so that I can think of would be to have the virtual +# merge base place 'x' in some directory other than either 'a/' or +# 'b/', which seems a little weird -- especially since it'd result +# in a rename/rename(1to2) conflict with a source path that never +# existed in any version. +# +# So, for now, when directory rename detection is set to +# 'conflict' just avoid doing directory rename detection at all in +# the recursive case. This will not allow us to detect a conflict +# in the outer merge for this special kind of setup, but it at +# least avoids hitting a BUG(). +# +test_setup_13e () { + test_create_repo 13e && + ( + cd 13e && + + mkdir a && + echo z >a/z && + echo y >a/y && + git add a && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv a/ b/ && + test_tick && + git commit -m "A" && + + git checkout B && + echo x >a/x && + git add a && + test_tick && + git commit -m "B" && + + git branch C A && + git branch D B && + + git checkout C && + test_must_fail git -c merge.directoryRenames=conflict merge B && + git add b/x && + test_tick && + git commit -m "C" && + + + git checkout D && + test_must_fail git -c merge.directoryRenames=conflict merge A && + git add b/x && + mkdir a && + git mv b/x a/x && + test_tick && + git commit -m "D" + ) +} + +test_expect_success '13e: directory rename detection in recursive case' ' + test_setup_13e && + ( + cd 13e && + + git checkout --quiet D^0 && + + git -c merge.directoryRenames=conflict merge -s recursive C^0 >out 2>err && + + test_i18ngrep ! CONFLICT out && + test_i18ngrep ! BUG: err && + test_i18ngrep ! core.dumped err && + test_must_be_empty err && + + git ls-files >paths && + ! grep a/x paths && + grep b/x paths + ) +' + +test_done diff --git a/t/t6424-merge-unrelated-index-changes.sh b/t/t6424-merge-unrelated-index-changes.sh new file mode 100755 index 0000000..5e3779e --- /dev/null +++ b/t/t6424-merge-unrelated-index-changes.sh @@ -0,0 +1,216 @@ +#!/bin/sh + +test_description="merges with unrelated index changes" + +. ./test-lib.sh + +# Testcase for some simple merges +# A +# o-------o B +# \ +# \-----o C +# \ +# \---o D +# \ +# \-o E +# \ +# o F +# Commit A: some file a +# Commit B: adds file b, modifies end of a +# Commit C: adds file c +# Commit D: adds file d, modifies beginning of a +# Commit E: renames a->subdir/a, adds subdir/e +# Commit F: empty commit + +test_expect_success 'setup trivial merges' ' + test_seq 1 10 >a && + git add a && + test_tick && git commit -m A && + + git branch A && + git branch B && + git branch C && + git branch D && + git branch E && + git branch F && + + git checkout B && + echo b >b && + echo 11 >>a && + git add a b && + test_tick && git commit -m B && + + git checkout C && + echo c >c && + git add c && + test_tick && git commit -m C && + + git checkout D && + test_seq 2 10 >a && + echo d >d && + git add a d && + test_tick && git commit -m D && + + git checkout E && + mkdir subdir && + git mv a subdir/a && + echo e >subdir/e && + git add subdir && + test_tick && git commit -m E && + + git checkout F && + test_tick && git commit --allow-empty -m F +' + +test_expect_success 'ff update' ' + git reset --hard && + git checkout A^0 && + + touch random_file && git add random_file && + + git merge E^0 && + + test_must_fail git rev-parse HEAD:random_file && + test "$(git diff --name-only --cached E)" = "random_file" +' + +test_expect_success 'ff update, important file modified' ' + git reset --hard && + git checkout A^0 && + + mkdir subdir && + touch subdir/e && + git add subdir/e && + + test_must_fail git merge E^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'resolve, trivial' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s resolve C^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'resolve, non-trivial' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s resolve D^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'recursive' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s recursive C^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'recursive, when merge branch matches merge base' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s recursive F^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'merge-recursive, when index==head but head!=HEAD' ' + git reset --hard && + git checkout C^0 && + + # Make index match B + git diff C B -- | git apply --cached && + # Merge B & F, with B as "head" + git merge-recursive A -- B F > out && + test_i18ngrep "Already up to date" out +' + +test_expect_success 'recursive, when file has staged changes not matching HEAD nor what a merge would give' ' + git reset --hard && + git checkout B^0 && + + mkdir subdir && + test_seq 1 10 >subdir/a && + git add subdir/a && + + # We have staged changes; merge should error out + test_must_fail git merge -s recursive E^0 2>err && + test_i18ngrep "changes to the following files would be overwritten" err +' + +test_expect_success 'recursive, when file has staged changes matching what a merge would give' ' + git reset --hard && + git checkout B^0 && + + mkdir subdir && + test_seq 1 11 >subdir/a && + git add subdir/a && + + # We have staged changes; merge should error out + test_must_fail git merge -s recursive E^0 2>err && + test_i18ngrep "changes to the following files would be overwritten" err +' + +test_expect_success 'octopus, unrelated file touched' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge C^0 D^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'octopus, related file removed' ' + git reset --hard && + git checkout B^0 && + + git rm b && + + test_must_fail git merge C^0 D^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'octopus, related file modified' ' + git reset --hard && + git checkout B^0 && + + echo 12 >>a && git add a && + + test_must_fail git merge C^0 D^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'ours' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s ours C^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'subtree' ' + git reset --hard && + git checkout B^0 && + + touch random_file && git add random_file && + + test_must_fail git merge -s subtree E^0 && + test_path_is_missing .git/MERGE_HEAD +' + +test_done diff --git a/t/t6425-merge-rename-delete.sh b/t/t6425-merge-rename-delete.sh new file mode 100755 index 0000000..5d33577 --- /dev/null +++ b/t/t6425-merge-rename-delete.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +test_description='Merge-recursive rename/delete conflict message' +. ./test-lib.sh + +test_expect_success 'rename/delete' ' + echo foo >A && + git add A && + git commit -m "initial" && + + git checkout -b rename && + git mv A B && + git commit -m "rename" && + + git checkout master && + git rm A && + git commit -m "delete" && + + test_must_fail git merge --strategy=recursive rename >output && + test_i18ngrep "CONFLICT (rename/delete): A deleted in HEAD and renamed to B in rename. Version rename of B left in tree." output +' + +test_done diff --git a/t/t6426-merge-skip-unneeded-updates.sh b/t/t6426-merge-skip-unneeded-updates.sh new file mode 100755 index 0000000..5a2d07e --- /dev/null +++ b/t/t6426-merge-skip-unneeded-updates.sh @@ -0,0 +1,770 @@ +#!/bin/sh + +test_description="merge cases" + +# The setup for all of them, pictorially, is: +# +# A +# o +# / \ +# O o ? +# \ / +# o +# B +# +# To help make it easier to follow the flow of tests, they have been +# divided into sections and each test will start with a quick explanation +# of what commits O, A, and B contain. +# +# Notation: +# z/{b,c} means files z/b and z/c both exist +# x/d_1 means file x/d exists with content d1. (Purpose of the +# underscore notation is to differentiate different +# files that might be renamed into each other's paths.) + +. ./test-lib.sh + + +########################################################################### +# SECTION 1: Cases involving no renames (one side has subset of changes of +# the other side) +########################################################################### + +# Testcase 1a, Changes on A, subset of changes on B +# Commit O: b_1 +# Commit A: b_2 +# Commit B: b_3 +# Expected: b_2 + +test_setup_1a () { + test_create_repo 1a_$1 && + ( + cd 1a_$1 && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '1a-L: Modify(A)/Modify(B), change on B subset of A' ' + test_setup_1a L && + ( + cd 1a_L && + + git checkout A^0 && + + test-tool chmtime --get -3600 b >old-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_must_be_empty err && + + # Make sure b was NOT updated + test-tool chmtime --get b >new-mtime && + test_cmp old-mtime new-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:b && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object b >actual && + git rev-parse A:b >expect && + test_cmp expect actual + ) +' + +test_expect_success '1a-R: Modify(A)/Modify(B), change on B subset of A' ' + test_setup_1a R && + ( + cd 1a_R && + + git checkout B^0 && + + test-tool chmtime --get -3600 b >old-mtime && + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + # Make sure b WAS updated + test-tool chmtime --get b >new-mtime && + test $(cat old-mtime) -lt $(cat new-mtime) && + + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:b && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object b >actual && + git rev-parse A:b >expect && + test_cmp expect actual + ) +' + + +########################################################################### +# SECTION 2: Cases involving basic renames +########################################################################### + +# Testcase 2a, Changes on A, rename on B +# Commit O: b_1 +# Commit A: b_2 +# Commit B: c_1 +# Expected: c_2 + +test_setup_2a () { + test_create_repo 2a_$1 && + ( + cd 2a_$1 && + + test_seq 1 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 1 11 >b && + git add b && + test_tick && + git commit -m "A" && + + git checkout B && + git mv b c && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '2a-L: Modify/rename, merge into modify side' ' + test_setup_2a L && + ( + cd 2a_L && + + git checkout A^0 && + + test_path_is_missing c && + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_path_is_file c && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:b >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +test_expect_success '2a-R: Modify/rename, merge into rename side' ' + test_setup_2a R && + ( + cd 2a_R && + + git checkout B^0 && + + test-tool chmtime --get -3600 c >old-mtime && + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + # Make sure c WAS updated + test-tool chmtime --get c >new-mtime && + test $(cat old-mtime) -lt $(cat new-mtime) && + + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:b >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +# Testcase 2b, Changed and renamed on A, subset of changes on B +# Commit O: b_1 +# Commit A: c_2 +# Commit B: b_3 +# Expected: c_2 + +test_setup_2b () { + test_create_repo 2b_$1 && + ( + cd 2b_$1 && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + git mv b c && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '2b-L: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_setup_2b L && + ( + cd 2b_L && + + git checkout A^0 && + + test-tool chmtime --get -3600 c >old-mtime && + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_must_be_empty err && + + # Make sure c WAS updated + test-tool chmtime --get c >new-mtime && + test_cmp old-mtime new-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:c && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:c >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +test_expect_success '2b-R: Rename+Mod(A)/Mod(B), B mods subset of A' ' + test_setup_2b R && + ( + cd 2b_R && + + git checkout B^0 && + + test_path_is_missing c && + GIT_MERGE_VERBOSITY=3 git merge -s recursive A^0 >out 2>err && + + # Make sure c now present (and thus was updated) + test_path_is_file c && + + test_must_be_empty err && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual HEAD:c && + git rev-parse >expect A:c && + test_cmp expect actual && + + git hash-object c >actual && + git rev-parse A:c >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +# Testcase 2c, Changes on A, rename on B +# Commit O: b_1 +# Commit A: b_2, c_3 +# Commit B: c_1 +# Expected: rename/add conflict c_2 vs c_3 +# +# NOTE: Since A modified b_1->b_2, and B renamed b_1->c_1, the threeway +# merge of those files should result in c_2. We then should have a +# rename/add conflict between c_2 and c_3. However, if we note in +# merge_content() that A had the right contents (b_2 has same +# contents as c_2, just at a different name), and that A had the +# right path present (c_3 existed) and thus decides that it can +# skip the update, then we're in trouble. This test verifies we do +# not make that particular mistake. + +test_setup_2c () { + test_create_repo 2c && + ( + cd 2c && + + test_seq 1 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 1 11 >b && + echo whatever >c && + git add b c && + test_tick && + git commit -m "A" && + + git checkout B && + git mv b c && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '2c: Modify b & add c VS rename b->c' ' + test_setup_2c && + ( + cd 2c && + + git checkout A^0 && + + test-tool chmtime --get -3600 c >old-mtime && + GIT_MERGE_VERBOSITY=3 && + export GIT_MERGE_VERBOSITY && + test_must_fail git merge -s recursive B^0 >out 2>err && + + test_i18ngrep "CONFLICT (rename/add): Rename b->c" out && + test_must_be_empty err && + + # Make sure c WAS updated + test-tool chmtime --get c >new-mtime && + test $(cat old-mtime) -lt $(cat new-mtime) + + # FIXME: rename/add conflicts are horribly broken right now; + # when I get back to my patch series fixing it and + # rename/rename(2to1) conflicts to bring them in line with + # how add/add conflicts behave, then checks like the below + # could be added. But that patch series is waiting until + # the rename-directory-detection series lands, which this + # is part of. And in the mean time, I do not want to further + # enforce broken behavior. So for now, the main test is the + # one above that err is an empty file. + + #git ls-files -s >index_files && + #test_line_count = 2 index_files && + + #git rev-parse >actual :2:c :3:c && + #git rev-parse >expect A:b A:c && + #test_cmp expect actual && + + #git cat-file -p A:b >>merged && + #git cat-file -p A:c >>merge-me && + #>empty && + #test_must_fail git merge-file \ + # -L "Temporary merge branch 1" \ + # -L "" \ + # -L "Temporary merge branch 2" \ + # merged empty merge-me && + #sed -e "s/^\([<=>]\)/\1\1\1/" merged >merged-internal && + + #git hash-object c >actual && + #git hash-object merged-internal >expect && + #test_cmp expect actual && + + #test_path_is_missing b + ) +' + + +########################################################################### +# SECTION 3: Cases involving directory renames +# +# NOTE: +# Directory renames only apply when one side renames a directory, and the +# other side adds or renames a path into that directory. Applying the +# directory rename to that new path creates a new pathname that didn't +# exist on either side of history. Thus, it is impossible for the +# merge contents to already be at the right path, so all of these checks +# exist just to make sure that updates are not skipped. +########################################################################### + +# Testcase 3a, Change + rename into dir foo on A, dir rename foo->bar on B +# Commit O: bq_1, foo/whatever +# Commit A: foo/{bq_2, whatever} +# Commit B: bq_1, bar/whatever +# Expected: bar/{bq_2, whatever} + +test_setup_3a () { + test_create_repo 3a_$1 && + ( + cd 3a_$1 && + + mkdir foo && + test_seq 1 10 >bq && + test_write_lines a b c d e f g h i j k >foo/whatever && + git add bq foo/whatever && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_seq 1 11 >bq && + git add bq && + git mv bq foo/ && + test_tick && + git commit -m "A" && + + git checkout B && + git mv foo/ bar/ && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '3a-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_setup_3a L && + ( + cd 3a_L && + + git checkout A^0 && + + test_path_is_missing bar/bq && + GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && + + test_must_be_empty err && + + test_path_is_file bar/bq && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect A:foo/bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse A:foo/bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +test_expect_success '3a-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_setup_3a R && + ( + cd 3a_R && + + git checkout B^0 && + + test_path_is_missing bar/bq && + GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err && + + test_must_be_empty err && + + test_path_is_file bar/bq && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect A:foo/bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse A:foo/bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +# Testcase 3b, rename into dir foo on A, dir rename foo->bar + change on B +# Commit O: bq_1, foo/whatever +# Commit A: foo/{bq_1, whatever} +# Commit B: bq_2, bar/whatever +# Expected: bar/{bq_2, whatever} + +test_setup_3b () { + test_create_repo 3b_$1 && + ( + cd 3b_$1 && + + mkdir foo && + test_seq 1 10 >bq && + test_write_lines a b c d e f g h i j k >foo/whatever && + git add bq foo/whatever && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + git mv bq foo/ && + test_tick && + git commit -m "A" && + + git checkout B && + test_seq 1 11 >bq && + git add bq && + git mv foo/ bar/ && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '3b-L: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_setup_3b L && + ( + cd 3b_L && + + git checkout A^0 && + + test_path_is_missing bar/bq && + GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive B^0 >out 2>err && + + test_must_be_empty err && + + test_path_is_file bar/bq && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect B:bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse B:bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +test_expect_success '3b-R: bq_1->foo/bq_2 on A, foo/->bar/ on B' ' + test_setup_3b R && + ( + cd 3b_R && + + git checkout B^0 && + + test_path_is_missing bar/bq && + GIT_MERGE_VERBOSITY=3 git -c merge.directoryRenames=true merge -s recursive A^0 >out 2>err && + + test_must_be_empty err && + + test_path_is_file bar/bq && + + git ls-files -s >index_files && + test_line_count = 2 index_files && + + git rev-parse >actual HEAD:bar/bq HEAD:bar/whatever && + git rev-parse >expect B:bq A:foo/whatever && + test_cmp expect actual && + + git hash-object bar/bq bar/whatever >actual && + git rev-parse B:bq A:foo/whatever >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:bq HEAD:foo/bq && + test_path_is_missing bq foo/bq foo/whatever + ) +' + +########################################################################### +# SECTION 4: Cases involving dirty changes +########################################################################### + +# Testcase 4a, Changed on A, subset of changes on B, locally modified +# Commit O: b_1 +# Commit A: b_2 +# Commit B: b_3 +# Working copy: b_4 +# Expected: b_2 for merge, b_4 in working copy + +test_setup_4a () { + test_create_repo 4a && + ( + cd 4a && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +} + +# NOTE: For as long as we continue using unpack_trees() without index_only +# set to true, it will error out on a case like this claiming that the locally +# modified file would be overwritten by the merge. Getting this testcase +# correct requires doing the merge in-memory first, then realizing that no +# updates to the file are necessary, and thus that we can just leave the path +# alone. +test_expect_failure '4a: Change on A, change on B subset of A, dirty mods present' ' + test_setup_4a && + ( + cd 4a && + + git checkout A^0 && + echo "File rewritten" >b && + + test-tool chmtime --get -3600 b >old-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_must_be_empty err && + + # Make sure b was NOT updated + test-tool chmtime --get b >new-mtime && + test_cmp old-mtime new-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual :0:b && + git rev-parse >expect A:b && + test_cmp expect actual && + + git hash-object b >actual && + echo "File rewritten" | git hash-object --stdin >expect && + test_cmp expect actual + ) +' + +# Testcase 4b, Changed+renamed on A, subset of changes on B, locally modified +# Commit O: b_1 +# Commit A: c_2 +# Commit B: b_3 +# Working copy: c_4 +# Expected: c_2 + +test_setup_4b () { + test_create_repo 4b && + ( + cd 4b && + + test_write_lines 1 2 3 4 5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "O" && + + git branch O && + git branch A && + git branch B && + + git checkout A && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 10.5 >b && + git add b && + git mv b c && + test_tick && + git commit -m "A" && + + git checkout B && + test_write_lines 1 2 3 4 5 5.5 6 7 8 9 10 >b && + git add b && + test_tick && + git commit -m "B" + ) +} + +test_expect_success '4b: Rename+Mod(A)/Mod(B), change on B subset of A, dirty mods present' ' + test_setup_4b && + ( + cd 4b && + + git checkout A^0 && + echo "File rewritten" >c && + + test-tool chmtime --get -3600 c >old-mtime && + + GIT_MERGE_VERBOSITY=3 git merge -s recursive B^0 >out 2>err && + + test_must_be_empty err && + + # Make sure c was NOT updated + test-tool chmtime --get c >new-mtime && + test_cmp old-mtime new-mtime && + + git ls-files -s >index_files && + test_line_count = 1 index_files && + + git rev-parse >actual :0:c && + git rev-parse >expect A:c && + test_cmp expect actual && + + git hash-object c >actual && + echo "File rewritten" | git hash-object --stdin >expect && + test_cmp expect actual && + + test_must_fail git rev-parse HEAD:b && + test_path_is_missing b + ) +' + +test_done diff --git a/t/t6427-diff3-conflict-markers.sh b/t/t6427-diff3-conflict-markers.sh new file mode 100755 index 0000000..f4655bb --- /dev/null +++ b/t/t6427-diff3-conflict-markers.sh @@ -0,0 +1,211 @@ +#!/bin/sh + +test_description='recursive merge diff3 style conflict markers' + +. ./test-lib.sh + +# Setup: +# L1 +# \ +# ? +# / +# R1 +# +# Where: +# L1 and R1 both have a file named 'content' but have no common history +# + +test_expect_success 'setup no merge base' ' + test_create_repo no_merge_base && + ( + cd no_merge_base && + + git checkout -b L && + test_commit A content A && + + git checkout --orphan R && + test_commit B content B + ) +' + +test_expect_success 'check no merge base' ' + ( + cd no_merge_base && + + git checkout L^0 && + + test_must_fail git -c merge.conflictstyle=diff3 merge --allow-unrelated-histories -s recursive R^0 && + + grep "|||||| empty tree" content + ) +' + +# Setup: +# L1 +# / \ +# master ? +# \ / +# R1 +# +# Where: +# L1 and R1 have modified the same file ('content') in conflicting ways +# + +test_expect_success 'setup unique merge base' ' + test_create_repo unique_merge_base && + ( + cd unique_merge_base && + + test_commit base content "1 +2 +3 +4 +5 +" && + + git branch L && + git branch R && + + git checkout L && + test_commit L content "1 +2 +3 +4 +5 +7" && + + git checkout R && + git rm content && + test_commit R renamed "1 +2 +3 +4 +5 +six" + ) +' + +test_expect_success 'check unique merge base' ' + ( + cd unique_merge_base && + + git checkout L^0 && + MASTER=$(git rev-parse --short master) && + + test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 && + + grep "|||||| $MASTER:content" renamed + ) +' + +# Setup: +# L1---L2--L3 +# / \ / \ +# master X1 ? +# \ / \ / +# R1---R2--R3 +# +# Where: +# commits L1 and R1 have modified the same file in non-conflicting ways +# X1 is an auto-generated merge-base used when merging L1 and R1 +# commits L2 and R2 are merges of R1 and L1 into L1 and R1, respectively +# commits L3 and R3 both modify 'content' in conflicting ways +# + +test_expect_success 'setup multiple merge bases' ' + test_create_repo multiple_merge_bases && + ( + cd multiple_merge_bases && + + test_commit initial content "1 +2 +3 +4 +5" && + + git branch L && + git branch R && + + # Create L1 + git checkout L && + test_commit L1 content "0 +1 +2 +3 +4 +5" && + + # Create R1 + git checkout R && + test_commit R1 content "1 +2 +3 +4 +5 +6" && + + # Create L2 + git checkout L && + git merge R1 && + + # Create R2 + git checkout R && + git merge L1 && + + # Create L3 + git checkout L && + test_commit L3 content "0 +1 +2 +3 +4 +5 +A" && + + # Create R3 + git checkout R && + git rm content && + test_commit R3 renamed "0 +2 +3 +4 +5 +six" + ) +' + +test_expect_success 'check multiple merge bases' ' + ( + cd multiple_merge_bases && + + git checkout L^0 && + + test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R^0 && + + grep "|||||| merged common ancestors:content" renamed + ) +' + +test_expect_success 'rebase --merge describes parent of commit being picked' ' + test_create_repo rebase && + ( + cd rebase && + test_commit base file && + test_commit master file && + git checkout -b side HEAD^ && + test_commit side file && + test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master && + grep "||||||| parent of" file + ) +' + +test_expect_success 'rebase --apply describes fake ancestor base' ' + ( + cd rebase && + git rebase --abort && + test_must_fail git -c merge.conflictstyle=diff3 rebase --apply master && + grep "||||||| constructed merge base" file + ) +' + +test_done diff --git a/t/t6430-merge-recursive.sh b/t/t6430-merge-recursive.sh new file mode 100755 index 0000000..d48d211 --- /dev/null +++ b/t/t6430-merge-recursive.sh @@ -0,0 +1,770 @@ +#!/bin/sh + +test_description='merge-recursive backend test' + +. ./test-lib.sh + +test_expect_success 'setup 1' ' + + echo hello >a && + o0=$(git hash-object a) && + cp a b && + cp a c && + mkdir d && + cp a d/e && + + test_tick && + git add a b c d/e && + git commit -m initial && + c0=$(git rev-parse --verify HEAD) && + git branch side && + git branch df-1 && + git branch df-2 && + git branch df-3 && + git branch remove && + git branch submod && + git branch copy && + git branch rename && + git branch rename-ln && + + echo hello >>a && + cp a d/e && + o1=$(git hash-object a) && + + git add a d/e && + + test_tick && + git commit -m "master modifies a and d/e" && + c1=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o1 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o1 d/e" && + echo "100644 $o1 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'setup 2' ' + + rm -rf [abcd] && + git checkout side && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" + ) >expected && + test_cmp expected actual && + + echo goodbye >>a && + o2=$(git hash-object a) && + + git add a && + + test_tick && + git commit -m "side modifies a" && + c2=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o2 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o2 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'setup 3' ' + + rm -rf [abcd] && + git checkout df-1 && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" + ) >expected && + test_cmp expected actual && + + rm -f b && mkdir b && echo df-1 >b/c && git add b/c && + o3=$(git hash-object b/c) && + + test_tick && + git commit -m "df-1 makes b/c" && + c3=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o0 a" && + echo "100644 blob $o3 b/c" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o3 0 b/c" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'setup 4' ' + + rm -rf [abcd] && + git checkout df-2 && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" + ) >expected && + test_cmp expected actual && + + rm -f a && mkdir a && echo df-2 >a/c && git add a/c && + o4=$(git hash-object a/c) && + + test_tick && + git commit -m "df-2 makes a/c" && + c4=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o4 a/c" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o4 0 a/c" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'setup 5' ' + + rm -rf [abcd] && + git checkout remove && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" + ) >expected && + test_cmp expected actual && + + rm -f b && + echo remove-conflict >a && + + git add a && + git rm b && + o5=$(git hash-object a) && + + test_tick && + git commit -m "remove removes b and modifies a" && + c5=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o5 a" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o5 0 a" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" + ) >expected && + test_cmp expected actual + +' + +test_expect_success 'setup 6' ' + + rm -rf [abcd] && + git checkout df-3 && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" + ) >expected && + test_cmp expected actual && + + rm -fr d && echo df-3 >d && git add d && + o6=$(git hash-object d) && + + test_tick && + git commit -m "df-3 makes d" && + c6=$(git rev-parse --verify HEAD) && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o0 a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o6 d" && + echo "100644 $o0 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o6 0 d" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'setup 7' ' + + git checkout submod && + git rm d/e && + test_tick && + git commit -m "remove d/e" && + git update-index --add --cacheinfo 160000 $c1 d && + test_tick && + git commit -m "make d/ a submodule" +' + +test_expect_success 'setup 8' ' + git checkout rename && + git mv a e && + git add e && + test_tick && + git commit -m "rename a->e" && + c7=$(git rev-parse --verify HEAD) && + git checkout rename-ln && + git mv a e && + test_ln_s_add e a && + test_tick && + git commit -m "rename a->e, symlink a->e" && + oln=$(printf e | git hash-object --stdin) +' + +test_expect_success 'setup 9' ' + git checkout copy && + cp a e && + git add e && + test_tick && + git commit -m "copy a->e" +' + +test_expect_success 'merge-recursive simple' ' + + rm -fr [abcd] && + git checkout -f "$c2" && + + test_expect_code 1 git merge-recursive "$c0" -- "$c2" "$c1" +' + +test_expect_success 'merge-recursive result' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 1 a" && + echo "100644 $o2 2 a" && + echo "100644 $o1 3 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" + ) >expected && + test_cmp expected actual + +' + +test_expect_success 'fail if the index has unresolved entries' ' + + rm -fr [abcd] && + git checkout -f "$c1" && + + test_must_fail git merge "$c5" && + test_must_fail git merge "$c5" 2> out && + test_i18ngrep "not possible because you have unmerged files" out && + git add -u && + test_must_fail git merge "$c5" 2> out && + test_i18ngrep "You have not concluded your merge" out && + rm -f .git/MERGE_HEAD && + test_must_fail git merge "$c5" 2> out && + test_i18ngrep "Your local changes to the following files would be overwritten by merge:" out +' + +test_expect_success 'merge-recursive remove conflict' ' + + rm -fr [abcd] && + git checkout -f "$c1" && + + test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c5" +' + +test_expect_success 'merge-recursive remove conflict' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 1 a" && + echo "100644 $o1 2 a" && + echo "100644 $o5 3 a" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" + ) >expected && + test_cmp expected actual + +' + +test_expect_success 'merge-recursive d/f simple' ' + rm -fr [abcd] && + git reset --hard && + git checkout -f "$c1" && + + git merge-recursive "$c0" -- "$c1" "$c3" +' + +test_expect_success 'merge-recursive result' ' + + git ls-files -s >actual && + ( + echo "100644 $o1 0 a" && + echo "100644 $o3 0 b/c" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" + ) >expected && + test_cmp expected actual + +' + +test_expect_success 'merge-recursive d/f conflict' ' + + rm -fr [abcd] && + git reset --hard && + git checkout -f "$c1" && + + test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c4" +' + +test_expect_success 'merge-recursive d/f conflict result' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 1 a" && + echo "100644 $o1 2 a" && + echo "100644 $o4 0 a/c" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" + ) >expected && + test_cmp expected actual + +' + +test_expect_success 'merge-recursive d/f conflict the other way' ' + + rm -fr [abcd] && + git reset --hard && + git checkout -f "$c4" && + + test_expect_code 1 git merge-recursive "$c0" -- "$c4" "$c1" +' + +test_expect_success 'merge-recursive d/f conflict result the other way' ' + + git ls-files -s >actual && + ( + echo "100644 $o0 1 a" && + echo "100644 $o1 3 a" && + echo "100644 $o4 0 a/c" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" + ) >expected && + test_cmp expected actual + +' + +test_expect_success 'merge-recursive d/f conflict' ' + + rm -fr [abcd] && + git reset --hard && + git checkout -f "$c1" && + + test_expect_code 1 git merge-recursive "$c0" -- "$c1" "$c6" +' + +test_expect_success 'merge-recursive d/f conflict result' ' + + git ls-files -s >actual && + ( + echo "100644 $o1 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o6 3 d" && + echo "100644 $o0 1 d/e" && + echo "100644 $o1 2 d/e" + ) >expected && + test_cmp expected actual + +' + +test_expect_success 'merge-recursive d/f conflict' ' + + rm -fr [abcd] && + git reset --hard && + git checkout -f "$c6" && + + test_expect_code 1 git merge-recursive "$c0" -- "$c6" "$c1" +' + +test_expect_success 'merge-recursive d/f conflict result' ' + + git ls-files -s >actual && + ( + echo "100644 $o1 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o6 2 d" && + echo "100644 $o0 1 d/e" && + echo "100644 $o1 3 d/e" + ) >expected && + test_cmp expected actual + +' + +test_expect_success SYMLINKS 'dir in working tree with symlink ancestor does not produce d/f conflict' ' + git init sym && + ( + cd sym && + ln -s . foo && + mkdir bar && + >bar/file && + git add foo bar/file && + git commit -m "foo symlink" && + + git checkout -b branch1 && + git commit --allow-empty -m "empty commit" && + + git checkout master && + git rm foo && + mkdir foo && + >foo/bar && + git add foo/bar && + git commit -m "replace foo symlink with real foo dir and foo/bar file" && + + git checkout branch1 && + + git cherry-pick master && + test_path_is_dir foo && + test_path_is_file foo/bar + ) +' + +test_expect_success 'reset and 3-way merge' ' + + git reset --hard "$c2" && + git read-tree -m "$c0" "$c2" "$c1" + +' + +test_expect_success 'reset and bind merge' ' + + git reset --hard master && + git read-tree --prefix=M/ master && + git ls-files -s >actual && + ( + echo "100644 $o1 0 M/a" && + echo "100644 $o0 0 M/b" && + echo "100644 $o0 0 M/c" && + echo "100644 $o1 0 M/d/e" && + echo "100644 $o1 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" + ) >expected && + test_cmp expected actual && + + git read-tree --prefix=a1/ master && + git ls-files -s >actual && + ( + echo "100644 $o1 0 M/a" && + echo "100644 $o0 0 M/b" && + echo "100644 $o0 0 M/c" && + echo "100644 $o1 0 M/d/e" && + echo "100644 $o1 0 a" && + echo "100644 $o1 0 a1/a" && + echo "100644 $o0 0 a1/b" && + echo "100644 $o0 0 a1/c" && + echo "100644 $o1 0 a1/d/e" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" + ) >expected && + test_cmp expected actual && + + git read-tree --prefix=z/ master && + git ls-files -s >actual && + ( + echo "100644 $o1 0 M/a" && + echo "100644 $o0 0 M/b" && + echo "100644 $o0 0 M/c" && + echo "100644 $o1 0 M/d/e" && + echo "100644 $o1 0 a" && + echo "100644 $o1 0 a1/a" && + echo "100644 $o0 0 a1/b" && + echo "100644 $o0 0 a1/c" && + echo "100644 $o1 0 a1/d/e" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o1 0 d/e" && + echo "100644 $o1 0 z/a" && + echo "100644 $o0 0 z/b" && + echo "100644 $o0 0 z/c" && + echo "100644 $o1 0 z/d/e" + ) >expected && + test_cmp expected actual + +' + +test_expect_success 'merge-recursive w/ empty work tree - ours has rename' ' + ( + GIT_WORK_TREE="$PWD/ours-has-rename-work" && + export GIT_WORK_TREE && + GIT_INDEX_FILE="$PWD/ours-has-rename-index" && + export GIT_INDEX_FILE && + mkdir "$GIT_WORK_TREE" && + 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_expect_success 'merge-recursive w/ empty work tree - theirs has rename' ' + ( + GIT_WORK_TREE="$PWD/theirs-has-rename-work" && + export GIT_WORK_TREE && + GIT_INDEX_FILE="$PWD/theirs-has-rename-index" && + export GIT_INDEX_FILE && + mkdir "$GIT_WORK_TREE" && + 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_expect_success 'merge removes empty directories' ' + + git reset --hard master && + git checkout -b rm && + git rm d/e && + git commit -mremoved-d/e && + git checkout master && + git merge -s recursive rm && + test_path_is_missing d +' + +test_expect_success 'merge-recursive simple w/submodule' ' + + git checkout submod && + git merge remove +' + +test_expect_success 'merge-recursive simple w/submodule result' ' + + git ls-files -s >actual && + ( + echo "100644 $o5 0 a" && + echo "100644 $o0 0 c" && + echo "160000 $c1 0 d" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'merge-recursive copy vs. rename' ' + git checkout -f copy && + git merge rename && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 blob $o0 e" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" && + echo "100644 $o0 0 e" + ) >expected && + test_cmp expected actual +' + +test_expect_failure 'merge-recursive rename vs. rename/symlink' ' + + git checkout -f rename && + git merge rename-ln && + ( git ls-tree -r HEAD && git ls-files -s ) >actual && + ( + echo "120000 blob $oln a" && + echo "100644 blob $o0 b" && + echo "100644 blob $o0 c" && + echo "100644 blob $o0 d/e" && + echo "100644 blob $o0 e" && + echo "120000 $oln 0 a" && + echo "100644 $o0 0 b" && + echo "100644 $o0 0 c" && + echo "100644 $o0 0 d/e" && + echo "100644 $o0 0 e" + ) >expected && + test_cmp expected actual +' + +test_expect_success 'merging with triple rename across D/F conflict' ' + git reset --hard HEAD && + git checkout -b main && + git rm -rf . && + + echo "just a file" >sub1 && + mkdir -p sub2 && + echo content1 >sub2/file1 && + echo content2 >sub2/file2 && + echo content3 >sub2/file3 && + mkdir simple && + echo base >simple/bar && + git add -A && + test_tick && + git commit -m base && + + git checkout -b other && + echo more >>simple/bar && + test_tick && + git commit -a -m changesimplefile && + + git checkout main && + git rm sub1 && + git mv sub2 sub1 && + test_tick && + git commit -m changefiletodir && + + test_tick && + git merge other +' + +test_expect_success 'merge-recursive remembers the names of all base trees' ' + git reset --hard HEAD && + + # make the index match $c1 so that merge-recursive below does not + # fail early + git diff --binary HEAD $c1 -- | git apply --cached && + + # more trees than static slots used by oid_to_hex() + for commit in $c0 $c2 $c4 $c5 $c6 $c7 + do + git rev-parse "$commit^{tree}" + done >trees && + + # ignore the return code; it only fails because the input is weird... + test_must_fail git -c merge.verbosity=5 merge-recursive $(cat trees) -- $c1 $c3 >out && + + # ...but make sure it fails in the expected way + test_i18ngrep CONFLICT.*rename/rename out && + + # merge-recursive prints in reverse order, but we do not care + sort expect && + sed -n "s/^virtual //p" out | sort >actual && + test_cmp expect actual +' + +test_expect_success 'merge-recursive internal merge resolves to the sameness' ' + git reset --hard HEAD && + + # We are going to create a history leading to two criss-cross + # branches A and B. The common ancestor at the bottom, O0, + # has two child commits O1 and O2, both of which will be merge + # base between A and B, like so: + # + # O1---A + # / \ / + # O0 . + # \ / \ + # O2---B + # + # The recently added "check to see if the index is different from + # the tree into which something else is getting merged" check must + # NOT kick in when an inner merge between O1 and O2 is made. Both + # O1 and O2 happen to have the same tree as O0 in this test to + # trigger the bug---whether the inner merge is made by merging O2 + # into O1 or O1 into O2, their common ancestor O0 and the branch + # being merged have the same tree. We should not trigger the "is + # the index dirty?" check in this case. + + echo "zero" >file && + git add file && + test_tick && + git commit -m "O0" && + O0=$(git rev-parse HEAD) && + + test_tick && + git commit --allow-empty -m "O1" && + O1=$(git rev-parse HEAD) && + + git reset --hard $O0 && + test_tick && + git commit --allow-empty -m "O2" && + O2=$(git rev-parse HEAD) && + + test_tick && + git merge -s ours $O1 && + B=$(git rev-parse HEAD) && + + git reset --hard $O1 && + test_tick && + git merge -s ours $O2 && + A=$(git rev-parse HEAD) && + + git merge $B +' + +test_done diff --git a/t/t6431-merge-criscross.sh b/t/t6431-merge-criscross.sh new file mode 100755 index 0000000..3824756 --- /dev/null +++ b/t/t6431-merge-criscross.sh @@ -0,0 +1,95 @@ +#!/bin/sh + +test_description='merge-recursive backend test' + +. ./test-lib.sh + +# A <- create some files +# / \ +# B C <- cause rename/delete conflicts between B and C +# / \ +# |\ /| +# | D E | +# | \ / | +# | X | +# | / \ | +# | / \ | +# |/ \| +# F G <- merge E into B, D into C +# \ / +# \ / +# \ / +# H <- recursive merge crashes +# + +# initialize +test_expect_success 'setup repo with criss-cross history' ' + mkdir data && + + # create a bunch of files + n=1 && + while test $n -le 10 + do + echo $n > data/$n && + n=$(($n+1)) || + return 1 + done && + + # check them in + git add data && + git commit -m A && + git branch A && + + # a file in one branch + git checkout -b B A && + git rm data/9 && + git add data && + git commit -m B && + + # with a branch off of it + git branch D && + + # put some commits on D + git checkout D && + echo testD > data/testD && + git add data && + git commit -m D && + + # back up to the top, create another branch and cause + # a rename conflict with the file we deleted earlier + git checkout -b C A && + git mv data/9 data/new-9 && + git add data && + git commit -m C && + + # with a branch off of it + git branch E && + + # put a commit on E + git checkout E && + echo testE > data/testE && + git add data && + git commit -m E && + + # now, merge E into B + git checkout B && + test_must_fail git merge E && + # force-resolve + git add data && + git commit -m F && + git branch F && + + # and merge D into C + git checkout C && + test_must_fail git merge D && + # force-resolve + git add data && + git commit -m G && + git branch G +' + +test_expect_success 'recursive merge between F and G does not cause segfault' ' + git merge F +' + +test_done diff --git a/t/t6432-merge-recursive-space-options.sh b/t/t6432-merge-recursive-space-options.sh new file mode 100755 index 0000000..b56180e --- /dev/null +++ b/t/t6432-merge-recursive-space-options.sh @@ -0,0 +1,207 @@ +#!/bin/sh + +test_description='merge-recursive space options + +* [master] Clarify + ! [remote] Remove cruft +-- + + [remote] Remove cruft +* [master] Clarify +*+ [remote^] Initial revision +* ok 1: setup +' + +. ./test-lib.sh + +test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b +if test_have_prereq GREP_STRIPS_CR +then + GREP_OPTIONS=-U + export GREP_OPTIONS +fi + +test_expect_success 'setup' ' + conflict_hunks () { + sed $SED_OPTIONS -n -e " + /^<<<>>>/ b + n + b conflict + " "$@" + } && + + cat <<-\EOF >text.txt && + Hope, he says, cherishes the soul of him who lives in + justice and holiness and is the nurse of his age and the + companion of his journey;--hope which is mightiest to sway + the restless soul of man. + + How admirable are his words! And the great blessing of riches, I do + not say to every man, but to a good man, is, that he has had no + occasion to deceive or to defraud others, either intentionally or + unintentionally; and when he departs to the world below he is not in + any apprehension about offerings due to the gods or debts which he owes + to men. Now to this peace of mind the possession of wealth greatly + contributes; and therefore I say, that, setting one thing against + another, of the many advantages which wealth has to give, to a man of + sense this is in my opinion the greatest. + + Well said, Cephalus, I replied; but as concerning justice, what is + it?--to speak the truth and to pay your debts--no more than this? And + even to this are there not exceptions? Suppose that a friend when in + his right mind has deposited arms with me and he asks for them when he + is not in his right mind, ought I to give them back to him? No one + would say that I ought or that I should be right in doing so, any more + than they would say that I ought always to speak the truth to one who + is in his condition. + + You are quite right, he replied. + + But then, I said, speaking the truth and paying your debts is not a + correct definition of justice. + + CEPHALUS - SOCRATES - POLEMARCHUS + + Quite correct, Socrates, if Simonides is to be believed, said + Polemarchus interposing. + + I fear, said Cephalus, that I must go now, for I have to look after the + sacrifices, and I hand over the argument to Polemarchus and the company. + EOF + git add text.txt && + test_tick && + git commit -m "Initial revision" && + + git checkout -b remote && + sed -e " + s/\. /\. /g + s/[?] /? /g + s/ / /g + s/--/---/g + s/but as concerning/but as con cerning/ + /CEPHALUS - SOCRATES - POLEMARCHUS/ d + " text.txt >text.txt+ && + mv text.txt+ text.txt && + git commit -a -m "Remove cruft" && + + git checkout master && + sed -e " + s/\(not in his right mind\),\(.*\)/\1;\2Q/ + s/Quite correct\(.*\)/It is too correct\1Q/ + s/unintentionally/un intentionally/ + /un intentionally/ s/$/Q/ + s/Polemarchus interposing./Polemarchus, interposing.Q/ + /justice and holiness/ s/$/Q/ + /pay your debts/ s/$/Q/ + " text.txt | q_to_cr >text.txt+ && + mv text.txt+ text.txt && + git commit -a -m "Clarify" && + git show-branch --all +' + +test_expect_success 'naive merge fails' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive HEAD^ -- HEAD remote && + test_must_fail git update-index --refresh && + grep "<<<<<<" text.txt +' + +test_expect_success '--ignore-space-change makes merge succeed' ' + git read-tree --reset -u HEAD && + git merge-recursive --ignore-space-change HEAD^ -- HEAD remote +' + +test_expect_success 'naive cherry-pick fails' ' + git read-tree --reset -u HEAD && + test_must_fail git cherry-pick --no-commit remote && + git read-tree --reset -u HEAD && + test_must_fail git cherry-pick remote && + test_must_fail git update-index --refresh && + grep "<<<<<<" text.txt +' + +test_expect_success '-Xignore-space-change makes cherry-pick succeed' ' + git read-tree --reset -u HEAD && + git cherry-pick --no-commit -Xignore-space-change remote +' + +test_expect_success '--ignore-space-change: our w/s-only change wins' ' + q_to_cr <<-\EOF >expected && + justice and holiness and is the nurse of his age and theQ + EOF + + git read-tree --reset -u HEAD && + git merge-recursive --ignore-space-change HEAD^ -- HEAD remote && + grep "justice and holiness" text.txt >actual && + test_cmp expected actual +' + +test_expect_success '--ignore-space-change: their real change wins over w/s' ' + cat <<-\EOF >expected && + it?---to speak the truth and to pay your debts---no more than this? And + EOF + + git read-tree --reset -u HEAD && + git merge-recursive --ignore-space-change HEAD^ -- HEAD remote && + grep "pay your debts" text.txt >actual && + test_cmp expected actual +' + +test_expect_success '--ignore-space-change: does not ignore new spaces' ' + cat <<-\EOF >expected1 && + Well said, Cephalus, I replied; but as con cerning justice, what is + EOF + q_to_cr <<-\EOF >expected2 && + un intentionally; and when he departs to the world below he is not inQ + EOF + + git read-tree --reset -u HEAD && + git merge-recursive --ignore-space-change HEAD^ -- HEAD remote && + grep "Well said" text.txt >actual1 && + grep "when he departs" text.txt >actual2 && + test_cmp expected1 actual1 && + test_cmp expected2 actual2 +' + +test_expect_success '--ignore-all-space drops their new spaces' ' + cat <<-\EOF >expected && + Well said, Cephalus, I replied; but as concerning justice, what is + EOF + + git read-tree --reset -u HEAD && + git merge-recursive --ignore-all-space HEAD^ -- HEAD remote && + grep "Well said" text.txt >actual && + test_cmp expected actual +' + +test_expect_success '--ignore-all-space keeps our new spaces' ' + q_to_cr <<-\EOF >expected && + un intentionally; and when he departs to the world below he is not inQ + EOF + + git read-tree --reset -u HEAD && + git merge-recursive --ignore-all-space HEAD^ -- HEAD remote && + grep "when he departs" text.txt >actual && + test_cmp expected actual +' + +test_expect_success '--ignore-space-at-eol' ' + q_to_cr <<-\EOF >expected && + <<<<<<< HEAD + is not in his right mind; ought I to give them back to him? No oneQ + ======= + is not in his right mind, ought I to give them back to him? No one + >>>>>>> remote + EOF + + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --ignore-space-at-eol \ + HEAD^ -- HEAD remote && + conflict_hunks text.txt >actual && + test_cmp expected actual +' + +test_done diff --git a/t/t6433-merge-toplevel.sh b/t/t6433-merge-toplevel.sh new file mode 100755 index 0000000..e29c284 --- /dev/null +++ b/t/t6433-merge-toplevel.sh @@ -0,0 +1,174 @@ +#!/bin/sh + +test_description='"git merge" top-level frontend' + +. ./test-lib.sh + +t3033_reset () { + git checkout -B master two && + git branch -f left three && + git branch -f right four +} + +test_expect_success setup ' + test_commit one && + git branch left && + git branch right && + test_commit two && + git checkout left && + test_commit three && + git checkout right && + test_commit four && + git checkout --orphan newroot && + test_commit five && + git checkout master +' + +# Local branches + +test_expect_success 'merge an octopus into void' ' + t3033_reset && + git checkout --orphan test && + git rm -fr . && + test_must_fail git merge left right && + test_must_fail git rev-parse --verify HEAD && + git diff --quiet && + test_must_fail git rev-parse HEAD +' + +test_expect_success 'merge an octopus, fast-forward (ff)' ' + t3033_reset && + git reset --hard one && + git merge left right && + # one is ancestor of three (left) and four (right) + test_must_fail git rev-parse --verify HEAD^3 && + git rev-parse HEAD^1 HEAD^2 | sort >actual && + git rev-parse three four | sort >expect && + test_cmp expect actual +' + +test_expect_success 'merge octopus, non-fast-forward (ff)' ' + t3033_reset && + git reset --hard one && + git merge --no-ff left right && + # one is ancestor of three (left) and four (right) + test_must_fail git rev-parse --verify HEAD^4 && + git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && + git rev-parse one three four | sort >expect && + test_cmp expect actual +' + +test_expect_success 'merge octopus, fast-forward (does not ff)' ' + t3033_reset && + git merge left right && + # two (master) is not an ancestor of three (left) and four (right) + test_must_fail git rev-parse --verify HEAD^4 && + git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && + git rev-parse two three four | sort >expect && + test_cmp expect actual +' + +test_expect_success 'merge octopus, non-fast-forward' ' + t3033_reset && + git merge --no-ff left right && + test_must_fail git rev-parse --verify HEAD^4 && + git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && + git rev-parse two three four | sort >expect && + test_cmp expect actual +' + +# The same set with FETCH_HEAD + +test_expect_success 'merge FETCH_HEAD octopus into void' ' + t3033_reset && + git checkout --orphan test && + git rm -fr . && + git fetch . left right && + test_must_fail git merge FETCH_HEAD && + test_must_fail git rev-parse --verify HEAD && + git diff --quiet && + test_must_fail git rev-parse HEAD +' + +test_expect_success 'merge FETCH_HEAD octopus fast-forward (ff)' ' + t3033_reset && + git reset --hard one && + git fetch . left right && + git merge FETCH_HEAD && + # one is ancestor of three (left) and four (right) + test_must_fail git rev-parse --verify HEAD^3 && + git rev-parse HEAD^1 HEAD^2 | sort >actual && + git rev-parse three four | sort >expect && + test_cmp expect actual +' + +test_expect_success 'merge FETCH_HEAD octopus non-fast-forward (ff)' ' + t3033_reset && + git reset --hard one && + git fetch . left right && + git merge --no-ff FETCH_HEAD && + # one is ancestor of three (left) and four (right) + test_must_fail git rev-parse --verify HEAD^4 && + git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && + git rev-parse one three four | sort >expect && + test_cmp expect actual +' + +test_expect_success 'merge FETCH_HEAD octopus fast-forward (does not ff)' ' + t3033_reset && + git fetch . left right && + git merge FETCH_HEAD && + # two (master) is not an ancestor of three (left) and four (right) + test_must_fail git rev-parse --verify HEAD^4 && + git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && + git rev-parse two three four | sort >expect && + test_cmp expect actual +' + +test_expect_success 'merge FETCH_HEAD octopus non-fast-forward' ' + t3033_reset && + git fetch . left right && + git merge --no-ff FETCH_HEAD && + test_must_fail git rev-parse --verify HEAD^4 && + git rev-parse HEAD^1 HEAD^2 HEAD^3 | sort >actual && + git rev-parse two three four | sort >expect && + test_cmp expect actual +' + +# two-project merge +test_expect_success 'refuse two-project merge by default' ' + t3033_reset && + git reset --hard four && + test_must_fail git merge five +' + +test_expect_success 'refuse two-project merge by default, quit before --autostash happens' ' + t3033_reset && + git reset --hard four && + echo change >>one.t && + git diff >expect && + test_must_fail git merge --autostash five 2>err && + test_i18ngrep ! "stash" err && + git diff >actual && + test_cmp expect actual +' + +test_expect_success 'two-project merge with --allow-unrelated-histories' ' + t3033_reset && + git reset --hard four && + git merge --allow-unrelated-histories five && + git diff --exit-code five +' + +test_expect_success 'two-project merge with --allow-unrelated-histories with --autostash' ' + t3033_reset && + git reset --hard four && + echo change >>one.t && + git diff one.t >expect && + git merge --allow-unrelated-histories --autostash five 2>err && + test_i18ngrep "Applied autostash." err && + git diff one.t >actual && + test_cmp expect actual +' + +test_done diff --git a/t/t6434-merge-recursive-rename-options.sh b/t/t6434-merge-recursive-rename-options.sh new file mode 100755 index 0000000..3d9fae6 --- /dev/null +++ b/t/t6434-merge-recursive-rename-options.sh @@ -0,0 +1,330 @@ +#!/bin/sh + +test_description='merge-recursive rename options + +Test rename detection by examining rename/delete conflicts. + +* (HEAD -> rename) rename +| * (master) delete +|/ +* base + +git diff --name-status base master +D 0-old +D 1-old +D 2-old +D 3-old + +git diff --name-status -M01 base rename +R025 0-old 0-new +R050 1-old 1-new +R075 2-old 2-new +R100 3-old 3-new + +Actual similarity indices are parsed from diff output. We rely on the fact that +they are rounded down (see, e.g., Documentation/diff-generate-patch.txt, which +mentions this in a different context). +' + +. ./test-lib.sh + +get_expected_stages () { + git checkout rename -- $1-new && + git ls-files --stage $1-new >expected-stages-undetected-$1 && + sed "s/ 0 / 2 /" expected-stages-detected-$1 && + git read-tree -u --reset HEAD +} + +rename_detected () { + git ls-files --stage $1-old $1-new >stages-actual-$1 && + test_cmp expected-stages-detected-$1 stages-actual-$1 +} + +rename_undetected () { + git ls-files --stage $1-old $1-new >stages-actual-$1 && + test_cmp expected-stages-undetected-$1 stages-actual-$1 +} + +check_common () { + git ls-files --stage >stages-actual && + test_line_count = 4 stages-actual +} + +check_threshold_0 () { + check_common && + rename_detected 0 && + rename_detected 1 && + rename_detected 2 && + rename_detected 3 +} + +check_threshold_1 () { + check_common && + rename_undetected 0 && + rename_detected 1 && + rename_detected 2 && + rename_detected 3 +} + +check_threshold_2 () { + check_common && + rename_undetected 0 && + rename_undetected 1 && + rename_detected 2 && + rename_detected 3 +} + +check_exact_renames () { + check_common && + rename_undetected 0 && + rename_undetected 1 && + rename_undetected 2 && + rename_detected 3 +} + +check_no_renames () { + check_common && + rename_undetected 0 && + rename_undetected 1 && + rename_undetected 2 && + rename_undetected 3 +} + +test_expect_success 'setup repo' ' + cat <<-\EOF >3-old && + 33a + 33b + 33c + 33d + EOF + sed s/33/22/ <3-old >2-old && + sed s/33/11/ <3-old >1-old && + sed s/33/00/ <3-old >0-old && + git add [0-3]-old && + git commit -m base && + git rm [0-3]-old && + git commit -m delete && + git checkout -b rename HEAD^ && + cp 3-old 3-new && + sed 1,1s/./x/ <2-old >2-new && + sed 1,2s/./x/ <1-old >1-new && + sed 1,3s/./x/ <0-old >0-new && + git add [0-3]-new && + git rm [0-3]-old && + git commit -m rename && + get_expected_stages 0 && + get_expected_stages 1 && + get_expected_stages 2 && + get_expected_stages 3 && + check_50="false" && + tail="HEAD^ -- HEAD master" +' + +test_expect_success 'setup thresholds' ' + git diff --name-status -M01 HEAD^ HEAD >diff-output && + test_debug "cat diff-output" && + test_line_count = 4 diff-output && + grep "R[0-9][0-9][0-9] \([0-3]\)-old \1-new" diff-output \ + >grep-output && + test_cmp diff-output grep-output && + th0=$(sed -n "s/R\(...\) 0-old 0-new/\1/p" diff-output-0 && + git diff --name-status -M$th1 --diff-filter=R HEAD^ HEAD \ + >diff-output-1 && + git diff --name-status -M$th2 --diff-filter=R HEAD^ HEAD \ + >diff-output-2 && + git diff --name-status -M100% --diff-filter=R HEAD^ HEAD \ + >diff-output-3 && + test_line_count = 4 diff-output-0 && + test_line_count = 3 diff-output-1 && + test_line_count = 2 diff-output-2 && + test_line_count = 1 diff-output-3 +' + +test_expect_success 'default similarity threshold is 50%' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive $tail && + $check_50 +' + +test_expect_success 'low rename threshold' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=$th0 $tail && + check_threshold_0 +' + +test_expect_success 'medium rename threshold' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=$th1 $tail && + check_threshold_1 +' + +test_expect_success 'high rename threshold' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=$th2 $tail && + check_threshold_2 +' + +test_expect_success 'exact renames only' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=100% $tail && + check_exact_renames +' + +test_expect_success 'rename threshold is truncated' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=200% $tail && + check_exact_renames +' + +test_expect_success 'disabled rename detection' ' + git read-tree --reset -u HEAD && + git merge-recursive --no-renames $tail && + check_no_renames +' + +test_expect_success 'last wins in --find-renames= --find-renames=' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive \ + --find-renames=$th0 --find-renames=$th2 $tail && + check_threshold_2 +' + +test_expect_success '--find-renames resets threshold' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive \ + --find-renames=$th0 --find-renames $tail && + $check_50 +' + +test_expect_success 'last wins in --no-renames --find-renames' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --no-renames --find-renames $tail && + $check_50 +' + +test_expect_success 'last wins in --find-renames --no-renames' ' + git read-tree --reset -u HEAD && + git merge-recursive --find-renames --no-renames $tail && + check_no_renames +' + +test_expect_success 'assumption for further tests: trivial merge succeeds' ' + git read-tree --reset -u HEAD && + git merge-recursive HEAD -- HEAD HEAD && + git diff --quiet --cached && + git merge-recursive --find-renames=$th0 HEAD -- HEAD HEAD && + git diff --quiet --cached && + git merge-recursive --find-renames=$th2 HEAD -- HEAD HEAD && + git diff --quiet --cached && + git merge-recursive --find-renames=100% HEAD -- HEAD HEAD && + git diff --quiet --cached && + git merge-recursive --no-renames HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success '--find-renames rejects negative argument' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=-25 \ + HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success '--find-renames rejects non-numbers' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --find-renames=0xf \ + HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success 'rename-threshold= is a synonym for find-renames=' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --rename-threshold=$th0 $tail && + check_threshold_0 +' + +test_expect_success 'last wins in --no-renames --rename-threshold=' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --no-renames --rename-threshold=$th0 $tail && + check_threshold_0 +' + +test_expect_success 'last wins in --rename-threshold= --no-renames' ' + git read-tree --reset -u HEAD && + git merge-recursive --rename-threshold=$th0 --no-renames $tail && + check_no_renames +' + +test_expect_success '--rename-threshold= rejects negative argument' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --rename-threshold=-25 \ + HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success '--rename-threshold= rejects non-numbers' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive --rename-threshold=0xf \ + HEAD -- HEAD HEAD && + git diff --quiet --cached +' + +test_expect_success 'last wins in --rename-threshold= --find-renames=' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive \ + --rename-threshold=$th0 --find-renames=$th2 $tail && + check_threshold_2 +' + +test_expect_success 'last wins in --find-renames= --rename-threshold=' ' + git read-tree --reset -u HEAD && + test_must_fail git merge-recursive \ + --find-renames=$th2 --rename-threshold=$th0 $tail && + check_threshold_0 +' + +test_expect_success 'merge.renames disables rename detection' ' + git read-tree --reset -u HEAD && + git -c merge.renames=false merge-recursive $tail && + check_no_renames +' + +test_expect_success 'merge.renames defaults to diff.renames' ' + git read-tree --reset -u HEAD && + git -c diff.renames=false merge-recursive $tail && + check_no_renames +' + +test_expect_success 'merge.renames overrides diff.renames' ' + git read-tree --reset -u HEAD && + test_must_fail git -c diff.renames=false -c merge.renames=true merge-recursive $tail && + $check_50 +' + +test_done diff --git a/t/t6435-merge-sparse.sh b/t/t6435-merge-sparse.sh new file mode 100755 index 0000000..74562e1 --- /dev/null +++ b/t/t6435-merge-sparse.sh @@ -0,0 +1,58 @@ +#!/bin/sh + +test_description='merge with sparse files' + +. ./test-lib.sh + +# test_file $filename $content +test_file () { + echo "$2" > "$1" && + git add "$1" +} + +# test_commit_this $message_and_tag +test_commit_this () { + git commit -m "$1" && + git tag "$1" +} + +test_expect_success 'setup' ' + test_file checked-out init && + test_file modify_delete modify_delete_init && + test_commit_this init && + test_file modify_delete modify_delete_theirs && + test_commit_this theirs && + git reset --hard init && + git rm modify_delete && + test_commit_this ours && + git config core.sparseCheckout true && + echo "/checked-out" >.git/info/sparse-checkout && + git reset --hard && + test_must_fail git merge theirs +' + +test_expect_success 'reset --hard works after the conflict' ' + git reset --hard +' + +test_expect_success 'is reset properly' ' + git status --porcelain -- modify_delete >out && + test_must_be_empty out && + test_path_is_missing modify_delete +' + +test_expect_success 'setup: conflict back' ' + test_must_fail git merge theirs +' + +test_expect_success 'Merge abort works after the conflict' ' + git merge --abort +' + +test_expect_success 'is aborted properly' ' + git status --porcelain -- modify_delete >out && + test_must_be_empty out && + test_path_is_missing modify_delete +' + +test_done diff --git a/t/t6436-merge-overwrite.sh b/t/t6436-merge-overwrite.sh new file mode 100755 index 0000000..dd8ab7e --- /dev/null +++ b/t/t6436-merge-overwrite.sh @@ -0,0 +1,195 @@ +#!/bin/sh + +test_description='git-merge + +Do not overwrite changes.' + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit c0 c0.c && + test_commit c1 c1.c && + test_commit c1a c1.c "c1 a" && + git reset --hard c0 && + test_commit c2 c2.c && + git reset --hard c0 && + mkdir sub && + echo "sub/f" > sub/f && + mkdir sub2 && + echo "sub2/f" > sub2/f && + git add sub/f sub2/f && + git commit -m sub && + git tag sub && + echo "VERY IMPORTANT CHANGES" > important +' + +test_expect_success 'will not overwrite untracked file' ' + git reset --hard c1 && + cp important c2.c && + test_must_fail git merge c2 && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important c2.c +' + +test_expect_success 'will overwrite tracked file' ' + git reset --hard c1 && + cp important c2.c && + git add c2.c && + git commit -m important && + git checkout c2 +' + +test_expect_success 'will not overwrite new file' ' + git reset --hard c1 && + cp important c2.c && + git add c2.c && + test_must_fail git merge c2 && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important c2.c +' + +test_expect_success 'will not overwrite staged changes' ' + git reset --hard c1 && + cp important c2.c && + git add c2.c && + rm c2.c && + test_must_fail git merge c2 && + test_path_is_missing .git/MERGE_HEAD && + git checkout c2.c && + test_cmp important c2.c +' + +test_expect_success 'will not overwrite removed file' ' + git reset --hard c1 && + git rm c1.c && + git commit -m "rm c1.c" && + cp important c1.c && + test_must_fail git merge c1a && + test_cmp important c1.c +' + +test_expect_success 'will not overwrite re-added file' ' + git reset --hard c1 && + git rm c1.c && + git commit -m "rm c1.c" && + cp important c1.c && + git add c1.c && + test_must_fail git merge c1a && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important c1.c +' + +test_expect_success 'will not overwrite removed file with staged changes' ' + git reset --hard c1 && + git rm c1.c && + git commit -m "rm c1.c" && + cp important c1.c && + git add c1.c && + rm c1.c && + test_must_fail git merge c1a && + test_path_is_missing .git/MERGE_HEAD && + git checkout c1.c && + test_cmp important c1.c +' + +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 && + cp important other.c && + test_must_fail git merge c1a >out && + test_i18ngrep "Refusing to lose dirty file at other.c" out && + test_path_is_file other.c~HEAD && + test $(git hash-object other.c~HEAD) = $(git rev-parse c1a:c1.c) && + test_cmp important other.c +' + +test_expect_success 'will not overwrite untracked subtree' ' + git reset --hard c0 && + rm -rf sub && + mkdir -p sub/f && + cp important sub/f/important && + test_must_fail git merge sub && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important sub/f/important +' + +cat >expect <<\EOF +error: The following untracked working tree files would be overwritten by merge: + sub + sub2 +Please move or remove them before you merge. +Aborting +EOF + +test_expect_success 'will not overwrite untracked file in leading path' ' + git reset --hard c0 && + rm -rf sub && + cp important sub && + cp important sub2 && + test_must_fail git merge sub 2>out && + test_i18ncmp out expect && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important sub && + test_cmp important sub2 && + rm -f sub sub2 +' + +test_expect_success SYMLINKS 'will not overwrite untracked symlink in leading path' ' + git reset --hard c0 && + rm -rf sub && + mkdir sub2 && + ln -s sub2 sub && + test_must_fail git merge sub && + test_path_is_missing .git/MERGE_HEAD +' + +test_expect_success 'will not be confused by symlink in leading path' ' + git reset --hard c0 && + rm -rf sub && + test_ln_s_add sub2 sub && + git commit -m ln && + git checkout sub +' + +cat >expect <<\EOF +error: Untracked working tree file 'c0.c' would be overwritten by merge. +fatal: read-tree failed +EOF + +test_expect_success 'will not overwrite untracked file on unborn branch' ' + git reset --hard c0 && + git rm -fr . && + git checkout --orphan new && + cp important c0.c && + test_must_fail git merge c0 2>out && + test_i18ncmp out expect +' + +test_expect_success 'will not overwrite untracked file on unborn branch .git/MERGE_HEAD sanity etc.' ' + test_when_finished "rm c0.c" && + test_path_is_missing .git/MERGE_HEAD && + test_cmp important c0.c +' + +test_expect_success 'failed merge leaves unborn branch in the womb' ' + test_must_fail git rev-parse --verify HEAD +' + +test_expect_success 'set up unborn branch and content' ' + git symbolic-ref HEAD refs/heads/unborn && + rm -f .git/index && + echo foo > tracked-file && + git add tracked-file && + echo bar > untracked-file +' + +test_expect_success 'will not clobber WT/index when merging into unborn' ' + git merge master && + grep foo tracked-file && + git show :tracked-file >expect && + grep foo expect && + grep bar untracked-file +' + +test_done diff --git a/t/t6437-submodule-merge.sh b/t/t6437-submodule-merge.sh new file mode 100755 index 0000000..aa33978 --- /dev/null +++ b/t/t6437-submodule-merge.sh @@ -0,0 +1,455 @@ +#!/bin/sh + +test_description='merging with submodules' + +. ./test-lib.sh + +# +# history +# +# a --- c +# / \ / +# root X +# \ / \ +# b --- d +# + +test_expect_success setup ' + + mkdir sub && + (cd sub && + git init && + echo original > file && + git add file && + test_tick && + git commit -m sub-root) && + git add sub && + test_tick && + git commit -m root && + + git checkout -b a master && + (cd sub && + echo A > file && + git add file && + test_tick && + git commit -m sub-a) && + git add sub && + test_tick && + git commit -m a && + + git checkout -b b master && + (cd sub && + echo B > file && + git add file && + test_tick && + git commit -m sub-b) && + git add sub && + test_tick && + git commit -m b && + + git checkout -b c a && + git merge -s ours b && + + git checkout -b d b && + git merge -s ours a +' + +# History setup +# +# b +# / \ +# init -- a d +# \ \ / +# g c +# +# a in the main repository records to sub-a in the submodule and +# analogous b and c. d should be automatically found by merging c into +# b in the main repository. +test_expect_success 'setup for merge search' ' + mkdir merge-search && + (cd merge-search && + git init && + mkdir sub && + (cd sub && + git init && + echo "file-a" > file-a && + git add file-a && + git commit -m "sub-a" && + git branch sub-a) && + git commit --allow-empty -m init && + git branch init && + git add sub && + git commit -m "a" && + git branch a && + + git checkout -b b && + (cd sub && + git checkout -b sub-b && + echo "file-b" > file-b && + git add file-b && + git commit -m "sub-b") && + git commit -a -m "b" && + + git checkout -b c a && + (cd sub && + git checkout -b sub-c sub-a && + echo "file-c" > file-c && + git add file-c && + git commit -m "sub-c") && + git commit -a -m "c" && + + git checkout -b d a && + (cd sub && + git checkout -b sub-d sub-b && + git merge sub-c) && + git commit -a -m "d" && + git branch test b && + + git checkout -b g init && + (cd sub && + git checkout -b sub-g sub-c) && + git add sub && + git commit -a -m "g") +' + +test_expect_success 'merge with one side as a fast-forward of the other' ' + (cd merge-search && + git checkout -b test-forward b && + git merge d && + git ls-tree test-forward sub | cut -f1 | cut -f3 -d" " > actual && + (cd sub && + git rev-parse sub-d > ../expect) && + test_cmp expect actual) +' + +test_expect_success 'merging should conflict for non fast-forward' ' + (cd merge-search && + git checkout -b test-nonforward b && + (cd sub && + git rev-parse sub-d > ../expect) && + test_must_fail git merge c 2> actual && + grep $(cat expect) actual > /dev/null && + git reset --hard) +' + +test_expect_success 'merging should fail for ambiguous common parent' ' + (cd merge-search && + git checkout -b test-ambiguous b && + (cd sub && + git checkout -b ambiguous sub-b && + git merge sub-c && + git rev-parse sub-d > ../expect1 && + git rev-parse ambiguous > ../expect2) && + test_must_fail git merge c 2> actual && + grep $(cat expect1) actual > /dev/null && + grep $(cat expect2) actual > /dev/null && + git reset --hard) +' + +# in a situation like this +# +# submodule tree: +# +# sub-a --- sub-b --- sub-d +# +# main tree: +# +# e (sub-a) +# / +# bb (sub-b) +# \ +# f (sub-d) +# +# A merge between e and f should fail because one of the submodule +# commits (sub-a) does not descend from the submodule merge-base (sub-b). +# +test_expect_success 'merging should fail for changes that are backwards' ' + (cd merge-search && + git checkout -b bb a && + (cd sub && + git checkout sub-b) && + git commit -a -m "bb" && + + git checkout -b e bb && + (cd sub && + git checkout sub-a) && + git commit -a -m "e" && + + git checkout -b f bb && + (cd sub && + git checkout sub-d) && + git commit -a -m "f" && + + git checkout -b test-backward e && + test_must_fail git merge f) +' + + +# Check that the conflicting submodule is detected when it is +# in the common ancestor. status should be 'U00...00" +test_expect_success 'git submodule status should display the merge conflict properly with merge base' ' + (cd merge-search && + cat >.gitmodules <expect < actual && + test_cmp expect actual && + git reset --hard) +' + +# Check that the conflicting submodule is detected when it is +# not in the common ancestor. status should be 'U00...00" +test_expect_success 'git submodule status should display the merge conflict properly without merge-base' ' + (cd merge-search && + git checkout -b test-no-merge-base g && + test_must_fail git merge b && + cat >.gitmodules <expect < actual && + test_cmp expect actual && + git reset --hard) +' + + +test_expect_success 'merging with a modify/modify conflict between merge bases' ' + git reset --hard HEAD && + git checkout -b test2 c && + git merge d +' + +# canonical criss-cross history in top and submodule +test_expect_success 'setup for recursive merge with submodule' ' + mkdir merge-recursive && + (cd merge-recursive && + git init && + mkdir sub && + (cd sub && + git init && + test_commit a && + git checkout -b sub-b master && + test_commit b && + git checkout -b sub-c master && + test_commit c && + git checkout -b sub-bc sub-b && + git merge sub-c && + git checkout -b sub-cb sub-c && + git merge sub-b && + git checkout master) && + git add sub && + git commit -m a && + git checkout -b top-b master && + (cd sub && git checkout sub-b) && + git add sub && + git commit -m b && + git checkout -b top-c master && + (cd sub && git checkout sub-c) && + git add sub && + git commit -m c && + git checkout -b top-bc top-b && + git merge -s ours --no-commit top-c && + (cd sub && git checkout sub-bc) && + git add sub && + git commit -m bc && + git checkout -b top-cb top-c && + git merge -s ours --no-commit top-b && + (cd sub && git checkout sub-cb) && + git add sub && + git commit -m cb) +' + +# merge should leave submodule unmerged in index +test_expect_success 'recursive merge with submodule' ' + (cd merge-recursive && + test_must_fail git merge top-bc && + echo "160000 $(git rev-parse top-cb:sub) 2 sub" > expect2 && + echo "160000 $(git rev-parse top-bc:sub) 3 sub" > expect3 && + git ls-files -u > actual && + grep "$(cat expect2)" actual > /dev/null && + grep "$(cat expect3)" actual > /dev/null) +' + +# File/submodule conflict +# Commit O: +# Commit A: path (submodule) +# Commit B: path +# Expected: path/ is submodule and file contents for B's path are somewhere + +test_expect_success 'setup file/submodule conflict' ' + test_create_repo file-submodule && + ( + cd file-submodule && + + git commit --allow-empty -m O && + + git branch A && + git branch B && + + git checkout B && + echo content >path && + git add path && + git commit -m B && + + git checkout A && + test_create_repo path && + test_commit -C path world && + git submodule add ./path && + git commit -m A + ) +' + +test_expect_failure 'file/submodule conflict' ' + test_when_finished "git -C file-submodule reset --hard" && + ( + cd file-submodule && + + git checkout A^0 && + test_must_fail git merge B^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 2 out && + + # path/ is still a submodule + test_path_is_dir path/.git && + + # There is a submodule at "path", so B:path cannot be written + # there. We expect it to be written somewhere in the same + # directory, though, so just grep for its content in all + # files, and ignore "grep: path: Is a directory" message + echo Checking if contents from B:path showed up anywhere && + grep -q content * 2>/dev/null + ) +' + +test_expect_success 'file/submodule conflict; merge --abort works afterward' ' + test_when_finished "git -C file-submodule reset --hard" && + ( + cd file-submodule && + + git checkout A^0 && + test_must_fail git merge B^0 >out 2>err && + + test_path_is_file .git/MERGE_HEAD && + git merge --abort + ) +' + +# Directory/submodule conflict +# Commit O: +# Commit A: path (submodule), with sole tracked file named 'world' +# Commit B1: path/file +# Commit B2: path/world +# +# Expected from merge of A & B1: +# Contents under path/ from commit B1 are renamed elsewhere; we do not +# want to write files from one of our tracked directories into a submodule +# +# Expected from merge of A & B2: +# Similar to last merge, but with a slight twist: we don't want paths +# under the submodule to be treated as untracked or in the way. + +test_expect_success 'setup directory/submodule conflict' ' + test_create_repo directory-submodule && + ( + cd directory-submodule && + + git commit --allow-empty -m O && + + git branch A && + git branch B1 && + git branch B2 && + + git checkout B1 && + mkdir path && + echo contents >path/file && + git add path/file && + git commit -m B1 && + + git checkout B2 && + mkdir path && + echo contents >path/world && + git add path/world && + git commit -m B2 && + + git checkout A && + test_create_repo path && + test_commit -C path hello world && + git submodule add ./path && + git commit -m A + ) +' + +test_expect_failure 'directory/submodule conflict; keep submodule clean' ' + test_when_finished "git -C directory-submodule reset --hard" && + ( + cd directory-submodule && + + git checkout A^0 && + test_must_fail git merge B1^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 1 out && + + # path/ is still a submodule + test_path_is_dir path/.git && + + echo Checking if contents from B1:path/file showed up && + # Would rather use grep -r, but that is GNU extension... + git ls-files -co | xargs grep -q contents 2>/dev/null && + + # However, B1:path/file should NOT have shown up at path/file, + # because we should not write into the submodule + test_path_is_missing path/file + ) +' + +test_expect_failure !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' ' + test_when_finished "git -C directory-submodule/path reset --hard" && + test_when_finished "git -C directory-submodule reset --hard" && + ( + cd directory-submodule && + + git checkout A^0 && + test_must_fail git merge B2^0 >out 2>err && + + # We do not want files within the submodule to prevent the + # merge from starting; we should not be writing to such paths + # anyway. + test_i18ngrep ! "refusing to lose untracked file at" err + ) +' + +test_expect_failure 'directory/submodule conflict; merge --abort works afterward' ' + test_when_finished "git -C directory-submodule/path reset --hard" && + test_when_finished "git -C directory-submodule reset --hard" && + ( + cd directory-submodule && + + git checkout A^0 && + test_must_fail git merge B2^0 && + test_path_is_file .git/MERGE_HEAD && + + # merge --abort should succeed, should clear .git/MERGE_HEAD, + # and should not leave behind any conflicted files + git merge --abort && + test_path_is_missing .git/MERGE_HEAD && + git ls-files -u >conflicts && + test_must_be_empty conflicts + ) +' + +test_done diff --git a/t/t6438-submodule-directory-file-conflicts.sh b/t/t6438-submodule-directory-file-conflicts.sh new file mode 100755 index 0000000..04bf4be --- /dev/null +++ b/t/t6438-submodule-directory-file-conflicts.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +test_description='merge can handle submodules' + +. ./test-lib.sh +. "$TEST_DIRECTORY"/lib-submodule-update.sh + +# merges without conflicts +test_submodule_switch "merge" + +test_submodule_switch "merge --ff" + +test_submodule_switch "merge --ff-only" + +KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 +KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 +test_submodule_switch "merge --no-ff" + +test_done diff --git a/t/t6439-merge-co-error-msgs.sh b/t/t6439-merge-co-error-msgs.sh new file mode 100755 index 0000000..5c8894d --- /dev/null +++ b/t/t6439-merge-co-error-msgs.sh @@ -0,0 +1,138 @@ +#!/bin/sh + +test_description='unpack-trees error messages' + +. ./test-lib.sh + + +test_expect_success 'setup' ' + echo one >one && + git add one && + git commit -a -m First && + + git checkout -b branch && + echo two >two && + echo three >three && + echo four >four && + echo five >five && + git add two three four five && + git commit -m Second && + + git checkout master && + echo other >two && + echo other >three && + echo other >four && + echo other >five +' + +cat >expect <<\EOF +error: The following untracked working tree files would be overwritten by merge: + five + four + three + two +Please move or remove them before you merge. +Aborting +EOF + +test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' ' + test_must_fail git merge branch 2>out && + test_i18ncmp out expect && + git commit --allow-empty -m empty && + ( + GIT_MERGE_VERBOSITY=0 && + export GIT_MERGE_VERBOSITY && + test_must_fail git merge branch 2>out2 + ) && + test_i18ncmp out2 expect && + git reset --hard HEAD^ +' + +cat >expect <<\EOF +error: Your local changes to the following files would be overwritten by merge: + four + three + two +Please commit your changes or stash them before you merge. +error: The following untracked working tree files would be overwritten by merge: + five +Please move or remove them before you merge. +Aborting +EOF + +test_expect_success 'untracked files or local changes ovewritten by merge' ' + git add two && + git add three && + git add four && + test_must_fail git merge branch 2>out && + test_i18ncmp out expect +' + +cat >expect <<\EOF +error: Your local changes to the following files would be overwritten by checkout: + rep/one + rep/two +Please commit your changes or stash them before you switch branches. +Aborting +EOF + +test_expect_success 'cannot switch branches because of local changes' ' + git add five && + mkdir rep && + echo one >rep/one && + echo two >rep/two && + git add rep/one rep/two && + git commit -m Fourth && + git checkout master && + echo uno >rep/one && + echo dos >rep/two && + test_must_fail git checkout branch 2>out && + test_i18ncmp out expect +' + +cat >expect <<\EOF +error: Your local changes to the following files would be overwritten by checkout: + rep/one + rep/two +Please commit your changes or stash them before you switch branches. +Aborting +EOF + +test_expect_success 'not uptodate file porcelain checkout error' ' + git add rep/one rep/two && + test_must_fail git checkout branch 2>out && + test_i18ncmp out expect +' + +cat >expect <<\EOF +error: Updating the following directories would lose untracked files in them: + rep + rep2 + +Aborting +EOF + +test_expect_success 'not_uptodate_dir porcelain checkout error' ' + git init uptodate && + cd uptodate && + mkdir rep && + mkdir rep2 && + touch rep/foo && + touch rep2/foo && + git add rep/foo rep2/foo && + git commit -m init && + git checkout -b branch && + git rm rep -r && + git rm rep2 -r && + >rep && + >rep2 && + git add rep rep2 && + git commit -m "added test as a file" && + git checkout master && + >rep/untracked-file && + >rep2/untracked-file && + test_must_fail git checkout branch 2>out && + test_i18ncmp out ../expect +' + +test_done diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh deleted file mode 100755 index aa33978..0000000 --- a/t/t7405-submodule-merge.sh +++ /dev/null @@ -1,455 +0,0 @@ -#!/bin/sh - -test_description='merging with submodules' - -. ./test-lib.sh - -# -# history -# -# a --- c -# / \ / -# root X -# \ / \ -# b --- d -# - -test_expect_success setup ' - - mkdir sub && - (cd sub && - git init && - echo original > file && - git add file && - test_tick && - git commit -m sub-root) && - git add sub && - test_tick && - git commit -m root && - - git checkout -b a master && - (cd sub && - echo A > file && - git add file && - test_tick && - git commit -m sub-a) && - git add sub && - test_tick && - git commit -m a && - - git checkout -b b master && - (cd sub && - echo B > file && - git add file && - test_tick && - git commit -m sub-b) && - git add sub && - test_tick && - git commit -m b && - - git checkout -b c a && - git merge -s ours b && - - git checkout -b d b && - git merge -s ours a -' - -# History setup -# -# b -# / \ -# init -- a d -# \ \ / -# g c -# -# a in the main repository records to sub-a in the submodule and -# analogous b and c. d should be automatically found by merging c into -# b in the main repository. -test_expect_success 'setup for merge search' ' - mkdir merge-search && - (cd merge-search && - git init && - mkdir sub && - (cd sub && - git init && - echo "file-a" > file-a && - git add file-a && - git commit -m "sub-a" && - git branch sub-a) && - git commit --allow-empty -m init && - git branch init && - git add sub && - git commit -m "a" && - git branch a && - - git checkout -b b && - (cd sub && - git checkout -b sub-b && - echo "file-b" > file-b && - git add file-b && - git commit -m "sub-b") && - git commit -a -m "b" && - - git checkout -b c a && - (cd sub && - git checkout -b sub-c sub-a && - echo "file-c" > file-c && - git add file-c && - git commit -m "sub-c") && - git commit -a -m "c" && - - git checkout -b d a && - (cd sub && - git checkout -b sub-d sub-b && - git merge sub-c) && - git commit -a -m "d" && - git branch test b && - - git checkout -b g init && - (cd sub && - git checkout -b sub-g sub-c) && - git add sub && - git commit -a -m "g") -' - -test_expect_success 'merge with one side as a fast-forward of the other' ' - (cd merge-search && - git checkout -b test-forward b && - git merge d && - git ls-tree test-forward sub | cut -f1 | cut -f3 -d" " > actual && - (cd sub && - git rev-parse sub-d > ../expect) && - test_cmp expect actual) -' - -test_expect_success 'merging should conflict for non fast-forward' ' - (cd merge-search && - git checkout -b test-nonforward b && - (cd sub && - git rev-parse sub-d > ../expect) && - test_must_fail git merge c 2> actual && - grep $(cat expect) actual > /dev/null && - git reset --hard) -' - -test_expect_success 'merging should fail for ambiguous common parent' ' - (cd merge-search && - git checkout -b test-ambiguous b && - (cd sub && - git checkout -b ambiguous sub-b && - git merge sub-c && - git rev-parse sub-d > ../expect1 && - git rev-parse ambiguous > ../expect2) && - test_must_fail git merge c 2> actual && - grep $(cat expect1) actual > /dev/null && - grep $(cat expect2) actual > /dev/null && - git reset --hard) -' - -# in a situation like this -# -# submodule tree: -# -# sub-a --- sub-b --- sub-d -# -# main tree: -# -# e (sub-a) -# / -# bb (sub-b) -# \ -# f (sub-d) -# -# A merge between e and f should fail because one of the submodule -# commits (sub-a) does not descend from the submodule merge-base (sub-b). -# -test_expect_success 'merging should fail for changes that are backwards' ' - (cd merge-search && - git checkout -b bb a && - (cd sub && - git checkout sub-b) && - git commit -a -m "bb" && - - git checkout -b e bb && - (cd sub && - git checkout sub-a) && - git commit -a -m "e" && - - git checkout -b f bb && - (cd sub && - git checkout sub-d) && - git commit -a -m "f" && - - git checkout -b test-backward e && - test_must_fail git merge f) -' - - -# Check that the conflicting submodule is detected when it is -# in the common ancestor. status should be 'U00...00" -test_expect_success 'git submodule status should display the merge conflict properly with merge base' ' - (cd merge-search && - cat >.gitmodules <expect < actual && - test_cmp expect actual && - git reset --hard) -' - -# Check that the conflicting submodule is detected when it is -# not in the common ancestor. status should be 'U00...00" -test_expect_success 'git submodule status should display the merge conflict properly without merge-base' ' - (cd merge-search && - git checkout -b test-no-merge-base g && - test_must_fail git merge b && - cat >.gitmodules <expect < actual && - test_cmp expect actual && - git reset --hard) -' - - -test_expect_success 'merging with a modify/modify conflict between merge bases' ' - git reset --hard HEAD && - git checkout -b test2 c && - git merge d -' - -# canonical criss-cross history in top and submodule -test_expect_success 'setup for recursive merge with submodule' ' - mkdir merge-recursive && - (cd merge-recursive && - git init && - mkdir sub && - (cd sub && - git init && - test_commit a && - git checkout -b sub-b master && - test_commit b && - git checkout -b sub-c master && - test_commit c && - git checkout -b sub-bc sub-b && - git merge sub-c && - git checkout -b sub-cb sub-c && - git merge sub-b && - git checkout master) && - git add sub && - git commit -m a && - git checkout -b top-b master && - (cd sub && git checkout sub-b) && - git add sub && - git commit -m b && - git checkout -b top-c master && - (cd sub && git checkout sub-c) && - git add sub && - git commit -m c && - git checkout -b top-bc top-b && - git merge -s ours --no-commit top-c && - (cd sub && git checkout sub-bc) && - git add sub && - git commit -m bc && - git checkout -b top-cb top-c && - git merge -s ours --no-commit top-b && - (cd sub && git checkout sub-cb) && - git add sub && - git commit -m cb) -' - -# merge should leave submodule unmerged in index -test_expect_success 'recursive merge with submodule' ' - (cd merge-recursive && - test_must_fail git merge top-bc && - echo "160000 $(git rev-parse top-cb:sub) 2 sub" > expect2 && - echo "160000 $(git rev-parse top-bc:sub) 3 sub" > expect3 && - git ls-files -u > actual && - grep "$(cat expect2)" actual > /dev/null && - grep "$(cat expect3)" actual > /dev/null) -' - -# File/submodule conflict -# Commit O: -# Commit A: path (submodule) -# Commit B: path -# Expected: path/ is submodule and file contents for B's path are somewhere - -test_expect_success 'setup file/submodule conflict' ' - test_create_repo file-submodule && - ( - cd file-submodule && - - git commit --allow-empty -m O && - - git branch A && - git branch B && - - git checkout B && - echo content >path && - git add path && - git commit -m B && - - git checkout A && - test_create_repo path && - test_commit -C path world && - git submodule add ./path && - git commit -m A - ) -' - -test_expect_failure 'file/submodule conflict' ' - test_when_finished "git -C file-submodule reset --hard" && - ( - cd file-submodule && - - git checkout A^0 && - test_must_fail git merge B^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 2 out && - - # path/ is still a submodule - test_path_is_dir path/.git && - - # There is a submodule at "path", so B:path cannot be written - # there. We expect it to be written somewhere in the same - # directory, though, so just grep for its content in all - # files, and ignore "grep: path: Is a directory" message - echo Checking if contents from B:path showed up anywhere && - grep -q content * 2>/dev/null - ) -' - -test_expect_success 'file/submodule conflict; merge --abort works afterward' ' - test_when_finished "git -C file-submodule reset --hard" && - ( - cd file-submodule && - - git checkout A^0 && - test_must_fail git merge B^0 >out 2>err && - - test_path_is_file .git/MERGE_HEAD && - git merge --abort - ) -' - -# Directory/submodule conflict -# Commit O: -# Commit A: path (submodule), with sole tracked file named 'world' -# Commit B1: path/file -# Commit B2: path/world -# -# Expected from merge of A & B1: -# Contents under path/ from commit B1 are renamed elsewhere; we do not -# want to write files from one of our tracked directories into a submodule -# -# Expected from merge of A & B2: -# Similar to last merge, but with a slight twist: we don't want paths -# under the submodule to be treated as untracked or in the way. - -test_expect_success 'setup directory/submodule conflict' ' - test_create_repo directory-submodule && - ( - cd directory-submodule && - - git commit --allow-empty -m O && - - git branch A && - git branch B1 && - git branch B2 && - - git checkout B1 && - mkdir path && - echo contents >path/file && - git add path/file && - git commit -m B1 && - - git checkout B2 && - mkdir path && - echo contents >path/world && - git add path/world && - git commit -m B2 && - - git checkout A && - test_create_repo path && - test_commit -C path hello world && - git submodule add ./path && - git commit -m A - ) -' - -test_expect_failure 'directory/submodule conflict; keep submodule clean' ' - test_when_finished "git -C directory-submodule reset --hard" && - ( - cd directory-submodule && - - git checkout A^0 && - test_must_fail git merge B1^0 && - - git ls-files -s >out && - test_line_count = 3 out && - git ls-files -u >out && - test_line_count = 1 out && - - # path/ is still a submodule - test_path_is_dir path/.git && - - echo Checking if contents from B1:path/file showed up && - # Would rather use grep -r, but that is GNU extension... - git ls-files -co | xargs grep -q contents 2>/dev/null && - - # However, B1:path/file should NOT have shown up at path/file, - # because we should not write into the submodule - test_path_is_missing path/file - ) -' - -test_expect_failure !FAIL_PREREQS 'directory/submodule conflict; should not treat submodule files as untracked or in the way' ' - test_when_finished "git -C directory-submodule/path reset --hard" && - test_when_finished "git -C directory-submodule reset --hard" && - ( - cd directory-submodule && - - git checkout A^0 && - test_must_fail git merge B2^0 >out 2>err && - - # We do not want files within the submodule to prevent the - # merge from starting; we should not be writing to such paths - # anyway. - test_i18ngrep ! "refusing to lose untracked file at" err - ) -' - -test_expect_failure 'directory/submodule conflict; merge --abort works afterward' ' - test_when_finished "git -C directory-submodule/path reset --hard" && - test_when_finished "git -C directory-submodule reset --hard" && - ( - cd directory-submodule && - - git checkout A^0 && - test_must_fail git merge B2^0 && - test_path_is_file .git/MERGE_HEAD && - - # merge --abort should succeed, should clear .git/MERGE_HEAD, - # and should not leave behind any conflicted files - git merge --abort && - test_path_is_missing .git/MERGE_HEAD && - git ls-files -u >conflicts && - test_must_be_empty conflicts - ) -' - -test_done diff --git a/t/t7607-merge-overwrite.sh b/t/t7607-merge-overwrite.sh deleted file mode 100755 index dd8ab7e..0000000 --- a/t/t7607-merge-overwrite.sh +++ /dev/null @@ -1,195 +0,0 @@ -#!/bin/sh - -test_description='git-merge - -Do not overwrite changes.' - -. ./test-lib.sh - -test_expect_success 'setup' ' - test_commit c0 c0.c && - test_commit c1 c1.c && - test_commit c1a c1.c "c1 a" && - git reset --hard c0 && - test_commit c2 c2.c && - git reset --hard c0 && - mkdir sub && - echo "sub/f" > sub/f && - mkdir sub2 && - echo "sub2/f" > sub2/f && - git add sub/f sub2/f && - git commit -m sub && - git tag sub && - echo "VERY IMPORTANT CHANGES" > important -' - -test_expect_success 'will not overwrite untracked file' ' - git reset --hard c1 && - cp important c2.c && - test_must_fail git merge c2 && - test_path_is_missing .git/MERGE_HEAD && - test_cmp important c2.c -' - -test_expect_success 'will overwrite tracked file' ' - git reset --hard c1 && - cp important c2.c && - git add c2.c && - git commit -m important && - git checkout c2 -' - -test_expect_success 'will not overwrite new file' ' - git reset --hard c1 && - cp important c2.c && - git add c2.c && - test_must_fail git merge c2 && - test_path_is_missing .git/MERGE_HEAD && - test_cmp important c2.c -' - -test_expect_success 'will not overwrite staged changes' ' - git reset --hard c1 && - cp important c2.c && - git add c2.c && - rm c2.c && - test_must_fail git merge c2 && - test_path_is_missing .git/MERGE_HEAD && - git checkout c2.c && - test_cmp important c2.c -' - -test_expect_success 'will not overwrite removed file' ' - git reset --hard c1 && - git rm c1.c && - git commit -m "rm c1.c" && - cp important c1.c && - test_must_fail git merge c1a && - test_cmp important c1.c -' - -test_expect_success 'will not overwrite re-added file' ' - git reset --hard c1 && - git rm c1.c && - git commit -m "rm c1.c" && - cp important c1.c && - git add c1.c && - test_must_fail git merge c1a && - test_path_is_missing .git/MERGE_HEAD && - test_cmp important c1.c -' - -test_expect_success 'will not overwrite removed file with staged changes' ' - git reset --hard c1 && - git rm c1.c && - git commit -m "rm c1.c" && - cp important c1.c && - git add c1.c && - rm c1.c && - test_must_fail git merge c1a && - test_path_is_missing .git/MERGE_HEAD && - git checkout c1.c && - test_cmp important c1.c -' - -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 && - cp important other.c && - test_must_fail git merge c1a >out && - test_i18ngrep "Refusing to lose dirty file at other.c" out && - test_path_is_file other.c~HEAD && - test $(git hash-object other.c~HEAD) = $(git rev-parse c1a:c1.c) && - test_cmp important other.c -' - -test_expect_success 'will not overwrite untracked subtree' ' - git reset --hard c0 && - rm -rf sub && - mkdir -p sub/f && - cp important sub/f/important && - test_must_fail git merge sub && - test_path_is_missing .git/MERGE_HEAD && - test_cmp important sub/f/important -' - -cat >expect <<\EOF -error: The following untracked working tree files would be overwritten by merge: - sub - sub2 -Please move or remove them before you merge. -Aborting -EOF - -test_expect_success 'will not overwrite untracked file in leading path' ' - git reset --hard c0 && - rm -rf sub && - cp important sub && - cp important sub2 && - test_must_fail git merge sub 2>out && - test_i18ncmp out expect && - test_path_is_missing .git/MERGE_HEAD && - test_cmp important sub && - test_cmp important sub2 && - rm -f sub sub2 -' - -test_expect_success SYMLINKS 'will not overwrite untracked symlink in leading path' ' - git reset --hard c0 && - rm -rf sub && - mkdir sub2 && - ln -s sub2 sub && - test_must_fail git merge sub && - test_path_is_missing .git/MERGE_HEAD -' - -test_expect_success 'will not be confused by symlink in leading path' ' - git reset --hard c0 && - rm -rf sub && - test_ln_s_add sub2 sub && - git commit -m ln && - git checkout sub -' - -cat >expect <<\EOF -error: Untracked working tree file 'c0.c' would be overwritten by merge. -fatal: read-tree failed -EOF - -test_expect_success 'will not overwrite untracked file on unborn branch' ' - git reset --hard c0 && - git rm -fr . && - git checkout --orphan new && - cp important c0.c && - test_must_fail git merge c0 2>out && - test_i18ncmp out expect -' - -test_expect_success 'will not overwrite untracked file on unborn branch .git/MERGE_HEAD sanity etc.' ' - test_when_finished "rm c0.c" && - test_path_is_missing .git/MERGE_HEAD && - test_cmp important c0.c -' - -test_expect_success 'failed merge leaves unborn branch in the womb' ' - test_must_fail git rev-parse --verify HEAD -' - -test_expect_success 'set up unborn branch and content' ' - git symbolic-ref HEAD refs/heads/unborn && - rm -f .git/index && - echo foo > tracked-file && - git add tracked-file && - echo bar > untracked-file -' - -test_expect_success 'will not clobber WT/index when merging into unborn' ' - git merge master && - grep foo tracked-file && - git show :tracked-file >expect && - grep foo expect && - grep bar untracked-file -' - -test_done diff --git a/t/t7609-merge-co-error-msgs.sh b/t/t7609-merge-co-error-msgs.sh deleted file mode 100755 index 5c8894d..0000000 --- a/t/t7609-merge-co-error-msgs.sh +++ /dev/null @@ -1,138 +0,0 @@ -#!/bin/sh - -test_description='unpack-trees error messages' - -. ./test-lib.sh - - -test_expect_success 'setup' ' - echo one >one && - git add one && - git commit -a -m First && - - git checkout -b branch && - echo two >two && - echo three >three && - echo four >four && - echo five >five && - git add two three four five && - git commit -m Second && - - git checkout master && - echo other >two && - echo other >three && - echo other >four && - echo other >five -' - -cat >expect <<\EOF -error: The following untracked working tree files would be overwritten by merge: - five - four - three - two -Please move or remove them before you merge. -Aborting -EOF - -test_expect_success 'untracked files overwritten by merge (fast and non-fast forward)' ' - test_must_fail git merge branch 2>out && - test_i18ncmp out expect && - git commit --allow-empty -m empty && - ( - GIT_MERGE_VERBOSITY=0 && - export GIT_MERGE_VERBOSITY && - test_must_fail git merge branch 2>out2 - ) && - test_i18ncmp out2 expect && - git reset --hard HEAD^ -' - -cat >expect <<\EOF -error: Your local changes to the following files would be overwritten by merge: - four - three - two -Please commit your changes or stash them before you merge. -error: The following untracked working tree files would be overwritten by merge: - five -Please move or remove them before you merge. -Aborting -EOF - -test_expect_success 'untracked files or local changes ovewritten by merge' ' - git add two && - git add three && - git add four && - test_must_fail git merge branch 2>out && - test_i18ncmp out expect -' - -cat >expect <<\EOF -error: Your local changes to the following files would be overwritten by checkout: - rep/one - rep/two -Please commit your changes or stash them before you switch branches. -Aborting -EOF - -test_expect_success 'cannot switch branches because of local changes' ' - git add five && - mkdir rep && - echo one >rep/one && - echo two >rep/two && - git add rep/one rep/two && - git commit -m Fourth && - git checkout master && - echo uno >rep/one && - echo dos >rep/two && - test_must_fail git checkout branch 2>out && - test_i18ncmp out expect -' - -cat >expect <<\EOF -error: Your local changes to the following files would be overwritten by checkout: - rep/one - rep/two -Please commit your changes or stash them before you switch branches. -Aborting -EOF - -test_expect_success 'not uptodate file porcelain checkout error' ' - git add rep/one rep/two && - test_must_fail git checkout branch 2>out && - test_i18ncmp out expect -' - -cat >expect <<\EOF -error: Updating the following directories would lose untracked files in them: - rep - rep2 - -Aborting -EOF - -test_expect_success 'not_uptodate_dir porcelain checkout error' ' - git init uptodate && - cd uptodate && - mkdir rep && - mkdir rep2 && - touch rep/foo && - touch rep2/foo && - git add rep/foo rep2/foo && - git commit -m init && - git checkout -b branch && - git rm rep -r && - git rm rep2 -r && - >rep && - >rep2 && - git add rep rep2 && - git commit -m "added test as a file" && - git checkout master && - >rep/untracked-file && - >rep2/untracked-file && - test_must_fail git checkout branch 2>out && - test_i18ncmp out ../expect -' - -test_done diff --git a/t/t7613-merge-submodule.sh b/t/t7613-merge-submodule.sh deleted file mode 100755 index 04bf4be..0000000 --- a/t/t7613-merge-submodule.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -test_description='merge can handle submodules' - -. ./test-lib.sh -. "$TEST_DIRECTORY"/lib-submodule-update.sh - -# merges without conflicts -test_submodule_switch "merge" - -test_submodule_switch "merge --ff" - -test_submodule_switch "merge --ff-only" - -KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1 -KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1 -test_submodule_switch "merge --no-ff" - -test_done -- cgit v0.10.2-6-g49f6