From bb05c9f7595274bcfabf775ea51b4765c503b055 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 7 Nov 2018 20:40:22 -0800 Subject: t6042: add tests for consistency in file collision conflict handling Add testcases dealing with file collisions for the following types of conflicts: * add/add * rename/add * rename/rename(2to1) All these conflict types simplify down to two files "colliding" and should thus be handled similarly. This means that rename/add and rename/rename(2to1) conflicts need to be modified to behave the same as add/add conflicts currently do: the colliding files should be two-way merged (instead of the current behavior of writing the two colliding files out to separate temporary unique pathnames). Add testcases which check this; subsequent commits will fix the conflict handling to make these tests pass. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- t/t6042-merge-rename-corner-cases.sh | 162 +++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) (limited to 't') diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index b97aca7fa2..b6fed2cb9a 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -937,4 +937,166 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename ) ' +test_conflicts_with_adds_and_renames() { + sideL=$1 + sideR=$2 + expect=$3 + + # 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_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_$expect "check simple $sideL/$sideR 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 failure +test_conflicts_with_adds_and_renames rename add failure +test_conflicts_with_adds_and_renames add rename failure +test_conflicts_with_adds_and_renames add add success + test_done -- cgit v1.2.3 From b28eeb3092fd12e24485fbadebfa57df457dd09a Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 7 Nov 2018 20:40:23 -0800 Subject: t6036, t6042: testcases for rename collision of already conflicting files When a single file is renamed, it can also be modified, yielding the possibility of that renamed file having content conflicts. If two different such files are renamed into the same location, then two-way merging those files may result in nested conflicts. Add a testcase that makes sure we get this case correct, and uses different lengths of conflict markers to differentiate between the different nestings. Also add another case with an extra (i.e. third) level of conflict markers due to using merge.conflictstyle=diff3 and the virtual merge base also having conflicts present. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- t/t6036-recursive-corner-cases.sh | 194 +++++++++++++++++++++++++++++++++++ t/t6042-merge-rename-corner-cases.sh | 118 +++++++++++++++++++++ 2 files changed, 312 insertions(+) (limited to 't') diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index e1cef58f2a..f229d7e47b 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -1402,4 +1402,198 @@ test_expect_failure 'check conflicting modes for regular file' ' ) ' +# 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 "verson 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_failure 'check nested conflicts' ' + ( + cd nested_conflicts && + + git clean -f && + 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 "merged common ancestors" \ + -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 "merged common ancestors" \ + -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 -a 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 + ) +' + test_done diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index b6fed2cb9a..23c3b6dffa 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -1099,4 +1099,122 @@ test_conflicts_with_adds_and_renames rename add failure test_conflicts_with_adds_and_renames add rename failure test_conflicts_with_adds_and_renames add add success +# 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_expect_success 'setup nested conflicts from rename/rename(2to1)' ' + 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 mv one three && + 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 mv two three && + mv -f file_v3 one && + mv -f file_v6 three && + git add one three && + test_tick && git commit -m german + ) +' + +test_expect_failure 'check nested conflicts from rename/rename(2to1)' ' + ( + 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 + ) +' + test_done -- cgit v1.2.3 From b2a7942b8bcf397d9a5d3bb6671853755e14de60 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 7 Nov 2018 20:40:24 -0800 Subject: merge-recursive: increase marker length with depth of recursion Later patches in this series will modify file collision conflict handling (e.g. from rename/add and rename/rename(2to1) conflicts) so that multiply nested conflict markers can arise even before considering conflicts in the virtual merge base. Including the virtual merge base will provide a way to get triply (or higher) nested conflict markers. This new way to get nested conflict markers will force the need for a more general mechanism to extend the length of conflict markers in order to differentiate between different nestings. Along with this change to conflict marker length handling, we want to make sure that we don't regress handling for other types of conflicts with nested conflict markers. Add a more involved testcase using merge.conflictstyle=diff3, where not only does the virtual merge base contain conflicts, but its virtual merge base does as well (i.e. a case with triply nested conflict markers). While there are multiple reasonable ways to handle nested conflict markers in the virtual merge base for this type of situation, the easiest approach that dovetails well with the new needs for the file collision conflict handling is to require that the length of the conflict markers increase with each subsequent nesting. Subsequent patches which change the rename/add and rename/rename(2to1) conflict handling will modify the extra_marker_size flag appropriately for their new needs. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- t/t6036-recursive-corner-cases.sh | 151 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) (limited to 't') diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index f229d7e47b..3091cbc06a 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -1596,4 +1596,155 @@ test_expect_failure 'check nested conflicts' ' ) ' +# 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 "verson 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 && + + 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 "merged common ancestors" \ + -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 -- cgit v1.2.3 From 7f8671656f31f5d1cc9d9748b7dc62b76a428cc5 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 7 Nov 2018 20:40:26 -0800 Subject: merge-recursive: fix rename/add conflict handling This makes the rename/add conflict handling make use of the new handle_file_collision() function, which fixes several bugs and improves things for the rename/add case significantly. Previously, rename/add would: * Not leave any higher order stage entries in the index, making it appear as if there were no conflict. * Would place the rename file at the colliding path, and move the added file elsewhere, which combined with the lack of higher order stage entries felt really odd. It's not clear to me why the rename should take precedence over the add; if one should be moved out of the way, they both probably should. * In the recursive case, it would do a two way merge of the added file and the version of the renamed file on the renamed side, completely excluding modifications to the renamed file on the unrenamed side of history. Use the new handle_file_collision() to fix all of these issues. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- t/t6036-recursive-corner-cases.sh | 24 +++++++++++++----------- t/t6042-merge-rename-corner-cases.sh | 4 ++-- 2 files changed, 15 insertions(+), 13 deletions(-) (limited to 't') diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 3091cbc06a..d6b7e568f5 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -185,7 +185,7 @@ test_expect_success 'setup differently handled merges of rename/add conflict' ' git branch B && git checkout -b C && echo 10 >>a && - echo "other content" >>new_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 && @@ -195,14 +195,14 @@ test_expect_success 'setup differently handled merges of rename/add conflict' ' git checkout B^0 && test_must_fail git merge C && - git clean -f && + 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 && - rm new_a~HEAD new_a && - printf "Incorrectly merged content" >>new_a && + 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 @@ -225,21 +225,23 @@ test_expect_success 'git detects differently handled merges conflict' ' test_line_count = 1 out && git rev-parse >expect \ - D:new_a E:new_a && + C:new_a D:new_a E:new_a && git rev-parse >actual \ - :2:new_a :3:new_a && + :1:new_a :2:new_a :3:new_a && test_cmp expect actual && - git cat-file -p C:new_a >ours && - git cat-file -p B:new_a >theirs && + # 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 "Temporary merge branch 1" \ + -L "HEAD" \ -L "" \ - -L "Temporary merge branch 2" \ + -L "E^0" \ ours empty theirs && sed -e "s/^\([<=>]\)/\1\1\1/" ours >expect && - git cat-file -p :1:new_a >actual && + git hash-object new_a >actual && + git hash-object ours >expect && test_cmp expect actual ) ' diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index 23c3b6dffa..3e934ae89e 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -1095,8 +1095,8 @@ test_conflicts_with_adds_and_renames() { } test_conflicts_with_adds_and_renames rename rename failure -test_conflicts_with_adds_and_renames rename add failure -test_conflicts_with_adds_and_renames add rename failure +test_conflicts_with_adds_and_renames rename add success +test_conflicts_with_adds_and_renames add rename success test_conflicts_with_adds_and_renames add add success # Setup: -- cgit v1.2.3 From bbafc9c44ae7109f636772350934b3c5d747da44 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 7 Nov 2018 20:40:27 -0800 Subject: merge-recursive: improve handling for rename/rename(2to1) conflicts This makes the rename/rename(2to1) conflicts use the new handle_file_collision() function. Since that function was based originally on the rename/rename(2to1) handling code, the main differences here are in what was added. In particular: * Instead of storing files at collide_path~HEAD and collide_path~MERGE, the files are two-way merged and recorded at collide_path. * Instead of recording the version of the renamed file that existed on the renamed side in the index (thus ignoring any changes that were made to the file on the side of history without the rename), we do a three-way content merge on the renamed path, then store that at either stage 2 or stage 3. * Note that since the content merge for each rename may have conflicts, and then we have to merge the two renamed files, we can end up with nested conflict markers. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- t/t6036-recursive-corner-cases.sh | 12 ++---- t/t6042-merge-rename-corner-cases.sh | 38 +++++++++++------ t/t6043-merge-rename-directories.sh | 83 +++++++++++++++++++++--------------- 3 files changed, 75 insertions(+), 58 deletions(-) (limited to 't') diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index d6b7e568f5..99cddb05af 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -64,15 +64,12 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' ' git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 out && git rev-parse >expect \ - L2:three R2:three \ L2:three R2:three && git rev-parse >actual \ :2:three :3:three && - git hash-object >>actual \ - three~HEAD three~R2^0 && test_cmp expect actual ) ' @@ -140,15 +137,12 @@ test_expect_success 'merge criss-cross + rename merges with basic modification' git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 out && git rev-parse >expect \ - L2:three R2:three \ L2:three R2:three && git rev-parse >actual \ :2:three :3:three && - git hash-object >>actual \ - three~HEAD three~R2^0 && test_cmp expect actual ) ' @@ -1512,7 +1506,7 @@ test_expect_success 'setup nested conflicts' ' ) ' -test_expect_failure 'check nested conflicts' ' +test_expect_success 'check nested conflicts' ' ( cd nested_conflicts && diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index 3e934ae89e..b93139f628 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -464,17 +464,28 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' ' git ls-files -u c >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 out && test_path_is_missing a && test_path_is_missing b && - test_path_is_file c~HEAD && - test_path_is_file c~C^0 && - git rev-parse >expect \ - C:a B:b && - git hash-object >actual \ - c~HEAD c~C^0 && + 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 ) ' @@ -940,7 +951,6 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename test_conflicts_with_adds_and_renames() { sideL=$1 sideR=$2 - expect=$3 # Setup: # L @@ -1048,7 +1058,7 @@ test_conflicts_with_adds_and_renames() { ) ' - test_expect_$expect "check simple $sideL/$sideR conflict" ' + test_expect_success "check simple $sideL/$sideR conflict" ' ( cd simple_${sideL}_${sideR} && @@ -1094,10 +1104,10 @@ test_conflicts_with_adds_and_renames() { ' } -test_conflicts_with_adds_and_renames rename rename failure -test_conflicts_with_adds_and_renames rename add success -test_conflicts_with_adds_and_renames add rename success -test_conflicts_with_adds_and_renames add add success +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 @@ -1168,7 +1178,7 @@ test_expect_success 'setup nested conflicts from rename/rename(2to1)' ' ) ' -test_expect_failure 'check nested conflicts from rename/rename(2to1)' ' +test_expect_success 'check nested conflicts from rename/rename(2to1)' ' ( cd nested_conflicts_from_rename_rename && diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh index 4a71f17edd..fedaeafc55 100755 --- a/t/t6043-merge-rename-directories.sh +++ b/t/t6043-merge-rename-directories.sh @@ -278,7 +278,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 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 && @@ -293,15 +293,16 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con A:y/wham B:z/wham && test_cmp expect actual && - test_path_is_missing x/wham && - test_path_is_file x/wham~HEAD && - test_path_is_file x/wham~B^0 && - - git hash-object >actual \ - x/wham~HEAD x/wham~B^0 && - git rev-parse >expect \ - A:y/wham B:z/wham && - test_cmp expect actual + # 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 ) ' @@ -1670,7 +1671,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 out && git rev-parse >actual \ :0:y/b :0:y/c :2:y/d :3:y/d && @@ -1678,15 +1679,16 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r O:z/b O:z/c O:w/d O:x/d && test_cmp expect actual && - test_path_is_missing y/d && - test_path_is_file y/d~HEAD && - test_path_is_file y/d~B^0 && - - git hash-object >actual \ - y/d~HEAD y/d~B^0 && - git rev-parse >expect \ - O:w/d O:x/d && - test_cmp expect actual + # 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 ) ' @@ -3165,7 +3167,7 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2) # Commit O: z/{a,b,c_1}, x/{d,e,f_2} # Commit A: y/{a,b}, x/{d,e,f_2,wham_1} + untracked y/wham # Commit B: z/{a,b,c_1,wham_2}, y/{d,e} -# Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+ +# 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) @@ -3219,7 +3221,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' ' git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 4 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 && @@ -3232,11 +3234,16 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' ' echo important >expect && test_cmp expect y/wham && - git hash-object >actual \ - y/wham~B^0 y/wham~HEAD && - git rev-parse >expect \ - O:x/f O:z/c && - test_cmp expect actual + # 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 ) ' @@ -3689,7 +3696,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena # Commit O: z/{a,b}, x/{c_1,d_2} # Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods # Commit B: z/{a,b,wham_2}, x/c_1 -# Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} + +# 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) @@ -3741,24 +3748,30 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena git ls-files -u >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 4 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 hash-object >actual \ - y/wham~B^0 y/wham~HEAD && - git rev-parse >expect \ - O:x/d O:x/c && - test_cmp expect actual && git rev-parse >actual \ :0:y/a :0:y/b :2:y/wham :3:y/wham && git rev-parse >expect \ O:z/a O:z/b O:x/c O:x/d && - test_cmp expect actual + 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 ) ' -- cgit v1.2.3 From 48c9cb9d6d058bcf18e931a1ed0d88792bb506c9 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 7 Nov 2018 20:40:29 -0800 Subject: merge-recursive: improve rename/rename(1to2)/add[/add] handling When we have a rename/rename(1to2) conflict, each of the renames can collide with a file addition. Each of these rename/add conflicts suffered from the same kinds of problems that normal rename/add suffered from. Make the code use handle_file_conflicts() as well so that we get all the same fixes and consistent behavior between the different conflict types. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- t/t6042-merge-rename-corner-cases.sh | 29 +++++++++++++++++++++-------- t/t6043-merge-rename-directories.sh | 24 +++++++++++++++--------- 2 files changed, 36 insertions(+), 17 deletions(-) (limited to 't') diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index b93139f628..7cc34e7579 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -684,7 +684,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting git ls-files -u c >out && test_line_count = 2 out && git ls-files -o >out && - test_line_count = 5 out && + test_line_count = 1 out && git rev-parse >expect \ A:a C:b B:b C:c B:c && @@ -692,14 +692,27 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting :1:a :2:b :3:b :2:c :3:c && test_cmp expect actual && - git rev-parse >expect \ - C:c B:c C:b B:b && - git hash-object >actual \ - c~HEAD c~B\^0 b~HEAD b~B\^0 && - test_cmp expect actual && + # 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_path_is_missing b && - test_path_is_missing c + # 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 ) ' diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh index fedaeafc55..5c01a0c14a 100755 --- a/t/t6043-merge-rename-directories.sh +++ b/t/t6043-merge-rename-directories.sh @@ -1078,7 +1078,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam git ls-files -u >out && test_line_count = 6 out && git ls-files -o >out && - test_line_count = 3 out && + test_line_count = 1 out && git rev-parse >actual \ :0:y/b :0:y/c :0:y/e && @@ -1094,9 +1094,9 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam test_cmp expect actual && git hash-object >actual \ - w/d~HEAD w/d~B^0 z/d && + z/d && git rev-parse >expect \ - O:x/d B:w/d O:x/d && + O:x/d && test_cmp expect actual && test_path_is_missing x/d && test_path_is_file y/d && @@ -3672,7 +3672,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena git ls-files -u >out && test_line_count = 4 out && git ls-files -o >out && - test_line_count = 4 out && + test_line_count = 3 out && echo different >expected && echo mods >>expected && @@ -3684,11 +3684,17 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena O:z/a O:z/b O:x/d O:x/c O:x/c A:y/c O:x/c && test_cmp expect actual && - git hash-object >actual \ - y/c~B^0 y/c~HEAD && - git rev-parse >expect \ - O:x/c A:y/c && - test_cmp expect actual + # 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 ) ' -- cgit v1.2.3 From b8cd1bb71306e489d73e5cf4ef75a6cdb4c223b7 Mon Sep 17 00:00:00 2001 From: Elijah Newren Date: Wed, 7 Nov 2018 20:40:30 -0800 Subject: t6036, t6043: increase code coverage for file collision handling Stolee's coverage reports found a few code blocks for file collision conflicts that had not previously been covered by testcases; add a few more testcases to cover those too. Signed-off-by: Elijah Newren Signed-off-by: Junio C Hamano --- t/t6036-recursive-corner-cases.sh | 51 +++++++++++++++++++++++++++++++++++++ t/t6043-merge-rename-directories.sh | 37 +++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) (limited to 't') diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 99cddb05af..b7488b00c0 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -240,6 +240,57 @@ test_expect_success 'git detects differently handled merges conflict' ' ) ' +# 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 differences; rest is copy-paste of last test + + git checkout D^0 && + test_must_fail git merge -s recursive E^0 && + + git ls-files -s >out && + test_line_count = 3 out && + git ls-files -u >out && + test_line_count = 3 out && + git ls-files -o >out && + test_line_count = 1 out && + + git rev-parse >expect \ + C:new_a 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: # diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh index 5c01a0c14a..62c564707b 100755 --- a/t/t6043-merge-rename-directories.sh +++ b/t/t6043-merge-rename-directories.sh @@ -3163,6 +3163,43 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2) ) ' +test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2), other direction' ' + ( + cd 10c && + + git reset --hard && + git clean -fdqx && + + git checkout B^0 && + mkdir y && + echo important >y/c && + + test_must_fail git 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 -- cgit v1.2.3 From cc4cb0902c2e4f9bc003b52f33da8fcb6020f649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlo=20Marcelo=20Arenas=20Bel=C3=B3n?= Date: Fri, 30 Nov 2018 18:52:12 -0800 Subject: t6036: avoid non-portable "cp -a" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit b8cd1bb713 ("t6036, t6043: increase code coverage for file collision handling", 2018-11-07) uses this GNU extension that is not available in a POSIX complaint cp. In this particular case, there is no need to use the option, as it is just copying a single file to create another file. Signed-off-by: Carlo Marcelo Arenas Belón Signed-off-by: Junio C Hamano --- t/t6036-recursive-corner-cases.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 't') diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index b7488b00c0..d23b948f27 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -1631,7 +1631,7 @@ test_expect_success 'check nested conflicts' ' # Compare m to expected contents >empty && - cp -a m_stage_2 expected_final_m && + cp m_stage_2 expected_final_m && test_must_fail git merge-file --diff3 \ -L "HEAD" \ -L "merged common ancestors" \ -- cgit v1.2.3