{"thread":{"id":"48815","subject":"[PATCH 0/3] Add missing rename-related corner cases for merging","startedAt":"2018-07-02T13:31:14Z","lastAt":"2018-07-02T13:31:44Z","messageCount":4,"participants":["Elijah Newren"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"351525","messageId":"20180702133054.18638-1-newren@gmail.com","threadId":"48815","inReplyTo":null,"subject":"[PATCH 0/3] Add missing rename-related corner cases for merging","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-07-02T13:30:51Z","receivedAt":"2018-07-02T13:31:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In merging, every rename acts as a two-piece lego in terms of conflicts.\nWe have long realized that the other branch can (1) delete the source\nside of a rename, giving a rename/delete conflict, or (2) add a file in\nthe way of the rename destination, giving a rename/add conflict, or (3)\nhave a rename of its own touching either the same destination or source\npath, giving either a rename/rename(2to1) or rename/rename(1to2)\nconflict.  But only in one case did we ever consider chaining these\nmerge-conflict lego pieces (namely with rename/rename(1to2)/add/add\nconflicts).  Add some testcases that show other ways these conflicts can\nbe chained.\n\nIn short, any rename's source side can attach to a delete or another\nrename, and any rename's destination side can attach to an add or\nanother rename.\n\nThis series was spurred by Robert Dailey's report back in March of a\nrename case that git currently handles poorly:\n  https://public-inbox.org/git/CAHd499Axo7HFviUJavigTZ6BGZCkj9iOSeNVndu1oPivkPv+5Q@mail.gmail.com/\n\nThis series adds a testcase covering the issue he reported, and then\nrounds things out additional testcases demonstrating other ways rename\nconflicts could be \"chained\" together.\n\n\nElijah Newren (3):\n  t6042: add testcase covering rename/add/delete conflict type\n  t6042: add testcase covering rename/rename(2to1)/delete/delete\n    conflict\n  t6042: add testcase covering long chains of rename conflicts\n\n t/t6042-merge-rename-corner-cases.sh | 245 +++++++++++++++++++++++++++\n 1 file changed, 245 insertions(+)\n\n-- \n2.18.0.130.gd703bbb5d\n\n"},{"id":"351526","messageId":"20180702133054.18638-3-newren@gmail.com","threadId":"48815","inReplyTo":"20180702133054.18638-1-newren@gmail.com","subject":"[PATCH 2/3] t6042: add testcase covering rename/rename(2to1)/delete/delete conflict","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-07-02T13:30:53Z","receivedAt":"2018-07-02T13:31:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If either side of a rename/rename(2to1) conflict is itself also involved\nin a rename/delete conflict, then the conflict is a little more complex;\nwe can even have what I'd call a rename/rename(2to1)/delete/delete\nconflict.  (In some ways, this is similar to a rename/rename(1to2)/add/add\nconflict, as added in commit 3672c9714830 (\"merge-recursive: Fix working\ncopy handling for rename/rename/add/add\", 2011-08-11)).  Add a testcase\nfor such a conflict.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6042-merge-rename-corner-cases.sh | 68 ++++++++++++++++++++++++++++\n 1 file changed, 68 insertions(+)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 788a31451..46e1aa7df 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -759,4 +759,72 @@ test_expect_failure 'rad-check: rename/add/delete conflict' '\n \t)\n '\n \n+# Testcase rrdd, rename/rename(2to1)/delete/delete\n+#   Commit O: foo, bar\n+#   Commit A: rename foo->baz, rm bar\n+#   Commit B: rename bar->baz, rm foo\n+#   Expected: CONFLICT (rename/rename/delete/delete), two-way merged baz\n+\n+test_expect_success 'rrdd-setup: rename/rename(2to1)/delete/delete conflict' '\n+\ttest_create_repo rrdd &&\n+\t(\n+\t\tcd rrdd &&\n+\t\techo foo >foo &&\n+\t\techo bar >bar &&\n+\t\tgit add foo bar &&\n+\t\tgit commit -m O &&\n+\n+\t\tgit branch O &&\n+\t\tgit branch A &&\n+\t\tgit branch B &&\n+\n+\t\tgit checkout A &&\n+\t\tgit mv foo baz &&\n+\t\tgit rm bar &&\n+\t\tgit commit -m \"Rename foo, remove bar\" &&\n+\n+\t\tgit checkout B &&\n+\t\tgit mv bar baz &&\n+\t\tgit rm foo &&\n+\t\tgit commit -m \"Rename bar, remove foo\"\n+\t)\n+'\n+\n+test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' '\n+\t(\n+\t\tcd rrdd &&\n+\n+\t\tgit checkout A^0 &&\n+\t\ttest_must_fail git merge -s recursive B^0 >out 2>err &&\n+\n+\t\t# Not sure whether the output should contain just one\n+\t\t# \"CONFLICT (rename/rename/delete/delete)\" line, or if it\n+\t\t# should break it into three: \"CONFLICT (rename/rename)\" and\n+\t\t# two \"CONFLICT (rename/delete)\" lines; allow for either.\n+\t\ttest_i18ngrep \"CONFLICT (rename/rename)\" out &&\n+\t\ttest_i18ngrep \"CONFLICT (rename.*delete)\" out &&\n+\t\ttest_must_be_empty err &&\n+\n+\t\tgit ls-files -s >file_count &&\n+\t\ttest_line_count = 2 file_count &&\n+\t\tgit ls-files -u >file_count &&\n+\t\ttest_line_count = 2 file_count &&\n+\t\tgit ls-files -o >file_count &&\n+\t\ttest_line_count = 2 file_count &&\n+\n+\t\tgit rev-parse >actual \\\n+\t\t\t:2:baz :3:baz &&\n+\t\tgit rev-parse >expect \\\n+\t\t\tO:foo  O:bar  &&\n+\n+\t\ttest_cmp file_is_missing foo &&\n+\t\ttest_cmp file_is_missing bar &&\n+\t\t# baz should have two-way merged contents of the original\n+\t\t# contents of foo and bar; check that content from both sides\n+\t\t# is present.\n+\t\tgrep foo baz &&\n+\t\tgrep bar baz\n+\t)\n+'\n+\n test_done\n-- \n2.18.0.130.gd703bbb5d\n\n"},{"id":"351527","messageId":"20180702133054.18638-4-newren@gmail.com","threadId":"48815","inReplyTo":"20180702133054.18638-1-newren@gmail.com","subject":"[PATCH 3/3] t6042: add testcase covering long chains of rename conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-07-02T13:30:54Z","receivedAt":"2018-07-02T13:31:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Each rename is a lego: the source side could be connected to a delete or\nanother rename, and the destination side could be connected to a rename or a\nconflicting add.  Previous tests combined these to get e.g.\nrename/rename(1to2)/add/add, rename/rename(2to1)/delete/delete, and\nrename/add/delete.  But we can also build bigger chains of conflicts.  Add a\ntestcase demonstrating this.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6042-merge-rename-corner-cases.sh | 111 +++++++++++++++++++++++++++\n 1 file changed, 111 insertions(+)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 46e1aa7df..d9c4068e9 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -827,4 +827,115 @@ test_expect_failure 'rrdd-check: rename/rename(2to1)/delete/delete conflict' '\n \t)\n '\n \n+# Testcase mod6, chains of rename/rename(1to2) and rename/rename(2to1)\n+#   Commit O: one,      three,       five\n+#   Commit A: one->two, three->four, five->six\n+#   Commit B: one->six, three->two,  five->four\n+#   Expected: six CONFLICT(rename/rename) messages, each path in two of the\n+#             multi-way merged contents found in two, four, six\n+\n+test_expect_success 'mod6-setup: chains of rename/rename(1to2) and rename/rename(2to1)' '\n+\ttest_create_repo mod6 &&\n+\t(\n+\t\tcd mod6 &&\n+\t\ttest_seq 11 19 >one &&\n+\t\ttest_seq 31 39 >three &&\n+\t\ttest_seq 51 59 >five &&\n+\t\tgit add . &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"O\" &&\n+\n+\t\tgit branch O &&\n+\t\tgit branch A &&\n+\t\tgit branch B &&\n+\n+\t\tgit checkout A &&\n+\t\ttest_seq 10 19 >one &&\n+\t\techo 40        >>three &&\n+\t\tgit add one three &&\n+\t\tgit mv  one   two  &&\n+\t\tgit mv  three four &&\n+\t\tgit mv  five  six  &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"A\" &&\n+\n+\t\tgit checkout B &&\n+\t\techo 20    >>one       &&\n+\t\techo forty >>three     &&\n+\t\techo 60    >>five      &&\n+\t\tgit add one three five &&\n+\t\tgit mv  one   six  &&\n+\t\tgit mv  three two  &&\n+\t\tgit mv  five  four &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"B\"\n+\t)\n+'\n+\n+test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename(2to1)' '\n+\t(\n+\t\tcd mod6 &&\n+\n+\t\tgit checkout A^0 &&\n+\n+\t\ttest_must_fail git merge -s recursive B^0 >out 2>err &&\n+\n+\t\ttest_i18ngrep \"CONFLICT (rename/rename)\" out &&\n+\t\ttest_must_be_empty err &&\n+\n+\t\tgit ls-files -s >file_count &&\n+\t\ttest_line_count = 6 file_count &&\n+\t\tgit ls-files -u >file_count &&\n+\t\ttest_line_count = 6 file_count &&\n+\t\tgit ls-files -o >file_count &&\n+\t\ttest_line_count = 3 file_count &&\n+\n+\t\ttest_seq 10 20 >merged-one &&\n+\t\ttest_seq 51 60 >merged-five &&\n+\t\t# Determine what the merge of three would give us.\n+\t\ttest_seq 30 40 >three-side-A &&\n+\t\ttest_seq 31 39 >three-side-B &&\n+\t\techo forty >three-side-B &&\n+\t\t>empty &&\n+\t\ttest_must_fail git merge-file \\\n+\t\t\t-L \"HEAD\" \\\n+\t\t\t-L \"\" \\\n+\t\t\t-L \"B^0\" \\\n+\t\t\tthree-side-A empty three-side-B &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1\\1/\" three-side-A >merged-three &&\n+\n+\t\t# Verify the index is as expected\n+\t\tgit rev-parse >actual         \\\n+\t\t\t:2:two       :3:two   \\\n+\t\t\t:2:four      :3:four  \\\n+\t\t\t:2:six       :3:six   &&\n+\t\tgit hash-object >expect           \\\n+\t\t\tmerged-one   merged-three \\\n+\t\t\tmerged-three merged-five  \\\n+\t\t\tmerged-five  merged-one   &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit cat-file -p :2:two >expect &&\n+\t\tgit cat-file -p :3:two >other &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD\"  -L \"\"  -L \"B^0\" \\\n+\t\t\texpect     empty  other &&\n+\t\ttest_cmp expect two &&\n+\n+\t\tgit cat-file -p :2:four >expect &&\n+\t\tgit cat-file -p :3:four >other &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD\"  -L \"\"  -L \"B^0\" \\\n+\t\t\texpect     empty  other &&\n+\t\ttest_cmp expect four &&\n+\n+\t\tgit cat-file -p :2:six >expect &&\n+\t\tgit cat-file -p :3:six >other &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD\"  -L \"\"  -L \"B^0\" \\\n+\t\t\texpect     empty  other &&\n+\t\ttest_cmp expect six\n+\t)\n+'\n+\n test_done\n-- \n2.18.0.130.gd703bbb5d\n\n"},{"id":"351528","messageId":"20180702133054.18638-2-newren@gmail.com","threadId":"48815","inReplyTo":"20180702133054.18638-1-newren@gmail.com","subject":"[PATCH 1/3] t6042: add testcase covering rename/add/delete conflict type","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-07-02T13:30:52Z","receivedAt":"2018-07-02T13:31:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If a file is renamed on one side of history, and the other side of history\nboth deletes the original file and adds a new unrelated file in the way of\nthe rename, then we have what I call a rename/add/delete conflict.  Add a\ntestcase covering this scenario.\n\nReported-by: Robert Dailey <rcdailey.lists@gmail.com>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6042-merge-rename-corner-cases.sh | 66 ++++++++++++++++++++++++++++\n 1 file changed, 66 insertions(+)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 1cbd946fc..788a31451 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -693,4 +693,70 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t)\n '\n \n+# Testcase rad, rename/add/delete\n+#   Commit O: foo\n+#   Commit A: rm foo, add different bar\n+#   Commit B: rename foo->bar\n+#   Expected: CONFLICT (rename/add/delete), two-way merged bar\n+\n+test_expect_success 'rad-setup: rename/add/delete conflict' '\n+\ttest_create_repo rad &&\n+\t(\n+\t\tcd rad &&\n+\t\techo \"original file\" >foo &&\n+\t\tgit add foo &&\n+\t\tgit commit -m \"original\" &&\n+\n+\t\tgit branch O &&\n+\t\tgit branch A &&\n+\t\tgit branch B &&\n+\n+\t\tgit checkout A &&\n+\t\tgit rm foo &&\n+\t\techo \"different file\" >bar &&\n+\t\tgit add bar &&\n+\t\tgit commit -m \"Remove foo, add bar\" &&\n+\n+\t\tgit checkout B &&\n+\t\tgit mv foo bar &&\n+\t\tgit commit -m \"rename foo to bar\"\n+\t)\n+'\n+\n+test_expect_failure 'rad-check: rename/add/delete conflict' '\n+\t(\n+\t\tcd rad &&\n+\n+\t\tgit checkout B^0 &&\n+\t\ttest_must_fail git merge -s recursive A^0 >out 2>err &&\n+\n+\t\t# Not sure whether the output should contain just one\n+\t\t# \"CONFLICT (rename/add/delete)\" line, or if it should break\n+\t\t# it into a pair of \"CONFLICT (rename/delete)\" and\n+\t\t# \"CONFLICT (rename/add)\"; allow for either.\n+\t\ttest_i18ngrep \"CONFLICT (rename.*add)\" out &&\n+\t\ttest_i18ngrep \"CONFLICT (rename.*delete)\" out &&\n+\t\ttest_must_be_empty err &&\n+\n+\t\tgit ls-files -s >file_count &&\n+\t\ttest_line_count = 2 file_count &&\n+\t\tgit ls-files -u >file_count &&\n+\t\ttest_line_count = 2 file_count &&\n+\t\tgit ls-files -o >file_count &&\n+\t\ttest_line_count = 2 file_count &&\n+\n+\t\tgit rev-parse >actual \\\n+\t\t\t:2:bar :3:bar &&\n+\t\tgit rev-parse >expect \\\n+\t\t\tB:bar  A:bar  &&\n+\n+\t\ttest_cmp file_is_missing foo &&\n+\t\t# bar should have two-way merged contents of the different\n+\t\t# versions of bar; check that content from both sides is\n+\t\t# present.\n+\t\tgrep original bar &&\n+\t\tgrep different bar\n+\t)\n+'\n+\n test_done\n-- \n2.18.0.130.gd703bbb5d\n\n"}]}