In merging, every rename acts as a two-piece lego in terms of conflicts.
We have long realized that the other branch can (1) delete the source
side of a rename, giving a rename/delete conflict, or (2) add a file in
the way of the rename destination, giving a rename/add conflict, or (3)
have a rename of its own touching either the same destination or source
path, giving either a rename/rename(2to1) or rename/rename(1to2)
conflict. But only in one case did we ever consider chaining these
merge-conflict lego pieces (namely with rename/rename(1to2)/add/add
conflicts). Add some testcases that show other ways these conflicts can
be chained.
In short, any rename's source side can attach to a delete or another
rename, and any rename's destination side can attach to an add or
another rename.
This series was spurred by Robert Dailey's report back in March of a
rename case that git currently handles poorly:
https://public-inbox.org/git/CAHd499Axo7HFviUJavigTZ6BGZCkj9iOSeNVndu1oPivkPv+5Q@mail.gmail.com/
This series adds a testcase covering the issue he reported, and then
rounds things out additional testcases demonstrating other ways rename
conflicts could be "chained" together.
Elijah Newren (3):
t6042: add testcase covering rename/add/delete conflict type
t6042: add testcase covering rename/rename(2to1)/delete/delete
conflict
t6042: add testcase covering long chains of rename conflicts
t/t6042-merge-rename-corner-cases.sh | 245 +++++++++++++++++++++++++++
1 file changed, 245 insertions(+)
--
2.18.0.130.gd703bbb5d
If either side of a rename/rename(2to1) conflict is itself also involved
in a rename/delete conflict, then the conflict is a little more complex;
we can even have what I'd call a rename/rename(2to1)/delete/delete
conflict. (In some ways, this is similar to a rename/rename(1to2)/add/add
conflict, as added in commit 3672c9714830 ("merge-recursive: Fix working
copy handling for rename/rename/add/add", 2011-08-11)). Add a testcase
for such a conflict.
Signed-off-by: Elijah Newren <redacted>
---
t/t6042-merge-rename-corner-cases.sh | 68 ++++++++++++++++++++++++++++
1 file changed, 68 insertions(+)
@@ -759,4 +759,72 @@ test_expect_failure 'rad-check: rename/add/delete conflict' ')'+# Testcase rrdd, rename/rename(2to1)/delete/delete+# Commit O: foo, bar+# Commit A: rename foo->baz, rm bar+# Commit B: rename bar->baz, rm foo+# Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz++test_expect_success'rrdd-setup: rename/rename(2to1)/delete/delete conflict''+test_create_reporrdd&&+(+cdrrdd&&+echofoo>foo&&+echobar>bar&&+gitaddfoobar&&+gitcommit-mO&&++gitbranchO&&+gitbranchA&&+gitbranchB&&++gitcheckoutA&&+gitmvfoobaz&&+gitrmbar&&+gitcommit-m"Rename foo, remove bar"&&++gitcheckoutB&&+gitmvbarbaz&&+gitrmfoo&&+gitcommit-m"Rename bar, remove foo"+)+'++test_expect_failure'rrdd-check: rename/rename(2to1)/delete/delete conflict''+(+cdrrdd&&++gitcheckoutA^0&&+test_must_failgitmerge-srecursiveB^0>out2>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_emptyerr&&++gitls-files-s>file_count&&+test_line_count=2file_count&&+gitls-files-u>file_count&&+test_line_count=2file_count&&+gitls-files-o>file_count&&+test_line_count=2file_count&&++gitrev-parse>actual\+:2:baz:3:baz&&+gitrev-parse>expect\+O:fooO:bar&&++test_cmpfile_is_missingfoo&&+test_cmpfile_is_missingbar&&+# baz should have two-way merged contents of the original+# contents of foo and bar; check that content from both sides+# is present.+grepfoobaz&&+grepbarbaz+)+'+ test_done
Each rename is a lego: the source side could be connected to a delete or
another rename, and the destination side could be connected to a rename or a
conflicting add. Previous tests combined these to get e.g.
rename/rename(1to2)/add/add, rename/rename(2to1)/delete/delete, and
rename/add/delete. But we can also build bigger chains of conflicts. Add a
testcase demonstrating this.
Signed-off-by: Elijah Newren <redacted>
---
t/t6042-merge-rename-corner-cases.sh | 111 +++++++++++++++++++++++++++
1 file changed, 111 insertions(+)
@@ -827,4 +827,115 @@ test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' ')'+# Testcase mod6, chains of rename/rename(1to2) and rename/rename(2to1)+# Commit O: one, three, five+# Commit A: one->two, three->four, five->six+# Commit B: one->six, three->two, five->four+# Expected: six CONFLICT(rename/rename) messages, each path in two of the+# multi-way merged contents found in two, four, six++test_expect_success'mod6-setup: chains of rename/rename(1to2) and rename/rename(2to1)''+test_create_repomod6&&+(+cdmod6&&+test_seq1119>one&&+test_seq3139>three&&+test_seq5159>five&&+gitadd.&&+test_tick&&+gitcommit-m"O"&&++gitbranchO&&+gitbranchA&&+gitbranchB&&++gitcheckoutA&&+test_seq1019>one&&+echo40>>three&&+gitaddonethree&&+gitmvonetwo&&+gitmvthreefour&&+gitmvfivesix&&+test_tick&&+gitcommit-m"A"&&++gitcheckoutB&&+echo20>>one&&+echoforty>>three&&+echo60>>five&&+gitaddonethreefive&&+gitmvonesix&&+gitmvthreetwo&&+gitmvfivefour&&+test_tick&&+gitcommit-m"B"+)+'++test_expect_failure'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)''+(+cdmod6&&++gitcheckoutA^0&&++test_must_failgitmerge-srecursiveB^0>out2>err&&++test_i18ngrep"CONFLICT (rename/rename)"out&&+test_must_be_emptyerr&&++gitls-files-s>file_count&&+test_line_count=6file_count&&+gitls-files-u>file_count&&+test_line_count=6file_count&&+gitls-files-o>file_count&&+test_line_count=3file_count&&++test_seq1020>merged-one&&+test_seq5160>merged-five&&+# Determine what the merge of three would give us.+test_seq3040>three-side-A&&+test_seq3139>three-side-B&&+echoforty>three-side-B&&+>empty&&+test_must_failgitmerge-file\+-L"HEAD"\+-L""\+-L"B^0"\+three-side-Aemptythree-side-B&&+sed-e"s/^\([<=>]\)/\1\1\1/"three-side-A>merged-three&&++# Verify the index is as expected+gitrev-parse>actual\+:2:two:3:two\+:2:four:3:four\+:2:six:3:six&&+githash-object>expect\+merged-onemerged-three\+merged-threemerged-five\+merged-fivemerged-one&&+test_cmpexpectactual&&++gitcat-file-p:2:two>expect&&+gitcat-file-p:3:two>other&&+test_must_failgitmerge-file\+-L"HEAD"-L""-L"B^0"\+expectemptyother&&+test_cmpexpecttwo&&++gitcat-file-p:2:four>expect&&+gitcat-file-p:3:four>other&&+test_must_failgitmerge-file\+-L"HEAD"-L""-L"B^0"\+expectemptyother&&+test_cmpexpectfour&&++gitcat-file-p:2:six>expect&&+gitcat-file-p:3:six>other&&+test_must_failgitmerge-file\+-L"HEAD"-L""-L"B^0"\+expectemptyother&&+test_cmpexpectsix+)+'+ test_done
If a file is renamed on one side of history, and the other side of history
both deletes the original file and adds a new unrelated file in the way of
the rename, then we have what I call a rename/add/delete conflict. Add a
testcase covering this scenario.
Reported-by: Robert Dailey <redacted>
Signed-off-by: Elijah Newren <redacted>
---
t/t6042-merge-rename-corner-cases.sh | 66 ++++++++++++++++++++++++++++
1 file changed, 66 insertions(+)
@@ -693,4 +693,70 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting)'+# Testcase rad, rename/add/delete+# Commit O: foo+# Commit A: rm foo, add different bar+# Commit B: rename foo->bar+# Expected: CONFLICT (rename/add/delete), two-way merged bar++test_expect_success'rad-setup: rename/add/delete conflict''+test_create_reporad&&+(+cdrad&&+echo"original file">foo&&+gitaddfoo&&+gitcommit-m"original"&&++gitbranchO&&+gitbranchA&&+gitbranchB&&++gitcheckoutA&&+gitrmfoo&&+echo"different file">bar&&+gitaddbar&&+gitcommit-m"Remove foo, add bar"&&++gitcheckoutB&&+gitmvfoobar&&+gitcommit-m"rename foo to bar"+)+'++test_expect_failure'rad-check: rename/add/delete conflict''+(+cdrad&&++gitcheckoutB^0&&+test_must_failgitmerge-srecursiveA^0>out2>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_emptyerr&&++gitls-files-s>file_count&&+test_line_count=2file_count&&+gitls-files-u>file_count&&+test_line_count=2file_count&&+gitls-files-o>file_count&&+test_line_count=2file_count&&++gitrev-parse>actual\+:2:bar:3:bar&&+gitrev-parse>expect\+B:barA:bar&&++test_cmpfile_is_missingfoo&&+# bar should have two-way merged contents of the different+# versions of bar; check that content from both sides is+# present.+greporiginalbar&&+grepdifferentbar+)+'+ test_done