{"thread":{"id":"49559","subject":"[RFC PATCH v2 0/7] Improve path collision conflict resolutions","startedAt":"2018-10-14T02:05:57Z","lastAt":"2018-11-08T05:25:30Z","messageCount":52,"participants":["Elijah Newren","Derrick Stolee","Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":7},"messages":[{"id":"360389","messageId":"20181014020537.17991-1-newren@gmail.com","threadId":"49559","inReplyTo":null,"subject":"[RFC PATCH v2 0/7] Improve path collision conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-14T02:05:30Z","receivedAt":"2018-10-14T02:05:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This RFC series depends on this series submitted yesterday:\n  https://public-inbox.org/git/20181012212551.7689-1-newren@gmail.com/\n(which in turn depends on en/merge-cleanup in next).\n\nAlthough this is an \"update\" to my previous RFC series from six months\nago:\n  https://public-inbox.org/git/20180305171125.22331-1-newren@gmail.com/\nIt's essentially a complete rewrite to instead use the strategy proposed\nby Jonathan and Junio (from a separate thread discussing that RFC series):\n  https://public-inbox.org/git/20180312213521.GB58506@aiede.svl.corp.google.com/\n  https://public-inbox.org/git/CAPc5daVu8vv9RdGON8JiXEO3ycDVqQ38ySzZc-cpo+AQcAKXjA@mail.gmail.com\n\nThe basic idea is to make the \"file collision\" conflict types all behave\nlike add/add.  These types are:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n  * each rename/add piece of a rename/rename(1to2)/add[/add] conflict\n\nSpecific bits that make this RFC:\n  * Depends on another series not even in pu yet.\n  * In order to simplify review, I add a new common function for all\n    these conflict types to call in one commit, and since the function\n    is not yet used at that point, I wrap the function in an #ifdef 0.\n    In later patches, I remove the #ifdef.  Is that...okay?\n  * There are a few FIXMEs in the code.  I'm okay leaving them there,\n    but are others upset by them?\n  * Every time I attempt to complete this series, I spot more problems;\n    many of the merge-recursive series I've submitted since March were\n    issues I discovered while trying to complete this series -- or\n    secondary tangents discovered while working on the side-issues to\n    this one.  I've gotten to the point of automatically second-guessing\n    myself on whether I could have actually completed the necessary\n    fixes.\n\nElijah Newren (7):\n  Add testcases for consistency in file collision conflict handling\n  t6036, t6042: testcases for rename collision of already conflicting\n    files\n  merge-recursive: new function for better colliding conflict\n    resolutions\n  merge-recursive: fix rename/add conflict handling\n  merge-recursive: improve handling for rename/rename(2to1) conflicts\n  merge-recursive: use handle_file_collision for add/add conflicts\n  merge-recursive: improve rename/rename(1to2)/add[/add] handling\n\n merge-recursive.c                    | 513 ++++++++++++++++-----------\n t/t6036-recursive-corner-cases.sh    | 228 +++++++++++-\n t/t6042-merge-rename-corner-cases.sh | 333 ++++++++++++++++-\n t/t6043-merge-rename-directories.sh  | 107 +++---\n 4 files changed, 893 insertions(+), 288 deletions(-)\n\n-- \n2.19.0.3.g98f21ceff2.dirty\n\n"},{"id":"360390","messageId":"20181014020537.17991-2-newren@gmail.com","threadId":"49559","inReplyTo":"20181014020537.17991-1-newren@gmail.com","subject":"[RFC PATCH v2 1/7] Add testcases for consistency in file collision conflict handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-14T02:05:31Z","receivedAt":"2018-10-14T02:05:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Add testcases dealing with file collisions for the following types of\nconflicts:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n\nAll these conflict types simplify down to two files \"colliding\"\nand should thus be handled similarly.  This means that rename/add and\nrename/rename(2to1) conflicts need to be modified to behave the same as\nadd/add conflicts currently do: the colliding files should be two-way\nmerged (instead of the current behavior of writing the two colliding\nfiles out to separate temporary unique pathnames).  Add testcases which\ncheck this; subsequent commits will fix the conflict handling to make\nthese tests pass.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6042-merge-rename-corner-cases.sh | 162 +++++++++++++++++++++++++++\n 1 file changed, 162 insertions(+)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b97aca7fa2..b6fed2cb9a 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -937,4 +937,166 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename\n \t)\n '\n \n+test_conflicts_with_adds_and_renames() {\n+\tsideL=$1\n+\tsideR=$2\n+\texpect=$3\n+\n+\t# Setup:\n+\t#          L\n+\t#         / \\\n+\t#   master   ?\n+\t#         \\ /\n+\t#          R\n+\t#\n+\t# Where:\n+\t#   Both L and R have files named 'three' which collide.  Each of\n+\t#   the colliding files could have been involved in a rename, in\n+\t#   which case there was a file named 'one' or 'two' that was\n+\t#   modified on the opposite side of history and renamed into the\n+\t#   collision on this side of history.\n+\t#\n+\t# Questions:\n+\t#   1) The index should contain both a stage 2 and stage 3 entry\n+\t#      for the colliding file.  Does it?\n+\t#   2) When renames are involved, the content merges are clean, so\n+\t#      the index should reflect the content merges, not merely the\n+\t#      version of the colliding file from the prior commit.  Does\n+\t#      it?\n+\t#   3) There should be a file in the worktree named 'three'\n+\t#      containing the two-way merged contents of the content-merged\n+\t#      versions of 'three' from each of the two colliding\n+\t#      files.  Is it present?\n+\t#   4) There should not be any three~* files in the working\n+\t#      tree\n+\ttest_expect_success \"setup simple $sideL/$sideR conflict\" '\n+\t\ttest_create_repo simple_${sideL}_${sideR} &&\n+\t\t(\n+\t\t\tcd simple_${sideL}_${sideR} &&\n+\n+\t\t\t# Create some related files now\n+\t\t\tfor i in $(test_seq 1 10)\n+\t\t\tdo\n+\t\t\t\techo Random base content line $i\n+\t\t\tdone >file_v1 &&\n+\t\t\tcp file_v1 file_v2 &&\n+\t\t\techo modification >>file_v2 &&\n+\n+\t\t\tcp file_v1 file_v3 &&\n+\t\t\techo more stuff >>file_v3 &&\n+\t\t\tcp file_v3 file_v4 &&\n+\t\t\techo yet more stuff >>file_v4 &&\n+\n+\t\t\t# Use a tag to record both these files for simple\n+\t\t\t# access, and clean out these untracked files\n+\t\t\tgit tag file_v1 $(git hash-object -w file_v1) &&\n+\t\t\tgit tag file_v2 $(git hash-object -w file_v2) &&\n+\t\t\tgit tag file_v3 $(git hash-object -w file_v3) &&\n+\t\t\tgit tag file_v4 $(git hash-object -w file_v4) &&\n+\t\t\tgit clean -f &&\n+\n+\t\t\t# Setup original commit (or merge-base), consisting of\n+\t\t\t# files named \"one\" and \"two\" if renames were involved.\n+\t\t\ttouch irrelevant_file &&\n+\t\t\tgit add irrelevant_file &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v1 >one &&\n+\t\t\t\tgit add one\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v3 >two &&\n+\t\t\t\tgit add two\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m initial &&\n+\n+\t\t\tgit branch L &&\n+\t\t\tgit branch R &&\n+\n+\t\t\t# Handle the left side\n+\t\t\tgit checkout L &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit mv one three\n+\t\t\telse\n+\t\t\t\tgit show file_v2 >three &&\n+\t\t\t\tgit add three\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v4 >two &&\n+\t\t\t\tgit add two\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m L &&\n+\n+\t\t\t# Handle the right side\n+\t\t\tgit checkout R &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v2 >one &&\n+\t\t\t\tgit add one\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit mv two three\n+\t\t\telse\n+\t\t\t\tgit show file_v4 >three &&\n+\t\t\t\tgit add three\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m R\n+\t\t)\n+\t'\n+\n+\ttest_expect_$expect \"check simple $sideL/$sideR conflict\" '\n+\t\t(\n+\t\t\tcd simple_${sideL}_${sideR} &&\n+\n+\t\t\tgit checkout L^0 &&\n+\n+\t\t\t# Merge must fail; there is a conflict\n+\t\t\ttest_must_fail git merge -s recursive R^0 &&\n+\n+\t\t\t# Make sure the index has the right number of entries\n+\t\t\tgit ls-files -s >out &&\n+\t\t\ttest_line_count = 3 out &&\n+\t\t\tgit ls-files -u >out &&\n+\t\t\ttest_line_count = 2 out &&\n+\t\t\t# Ensure we have the correct number of untracked files\n+\t\t\tgit ls-files -o >out &&\n+\t\t\ttest_line_count = 1 out &&\n+\n+\t\t\t# Nothing should have touched irrelevant_file\n+\t\t\tgit rev-parse >actual      \\\n+\t\t\t\t:0:irrelevant_file \\\n+\t\t\t\t:2:three           \\\n+\t\t\t\t:3:three           &&\n+\t\t\tgit rev-parse >expected        \\\n+\t\t\t\tmaster:irrelevant_file \\\n+\t\t\t\tfile_v2                \\\n+\t\t\t\tfile_v4                &&\n+\t\t\ttest_cmp expected actual &&\n+\n+\t\t\t# Make sure we have the correct merged contents for\n+\t\t\t# three\n+\t\t\tgit show file_v1 >expected &&\n+\t\t\tcat <<-\\EOF >>expected &&\n+\t\t\t<<<<<<< HEAD\n+\t\t\tmodification\n+\t\t\t=======\n+\t\t\tmore stuff\n+\t\t\tyet more stuff\n+\t\t\t>>>>>>> R^0\n+\t\t\tEOF\n+\n+\t\t\ttest_cmp expected three\n+\t\t)\n+\t'\n+}\n+\n+test_conflicts_with_adds_and_renames rename rename failure\n+test_conflicts_with_adds_and_renames rename add    failure\n+test_conflicts_with_adds_and_renames add    rename failure\n+test_conflicts_with_adds_and_renames add    add    success\n+\n test_done\n-- \n2.19.0.3.g98f21ceff2.dirty\n\n"},{"id":"360391","messageId":"20181014020537.17991-5-newren@gmail.com","threadId":"49559","inReplyTo":"20181014020537.17991-1-newren@gmail.com","subject":"[RFC PATCH v2 4/7] merge-recursive: fix rename/add conflict handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-14T02:05:34Z","receivedAt":"2018-10-14T02:05:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This makes the rename/add conflict handling make use of the new\nhandle_file_collision() function, which fixes several bugs and improves\nthings for the rename/add case significantly.  Previously, rename/add\nwould:\n\n  * Not leave any higher order stage entries in the index, making it\n    appear as if there were no conflict.\n  * Would place the rename file at the colliding path, and move the\n    added file elsewhere, which combined with the lack of higher order\n    stage entries felt really odd.  It's not clear to me why the\n    rename should take precedence over the add; if one should be moved\n    out of the way, they both probably should.\n  * In the recursive case, it would do a two way merge of the added\n    file and the version of the renamed file on the renamed side,\n    completely excluding modifications to the renamed file on the\n    unrenamed side of history.\n\nUsing the new handle_file_collision() fixes all of these issues, and\nadds smarts to allow two-way merge OR move colliding files to separate\npaths depending on the similarity of the colliding files.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 137 +++++++++++++++++----------\n t/t6036-recursive-corner-cases.sh    |  24 ++---\n t/t6042-merge-rename-corner-cases.sh |   4 +-\n 3 files changed, 101 insertions(+), 64 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 24d979022e..0805095168 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -186,6 +186,7 @@ static int oid_eq(const struct object_id *a, const struct object_id *b)\n enum rename_type {\n \tRENAME_NORMAL = 0,\n \tRENAME_VIA_DIR,\n+\tRENAME_ADD,\n \tRENAME_DELETE,\n \tRENAME_ONE_FILE_TO_ONE,\n \tRENAME_ONE_FILE_TO_TWO,\n@@ -228,6 +229,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\t\t\t\t      struct stage_data *src_entry1,\n \t\t\t\t\t      struct stage_data *src_entry2)\n {\n+\tint ostage1 = 0, ostage2;\n \tstruct rename_conflict_info *ci;\n \n \t/*\n@@ -264,18 +266,22 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\tdst_entry2->rename_conflict_info = ci;\n \t}\n \n-\tif (rename_type == RENAME_TWO_FILES_TO_ONE) {\n-\t\t/*\n-\t\t * For each rename, there could have been\n-\t\t * modifications on the side of history where that\n-\t\t * file was not renamed.\n-\t\t */\n-\t\tint ostage1 = o->branch1 == branch1 ? 3 : 2;\n-\t\tint ostage2 = ostage1 ^ 1;\n+\t/*\n+\t * For each rename, there could have been\n+\t * modifications on the side of history where that\n+\t * file was not renamed.\n+\t */\n+\tif (rename_type == RENAME_ADD ||\n+\t    rename_type == RENAME_TWO_FILES_TO_ONE) {\n+\t\tostage1 = o->branch1 == branch1 ? 3 : 2;\n \n \t\tci->ren1_other.path = pair1->one->path;\n \t\toidcpy(&ci->ren1_other.oid, &src_entry1->stages[ostage1].oid);\n \t\tci->ren1_other.mode = src_entry1->stages[ostage1].mode;\n+\t}\n+\n+\tif (rename_type == RENAME_TWO_FILES_TO_ONE) {\n+\t\tostage2 = ostage1 ^ 1;\n \n \t\tci->ren2_other.path = pair2->one->path;\n \t\toidcpy(&ci->ren2_other.oid, &src_entry2->stages[ostage2].oid);\n@@ -1559,7 +1565,6 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,\n \treturn target;\n }\n \n-#if 0 // #if-0-ing avoids unused function warning; will make live in next commit\n static int handle_file_collision(struct merge_options *o,\n \t\t\t\t const char *collide_path,\n \t\t\t\t const char *prev_path1,\n@@ -1575,6 +1580,20 @@ static int handle_file_collision(struct merge_options *o,\n \tchar *alt_path = NULL;\n \tconst char *update_path = collide_path;\n \n+\t/*\n+\t * It's easiest to get the correct things into stage 2 and 3, and\n+\t * to make sure that the content merge puts HEAD before the other\n+\t * branch if we just ensure that branch1 == o->branch1.  So, simply\n+\t * flip arguments around if we don't have that.\n+\t */\n+\tif (branch1 != o->branch1) {\n+\t\treturn handle_file_collision(o, collide_path,\n+\t\t\t\t\t     prev_path2, prev_path1,\n+\t\t\t\t\t     branch2, branch1,\n+\t\t\t\t\t     b_oid, b_mode,\n+\t\t\t\t\t     a_oid, a_mode);\n+\t}\n+\n \t/*\n \t * In the recursive case, we just opt to undo renames\n \t */\n@@ -1678,7 +1697,38 @@ static int handle_file_collision(struct merge_options *o,\n \t */\n \treturn mfi.clean;\n }\n-#endif\n+\n+static int handle_rename_add(struct merge_options *o,\n+\t\t\t     struct rename_conflict_info *ci)\n+{\n+\t/* a was renamed to c, and a separate c was added. */\n+\tstruct diff_filespec *a = ci->pair1->one;\n+\tstruct diff_filespec *c = ci->pair1->two;\n+\tchar *path = c->path;\n+\tchar *prev_path_desc;\n+\tstruct merge_file_info mfi;\n+\n+\tint other_stage = (ci->branch1 == o->branch1 ? 3 : 2);\n+\n+\toutput(o, 1, _(\"CONFLICT (rename/add): \"\n+\t       \"Rename %s->%s in %s.  Added %s in %s\"),\n+\t       a->path, c->path, ci->branch1,\n+\t       c->path, ci->branch2);\n+\n+\tprev_path_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n+\tif (merge_mode_and_contents(o, a, c, &ci->ren1_other, prev_path_desc,\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    1 + o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tfree(prev_path_desc);\n+\n+\treturn handle_file_collision(o,\n+\t\t\t\t     c->path, a->path, NULL,\n+\t\t\t\t     ci->branch1, ci->branch2,\n+\t\t\t\t     &mfi.oid, mfi.mode,\n+\t\t\t\t     &ci->dst_entry1->stages[other_stage].oid,\n+\t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n+}\n \n static int handle_file(struct merge_options *o,\n \t\t\tstruct diff_filespec *rename,\n@@ -2860,47 +2910,23 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t      0  /* update_wd    */))\n \t\t\t\t\tclean_merge = -1;\n \t\t\t} else if (!oid_eq(&dst_other.oid, &null_oid)) {\n-\t\t\t\tclean_merge = 0;\n-\t\t\t\ttry_merge = 1;\n-\t\t\t\toutput(o, 1, _(\"CONFLICT (rename/add): Rename %s->%s in %s. \"\n-\t\t\t\t       \"%s added in %s\"),\n-\t\t\t\t       ren1_src, ren1_dst, branch1,\n-\t\t\t\t       ren1_dst, branch2);\n-\t\t\t\tif (o->call_depth) {\n-\t\t\t\t\tstruct merge_file_info mfi;\n-\t\t\t\t\tstruct diff_filespec one, a, b;\n-\n-\t\t\t\t\toidcpy(&one.oid, &null_oid);\n-\t\t\t\t\tone.mode = 0;\n-\t\t\t\t\tone.path = ren1->pair->two->path;\n-\n-\t\t\t\t\toidcpy(&a.oid, &ren1->pair->two->oid);\n-\t\t\t\t\ta.mode = ren1->pair->two->mode;\n-\t\t\t\t\ta.path = one.path;\n-\n-\t\t\t\t\toidcpy(&b.oid, &dst_other.oid);\n-\t\t\t\t\tb.mode = dst_other.mode;\n-\t\t\t\t\tb.path = one.path;\n-\n-\t\t\t\t\tif (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,\n-\t\t\t\t\t\t\t\t    branch1, branch2,\n-\t\t\t\t\t\t\t\t    o->call_depth * 2, &mfi)) {\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\t\tgoto cleanup_and_return;\n-\t\t\t\t\t}\n-\t\t\t\t\toutput(o, 1, _(\"Adding merged %s\"), ren1_dst);\n-\t\t\t\t\tif (update_file(o, 0, &mfi.oid,\n-\t\t\t\t\t\t\tmfi.mode, ren1_dst))\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\ttry_merge = 0;\n-\t\t\t\t} else {\n-\t\t\t\t\tchar *new_path = unique_path(o, ren1_dst, branch2);\n-\t\t\t\t\toutput(o, 1, _(\"Adding as %s instead\"), new_path);\n-\t\t\t\t\tif (update_file(o, 0, &dst_other.oid,\n-\t\t\t\t\t\t\tdst_other.mode, new_path))\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\tfree(new_path);\n-\t\t\t\t}\n+\t\t\t\t/*\n+\t\t\t\t * Probably not a clean merge, but it's\n+\t\t\t\t * premature to set clean_merge to 0 here,\n+\t\t\t\t * because if the rename merges cleanly and\n+\t\t\t\t * the merge exactly matches the newly added\n+\t\t\t\t * file, then the merge will be clean.\n+\t\t\t\t */\n+\t\t\t\tsetup_rename_conflict_info(RENAME_ADD,\n+\t\t\t\t\t\t\t   ren1->pair,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   branch1,\n+\t\t\t\t\t\t\t   branch2,\n+\t\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   o,\n+\t\t\t\t\t\t\t   ren1->src_entry,\n+\t\t\t\t\t\t\t   NULL);\n \t\t\t} else\n \t\t\t\ttry_merge = 1;\n \n@@ -3312,6 +3338,15 @@ static int process_entry(struct merge_options *o,\n \t\t\t\t\t\t  conflict_info->branch2))\n \t\t\t\tclean_merge = -1;\n \t\t\tbreak;\n+\t\tcase RENAME_ADD:\n+\t\t\t/*\n+\t\t\t * Probably unclean merge, but if the renamed file\n+\t\t\t * merges cleanly and the result can then be\n+\t\t\t * two-way merged cleanly with the added file, I\n+\t\t\t * guess it's a clean merge?\n+\t\t\t */\n+\t\t\tclean_merge = handle_rename_add(o, conflict_info);\n+\t\t\tbreak;\n \t\tcase RENAME_DELETE:\n \t\t\tclean_merge = 0;\n \t\t\tif (handle_rename_delete(o,\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex a4c8041448..de35ae81ba 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -185,7 +185,7 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '\n \t\tgit branch B &&\n \t\tgit checkout -b C &&\n \t\techo 10 >>a &&\n-\t\techo \"other content\" >>new_a &&\n+\t\ttest_write_lines 0 1 2 3 4 5 6 7 foobar >new_a &&\n \t\tgit add a new_a &&\n \t\ttest_tick && git commit -m C &&\n \n@@ -195,14 +195,14 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '\n \n \t\tgit checkout B^0 &&\n \t\ttest_must_fail git merge C &&\n-\t\tgit clean -f &&\n+\t\tgit show :2:new_a >new_a &&\n+\t\tgit add new_a &&\n \t\ttest_tick && git commit -m D &&\n \t\tgit tag D &&\n \n \t\tgit checkout C^0 &&\n \t\ttest_must_fail git merge B &&\n-\t\trm new_a~HEAD new_a &&\n-\t\tprintf \"Incorrectly merged content\" >>new_a &&\n+\t\ttest_write_lines 0 1 2 3 4 5 6 7 bad_merge >new_a &&\n \t\tgit add -u &&\n \t\ttest_tick && git commit -m E &&\n \t\tgit tag E\n@@ -225,21 +225,23 @@ test_expect_success 'git detects differently handled merges conflict' '\n \t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tD:new_a  E:new_a &&\n+\t\t\tC:new_a  D:new_a  E:new_a &&\n \t\tgit rev-parse   >actual     \\\n-\t\t\t:2:new_a :3:new_a &&\n+\t\t\t:1:new_a :2:new_a :3:new_a &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit cat-file -p C:new_a >ours &&\n-\t\tgit cat-file -p B:new_a >theirs &&\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p D:new_a >ours &&\n+\t\tgit cat-file -p E:new_a >theirs &&\n \t\t>empty &&\n \t\ttest_must_fail git merge-file \\\n-\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"HEAD\" \\\n \t\t\t-L \"\" \\\n-\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\t-L \"E^0\" \\\n \t\t\tours empty theirs &&\n \t\tsed -e \"s/^\\([<=>]\\)/\\1\\1\\1/\" ours >expect &&\n-\t\tgit cat-file -p :1:new_a >actual &&\n+\t\tgit hash-object new_a >actual &&\n+\t\tgit hash-object ours  >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 5e69e60b48..f1c4a06dd6 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -1095,8 +1095,8 @@ test_conflicts_with_adds_and_renames() {\n }\n \n test_conflicts_with_adds_and_renames rename rename failure\n-test_conflicts_with_adds_and_renames rename add    failure\n-test_conflicts_with_adds_and_renames add    rename failure\n+test_conflicts_with_adds_and_renames rename add    success\n+test_conflicts_with_adds_and_renames add    rename success\n test_conflicts_with_adds_and_renames add    add    success\n \n # Setup:\n-- \n2.19.0.3.g98f21ceff2.dirty\n\n"},{"id":"360392","messageId":"20181014020537.17991-4-newren@gmail.com","threadId":"49559","inReplyTo":"20181014020537.17991-1-newren@gmail.com","subject":"[RFC PATCH v2 3/7] merge-recursive: new function for better colliding conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-14T02:05:33Z","receivedAt":"2018-10-14T02:05:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There are three conflict types that represent two (possibly entirely\nunrelated) files colliding at the same location:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n\nThese three conflict types already share more similarity than might be\nimmediately apparent from their description: (1) the handling of the\nrename variants already involves removing any entries from the index\ncorresponding to the original file names[*], thus only leaving entries\nin the index for the colliding path; (2) likewise, any trace of the\noriginal file name in the working tree is also removed.  So, in all\nthree cases we're left with how to represent two colliding files in both\nthe index and the working copy.\n\n[*] Technically, this isn't quite true because rename/rename(2to1)\nconflicts in the recursive (o->call_depth > 0) case do an \"unrename\"\nsince about seven years ago.  But even in that case, Junio felt\ncompelled to explain that my decision to \"unrename\" wasn't necessarily\nthe only or right answer -- search for \"Comment from Junio\" in t6036 for\ndetails.\n\nMy initial motivation for looking at these three conflict types was that\nif the handling of these three conflict types is the same, at least in\nthe limited set of cases where a renamed file is unmodified on the side\nof history where the file is not renamed, then a significant performance\nimprovement for rename detection during merges is possible.  However,\nwhile that served as motivation to look at these three types of\nconflicts, the actual goal of this new function is to try to improve the\nhandling for all three cases, not to merely make them the same as each\nother in that special circumstance.\n\n=== Handling the working tree ===\n\nThe previous behavior for these conflict types in regards to the\nworking tree (assuming the file collision occurs at 'foo') was:\n  * add/add does a two-way merge of the two files and records it as 'foo'.\n  * rename/rename(2to1) records the two different files into two new\n    uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'\n    from the working tree.\n  * rename/add records the two different files into two different\n    locations, recording the add at foo~$SIDE and, oddly, recording\n    the rename at foo (why is the rename more important than the add?)\n\nSo, the question for what to write to the working tree boils down to\nwhether the two colliding files should be two-way merged and recorded in\nplace, or recorded into separate files.  As per discussion on the git\nmailing lit, two-way merging was deemed to always be preferred, as that\nmakes these cases all more like content conflicts that users can handle\nfrom within their favorite editor, IDE, or merge tool.  Note that since\nrenames already involve a content merge, rename/add and\nrename/rename(2to1) conflicts could result in nested conflict markers.\n\n=== Handling of the index ===\n\nFor a typical rename, unpack_trees() would set up the index in the\nfollowing fashion:\n           old_path  new_path\n   stage1: 5ca1ab1e  00000000\n   stage2: f005ba11  00000000\n   stage3: 00000000  b0a710ad\nAnd merge-recursive would rewrite this to\n           new_path\n   stage1: 5ca1ab1e\n   stage2: f005ba11\n   stage3: b0a710ad\nRemoving old_path from the index means the user won't have to `git rm\nold_path` manually every time a renamed path has a content conflict.\nIt also means they can use `git checkout [--ours|--theirs|--conflict|-m]\nnew_path`, `git diff [--ours|--theirs]` and various other commands that\nwould be difficult otherwise.\n\nThis strategy becomes a problem when we have a rename/add or\nrename/rename(2to1) conflict, however, because then we have only three\nslots to store blob sha1s and we need either four or six.  Previously,\nthis was handled by continuing to delete old_path from the index, and\njust outright ignoring any blob shas from old_path.  That had the\ndownside of deleting any trace of changes made to old_path on the other\nside of history.  This function instead does a three-way content merge of\nthe renamed file, and stores the blob sha1 for that at either stage2 or\nstage3 for new_path (depending on which side the rename came from).  That\nhas the advantage of bringing information about changes on both sides and\nstill allows for easy resolution (no need to git rm old_path, etc.), but\ndoes have the downside that if the content merge had conflict markers,\nthen what we store in the index is the sha1 of a blob with conflict\nmarkers.  While that is a downside, it seems less problematic than the\ndownsides of any obvious alternatives, and certainly makes more sense\nthan the previous handling.  Further, it has a precedent in that when we\ndo recursive merges, we may accept a file with conflict markers as the\nresolution for the merge of the merge-bases, which will then show up in\nthe index of the outer merge at stage 1 if a conflict exists at the outer\nlevel.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 121 insertions(+)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f795c92a69..24d979022e 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1559,6 +1559,127 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,\n \treturn target;\n }\n \n+#if 0 // #if-0-ing avoids unused function warning; will make live in next commit\n+static int handle_file_collision(struct merge_options *o,\n+\t\t\t\t const char *collide_path,\n+\t\t\t\t const char *prev_path1,\n+\t\t\t\t const char *prev_path2,\n+\t\t\t\t const char *branch1, const char *branch2,\n+\t\t\t\t const struct object_id *a_oid,\n+\t\t\t\t unsigned int a_mode,\n+\t\t\t\t const struct object_id *b_oid,\n+\t\t\t\t unsigned int b_mode)\n+{\n+\tstruct merge_file_info mfi;\n+\tstruct diff_filespec null, a, b;\n+\tchar *alt_path = NULL;\n+\tconst char *update_path = collide_path;\n+\n+\t/*\n+\t * In the recursive case, we just opt to undo renames\n+\t */\n+\tif (o->call_depth && (prev_path1 || prev_path2)) {\n+\t\t/* Put first file (a_oid, a_mode) in its original spot */\n+\t\tif (prev_path1) {\n+\t\t\tif (update_file(o, 1, a_oid, a_mode, prev_path1))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tif (update_file(o, 1, a_oid, a_mode, collide_path))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Put second file (b_oid, b_mode) in its original spot */\n+\t\tif (prev_path2) {\n+\t\t\tif (update_file(o, 1, b_oid, b_mode, prev_path2))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tif (update_file(o, 1, b_oid, b_mode, collide_path))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Don't leave something at collision path if unrenaming both */\n+\t\tif (prev_path1 && prev_path2)\n+\t\t\tremove_file(o, 1, collide_path, 0);\n+\n+\t\treturn 0;\n+\t}\n+\n+\t/* Remove rename sources if rename/add or rename/rename(2to1) */\n+\tif (prev_path1)\n+\t\tremove_file(o, 1, prev_path1,\n+\t\t\t    o->call_depth || would_lose_untracked(prev_path1));\n+\tif (prev_path2)\n+\t\tremove_file(o, 1, prev_path2,\n+\t\t\t    o->call_depth || would_lose_untracked(prev_path2));\n+\n+\t/*\n+\t * Remove the collision path, if it wouldn't cause dirty contents\n+\t * or an untracked file to get lost.  We'll either overwrite with\n+\t * merged contents, or just write out to differently named files.\n+\t */\n+\tif (was_dirty(o, collide_path)) {\n+\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n+\t\t       collide_path);\n+\t\tupdate_path = alt_path = unique_path(o, collide_path, \"merged\");\n+\t} else if (would_lose_untracked(collide_path)) {\n+\t\t/*\n+\t\t * Only way we get here is if both renames were from\n+\t\t * a directory rename AND user had an untracked file\n+\t\t * at the location where both files end up after the\n+\t\t * two directory renames.  See testcase 10d of t6043.\n+\t\t */\n+\t\toutput(o, 1, _(\"Refusing to lose untracked file at \"\n+\t\t\t       \"%s, even though it's in the way.\"),\n+\t\t       collide_path);\n+\t\tupdate_path = alt_path = unique_path(o, collide_path, \"merged\");\n+\t} else {\n+\t\t/*\n+\t\t * FIXME: It's possible that the two files are identical\n+\t\t * and that the current working copy happens to match, in\n+\t\t * which case we are unnecessarily touching the working\n+\t\t * tree file.  It's not a likely enough scenario that I\n+\t\t * want to code up the checks for it and a better fix is\n+\t\t * available if we restructure how unpack_trees() and\n+\t\t * merge-recursive interoperate anyway, so punting for\n+\t\t * now...\n+\t\t */\n+\t\tremove_file(o, 0, collide_path, 0);\n+\t}\n+\n+\t/* Store things in diff_filespecs for functions that need it */\n+\tmemset(&a, 0, sizeof(struct diff_filespec));\n+\tmemset(&b, 0, sizeof(struct diff_filespec));\n+\tnull.path = a.path = b.path = (char *)collide_path;\n+\toidcpy(&null.oid, &null_oid);\n+\tnull.mode = 0;\n+\toidcpy(&a.oid, a_oid);\n+\ta.mode = a_mode;\n+\ta.oid_valid = 1;\n+\toidcpy(&b.oid, b_oid);\n+\tb.mode = b_mode;\n+\tb.oid_valid = 1;\n+\n+\tif (merge_mode_and_contents(o, &null, &a, &b, collide_path,\n+\t\t\t\t    branch1, branch2, o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tmfi.clean &= !alt_path;\n+\tif (update_file(o, mfi.clean, &mfi.oid, mfi.mode, update_path))\n+\t\treturn -1;\n+\tif (!mfi.clean && !o->call_depth &&\n+\t    update_stages(o, collide_path, NULL, &a, &b))\n+\t\treturn -1;\n+\tfree(alt_path);\n+\t/*\n+\t * FIXME: If both a & b both started with conflicts (only possible\n+\t * if they came from a rename/rename(2to1)), but had IDENTICAL\n+\t * contents including those conflicts, then in the next line we claim\n+\t * it was clean.  If someone cares about this case, we should have the\n+\t * caller notify us if we started with conflicts.\n+\t */\n+\treturn mfi.clean;\n+}\n+#endif\n+\n static int handle_file(struct merge_options *o,\n \t\t\tstruct diff_filespec *rename,\n \t\t\tint stage,\n-- \n2.19.0.3.g98f21ceff2.dirty\n\n"},{"id":"360393","messageId":"20181014020537.17991-3-newren@gmail.com","threadId":"49559","inReplyTo":"20181014020537.17991-1-newren@gmail.com","subject":"[RFC PATCH v2 2/7] t6036, t6042: testcases for rename collision of already conflicting files","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-14T02:05:32Z","receivedAt":"2018-10-14T02:05:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When a single file is renamed, it can also be modified, yielding the\npossibility of that renamed file having content conflicts.  If two\ndifferent such files are renamed into the same location, then two-way\nmerging those files may result in nested conflicts.  Add a testcase that\nmakes sure we get this case correct, and uses different lengths of\nconflict markers to differentiate between the different nestings.\n\nAlso add another case with an extra (i.e. third) level of conflict\nmarkers due to using merge.conflictstyle=diff3 and the virtual merge\nbase also having conflicts present.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh    | 194 +++++++++++++++++++++++++++\n t/t6042-merge-rename-corner-cases.sh | 118 ++++++++++++++++\n 2 files changed, 312 insertions(+)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 276b4e8792..a4c8041448 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1553,4 +1553,198 @@ test_expect_success \"check virtual merge base with nested conflicts\" '\n \t)\n '\n \n+# Setup:\n+#          L1---L2\n+#         /  \\ /  \\\n+#   master    X    ?\n+#         \\  / \\  /\n+#          R1---R2\n+#\n+# Where:\n+#   master has two files, named 'b' and 'a'\n+#   branches L1 and R1 both modify each of the two files in conflicting ways\n+#\n+#   L2 is a merge of R1 into L1; more on it later.\n+#   R2 is a merge of L1 into R1; more on it later.\n+#\n+#   X is an auto-generated merge-base used when merging L2 and R2.\n+#   since X is a merge of L1 and R1, it has conflicting versions of each file\n+#\n+#   More about L2 and R2:\n+#     - both resolve the conflicts in 'b' and 'a' differently\n+#     - L2 renames 'b' to 'm'\n+#     - R2 renames 'a' to 'm'\n+#\n+#   In the end, in file 'm' we have four different conflicting files (from\n+#   two versions of 'b' and two of 'a').  In addition, if\n+#   merge.conflictstyle is diff3, then the base version also has\n+#   conflict markers of its own, leading to a total of three levels of\n+#   conflict markers.  This is a pretty weird corner case, but we just want\n+#   to ensure that we handle it as well as practical.\n+\n+test_expect_success \"setup nested conflicts\" '\n+\ttest_create_repo nested_conflicts &&\n+\t(\n+\t\tcd nested_conflicts &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >initial &&\n+\n+\t\tcp initial b_L1 &&\n+\t\tcp initial b_R1 &&\n+\t\tcp initial b_L2 &&\n+\t\tcp initial b_R2 &&\n+\t\tcp initial a_L1 &&\n+\t\tcp initial a_R1 &&\n+\t\tcp initial a_L2 &&\n+\t\tcp initial a_R2 &&\n+\n+\t\ttest_write_lines b b_L1 >>b_L1 &&\n+\t\ttest_write_lines b b_R1 >>b_R1 &&\n+\t\ttest_write_lines b b_L2 >>b_L2 &&\n+\t\ttest_write_lines b b_R2 >>b_R2 &&\n+\t\ttest_write_lines a a_L1 >>a_L1 &&\n+\t\ttest_write_lines a a_R1 >>a_R1 &&\n+\t\ttest_write_lines a a_L2 >>a_L2 &&\n+\t\ttest_write_lines a a_R2 >>a_R2 &&\n+\n+\t\t# Setup original commit (or merge-base), consisting of\n+\t\t# files named \"b\" and \"a\"\n+\t\tcp initial b &&\n+\t\tcp initial a &&\n+\t\techo b >>b &&\n+\t\techo a >>a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m initial &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Handle the left side\n+\t\tgit checkout L &&\n+\t\tmv -f b_L1 b &&\n+\t\tmv -f a_L1 a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m \"version L1 of files\" &&\n+\t\tgit tag L1 &&\n+\n+\t\t# Handle the right side\n+\t\tgit checkout R &&\n+\t\tmv -f b_R1 b &&\n+\t\tmv -f a_R1 a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m \"verson R1 of files\" &&\n+\t\tgit tag R1 &&\n+\n+\t\t# Create first merge on left side\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git merge R1 &&\n+\t\tmv -f b_L2 b &&\n+\t\tmv -f a_L2 a &&\n+\t\tgit add b a &&\n+\t\tgit mv b m &&\n+\t\ttest_tick && git commit -m \"left merge, rename b->m\" &&\n+\t\tgit tag L2 &&\n+\n+\t\t# Create first merge on right side\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git merge L1 &&\n+\t\tmv -f b_R2 b &&\n+\t\tmv -f a_R2 a &&\n+\t\tgit add b a &&\n+\t\tgit mv a m &&\n+\t\ttest_tick && git commit -m \"right merge, rename a->m\" &&\n+\t\tgit tag R2\n+\t)\n+'\n+\n+test_expect_failure \"check nested conflicts\" '\n+\t(\n+\t\tcd nested_conflicts &&\n+\n+\t\tgit clean -f &&\n+\t\tgit checkout L2^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R2^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Create a and b from virtual merge base X\n+\t\tgit cat-file -p master:a >base &&\n+\t\tgit cat-file -p L1:a >ours &&\n+\t\tgit cat-file -p R1:a >theirs &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tours  \\\n+\t\t\tbase  \\\n+\t\t\ttheirs &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >vmb_a &&\n+\n+\t\tgit cat-file -p master:b >base &&\n+\t\tgit cat-file -p L1:b >ours &&\n+\t\tgit cat-file -p R1:b >theirs &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tours  \\\n+\t\t\tbase  \\\n+\t\t\ttheirs &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >vmb_b &&\n+\n+\t\t# Compare :2:m to expected values\n+\t\tgit cat-file -p L2:m >ours &&\n+\t\tgit cat-file -p R2:b >theirs &&\n+\t\ttest_must_fail git merge-file --diff3  \\\n+\t\t\t-L \"HEAD:m\"                    \\\n+\t\t\t-L \"merged common ancestors:b\" \\\n+\t\t\t-L \"R2^0:b\"                    \\\n+\t\t\tours                           \\\n+\t\t\tvmb_b                          \\\n+\t\t\ttheirs                         &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >m_stage_2 &&\n+\t\tgit cat-file -p :2:m >actual &&\n+\t\ttest_cmp m_stage_2 actual &&\n+\n+\t\t# Compare :3:m to expected values\n+\t\tgit cat-file -p L2:a >ours &&\n+\t\tgit cat-file -p R2:m >theirs &&\n+\t\ttest_must_fail git merge-file --diff3  \\\n+\t\t\t-L \"HEAD:a\"                    \\\n+\t\t\t-L \"merged common ancestors:a\" \\\n+\t\t\t-L \"R2^0:m\"                    \\\n+\t\t\tours                           \\\n+\t\t\tvmb_a                          \\\n+\t\t\ttheirs                         &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >m_stage_3 &&\n+\t\tgit cat-file -p :3:m >actual &&\n+\t\ttest_cmp m_stage_3 actual &&\n+\n+\t\t# Compare m to expected contents\n+\t\t>empty &&\n+\t\tcp -a m_stage_2 expected_final_m &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"HEAD\"                     \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"R2^0\"                     \\\n+\t\t\texpected_final_m              \\\n+\t\t\tempty                         \\\n+\t\t\tm_stage_3                     &&\n+\t\ttest_cmp expected_final_m m\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b6fed2cb9a..5e69e60b48 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -1099,4 +1099,122 @@ test_conflicts_with_adds_and_renames rename add    failure\n test_conflicts_with_adds_and_renames add    rename failure\n test_conflicts_with_adds_and_renames add    add    success\n \n+# Setup:\n+#          L\n+#         / \\\n+#   master   ?\n+#         \\ /\n+#          R\n+#\n+# Where:\n+#   master has two files, named 'one' and 'two'.\n+#   branches L and R both modify 'one', in conflicting ways.\n+#   branches L and R both modify 'two', in conflicting ways.\n+#   branch L also renames 'one' to 'three'.\n+#   branch R also renames 'two' to 'three'.\n+#\n+#   So, we have four different conflicting files that all end up at path\n+#   'three'.\n+test_expect_success \"setup nested conflicts from rename/rename(2to1)\" '\n+\ttest_create_repo nested_conflicts_from_rename_rename &&\n+\t(\n+\t\tcd nested_conflicts_from_rename_rename &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >file_v1 &&\n+\n+\t\tcp file_v1 file_v2 &&\n+\t\tcp file_v1 file_v3 &&\n+\t\tcp file_v1 file_v4 &&\n+\t\tcp file_v1 file_v5 &&\n+\t\tcp file_v1 file_v6 &&\n+\n+\t\techo one  >>file_v1 &&\n+\t\techo uno  >>file_v2 &&\n+\t\techo eins >>file_v3 &&\n+\n+\t\techo two  >>file_v4 &&\n+\t\techo dos  >>file_v5 &&\n+\t\techo zwei >>file_v6 &&\n+\n+\t\t# Setup original commit (or merge-base), consisting of\n+\t\t# files named \"one\" and \"two\".\n+\t\tmv file_v1 one &&\n+\t\tmv file_v4 two &&\n+\t\tgit add one two &&\n+\t\ttest_tick && git commit -m english &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Handle the left side\n+\t\tgit checkout L &&\n+\t\tgit mv one three &&\n+\t\tmv -f file_v2 three &&\n+\t\tmv -f file_v5 two &&\n+\t\tgit add two three &&\n+\t\ttest_tick && git commit -m spanish &&\n+\n+\t\t# Handle the right side\n+\t\tgit checkout R &&\n+\t\tgit mv two three &&\n+\t\tmv -f file_v3 one &&\n+\t\tmv -f file_v6 three &&\n+\t\tgit add one three &&\n+\t\ttest_tick && git commit -m german\n+\t)\n+'\n+\n+test_expect_failure \"check nested conflicts from rename/rename(2to1)\" '\n+\t(\n+\t\tcd nested_conflicts_from_rename_rename &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git merge -s recursive R^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Compare :2:three to expected values\n+\t\tgit cat-file -p master:one >base &&\n+\t\tgit cat-file -p L:three >ours &&\n+\t\tgit cat-file -p R:one >theirs &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD:three\"  -L \"\"  -L \"R^0:one\" \\\n+\t\t\tours             base   theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1/\" ours >L-three &&\n+\t\tgit cat-file -p :2:three >expect &&\n+\t\ttest_cmp expect L-three &&\n+\n+\t\t# Compare :2:three to expected values\n+\t\tgit cat-file -p master:two >base &&\n+\t\tgit cat-file -p L:two >ours &&\n+\t\tgit cat-file -p R:three >theirs &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD:two\"  -L \"\"  -L \"R^0:three\" \\\n+\t\t\tours           base   theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1/\" ours >R-three &&\n+\t\tgit cat-file -p :3:three >expect &&\n+\t\ttest_cmp expect R-three &&\n+\n+\t\t# Compare three to expected contents\n+\t\t>empty &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD\"  -L \"\"  -L \"R^0\" \\\n+\t\t\tL-three    empty  R-three &&\n+\t\ttest_cmp three L-three\n+\t)\n+'\n+\n test_done\n-- \n2.19.0.3.g98f21ceff2.dirty\n\n"},{"id":"360394","messageId":"20181014020537.17991-7-newren@gmail.com","threadId":"49559","inReplyTo":"20181014020537.17991-1-newren@gmail.com","subject":"[RFC PATCH v2 6/7] merge-recursive: use handle_file_collision for add/add conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-14T02:05:36Z","receivedAt":"2018-10-14T02:05:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This results in no-net change of behavior, it simply ensures that all\nfile-collision conflict handling types are being handled the same by\ncalling the same function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 29 +++++++++++++++++++++--------\n 1 file changed, 21 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ead6054a75..c78b347112 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3355,14 +3355,27 @@ static int process_entry(struct merge_options *o,\n \t\t\t\tclean_merge = -1;\n \t\t}\n \t} else if (a_oid && b_oid) {\n-\t\t/* Case C: Added in both (check for same permissions) and */\n-\t\t/* case D: Modified in both, but differently. */\n-\t\tint is_dirty = 0; /* unpack_trees would have bailed if dirty */\n-\t\tclean_merge = handle_content_merge(o, path, is_dirty,\n-\t\t\t\t\t\t   o_oid, o_mode,\n-\t\t\t\t\t\t   a_oid, a_mode,\n-\t\t\t\t\t\t   b_oid, b_mode,\n-\t\t\t\t\t\t   NULL);\n+\t\tif (!o_oid) {\n+\t\t\t/* Case C: Added in both (check for same permissions) */\n+\t\t\toutput(o, 1,\n+\t\t\t       _(\"CONFLICT (add/add): Merge conflict in %s\"),\n+\t\t\t       path);\n+\t\t\tclean_merge = handle_file_collision(o,\n+\t\t\t\t\t\t\t    path, NULL, NULL,\n+\t\t\t\t\t\t\t    o->branch1,\n+\t\t\t\t\t\t\t    o->branch2,\n+\t\t\t\t\t\t\t    a_oid, a_mode,\n+\t\t\t\t\t\t\t    b_oid, b_mode);\n+\t\t} else {\n+\t\t\t/* case D: Modified in both, but differently. */\n+\t\t\tint is_dirty = 0; /* unpack_trees would have bailed if dirty */\n+\t\t\tclean_merge = handle_content_merge(o, path,\n+\t\t\t\t\t\t\t   is_dirty,\n+\t\t\t\t\t\t\t   o_oid, o_mode,\n+\t\t\t\t\t\t\t   a_oid, a_mode,\n+\t\t\t\t\t\t\t   b_oid, b_mode,\n+\t\t\t\t\t\t\t   NULL);\n+\t\t}\n \t} else if (!o_oid && !a_oid && !b_oid) {\n \t\t/*\n \t\t * this entry was deleted altogether. a_mode == 0 means\n-- \n2.19.0.3.g98f21ceff2.dirty\n\n"},{"id":"360395","messageId":"20181014020537.17991-6-newren@gmail.com","threadId":"49559","inReplyTo":"20181014020537.17991-1-newren@gmail.com","subject":"[RFC PATCH v2 5/7] merge-recursive: improve handling for rename/rename(2to1) conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-14T02:05:35Z","receivedAt":"2018-10-14T02:05:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This makes the rename/rename(2to1) conflicts use the new\nhandle_file_collision() function.  Since that function was based\noriginally on the rename/rename(2to1) handling code, the main\ndifferences here are in what was added.  In particular:\n\n  * Instead of storing files at collide_path~HEAD and collide_path~MERGE,\n    the files are two-way merged and recorded at collide_path.\n\n  * Instead of recording the version of the renamed file that existed\n    on the renamed side in the index (thus ignoring any changes that\n    were made to the file on the side of history without the rename),\n    we do a three-way content merge on the renamed path, then store\n    that at either stage 2 or stage 3.\n\n  * Note that since the content merge for each rename may have conflicts,\n    and then we have to merge the two renamed files, we can end up with\n    nested conflict markers.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 104 ++++-----------------------\n t/t6036-recursive-corner-cases.sh    |  12 +---\n t/t6042-merge-rename-corner-cases.sh |  38 ++++++----\n t/t6043-merge-rename-directories.sh  |  83 ++++++++++++---------\n 4 files changed, 89 insertions(+), 148 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0805095168..ead6054a75 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -696,27 +696,6 @@ static int update_stages(struct merge_options *opt, const char *path,\n \treturn 0;\n }\n \n-static int update_stages_for_stage_data(struct merge_options *opt,\n-\t\t\t\t\tconst char *path,\n-\t\t\t\t\tconst struct stage_data *stage_data)\n-{\n-\tstruct diff_filespec o, a, b;\n-\n-\to.mode = stage_data->stages[1].mode;\n-\toidcpy(&o.oid, &stage_data->stages[1].oid);\n-\n-\ta.mode = stage_data->stages[2].mode;\n-\toidcpy(&a.oid, &stage_data->stages[2].oid);\n-\n-\tb.mode = stage_data->stages[3].mode;\n-\toidcpy(&b.oid, &stage_data->stages[3].oid);\n-\n-\treturn update_stages(opt, path,\n-\t\t\t     is_null_oid(&o.oid) ? NULL : &o,\n-\t\t\t     is_null_oid(&a.oid) ? NULL : &a,\n-\t\t\t     is_null_oid(&b.oid) ? NULL : &b);\n-}\n-\n static void update_entry(struct stage_data *entry,\n \t\t\t struct diff_filespec *o,\n \t\t\t struct diff_filespec *a,\n@@ -1870,7 +1849,6 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \tchar *path_side_2_desc;\n \tstruct merge_file_info mfi_c1;\n \tstruct merge_file_info mfi_c2;\n-\tint ret;\n \n \toutput(o, 1, _(\"CONFLICT (rename/rename): \"\n \t       \"Rename %s->%s in %s. \"\n@@ -1878,81 +1856,22 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \t       a->path, c1->path, ci->branch1,\n \t       b->path, c2->path, ci->branch2);\n \n-\tremove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));\n-\tremove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));\n-\n \tpath_side_1_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n \tpath_side_2_desc = xstrfmt(\"version of %s from %s\", path, b->path);\n \tif (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,\n \t\t\t\t    o->branch1, o->branch2,\n-\t\t\t\t    o->call_depth * 2, &mfi_c1) ||\n+\t\t\t\t    1 + o->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,\n \t\t\t\t    o->branch1, o->branch2,\n-\t\t\t\t    o->call_depth * 2, &mfi_c2))\n+\t\t\t\t    1 + o->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n \n-\tif (o->call_depth) {\n-\t\t/*\n-\t\t * If mfi_c1.clean && mfi_c2.clean, then it might make\n-\t\t * sense to do a two-way merge of those results.  But, I\n-\t\t * think in all cases, it makes sense to have the virtual\n-\t\t * merge base just undo the renames; they can be detected\n-\t\t * again later for the non-recursive merge.\n-\t\t */\n-\t\tremove_file(o, 0, path, 0);\n-\t\tret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);\n-\t\tif (!ret)\n-\t\t\tret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,\n-\t\t\t\t\t  b->path);\n-\t} else {\n-\t\tchar *new_path1 = unique_path(o, path, ci->branch1);\n-\t\tchar *new_path2 = unique_path(o, path, ci->branch2);\n-\t\toutput(o, 1, _(\"Renaming %s to %s and %s to %s instead\"),\n-\t\t       a->path, new_path1, b->path, new_path2);\n-\t\tif (was_dirty(o, path))\n-\t\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n-\t\t\t       path);\n-\t\telse if (would_lose_untracked(path))\n-\t\t\t/*\n-\t\t\t * Only way we get here is if both renames were from\n-\t\t\t * a directory rename AND user had an untracked file\n-\t\t\t * at the location where both files end up after the\n-\t\t\t * two directory renames.  See testcase 10d of t6043.\n-\t\t\t */\n-\t\t\toutput(o, 1, _(\"Refusing to lose untracked file at \"\n-\t\t\t\t       \"%s, even though it's in the way.\"),\n-\t\t\t       path);\n-\t\telse\n-\t\t\tremove_file(o, 0, path, 0);\n-\t\tret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);\n-\t\tif (!ret)\n-\t\t\tret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,\n-\t\t\t\t\t  new_path2);\n-\t\t/*\n-\t\t * unpack_trees() actually populates the index for us for\n-\t\t * \"normal\" rename/rename(2to1) situtations so that the\n-\t\t * correct entries are at the higher stages, which would\n-\t\t * make the call below to update_stages_for_stage_data\n-\t\t * unnecessary.  However, if either of the renames came\n-\t\t * from a directory rename, then unpack_trees() will not\n-\t\t * have gotten the right data loaded into the index, so we\n-\t\t * need to do so now.  (While it'd be tempting to move this\n-\t\t * call to update_stages_for_stage_data() to\n-\t\t * apply_directory_rename_modifications(), that would break\n-\t\t * our intermediate calls to would_lose_untracked() since\n-\t\t * those rely on the current in-memory index.  See also the\n-\t\t * big \"NOTE\" in update_stages()).\n-\t\t */\n-\t\tif (update_stages_for_stage_data(o, path, ci->dst_entry1))\n-\t\t\tret = -1;\n-\n-\t\tfree(new_path2);\n-\t\tfree(new_path1);\n-\t}\n-\n-\treturn ret;\n+\treturn handle_file_collision(o, path, a->path, b->path,\n+\t\t\t\t     ci->branch1, ci->branch2,\n+\t\t\t\t     &mfi_c1.oid, mfi_c1.mode,\n+\t\t\t\t     &mfi_c2.oid, mfi_c2.mode);\n }\n \n /*\n@@ -3361,9 +3280,14 @@ static int process_entry(struct merge_options *o,\n \t\t\t\tclean_merge = -1;\n \t\t\tbreak;\n \t\tcase RENAME_TWO_FILES_TO_ONE:\n-\t\t\tclean_merge = 0;\n-\t\t\tif (handle_rename_rename_2to1(o, conflict_info))\n-\t\t\t\tclean_merge = -1;\n+\t\t\t/*\n+\t\t\t * Probably unclean merge, but if the two renamed\n+\t\t\t * files merge cleanly and the two resulting files\n+\t\t\t * can then be two-way merged cleanly, I guess it's\n+\t\t\t * a clean merge?\n+\t\t\t */\n+\t\t\tclean_merge = handle_rename_rename_2to1(o,\n+\t\t\t\t\t\t\t\tconflict_info);\n \t\t\tbreak;\n \t\tdefault:\n \t\t\tentry->processed = 0;\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex de35ae81ba..aebc74f921 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -64,15 +64,12 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tL2:three   R2:three \\\n \t\t\tL2:three   R2:three &&\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n-\t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -140,15 +137,12 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tL2:three   R2:three \\\n \t\t\tL2:three   R2:three &&\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n-\t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -1663,7 +1657,7 @@ test_expect_success \"setup nested conflicts\" '\n \t)\n '\n \n-test_expect_failure \"check nested conflicts\" '\n+test_expect_success \"check nested conflicts\" '\n \t(\n \t\tcd nested_conflicts &&\n \ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex f1c4a06dd6..4cfcde3082 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -464,17 +464,28 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' '\n \t\tgit ls-files -u c >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\ttest_path_is_missing a &&\n \t\ttest_path_is_missing b &&\n-\t\ttest_path_is_file c~HEAD &&\n-\t\ttest_path_is_file c~C^0 &&\n \n-\t\tgit rev-parse >expect   \\\n-\t\t\tC:a     B:b     &&\n-\t\tgit hash-object >actual \\\n-\t\t\tc~HEAD  c~C^0   &&\n+\t\tgit rev-parse >expect  \\\n+\t\t\tC:a     B:b    &&\n+\t\tgit rev-parse >actual  \\\n+\t\t\t:2:c    :3:c   &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p :2:c >>ours &&\n+\t\tgit cat-file -p :3:c >>theirs &&\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 \"C^0\" \\\n+\t\t\tours empty theirs &&\n+\t\tgit hash-object c >actual &&\n+\t\tgit hash-object ours >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -940,7 +951,6 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename\n test_conflicts_with_adds_and_renames() {\n \tsideL=$1\n \tsideR=$2\n-\texpect=$3\n \n \t# Setup:\n \t#          L\n@@ -1048,7 +1058,7 @@ test_conflicts_with_adds_and_renames() {\n \t\t)\n \t'\n \n-\ttest_expect_$expect \"check simple $sideL/$sideR conflict\" '\n+\ttest_expect_success \"check simple $sideL/$sideR conflict\" '\n \t\t(\n \t\t\tcd simple_${sideL}_${sideR} &&\n \n@@ -1094,10 +1104,10 @@ test_conflicts_with_adds_and_renames() {\n \t'\n }\n \n-test_conflicts_with_adds_and_renames rename rename failure\n-test_conflicts_with_adds_and_renames rename add    success\n-test_conflicts_with_adds_and_renames add    rename success\n-test_conflicts_with_adds_and_renames add    add    success\n+test_conflicts_with_adds_and_renames rename rename\n+test_conflicts_with_adds_and_renames rename add\n+test_conflicts_with_adds_and_renames add    rename\n+test_conflicts_with_adds_and_renames add    add\n \n # Setup:\n #          L\n@@ -1168,7 +1178,7 @@ test_expect_success \"setup nested conflicts from rename/rename(2to1)\" '\n \t)\n '\n \n-test_expect_failure \"check nested conflicts from rename/rename(2to1)\" '\n+test_expect_success \"check nested conflicts from rename/rename(2to1)\" '\n \t(\n \t\tcd nested_conflicts_from_rename_rename &&\n \ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex 4a71f17edd..fedaeafc55 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -278,7 +278,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n &&\n@@ -293,15 +293,16 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con\n \t\t\t A:y/wham  B:z/wham &&\n \t\ttest_cmp expect actual &&\n \n-\t\ttest_path_is_missing x/wham &&\n-\t\ttest_path_is_file x/wham~HEAD &&\n-\t\ttest_path_is_file x/wham~B^0 &&\n-\n-\t\tgit hash-object >actual \\\n-\t\t\tx/wham~HEAD x/wham~B^0 &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tA:y/wham    B:z/wham &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in x/wham is as expected\n+\t\tgit cat-file -p :2:x/wham >expect &&\n+\t\tgit cat-file -p :3:x/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect x/wham\n \t)\n '\n \n@@ -1670,7 +1671,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/b :0:y/c :2:y/d :3:y/d &&\n@@ -1678,15 +1679,16 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r\n \t\t\t O:z/b  O:z/c  O:w/d  O:x/d &&\n \t\ttest_cmp expect actual &&\n \n-\t\ttest_path_is_missing y/d &&\n-\t\ttest_path_is_file y/d~HEAD &&\n-\t\ttest_path_is_file y/d~B^0 &&\n-\n-\t\tgit hash-object >actual \\\n-\t\t\ty/d~HEAD y/d~B^0 &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:w/d    O:x/d &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in y/d is as expected\n+\t\tgit cat-file -p :2:y/d >expect &&\n+\t\tgit cat-file -p :3:y/d >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/d\n \t)\n '\n \n@@ -3165,7 +3167,7 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)\n #   Commit O: z/{a,b,c_1},        x/{d,e,f_2}\n #   Commit A: y/{a,b},            x/{d,e,f_2,wham_1} + untracked y/wham\n #   Commit B: z/{a,b,c_1,wham_2}, y/{d,e}\n-#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+\n+#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~merged}+\n #             CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham\n #             ERROR_MSG(Refusing to lose untracked file at y/wham)\n \n@@ -3219,7 +3221,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham &&\n@@ -3232,11 +3234,16 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '\n \t\techo important >expect &&\n \t\ttest_cmp expect y/wham &&\n \n-\t\tgit hash-object >actual \\\n-\t\t\ty/wham~B^0 y/wham~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/f      O:z/c &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in y/wham~merged is as expected\n+\t\tgit cat-file -p :2:y/wham >expect &&\n+\t\tgit cat-file -p :3:y/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/wham~merged\n \t)\n '\n \n@@ -3689,7 +3696,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n #   Commit O: z/{a,b},        x/{c_1,d_2}\n #   Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods\n #   Commit B: z/{a,b,wham_2}, x/c_1\n-#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} +\n+#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~merged} +\n #             y/wham with dirty changes from before merge +\n #             CONFLICT(rename/rename) x/c vs x/d -> y/wham\n #             ERROR_MSG(Refusing to lose dirty file at y/wham)\n@@ -3741,24 +3748,30 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\ttest_seq 1 10 >expected &&\n \t\techo important >>expected &&\n \t\ttest_cmp expected y/wham &&\n \n \t\ttest_must_fail git rev-parse :1:y/wham &&\n-\t\tgit hash-object >actual \\\n-\t\t\ty/wham~B^0 y/wham~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/d      O:x/c &&\n-\t\ttest_cmp expect actual &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/a :0:y/b :2:y/wham :3:y/wham &&\n \t\tgit rev-parse >expect \\\n \t\t\t O:z/a  O:z/b  O:x/c     O:x/d &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in y/wham~merged is as expected\n+\t\tgit cat-file -p :2:y/wham >expect &&\n+\t\tgit cat-file -p :3:y/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/wham~merged\n \t)\n '\n \n-- \n2.19.0.3.g98f21ceff2.dirty\n\n"},{"id":"360396","messageId":"20181014020537.17991-8-newren@gmail.com","threadId":"49559","inReplyTo":"20181014020537.17991-1-newren@gmail.com","subject":"[RFC PATCH v2 7/7] merge-recursive: improve rename/rename(1to2)/add[/add] handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-14T02:05:37Z","receivedAt":"2018-10-14T02:05:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When we have a rename/rename(1to2) conflict, each of the renames can\ncollide with a file addition.  Each of these rename/add conflicts suffered\nfrom the same kinds of problems that normal rename/add suffered from.\nMake the code use handle_file_conflicts() as well so that we get all the\nsame fixes and consistent behavior between the different conflict types.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 154 +++++++++++++--------------\n t/t6042-merge-rename-corner-cases.sh |  29 +++--\n t/t6043-merge-rename-directories.sh  |  24 +++--\n 3 files changed, 113 insertions(+), 94 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c78b347112..59811116b6 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1709,80 +1709,17 @@ static int handle_rename_add(struct merge_options *o,\n \t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n }\n \n-static int handle_file(struct merge_options *o,\n-\t\t\tstruct diff_filespec *rename,\n-\t\t\tint stage,\n-\t\t\tstruct rename_conflict_info *ci)\n-{\n-\tchar *dst_name = rename->path;\n-\tstruct stage_data *dst_entry;\n-\tconst char *cur_branch, *other_branch;\n-\tstruct diff_filespec other;\n-\tstruct diff_filespec *add;\n-\tint ret;\n-\n-\tif (stage == 2) {\n-\t\tdst_entry = ci->dst_entry1;\n-\t\tcur_branch = ci->branch1;\n-\t\tother_branch = ci->branch2;\n-\t} else {\n-\t\tdst_entry = ci->dst_entry2;\n-\t\tcur_branch = ci->branch2;\n-\t\tother_branch = ci->branch1;\n-\t}\n-\n-\tadd = filespec_from_entry(&other, dst_entry, stage ^ 1);\n-\tif (add) {\n-\t\tint ren_src_was_dirty = was_dirty(o, rename->path);\n-\t\tchar *add_name = unique_path(o, rename->path, other_branch);\n-\t\tif (update_file(o, 0, &add->oid, add->mode, add_name))\n-\t\t\treturn -1;\n-\n-\t\tif (ren_src_was_dirty) {\n-\t\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n-\t\t\t       rename->path);\n-\t\t}\n-\t\t/*\n-\t\t * Because the double negatives somehow keep confusing me...\n-\t\t *    1) update_wd iff !ren_src_was_dirty.\n-\t\t *    2) no_wd iff !update_wd\n-\t\t *    3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty\n-\t\t */\n-\t\tremove_file(o, 0, rename->path, ren_src_was_dirty);\n-\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t} else {\n-\t\tif (dir_in_way(rename->path, !o->call_depth, 0)) {\n-\t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t\t\toutput(o, 1, _(\"%s is a directory in %s adding as %s instead\"),\n-\t\t\t       rename->path, other_branch, dst_name);\n-\t\t} else if (!o->call_depth &&\n-\t\t\t   would_lose_untracked(rename->path)) {\n-\t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t\t\toutput(o, 1, _(\"Refusing to lose untracked file at %s; \"\n-\t\t\t\t       \"adding as %s instead\"),\n-\t\t\t       rename->path, dst_name);\n-\t\t}\n-\t}\n-\tif ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))\n-\t\t; /* fall through, do allow dst_name to be released */\n-\telse if (stage == 2)\n-\t\tret = update_stages(o, rename->path, NULL, rename, add);\n-\telse\n-\t\tret = update_stages(o, rename->path, NULL, add, rename);\n-\n-\tif (dst_name != rename->path)\n-\t\tfree(dst_name);\n-\n-\treturn ret;\n-}\n-\n static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t     struct rename_conflict_info *ci)\n {\n \t/* One file was renamed in both branches, but to different names. */\n+\tstruct merge_file_info mfi;\n+\tstruct diff_filespec other;\n+\tstruct diff_filespec *add;\n \tstruct diff_filespec *one = ci->pair1->one;\n \tstruct diff_filespec *a = ci->pair1->two;\n \tstruct diff_filespec *b = ci->pair2->two;\n+\tchar *path_desc;\n \n \toutput(o, 1, _(\"CONFLICT (rename/rename): \"\n \t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n@@ -1790,15 +1727,16 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t       one->path, a->path, ci->branch1,\n \t       one->path, b->path, ci->branch2,\n \t       o->call_depth ? _(\" (left unresolved)\") : \"\");\n-\tif (o->call_depth) {\n-\t\tstruct merge_file_info mfi;\n-\t\tstruct diff_filespec other;\n-\t\tstruct diff_filespec *add;\n-\t\tif (merge_mode_and_contents(o, one, a, b, one->path,\n-\t\t\t\t\t    ci->branch1, ci->branch2,\n-\t\t\t\t\t    o->call_depth * 2, &mfi))\n-\t\t\treturn -1;\n \n+\tpath_desc = xstrfmt(\"%s and %s, both renamed from %s\",\n+\t\t\t    a->path, b->path, one->path);\n+\tif (merge_mode_and_contents(o, one, a, b, path_desc,\n+\t\t\t\t    ci->branch1, ci->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tfree(path_desc);\n+\n+\tif (o->call_depth) {\n \t\t/*\n \t\t * FIXME: For rename/add-source conflicts (if we could detect\n \t\t * such), this is wrong.  We should instead find a unique\n@@ -1830,8 +1768,70 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t}\n \t\telse\n \t\t\tremove_file_from_cache(b->path);\n-\t} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))\n-\t\treturn -1;\n+\t} else {\n+\t\t/*\n+\t\t * For each destination path, we need to see if there is a\n+\t\t * rename/add collision.  If not, we can write the file out\n+\t\t * to the specified location.\n+\t\t */\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);\n+\t\tif (add) {\n+\t\t\tif (handle_file_collision(o, a->path,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  ci->branch1, ci->branch2,\n+\t\t\t\t\t\t  &mfi.oid, mfi.mode,\n+\t\t\t\t\t\t  &add->oid, add->mode) < 0)\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tchar *new_path = NULL;\n+\t\t\tif (dir_in_way(a->path, !o->call_depth, 0)) {\n+\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n+\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t\t\t       \"as %s instead\"),\n+\t\t\t\t       a->path, ci->branch2, new_path);\n+\t\t\t} else if (would_lose_untracked(a->path)) {\n+\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n+\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t\t\t       a->path, new_path);\n+\t\t\t}\n+\n+\t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))\n+\t\t\t\treturn -1;\n+\t\t\tfree(new_path);\n+\t\t\tif (update_stages(o, a->path, NULL, a, NULL))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);\n+\t\tif (add) {\n+\t\t\tif (handle_file_collision(o, b->path,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  ci->branch1, ci->branch2,\n+\t\t\t\t\t\t  &add->oid, add->mode,\n+\t\t\t\t\t\t  &mfi.oid, mfi.mode) < 0)\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tchar *new_path = NULL;\n+\t\t\tif (dir_in_way(b->path, !o->call_depth, 0)) {\n+\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n+\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t\t\t       \"as %s instead\"),\n+\t\t\t\t       b->path, ci->branch1, new_path);\n+\t\t\t} else if (would_lose_untracked(b->path)) {\n+\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n+\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t\t\t       b->path, new_path);\n+\t\t\t}\n+\n+\t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))\n+\t\t\t\treturn -1;\n+\t\t\tfree(new_path);\n+\t\t\tif (update_stages(o, b->path, NULL, NULL, b))\n+\t\t\t\treturn -1;\n+\t\t}\n+\t}\n \n \treturn 0;\n }\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 4cfcde3082..534f7b55a1 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -684,7 +684,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t\tgit ls-files -u c >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 5 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect               \\\n \t\t\tA:a   C:b   B:b   C:c   B:c &&\n@@ -692,14 +692,27 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t\t\t:1:a  :2:b  :3:b  :2:c  :3:c &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit rev-parse >expect               \\\n-\t\t\tC:c     B:c     C:b     B:b &&\n-\t\tgit hash-object >actual                \\\n-\t\t\tc~HEAD  c~B\\^0  b~HEAD  b~B\\^0 &&\n-\t\ttest_cmp expect actual &&\n+\t\t# Record some contents for re-doing merges\n+\t\tgit cat-file -p A:a >stuff &&\n+\t\tgit cat-file -p C:b >important_info &&\n+\t\tgit cat-file -p B:c >precious_data &&\n+\t\t>empty &&\n \n-\t\ttest_path_is_missing b &&\n-\t\ttest_path_is_missing c\n+\t\t# Test the merge in b\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\timportant_info empty stuff &&\n+\t\ttest_cmp important_info b &&\n+\n+\t\t# Test the merge in c\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\tstuff empty precious_data &&\n+\t\ttest_cmp stuff c\n \t)\n '\n \ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex fedaeafc55..5c01a0c14a 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -1078,7 +1078,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 6 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/b :0:y/c :0:y/e &&\n@@ -1094,9 +1094,9 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam\n \t\ttest_cmp expect actual &&\n \n \t\tgit hash-object >actual \\\n-\t\t\tw/d~HEAD w/d~B^0 z/d &&\n+\t\t\tz/d &&\n \t\tgit rev-parse >expect \\\n-\t\t\tO:x/d    B:w/d   O:x/d &&\n+\t\t\tO:x/d &&\n \t\ttest_cmp expect actual &&\n \t\ttest_path_is_missing x/d &&\n \t\ttest_path_is_file y/d &&\n@@ -3672,7 +3672,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 4 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\techo different >expected &&\n \t\techo mods >>expected &&\n@@ -3684,11 +3684,17 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n \t\t\t O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  A:y/c  O:x/c &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit hash-object >actual \\\n-\t\t\ty/c~B^0 y/c~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/c   A:y/c &&\n-\t\ttest_cmp expect actual\n+\t\t# See if y/c~merged has expected contents; requires manually\n+\t\t# doing the expected file merge\n+\t\tgit cat-file -p A:y/c >c1 &&\n+\t\tgit cat-file -p B:z/c >c2 &&\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\tc1 empty c2 &&\n+\t\ttest_cmp c1 y/c~merged\n \t)\n '\n \n-- \n2.19.0.3.g98f21ceff2.dirty\n\n"},{"id":"360969","messageId":"20181019193111.12051-4-newren@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-1-newren@gmail.com","subject":"[PATCH v3 3/8] merge-recursive: increase marker length with depth of recursion","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-19T19:31:06Z","receivedAt":"2018-10-19T19:31:21Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Later patches in this series will modify file collision conflict\nhandling (e.g. from rename/add and rename/rename(2to1) conflicts) so\nthat multiply nested conflict markers can arise even before considering\nconflicts in the virtual merge base.  Including the virtual merge base\nwill provide a way to get triply (or higher) nested conflict markers.\nThis new way to get nested conflict markers will force the need for a\nmore general mechanism to extend the length of conflict markers in order\nto differentiate between different nestings.\n\nAlong with this change to conflict marker length handling, we want to\nmake sure that we don't regress handling for other types of conflicts\nwith nested conflict markers.  Add a more involved testcase using\nmerge.conflictstyle=diff3, where not only does the virtual merge base\ncontain conflicts, but its virtual merge base does as well (i.e. a case\nwith triply nested conflict markers).  While there are multiple\nreasonable ways to handle nested conflict markers in the virtual merge\nbase for this type of situation, the easiest approach that dovetails\nwell with the new needs for the file collision conflict handling is to\nrequire that the length of the conflict markers increase with each\nsubsequent nesting.\n\nSubsequent patches which change the rename/add and rename/rename(2to1)\nconflict handling will modify the extra_marker_size flag appropriately\nfor their new needs.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n ll-merge.c                        |   4 +-\n ll-merge.h                        |   1 +\n merge-recursive.c                 |  25 +++--\n t/t6036-recursive-corner-cases.sh | 151 ++++++++++++++++++++++++++++++\n 4 files changed, 172 insertions(+), 9 deletions(-)\n\ndiff --git a/ll-merge.c b/ll-merge.c\nindex 0e2800f7bb..aabc1b5c2e 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -384,7 +384,9 @@ int ll_merge(mmbuffer_t *result_buf,\n \tif (opts->virtual_ancestor) {\n \t\tif (driver->recursive)\n \t\t\tdriver = find_ll_merge_driver(driver->recursive);\n-\t\tmarker_size += 2;\n+\t}\n+\tif (opts->extra_marker_size) {\n+\t\tmarker_size += opts->extra_marker_size;\n \t}\n \treturn driver->fn(driver, result_buf, path, ancestor, ancestor_label,\n \t\t\t  ours, our_label, theirs, their_label,\ndiff --git a/ll-merge.h b/ll-merge.h\nindex b72b19921e..5b4e158502 100644\n--- a/ll-merge.h\n+++ b/ll-merge.h\n@@ -11,6 +11,7 @@ struct ll_merge_options {\n \tunsigned virtual_ancestor : 1;\n \tunsigned variant : 2;\t/* favor ours, favor theirs, or union merge */\n \tunsigned renormalize : 1;\n+\tunsigned extra_marker_size;\n \tlong xdl_opts;\n };\n \ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 73b5710386..f795c92a69 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1058,7 +1058,8 @@ static int merge_3way(struct merge_options *o,\n \t\t      const struct diff_filespec *a,\n \t\t      const struct diff_filespec *b,\n \t\t      const char *branch1,\n-\t\t      const char *branch2)\n+\t\t      const char *branch2,\n+\t\t      const int extra_marker_size)\n {\n \tmmfile_t orig, src1, src2;\n \tstruct ll_merge_options ll_opts = {0};\n@@ -1066,6 +1067,7 @@ static int merge_3way(struct merge_options *o,\n \tint merge_status;\n \n \tll_opts.renormalize = o->renormalize;\n+\tll_opts.extra_marker_size = extra_marker_size;\n \tll_opts.xdl_opts = o->xdl_opts;\n \n \tif (o->call_depth) {\n@@ -1300,6 +1302,7 @@ static int merge_mode_and_contents(struct merge_options *o,\n \t\t\t\t   const char *filename,\n \t\t\t\t   const char *branch1,\n \t\t\t\t   const char *branch2,\n+\t\t\t\t   const int extra_marker_size,\n \t\t\t\t   struct merge_file_info *result)\n {\n \tif (o->branch1 != branch1) {\n@@ -1310,7 +1313,8 @@ static int merge_mode_and_contents(struct merge_options *o,\n \t\t */\n \t\treturn merge_mode_and_contents(o, one, b, a,\n \t\t\t\t\t       filename,\n-\t\t\t\t\t       branch2, branch1, result);\n+\t\t\t\t\t       branch2, branch1,\n+\t\t\t\t\t       extra_marker_size, result);\n \t}\n \n \tresult->merge = 0;\n@@ -1351,7 +1355,8 @@ static int merge_mode_and_contents(struct merge_options *o,\n \t\t\tint ret = 0, merge_status;\n \n \t\t\tmerge_status = merge_3way(o, &result_buf, one, a, b,\n-\t\t\t\t\t\t  branch1, branch2);\n+\t\t\t\t\t\t  branch1, branch2,\n+\t\t\t\t\t\t  extra_marker_size);\n \n \t\t\tif ((merge_status < 0) || !result_buf.ptr)\n \t\t\t\tret = err(o, _(\"Failed to execute internal merge\"));\n@@ -1640,7 +1645,8 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\tstruct diff_filespec other;\n \t\tstruct diff_filespec *add;\n \t\tif (merge_mode_and_contents(o, one, a, b, one->path,\n-\t\t\t\t\t    ci->branch1, ci->branch2, &mfi))\n+\t\t\t\t\t    ci->branch1, ci->branch2,\n+\t\t\t\t\t    o->call_depth * 2, &mfi))\n \t\t\treturn -1;\n \n \t\t/*\n@@ -1707,9 +1713,11 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \tpath_side_1_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n \tpath_side_2_desc = xstrfmt(\"version of %s from %s\", path, b->path);\n \tif (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,\n-\t\t\t\t    o->branch1, o->branch2, &mfi_c1) ||\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,\n-\t\t\t\t    o->branch1, o->branch2, &mfi_c2))\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n@@ -2755,7 +2763,7 @@ static int process_renames(struct merge_options *o,\n \n \t\t\t\t\tif (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,\n \t\t\t\t\t\t\t\t    branch1, branch2,\n-\t\t\t\t\t\t\t\t    &mfi)) {\n+\t\t\t\t\t\t\t\t    o->call_depth * 2, &mfi)) {\n \t\t\t\t\t\tclean_merge = -1;\n \t\t\t\t\t\tgoto cleanup_and_return;\n \t\t\t\t\t}\n@@ -3052,7 +3060,8 @@ static int handle_content_merge(struct merge_options *o,\n \t\t\tdf_conflict_remains = 1;\n \t}\n \tif (merge_mode_and_contents(o, &one, &a, &b, path,\n-\t\t\t\t    o->branch1, o->branch2, &mfi))\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi))\n \t\treturn -1;\n \n \t/*\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 78138a7fb4..32fd0a2995 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1596,4 +1596,155 @@ test_expect_failure \"check nested conflicts\" '\n \t)\n '\n \n+# Setup:\n+#          L1---L2---L3\n+#         /  \\ /  \\ /  \\\n+#   master    X1   X2   ?\n+#         \\  / \\  / \\  /\n+#          R1---R2---R3\n+#\n+# Where:\n+#   master has one file named 'content'\n+#   branches L1 and R1 both modify each of the two files in conflicting ways\n+#\n+#   L<n> (n>1) is a merge of R<n-1> into L<n-1>\n+#   R<n> (n>1) is a merge of L<n-1> into R<n-1>\n+#   L<n> and R<n> resolve the conflicts differently.\n+#\n+#   X<n> is an auto-generated merge-base used when merging L<n+1> and R<n+1>.\n+#   By construction, X1 has conflict markers due to conflicting versions.\n+#   X2, due to using merge.conflictstyle=3, has nested conflict markers.\n+#\n+#   So, merging R3 into L3 using merge.conflictstyle=3 should show the\n+#   nested conflict markers from X2 in the base version -- that means we\n+#   have three levels of conflict markers.  Can we distinguish all three?\n+\n+test_expect_success \"setup virtual merge base with nested conflicts\" '\n+\ttest_create_repo virtual_merge_base_has_nested_conflicts &&\n+\t(\n+\t\tcd virtual_merge_base_has_nested_conflicts &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >content &&\n+\n+\t\t# Setup original commit\n+\t\tgit add content &&\n+\t\ttest_tick && git commit -m initial &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Create L1\n+\t\tgit checkout L &&\n+\t\techo left >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick && git commit -m \"version L1 of content\" &&\n+\t\tgit tag L1 &&\n+\n+\t\t# Create R1\n+\t\tgit checkout R &&\n+\t\techo right >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick && git commit -m \"verson R1 of content\" &&\n+\t\tgit tag R1 &&\n+\n+\t\t# Create L2\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge R1 &&\n+\t\tgit checkout L1 content &&\n+\t\ttest_tick && git commit -m \"version L2 of content\" &&\n+\t\tgit tag L2 &&\n+\n+\t\t# Create R2\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge L1 &&\n+\t\tgit checkout R1 content &&\n+\t\ttest_tick && git commit -m \"version R2 of content\" &&\n+\t\tgit tag R2 &&\n+\n+\t\t# Create L3\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge R2 &&\n+\t\tgit checkout L1 content &&\n+\t\ttest_tick && git commit -m \"version L3 of content\" &&\n+\t\tgit tag L3 &&\n+\n+\t\t# Create R3\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge L2 &&\n+\t\tgit checkout R1 content &&\n+\t\ttest_tick && git commit -m \"version R3 of content\" &&\n+\t\tgit tag R3\n+\t)\n+'\n+\n+test_expect_success \"check virtual merge base with nested conflicts\" '\n+\t(\n+\t\tcd virtual_merge_base_has_nested_conflicts &&\n+\n+\t\tgit checkout L3^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R3^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Compare :[23]:content to expected values\n+\t\tgit rev-parse L1:content R1:content >expect &&\n+\t\tgit rev-parse :2:content :3:content >actual &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Imitate X1 merge base, except without long enough conflict\n+\t\t# markers because a subsequent sed will modify them.  Put\n+\t\t# result into vmb.\n+\t\tgit cat-file -p master:content >base &&\n+\t\tgit cat-file -p L:content >left &&\n+\t\tgit cat-file -p R:content >right &&\n+\t\tcp left merged-once &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tmerged-once \\\n+\t\t\tbase        \\\n+\t\t\tright       &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1\\1/\" merged-once >vmb &&\n+\n+\t\t# Imitate X2 merge base, overwriting vmb.  Note that we\n+\t\t# extend both sets of conflict markers to make them longer\n+\t\t# with the sed command.\n+\t\tcp left merged-twice &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tmerged-twice \\\n+\t\t\tvmb          \\\n+\t\t\tright        &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1\\1/\" merged-twice >vmb &&\n+\n+\t\t# Compare :1:content to expected value\n+\t\tgit cat-file -p :1:content >actual &&\n+\t\ttest_cmp vmb actual &&\n+\n+\t\t# Determine expected content in final outer merge, compare to\n+\t\t# what the merge generated.\n+\t\tcp -f left expect &&\n+\t\ttest_must_fail git merge-file --diff3                      \\\n+\t\t\t-L \"HEAD\"  -L \"merged common ancestors\"  -L \"R3^0\" \\\n+\t\t\texpect     vmb                           right     &&\n+\t\ttest_cmp expect content\n+\t)\n+'\n+\n test_done\n-- \n2.19.1.1079.gae8ff35a65\n\n"},{"id":"360970","messageId":"20181019193111.12051-1-newren@gmail.com","threadId":"49559","inReplyTo":"20181014020537.17991-1-newren@gmail.com","subject":"[PATCH v3 0/8] Improve path collision conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-19T19:31:03Z","receivedAt":"2018-10-19T19:31:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This series depends on en/merge-cleanup-more and is built on that\nseries.  (It merges cleanly to master, next, and pu).\n\nThis series makes all the \"file collision\" conflict types be handled\nconsistently; making them all behave like add/add (as suggested by\nJonathan[1] and Junio[2]).  These types are:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n  * each rename/add piece of a rename/rename(1to2)/add[/add] conflict\n\nChanges since v2:\n  * Removed RFC label (en/merge-cleanup-more is now pu -- in fact, v2\n    of that series is in pu; also, I'm starting to build other series\n    on this one which has increased my confidence in it)\n  * patch for increasing marker length with depth of recursion has been\n    pulled from the en/merge-cleanup-more series and added to this series\n  * Fixed an incorrect sentence in a commit message that was leftover\n    from v1.\n  \n[1] https://public-inbox.org/git/20180312213521.GB58506@aiede.svl.corp.google.com/\n[2] https://public-inbox.org/git/CAPc5daVu8vv9RdGON8JiXEO3ycDVqQ38ySzZc-cpo+AQcAKXjA@mail.gmail.com\n\n\nElijah Newren (8):\n  Add testcases for consistency in file collision conflict handling\n  t6036, t6042: testcases for rename collision of already conflicting\n    files\n  merge-recursive: increase marker length with depth of recursion\n  merge-recursive: new function for better colliding conflict\n    resolutions\n  merge-recursive: fix rename/add conflict handling\n  merge-recursive: improve handling for rename/rename(2to1) conflicts\n  merge-recursive: use handle_file_collision for add/add conflicts\n  merge-recursive: improve rename/rename(1to2)/add[/add] handling\n\n ll-merge.c                           |   4 +-\n ll-merge.h                           |   1 +\n merge-recursive.c                    | 528 ++++++++++++++++-----------\n t/t6036-recursive-corner-cases.sh    | 379 ++++++++++++++++++-\n t/t6042-merge-rename-corner-cases.sh | 333 ++++++++++++++++-\n t/t6043-merge-rename-directories.sh  | 107 +++---\n 6 files changed, 1060 insertions(+), 292 deletions(-)\n\n-- \n2.19.1.1036.gce51225f01.dirty\n\n"},{"id":"360971","messageId":"20181019193111.12051-3-newren@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-1-newren@gmail.com","subject":"[PATCH v3 2/8] t6036, t6042: testcases for rename collision of already conflicting files","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-19T19:31:05Z","receivedAt":"2018-10-19T19:31:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When a single file is renamed, it can also be modified, yielding the\npossibility of that renamed file having content conflicts.  If two\ndifferent such files are renamed into the same location, then two-way\nmerging those files may result in nested conflicts.  Add a testcase that\nmakes sure we get this case correct, and uses different lengths of\nconflict markers to differentiate between the different nestings.\n\nAlso add another case with an extra (i.e. third) level of conflict\nmarkers due to using merge.conflictstyle=diff3 and the virtual merge\nbase also having conflicts present.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh    | 194 +++++++++++++++++++++++++++\n t/t6042-merge-rename-corner-cases.sh | 118 ++++++++++++++++\n 2 files changed, 312 insertions(+)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex e1cef58f2a..78138a7fb4 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1402,4 +1402,198 @@ test_expect_failure 'check conflicting modes for regular file' '\n \t)\n '\n \n+# Setup:\n+#          L1---L2\n+#         /  \\ /  \\\n+#   master    X    ?\n+#         \\  / \\  /\n+#          R1---R2\n+#\n+# Where:\n+#   master has two files, named 'b' and 'a'\n+#   branches L1 and R1 both modify each of the two files in conflicting ways\n+#\n+#   L2 is a merge of R1 into L1; more on it later.\n+#   R2 is a merge of L1 into R1; more on it later.\n+#\n+#   X is an auto-generated merge-base used when merging L2 and R2.\n+#   since X is a merge of L1 and R1, it has conflicting versions of each file\n+#\n+#   More about L2 and R2:\n+#     - both resolve the conflicts in 'b' and 'a' differently\n+#     - L2 renames 'b' to 'm'\n+#     - R2 renames 'a' to 'm'\n+#\n+#   In the end, in file 'm' we have four different conflicting files (from\n+#   two versions of 'b' and two of 'a').  In addition, if\n+#   merge.conflictstyle is diff3, then the base version also has\n+#   conflict markers of its own, leading to a total of three levels of\n+#   conflict markers.  This is a pretty weird corner case, but we just want\n+#   to ensure that we handle it as well as practical.\n+\n+test_expect_success \"setup nested conflicts\" '\n+\ttest_create_repo nested_conflicts &&\n+\t(\n+\t\tcd nested_conflicts &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >initial &&\n+\n+\t\tcp initial b_L1 &&\n+\t\tcp initial b_R1 &&\n+\t\tcp initial b_L2 &&\n+\t\tcp initial b_R2 &&\n+\t\tcp initial a_L1 &&\n+\t\tcp initial a_R1 &&\n+\t\tcp initial a_L2 &&\n+\t\tcp initial a_R2 &&\n+\n+\t\ttest_write_lines b b_L1 >>b_L1 &&\n+\t\ttest_write_lines b b_R1 >>b_R1 &&\n+\t\ttest_write_lines b b_L2 >>b_L2 &&\n+\t\ttest_write_lines b b_R2 >>b_R2 &&\n+\t\ttest_write_lines a a_L1 >>a_L1 &&\n+\t\ttest_write_lines a a_R1 >>a_R1 &&\n+\t\ttest_write_lines a a_L2 >>a_L2 &&\n+\t\ttest_write_lines a a_R2 >>a_R2 &&\n+\n+\t\t# Setup original commit (or merge-base), consisting of\n+\t\t# files named \"b\" and \"a\"\n+\t\tcp initial b &&\n+\t\tcp initial a &&\n+\t\techo b >>b &&\n+\t\techo a >>a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m initial &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Handle the left side\n+\t\tgit checkout L &&\n+\t\tmv -f b_L1 b &&\n+\t\tmv -f a_L1 a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m \"version L1 of files\" &&\n+\t\tgit tag L1 &&\n+\n+\t\t# Handle the right side\n+\t\tgit checkout R &&\n+\t\tmv -f b_R1 b &&\n+\t\tmv -f a_R1 a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m \"verson R1 of files\" &&\n+\t\tgit tag R1 &&\n+\n+\t\t# Create first merge on left side\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git merge R1 &&\n+\t\tmv -f b_L2 b &&\n+\t\tmv -f a_L2 a &&\n+\t\tgit add b a &&\n+\t\tgit mv b m &&\n+\t\ttest_tick && git commit -m \"left merge, rename b->m\" &&\n+\t\tgit tag L2 &&\n+\n+\t\t# Create first merge on right side\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git merge L1 &&\n+\t\tmv -f b_R2 b &&\n+\t\tmv -f a_R2 a &&\n+\t\tgit add b a &&\n+\t\tgit mv a m &&\n+\t\ttest_tick && git commit -m \"right merge, rename a->m\" &&\n+\t\tgit tag R2\n+\t)\n+'\n+\n+test_expect_failure \"check nested conflicts\" '\n+\t(\n+\t\tcd nested_conflicts &&\n+\n+\t\tgit clean -f &&\n+\t\tgit checkout L2^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R2^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Create a and b from virtual merge base X\n+\t\tgit cat-file -p master:a >base &&\n+\t\tgit cat-file -p L1:a >ours &&\n+\t\tgit cat-file -p R1:a >theirs &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tours  \\\n+\t\t\tbase  \\\n+\t\t\ttheirs &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >vmb_a &&\n+\n+\t\tgit cat-file -p master:b >base &&\n+\t\tgit cat-file -p L1:b >ours &&\n+\t\tgit cat-file -p R1:b >theirs &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tours  \\\n+\t\t\tbase  \\\n+\t\t\ttheirs &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >vmb_b &&\n+\n+\t\t# Compare :2:m to expected values\n+\t\tgit cat-file -p L2:m >ours &&\n+\t\tgit cat-file -p R2:b >theirs &&\n+\t\ttest_must_fail git merge-file --diff3  \\\n+\t\t\t-L \"HEAD:m\"                    \\\n+\t\t\t-L \"merged common ancestors:b\" \\\n+\t\t\t-L \"R2^0:b\"                    \\\n+\t\t\tours                           \\\n+\t\t\tvmb_b                          \\\n+\t\t\ttheirs                         &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >m_stage_2 &&\n+\t\tgit cat-file -p :2:m >actual &&\n+\t\ttest_cmp m_stage_2 actual &&\n+\n+\t\t# Compare :3:m to expected values\n+\t\tgit cat-file -p L2:a >ours &&\n+\t\tgit cat-file -p R2:m >theirs &&\n+\t\ttest_must_fail git merge-file --diff3  \\\n+\t\t\t-L \"HEAD:a\"                    \\\n+\t\t\t-L \"merged common ancestors:a\" \\\n+\t\t\t-L \"R2^0:m\"                    \\\n+\t\t\tours                           \\\n+\t\t\tvmb_a                          \\\n+\t\t\ttheirs                         &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >m_stage_3 &&\n+\t\tgit cat-file -p :3:m >actual &&\n+\t\ttest_cmp m_stage_3 actual &&\n+\n+\t\t# Compare m to expected contents\n+\t\t>empty &&\n+\t\tcp -a m_stage_2 expected_final_m &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"HEAD\"                     \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"R2^0\"                     \\\n+\t\t\texpected_final_m              \\\n+\t\t\tempty                         \\\n+\t\t\tm_stage_3                     &&\n+\t\ttest_cmp expected_final_m m\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b6fed2cb9a..5e69e60b48 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -1099,4 +1099,122 @@ test_conflicts_with_adds_and_renames rename add    failure\n test_conflicts_with_adds_and_renames add    rename failure\n test_conflicts_with_adds_and_renames add    add    success\n \n+# Setup:\n+#          L\n+#         / \\\n+#   master   ?\n+#         \\ /\n+#          R\n+#\n+# Where:\n+#   master has two files, named 'one' and 'two'.\n+#   branches L and R both modify 'one', in conflicting ways.\n+#   branches L and R both modify 'two', in conflicting ways.\n+#   branch L also renames 'one' to 'three'.\n+#   branch R also renames 'two' to 'three'.\n+#\n+#   So, we have four different conflicting files that all end up at path\n+#   'three'.\n+test_expect_success \"setup nested conflicts from rename/rename(2to1)\" '\n+\ttest_create_repo nested_conflicts_from_rename_rename &&\n+\t(\n+\t\tcd nested_conflicts_from_rename_rename &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >file_v1 &&\n+\n+\t\tcp file_v1 file_v2 &&\n+\t\tcp file_v1 file_v3 &&\n+\t\tcp file_v1 file_v4 &&\n+\t\tcp file_v1 file_v5 &&\n+\t\tcp file_v1 file_v6 &&\n+\n+\t\techo one  >>file_v1 &&\n+\t\techo uno  >>file_v2 &&\n+\t\techo eins >>file_v3 &&\n+\n+\t\techo two  >>file_v4 &&\n+\t\techo dos  >>file_v5 &&\n+\t\techo zwei >>file_v6 &&\n+\n+\t\t# Setup original commit (or merge-base), consisting of\n+\t\t# files named \"one\" and \"two\".\n+\t\tmv file_v1 one &&\n+\t\tmv file_v4 two &&\n+\t\tgit add one two &&\n+\t\ttest_tick && git commit -m english &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Handle the left side\n+\t\tgit checkout L &&\n+\t\tgit mv one three &&\n+\t\tmv -f file_v2 three &&\n+\t\tmv -f file_v5 two &&\n+\t\tgit add two three &&\n+\t\ttest_tick && git commit -m spanish &&\n+\n+\t\t# Handle the right side\n+\t\tgit checkout R &&\n+\t\tgit mv two three &&\n+\t\tmv -f file_v3 one &&\n+\t\tmv -f file_v6 three &&\n+\t\tgit add one three &&\n+\t\ttest_tick && git commit -m german\n+\t)\n+'\n+\n+test_expect_failure \"check nested conflicts from rename/rename(2to1)\" '\n+\t(\n+\t\tcd nested_conflicts_from_rename_rename &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git merge -s recursive R^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Compare :2:three to expected values\n+\t\tgit cat-file -p master:one >base &&\n+\t\tgit cat-file -p L:three >ours &&\n+\t\tgit cat-file -p R:one >theirs &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD:three\"  -L \"\"  -L \"R^0:one\" \\\n+\t\t\tours             base   theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1/\" ours >L-three &&\n+\t\tgit cat-file -p :2:three >expect &&\n+\t\ttest_cmp expect L-three &&\n+\n+\t\t# Compare :2:three to expected values\n+\t\tgit cat-file -p master:two >base &&\n+\t\tgit cat-file -p L:two >ours &&\n+\t\tgit cat-file -p R:three >theirs &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD:two\"  -L \"\"  -L \"R^0:three\" \\\n+\t\t\tours           base   theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1/\" ours >R-three &&\n+\t\tgit cat-file -p :3:three >expect &&\n+\t\ttest_cmp expect R-three &&\n+\n+\t\t# Compare three to expected contents\n+\t\t>empty &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD\"  -L \"\"  -L \"R^0\" \\\n+\t\t\tL-three    empty  R-three &&\n+\t\ttest_cmp three L-three\n+\t)\n+'\n+\n test_done\n-- \n2.19.1.1079.gae8ff35a65\n\n"},{"id":"360972","messageId":"20181019193111.12051-5-newren@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-1-newren@gmail.com","subject":"[PATCH v3 4/8] merge-recursive: new function for better colliding conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-19T19:31:07Z","receivedAt":"2018-10-19T19:31:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There are three conflict types that represent two (possibly entirely\nunrelated) files colliding at the same location:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n\nThese three conflict types already share more similarity than might be\nimmediately apparent from their description: (1) the handling of the\nrename variants already involves removing any entries from the index\ncorresponding to the original file names[*], thus only leaving entries\nin the index for the colliding path; (2) likewise, any trace of the\noriginal file name in the working tree is also removed.  So, in all\nthree cases we're left with how to represent two colliding files in both\nthe index and the working copy.\n\n[*] Technically, this isn't quite true because rename/rename(2to1)\nconflicts in the recursive (o->call_depth > 0) case do an \"unrename\"\nsince about seven years ago.  But even in that case, Junio felt\ncompelled to explain that my decision to \"unrename\" wasn't necessarily\nthe only or right answer -- search for \"Comment from Junio\" in t6036 for\ndetails.\n\nMy initial motivation for looking at these three conflict types was that\nif the handling of these three conflict types is the same, at least in\nthe limited set of cases where a renamed file is unmodified on the side\nof history where the file is not renamed, then a significant performance\nimprovement for rename detection during merges is possible.  However,\nwhile that served as motivation to look at these three types of\nconflicts, the actual goal of this new function is to try to improve the\nhandling for all three cases, not to merely make them the same as each\nother in that special circumstance.\n\n=== Handling the working tree ===\n\nThe previous behavior for these conflict types in regards to the\nworking tree (assuming the file collision occurs at 'foo') was:\n  * add/add does a two-way merge of the two files and records it as 'foo'.\n  * rename/rename(2to1) records the two different files into two new\n    uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'\n    from the working tree.\n  * rename/add records the two different files into two different\n    locations, recording the add at foo~$SIDE and, oddly, recording\n    the rename at foo (why is the rename more important than the add?)\n\nSo, the question for what to write to the working tree boils down to\nwhether the two colliding files should be two-way merged and recorded in\nplace, or recorded into separate files.  As per discussion on the git\nmailing lit, two-way merging was deemed to always be preferred, as that\nmakes these cases all more like content conflicts that users can handle\nfrom within their favorite editor, IDE, or merge tool.  Note that since\nrenames already involve a content merge, rename/add and\nrename/rename(2to1) conflicts could result in nested conflict markers.\n\n=== Handling of the index ===\n\nFor a typical rename, unpack_trees() would set up the index in the\nfollowing fashion:\n           old_path  new_path\n   stage1: 5ca1ab1e  00000000\n   stage2: f005ba11  00000000\n   stage3: 00000000  b0a710ad\nAnd merge-recursive would rewrite this to\n           new_path\n   stage1: 5ca1ab1e\n   stage2: f005ba11\n   stage3: b0a710ad\nRemoving old_path from the index means the user won't have to `git rm\nold_path` manually every time a renamed path has a content conflict.\nIt also means they can use `git checkout [--ours|--theirs|--conflict|-m]\nnew_path`, `git diff [--ours|--theirs]` and various other commands that\nwould be difficult otherwise.\n\nThis strategy becomes a problem when we have a rename/add or\nrename/rename(2to1) conflict, however, because then we have only three\nslots to store blob sha1s and we need either four or six.  Previously,\nthis was handled by continuing to delete old_path from the index, and\njust outright ignoring any blob shas from old_path.  That had the\ndownside of deleting any trace of changes made to old_path on the other\nside of history.  This function instead does a three-way content merge of\nthe renamed file, and stores the blob sha1 for that at either stage2 or\nstage3 for new_path (depending on which side the rename came from).  That\nhas the advantage of bringing information about changes on both sides and\nstill allows for easy resolution (no need to git rm old_path, etc.), but\ndoes have the downside that if the content merge had conflict markers,\nthen what we store in the index is the sha1 of a blob with conflict\nmarkers.  While that is a downside, it seems less problematic than the\ndownsides of any obvious alternatives, and certainly makes more sense\nthan the previous handling.  Further, it has a precedent in that when we\ndo recursive merges, we may accept a file with conflict markers as the\nresolution for the merge of the merge-bases, which will then show up in\nthe index of the outer merge at stage 1 if a conflict exists at the outer\nlevel.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 121 insertions(+)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f795c92a69..24d979022e 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1559,6 +1559,127 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,\n \treturn target;\n }\n \n+#if 0 // #if-0-ing avoids unused function warning; will make live in next commit\n+static int handle_file_collision(struct merge_options *o,\n+\t\t\t\t const char *collide_path,\n+\t\t\t\t const char *prev_path1,\n+\t\t\t\t const char *prev_path2,\n+\t\t\t\t const char *branch1, const char *branch2,\n+\t\t\t\t const struct object_id *a_oid,\n+\t\t\t\t unsigned int a_mode,\n+\t\t\t\t const struct object_id *b_oid,\n+\t\t\t\t unsigned int b_mode)\n+{\n+\tstruct merge_file_info mfi;\n+\tstruct diff_filespec null, a, b;\n+\tchar *alt_path = NULL;\n+\tconst char *update_path = collide_path;\n+\n+\t/*\n+\t * In the recursive case, we just opt to undo renames\n+\t */\n+\tif (o->call_depth && (prev_path1 || prev_path2)) {\n+\t\t/* Put first file (a_oid, a_mode) in its original spot */\n+\t\tif (prev_path1) {\n+\t\t\tif (update_file(o, 1, a_oid, a_mode, prev_path1))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tif (update_file(o, 1, a_oid, a_mode, collide_path))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Put second file (b_oid, b_mode) in its original spot */\n+\t\tif (prev_path2) {\n+\t\t\tif (update_file(o, 1, b_oid, b_mode, prev_path2))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tif (update_file(o, 1, b_oid, b_mode, collide_path))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Don't leave something at collision path if unrenaming both */\n+\t\tif (prev_path1 && prev_path2)\n+\t\t\tremove_file(o, 1, collide_path, 0);\n+\n+\t\treturn 0;\n+\t}\n+\n+\t/* Remove rename sources if rename/add or rename/rename(2to1) */\n+\tif (prev_path1)\n+\t\tremove_file(o, 1, prev_path1,\n+\t\t\t    o->call_depth || would_lose_untracked(prev_path1));\n+\tif (prev_path2)\n+\t\tremove_file(o, 1, prev_path2,\n+\t\t\t    o->call_depth || would_lose_untracked(prev_path2));\n+\n+\t/*\n+\t * Remove the collision path, if it wouldn't cause dirty contents\n+\t * or an untracked file to get lost.  We'll either overwrite with\n+\t * merged contents, or just write out to differently named files.\n+\t */\n+\tif (was_dirty(o, collide_path)) {\n+\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n+\t\t       collide_path);\n+\t\tupdate_path = alt_path = unique_path(o, collide_path, \"merged\");\n+\t} else if (would_lose_untracked(collide_path)) {\n+\t\t/*\n+\t\t * Only way we get here is if both renames were from\n+\t\t * a directory rename AND user had an untracked file\n+\t\t * at the location where both files end up after the\n+\t\t * two directory renames.  See testcase 10d of t6043.\n+\t\t */\n+\t\toutput(o, 1, _(\"Refusing to lose untracked file at \"\n+\t\t\t       \"%s, even though it's in the way.\"),\n+\t\t       collide_path);\n+\t\tupdate_path = alt_path = unique_path(o, collide_path, \"merged\");\n+\t} else {\n+\t\t/*\n+\t\t * FIXME: It's possible that the two files are identical\n+\t\t * and that the current working copy happens to match, in\n+\t\t * which case we are unnecessarily touching the working\n+\t\t * tree file.  It's not a likely enough scenario that I\n+\t\t * want to code up the checks for it and a better fix is\n+\t\t * available if we restructure how unpack_trees() and\n+\t\t * merge-recursive interoperate anyway, so punting for\n+\t\t * now...\n+\t\t */\n+\t\tremove_file(o, 0, collide_path, 0);\n+\t}\n+\n+\t/* Store things in diff_filespecs for functions that need it */\n+\tmemset(&a, 0, sizeof(struct diff_filespec));\n+\tmemset(&b, 0, sizeof(struct diff_filespec));\n+\tnull.path = a.path = b.path = (char *)collide_path;\n+\toidcpy(&null.oid, &null_oid);\n+\tnull.mode = 0;\n+\toidcpy(&a.oid, a_oid);\n+\ta.mode = a_mode;\n+\ta.oid_valid = 1;\n+\toidcpy(&b.oid, b_oid);\n+\tb.mode = b_mode;\n+\tb.oid_valid = 1;\n+\n+\tif (merge_mode_and_contents(o, &null, &a, &b, collide_path,\n+\t\t\t\t    branch1, branch2, o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tmfi.clean &= !alt_path;\n+\tif (update_file(o, mfi.clean, &mfi.oid, mfi.mode, update_path))\n+\t\treturn -1;\n+\tif (!mfi.clean && !o->call_depth &&\n+\t    update_stages(o, collide_path, NULL, &a, &b))\n+\t\treturn -1;\n+\tfree(alt_path);\n+\t/*\n+\t * FIXME: If both a & b both started with conflicts (only possible\n+\t * if they came from a rename/rename(2to1)), but had IDENTICAL\n+\t * contents including those conflicts, then in the next line we claim\n+\t * it was clean.  If someone cares about this case, we should have the\n+\t * caller notify us if we started with conflicts.\n+\t */\n+\treturn mfi.clean;\n+}\n+#endif\n+\n static int handle_file(struct merge_options *o,\n \t\t\tstruct diff_filespec *rename,\n \t\t\tint stage,\n-- \n2.19.1.1079.gae8ff35a65\n\n"},{"id":"360973","messageId":"20181019193111.12051-2-newren@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-1-newren@gmail.com","subject":"[PATCH v3 1/8] Add testcases for consistency in file collision conflict handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-19T19:31:04Z","receivedAt":"2018-10-19T19:31:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Add testcases dealing with file collisions for the following types of\nconflicts:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n\nAll these conflict types simplify down to two files \"colliding\"\nand should thus be handled similarly.  This means that rename/add and\nrename/rename(2to1) conflicts need to be modified to behave the same as\nadd/add conflicts currently do: the colliding files should be two-way\nmerged (instead of the current behavior of writing the two colliding\nfiles out to separate temporary unique pathnames).  Add testcases which\ncheck this; subsequent commits will fix the conflict handling to make\nthese tests pass.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6042-merge-rename-corner-cases.sh | 162 +++++++++++++++++++++++++++\n 1 file changed, 162 insertions(+)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b97aca7fa2..b6fed2cb9a 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -937,4 +937,166 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename\n \t)\n '\n \n+test_conflicts_with_adds_and_renames() {\n+\tsideL=$1\n+\tsideR=$2\n+\texpect=$3\n+\n+\t# Setup:\n+\t#          L\n+\t#         / \\\n+\t#   master   ?\n+\t#         \\ /\n+\t#          R\n+\t#\n+\t# Where:\n+\t#   Both L and R have files named 'three' which collide.  Each of\n+\t#   the colliding files could have been involved in a rename, in\n+\t#   which case there was a file named 'one' or 'two' that was\n+\t#   modified on the opposite side of history and renamed into the\n+\t#   collision on this side of history.\n+\t#\n+\t# Questions:\n+\t#   1) The index should contain both a stage 2 and stage 3 entry\n+\t#      for the colliding file.  Does it?\n+\t#   2) When renames are involved, the content merges are clean, so\n+\t#      the index should reflect the content merges, not merely the\n+\t#      version of the colliding file from the prior commit.  Does\n+\t#      it?\n+\t#   3) There should be a file in the worktree named 'three'\n+\t#      containing the two-way merged contents of the content-merged\n+\t#      versions of 'three' from each of the two colliding\n+\t#      files.  Is it present?\n+\t#   4) There should not be any three~* files in the working\n+\t#      tree\n+\ttest_expect_success \"setup simple $sideL/$sideR conflict\" '\n+\t\ttest_create_repo simple_${sideL}_${sideR} &&\n+\t\t(\n+\t\t\tcd simple_${sideL}_${sideR} &&\n+\n+\t\t\t# Create some related files now\n+\t\t\tfor i in $(test_seq 1 10)\n+\t\t\tdo\n+\t\t\t\techo Random base content line $i\n+\t\t\tdone >file_v1 &&\n+\t\t\tcp file_v1 file_v2 &&\n+\t\t\techo modification >>file_v2 &&\n+\n+\t\t\tcp file_v1 file_v3 &&\n+\t\t\techo more stuff >>file_v3 &&\n+\t\t\tcp file_v3 file_v4 &&\n+\t\t\techo yet more stuff >>file_v4 &&\n+\n+\t\t\t# Use a tag to record both these files for simple\n+\t\t\t# access, and clean out these untracked files\n+\t\t\tgit tag file_v1 $(git hash-object -w file_v1) &&\n+\t\t\tgit tag file_v2 $(git hash-object -w file_v2) &&\n+\t\t\tgit tag file_v3 $(git hash-object -w file_v3) &&\n+\t\t\tgit tag file_v4 $(git hash-object -w file_v4) &&\n+\t\t\tgit clean -f &&\n+\n+\t\t\t# Setup original commit (or merge-base), consisting of\n+\t\t\t# files named \"one\" and \"two\" if renames were involved.\n+\t\t\ttouch irrelevant_file &&\n+\t\t\tgit add irrelevant_file &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v1 >one &&\n+\t\t\t\tgit add one\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v3 >two &&\n+\t\t\t\tgit add two\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m initial &&\n+\n+\t\t\tgit branch L &&\n+\t\t\tgit branch R &&\n+\n+\t\t\t# Handle the left side\n+\t\t\tgit checkout L &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit mv one three\n+\t\t\telse\n+\t\t\t\tgit show file_v2 >three &&\n+\t\t\t\tgit add three\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v4 >two &&\n+\t\t\t\tgit add two\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m L &&\n+\n+\t\t\t# Handle the right side\n+\t\t\tgit checkout R &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v2 >one &&\n+\t\t\t\tgit add one\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit mv two three\n+\t\t\telse\n+\t\t\t\tgit show file_v4 >three &&\n+\t\t\t\tgit add three\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m R\n+\t\t)\n+\t'\n+\n+\ttest_expect_$expect \"check simple $sideL/$sideR conflict\" '\n+\t\t(\n+\t\t\tcd simple_${sideL}_${sideR} &&\n+\n+\t\t\tgit checkout L^0 &&\n+\n+\t\t\t# Merge must fail; there is a conflict\n+\t\t\ttest_must_fail git merge -s recursive R^0 &&\n+\n+\t\t\t# Make sure the index has the right number of entries\n+\t\t\tgit ls-files -s >out &&\n+\t\t\ttest_line_count = 3 out &&\n+\t\t\tgit ls-files -u >out &&\n+\t\t\ttest_line_count = 2 out &&\n+\t\t\t# Ensure we have the correct number of untracked files\n+\t\t\tgit ls-files -o >out &&\n+\t\t\ttest_line_count = 1 out &&\n+\n+\t\t\t# Nothing should have touched irrelevant_file\n+\t\t\tgit rev-parse >actual      \\\n+\t\t\t\t:0:irrelevant_file \\\n+\t\t\t\t:2:three           \\\n+\t\t\t\t:3:three           &&\n+\t\t\tgit rev-parse >expected        \\\n+\t\t\t\tmaster:irrelevant_file \\\n+\t\t\t\tfile_v2                \\\n+\t\t\t\tfile_v4                &&\n+\t\t\ttest_cmp expected actual &&\n+\n+\t\t\t# Make sure we have the correct merged contents for\n+\t\t\t# three\n+\t\t\tgit show file_v1 >expected &&\n+\t\t\tcat <<-\\EOF >>expected &&\n+\t\t\t<<<<<<< HEAD\n+\t\t\tmodification\n+\t\t\t=======\n+\t\t\tmore stuff\n+\t\t\tyet more stuff\n+\t\t\t>>>>>>> R^0\n+\t\t\tEOF\n+\n+\t\t\ttest_cmp expected three\n+\t\t)\n+\t'\n+}\n+\n+test_conflicts_with_adds_and_renames rename rename failure\n+test_conflicts_with_adds_and_renames rename add    failure\n+test_conflicts_with_adds_and_renames add    rename failure\n+test_conflicts_with_adds_and_renames add    add    success\n+\n test_done\n-- \n2.19.1.1079.gae8ff35a65\n\n"},{"id":"360974","messageId":"20181019193111.12051-7-newren@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-1-newren@gmail.com","subject":"[PATCH v3 6/8] merge-recursive: improve handling for rename/rename(2to1) conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-19T19:31:09Z","receivedAt":"2018-10-19T19:31:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This makes the rename/rename(2to1) conflicts use the new\nhandle_file_collision() function.  Since that function was based\noriginally on the rename/rename(2to1) handling code, the main\ndifferences here are in what was added.  In particular:\n\n  * Instead of storing files at collide_path~HEAD and collide_path~MERGE,\n    the files are two-way merged and recorded at collide_path.\n\n  * Instead of recording the version of the renamed file that existed\n    on the renamed side in the index (thus ignoring any changes that\n    were made to the file on the side of history without the rename),\n    we do a three-way content merge on the renamed path, then store\n    that at either stage 2 or stage 3.\n\n  * Note that since the content merge for each rename may have conflicts,\n    and then we have to merge the two renamed files, we can end up with\n    nested conflict markers.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 104 ++++-----------------------\n t/t6036-recursive-corner-cases.sh    |  12 +---\n t/t6042-merge-rename-corner-cases.sh |  38 ++++++----\n t/t6043-merge-rename-directories.sh  |  83 ++++++++++++---------\n 4 files changed, 89 insertions(+), 148 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0805095168..ead6054a75 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -696,27 +696,6 @@ static int update_stages(struct merge_options *opt, const char *path,\n \treturn 0;\n }\n \n-static int update_stages_for_stage_data(struct merge_options *opt,\n-\t\t\t\t\tconst char *path,\n-\t\t\t\t\tconst struct stage_data *stage_data)\n-{\n-\tstruct diff_filespec o, a, b;\n-\n-\to.mode = stage_data->stages[1].mode;\n-\toidcpy(&o.oid, &stage_data->stages[1].oid);\n-\n-\ta.mode = stage_data->stages[2].mode;\n-\toidcpy(&a.oid, &stage_data->stages[2].oid);\n-\n-\tb.mode = stage_data->stages[3].mode;\n-\toidcpy(&b.oid, &stage_data->stages[3].oid);\n-\n-\treturn update_stages(opt, path,\n-\t\t\t     is_null_oid(&o.oid) ? NULL : &o,\n-\t\t\t     is_null_oid(&a.oid) ? NULL : &a,\n-\t\t\t     is_null_oid(&b.oid) ? NULL : &b);\n-}\n-\n static void update_entry(struct stage_data *entry,\n \t\t\t struct diff_filespec *o,\n \t\t\t struct diff_filespec *a,\n@@ -1870,7 +1849,6 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \tchar *path_side_2_desc;\n \tstruct merge_file_info mfi_c1;\n \tstruct merge_file_info mfi_c2;\n-\tint ret;\n \n \toutput(o, 1, _(\"CONFLICT (rename/rename): \"\n \t       \"Rename %s->%s in %s. \"\n@@ -1878,81 +1856,22 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \t       a->path, c1->path, ci->branch1,\n \t       b->path, c2->path, ci->branch2);\n \n-\tremove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));\n-\tremove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));\n-\n \tpath_side_1_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n \tpath_side_2_desc = xstrfmt(\"version of %s from %s\", path, b->path);\n \tif (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,\n \t\t\t\t    o->branch1, o->branch2,\n-\t\t\t\t    o->call_depth * 2, &mfi_c1) ||\n+\t\t\t\t    1 + o->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,\n \t\t\t\t    o->branch1, o->branch2,\n-\t\t\t\t    o->call_depth * 2, &mfi_c2))\n+\t\t\t\t    1 + o->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n \n-\tif (o->call_depth) {\n-\t\t/*\n-\t\t * If mfi_c1.clean && mfi_c2.clean, then it might make\n-\t\t * sense to do a two-way merge of those results.  But, I\n-\t\t * think in all cases, it makes sense to have the virtual\n-\t\t * merge base just undo the renames; they can be detected\n-\t\t * again later for the non-recursive merge.\n-\t\t */\n-\t\tremove_file(o, 0, path, 0);\n-\t\tret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);\n-\t\tif (!ret)\n-\t\t\tret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,\n-\t\t\t\t\t  b->path);\n-\t} else {\n-\t\tchar *new_path1 = unique_path(o, path, ci->branch1);\n-\t\tchar *new_path2 = unique_path(o, path, ci->branch2);\n-\t\toutput(o, 1, _(\"Renaming %s to %s and %s to %s instead\"),\n-\t\t       a->path, new_path1, b->path, new_path2);\n-\t\tif (was_dirty(o, path))\n-\t\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n-\t\t\t       path);\n-\t\telse if (would_lose_untracked(path))\n-\t\t\t/*\n-\t\t\t * Only way we get here is if both renames were from\n-\t\t\t * a directory rename AND user had an untracked file\n-\t\t\t * at the location where both files end up after the\n-\t\t\t * two directory renames.  See testcase 10d of t6043.\n-\t\t\t */\n-\t\t\toutput(o, 1, _(\"Refusing to lose untracked file at \"\n-\t\t\t\t       \"%s, even though it's in the way.\"),\n-\t\t\t       path);\n-\t\telse\n-\t\t\tremove_file(o, 0, path, 0);\n-\t\tret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);\n-\t\tif (!ret)\n-\t\t\tret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,\n-\t\t\t\t\t  new_path2);\n-\t\t/*\n-\t\t * unpack_trees() actually populates the index for us for\n-\t\t * \"normal\" rename/rename(2to1) situtations so that the\n-\t\t * correct entries are at the higher stages, which would\n-\t\t * make the call below to update_stages_for_stage_data\n-\t\t * unnecessary.  However, if either of the renames came\n-\t\t * from a directory rename, then unpack_trees() will not\n-\t\t * have gotten the right data loaded into the index, so we\n-\t\t * need to do so now.  (While it'd be tempting to move this\n-\t\t * call to update_stages_for_stage_data() to\n-\t\t * apply_directory_rename_modifications(), that would break\n-\t\t * our intermediate calls to would_lose_untracked() since\n-\t\t * those rely on the current in-memory index.  See also the\n-\t\t * big \"NOTE\" in update_stages()).\n-\t\t */\n-\t\tif (update_stages_for_stage_data(o, path, ci->dst_entry1))\n-\t\t\tret = -1;\n-\n-\t\tfree(new_path2);\n-\t\tfree(new_path1);\n-\t}\n-\n-\treturn ret;\n+\treturn handle_file_collision(o, path, a->path, b->path,\n+\t\t\t\t     ci->branch1, ci->branch2,\n+\t\t\t\t     &mfi_c1.oid, mfi_c1.mode,\n+\t\t\t\t     &mfi_c2.oid, mfi_c2.mode);\n }\n \n /*\n@@ -3361,9 +3280,14 @@ static int process_entry(struct merge_options *o,\n \t\t\t\tclean_merge = -1;\n \t\t\tbreak;\n \t\tcase RENAME_TWO_FILES_TO_ONE:\n-\t\t\tclean_merge = 0;\n-\t\t\tif (handle_rename_rename_2to1(o, conflict_info))\n-\t\t\t\tclean_merge = -1;\n+\t\t\t/*\n+\t\t\t * Probably unclean merge, but if the two renamed\n+\t\t\t * files merge cleanly and the two resulting files\n+\t\t\t * can then be two-way merged cleanly, I guess it's\n+\t\t\t * a clean merge?\n+\t\t\t */\n+\t\t\tclean_merge = handle_rename_rename_2to1(o,\n+\t\t\t\t\t\t\t\tconflict_info);\n \t\t\tbreak;\n \t\tdefault:\n \t\t\tentry->processed = 0;\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 44807603e3..a4a78b6cef 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -64,15 +64,12 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tL2:three   R2:three \\\n \t\t\tL2:three   R2:three &&\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n-\t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -140,15 +137,12 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tL2:three   R2:three \\\n \t\t\tL2:three   R2:three &&\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n-\t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -1512,7 +1506,7 @@ test_expect_success \"setup nested conflicts\" '\n \t)\n '\n \n-test_expect_failure \"check nested conflicts\" '\n+test_expect_success \"check nested conflicts\" '\n \t(\n \t\tcd nested_conflicts &&\n \ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex f1c4a06dd6..4cfcde3082 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -464,17 +464,28 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' '\n \t\tgit ls-files -u c >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\ttest_path_is_missing a &&\n \t\ttest_path_is_missing b &&\n-\t\ttest_path_is_file c~HEAD &&\n-\t\ttest_path_is_file c~C^0 &&\n \n-\t\tgit rev-parse >expect   \\\n-\t\t\tC:a     B:b     &&\n-\t\tgit hash-object >actual \\\n-\t\t\tc~HEAD  c~C^0   &&\n+\t\tgit rev-parse >expect  \\\n+\t\t\tC:a     B:b    &&\n+\t\tgit rev-parse >actual  \\\n+\t\t\t:2:c    :3:c   &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p :2:c >>ours &&\n+\t\tgit cat-file -p :3:c >>theirs &&\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 \"C^0\" \\\n+\t\t\tours empty theirs &&\n+\t\tgit hash-object c >actual &&\n+\t\tgit hash-object ours >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -940,7 +951,6 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename\n test_conflicts_with_adds_and_renames() {\n \tsideL=$1\n \tsideR=$2\n-\texpect=$3\n \n \t# Setup:\n \t#          L\n@@ -1048,7 +1058,7 @@ test_conflicts_with_adds_and_renames() {\n \t\t)\n \t'\n \n-\ttest_expect_$expect \"check simple $sideL/$sideR conflict\" '\n+\ttest_expect_success \"check simple $sideL/$sideR conflict\" '\n \t\t(\n \t\t\tcd simple_${sideL}_${sideR} &&\n \n@@ -1094,10 +1104,10 @@ test_conflicts_with_adds_and_renames() {\n \t'\n }\n \n-test_conflicts_with_adds_and_renames rename rename failure\n-test_conflicts_with_adds_and_renames rename add    success\n-test_conflicts_with_adds_and_renames add    rename success\n-test_conflicts_with_adds_and_renames add    add    success\n+test_conflicts_with_adds_and_renames rename rename\n+test_conflicts_with_adds_and_renames rename add\n+test_conflicts_with_adds_and_renames add    rename\n+test_conflicts_with_adds_and_renames add    add\n \n # Setup:\n #          L\n@@ -1168,7 +1178,7 @@ test_expect_success \"setup nested conflicts from rename/rename(2to1)\" '\n \t)\n '\n \n-test_expect_failure \"check nested conflicts from rename/rename(2to1)\" '\n+test_expect_success \"check nested conflicts from rename/rename(2to1)\" '\n \t(\n \t\tcd nested_conflicts_from_rename_rename &&\n \ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex 4a71f17edd..fedaeafc55 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -278,7 +278,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n &&\n@@ -293,15 +293,16 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con\n \t\t\t A:y/wham  B:z/wham &&\n \t\ttest_cmp expect actual &&\n \n-\t\ttest_path_is_missing x/wham &&\n-\t\ttest_path_is_file x/wham~HEAD &&\n-\t\ttest_path_is_file x/wham~B^0 &&\n-\n-\t\tgit hash-object >actual \\\n-\t\t\tx/wham~HEAD x/wham~B^0 &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tA:y/wham    B:z/wham &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in x/wham is as expected\n+\t\tgit cat-file -p :2:x/wham >expect &&\n+\t\tgit cat-file -p :3:x/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect x/wham\n \t)\n '\n \n@@ -1670,7 +1671,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/b :0:y/c :2:y/d :3:y/d &&\n@@ -1678,15 +1679,16 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r\n \t\t\t O:z/b  O:z/c  O:w/d  O:x/d &&\n \t\ttest_cmp expect actual &&\n \n-\t\ttest_path_is_missing y/d &&\n-\t\ttest_path_is_file y/d~HEAD &&\n-\t\ttest_path_is_file y/d~B^0 &&\n-\n-\t\tgit hash-object >actual \\\n-\t\t\ty/d~HEAD y/d~B^0 &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:w/d    O:x/d &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in y/d is as expected\n+\t\tgit cat-file -p :2:y/d >expect &&\n+\t\tgit cat-file -p :3:y/d >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/d\n \t)\n '\n \n@@ -3165,7 +3167,7 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)\n #   Commit O: z/{a,b,c_1},        x/{d,e,f_2}\n #   Commit A: y/{a,b},            x/{d,e,f_2,wham_1} + untracked y/wham\n #   Commit B: z/{a,b,c_1,wham_2}, y/{d,e}\n-#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+\n+#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~merged}+\n #             CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham\n #             ERROR_MSG(Refusing to lose untracked file at y/wham)\n \n@@ -3219,7 +3221,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham &&\n@@ -3232,11 +3234,16 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '\n \t\techo important >expect &&\n \t\ttest_cmp expect y/wham &&\n \n-\t\tgit hash-object >actual \\\n-\t\t\ty/wham~B^0 y/wham~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/f      O:z/c &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in y/wham~merged is as expected\n+\t\tgit cat-file -p :2:y/wham >expect &&\n+\t\tgit cat-file -p :3:y/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/wham~merged\n \t)\n '\n \n@@ -3689,7 +3696,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n #   Commit O: z/{a,b},        x/{c_1,d_2}\n #   Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods\n #   Commit B: z/{a,b,wham_2}, x/c_1\n-#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} +\n+#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~merged} +\n #             y/wham with dirty changes from before merge +\n #             CONFLICT(rename/rename) x/c vs x/d -> y/wham\n #             ERROR_MSG(Refusing to lose dirty file at y/wham)\n@@ -3741,24 +3748,30 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\ttest_seq 1 10 >expected &&\n \t\techo important >>expected &&\n \t\ttest_cmp expected y/wham &&\n \n \t\ttest_must_fail git rev-parse :1:y/wham &&\n-\t\tgit hash-object >actual \\\n-\t\t\ty/wham~B^0 y/wham~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/d      O:x/c &&\n-\t\ttest_cmp expect actual &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/a :0:y/b :2:y/wham :3:y/wham &&\n \t\tgit rev-parse >expect \\\n \t\t\t O:z/a  O:z/b  O:x/c     O:x/d &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in y/wham~merged is as expected\n+\t\tgit cat-file -p :2:y/wham >expect &&\n+\t\tgit cat-file -p :3:y/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/wham~merged\n \t)\n '\n \n-- \n2.19.1.1079.gae8ff35a65\n\n"},{"id":"360975","messageId":"20181019193111.12051-6-newren@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-1-newren@gmail.com","subject":"[PATCH v3 5/8] merge-recursive: fix rename/add conflict handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-19T19:31:08Z","receivedAt":"2018-10-19T19:31:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This makes the rename/add conflict handling make use of the new\nhandle_file_collision() function, which fixes several bugs and improves\nthings for the rename/add case significantly.  Previously, rename/add\nwould:\n\n  * Not leave any higher order stage entries in the index, making it\n    appear as if there were no conflict.\n  * Would place the rename file at the colliding path, and move the\n    added file elsewhere, which combined with the lack of higher order\n    stage entries felt really odd.  It's not clear to me why the\n    rename should take precedence over the add; if one should be moved\n    out of the way, they both probably should.\n  * In the recursive case, it would do a two way merge of the added\n    file and the version of the renamed file on the renamed side,\n    completely excluding modifications to the renamed file on the\n    unrenamed side of history.\n\nUse the new handle_file_collision() to fix all of these issues.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 137 +++++++++++++++++----------\n t/t6036-recursive-corner-cases.sh    |  24 ++---\n t/t6042-merge-rename-corner-cases.sh |   4 +-\n 3 files changed, 101 insertions(+), 64 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 24d979022e..0805095168 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -186,6 +186,7 @@ static int oid_eq(const struct object_id *a, const struct object_id *b)\n enum rename_type {\n \tRENAME_NORMAL = 0,\n \tRENAME_VIA_DIR,\n+\tRENAME_ADD,\n \tRENAME_DELETE,\n \tRENAME_ONE_FILE_TO_ONE,\n \tRENAME_ONE_FILE_TO_TWO,\n@@ -228,6 +229,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\t\t\t\t      struct stage_data *src_entry1,\n \t\t\t\t\t      struct stage_data *src_entry2)\n {\n+\tint ostage1 = 0, ostage2;\n \tstruct rename_conflict_info *ci;\n \n \t/*\n@@ -264,18 +266,22 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\tdst_entry2->rename_conflict_info = ci;\n \t}\n \n-\tif (rename_type == RENAME_TWO_FILES_TO_ONE) {\n-\t\t/*\n-\t\t * For each rename, there could have been\n-\t\t * modifications on the side of history where that\n-\t\t * file was not renamed.\n-\t\t */\n-\t\tint ostage1 = o->branch1 == branch1 ? 3 : 2;\n-\t\tint ostage2 = ostage1 ^ 1;\n+\t/*\n+\t * For each rename, there could have been\n+\t * modifications on the side of history where that\n+\t * file was not renamed.\n+\t */\n+\tif (rename_type == RENAME_ADD ||\n+\t    rename_type == RENAME_TWO_FILES_TO_ONE) {\n+\t\tostage1 = o->branch1 == branch1 ? 3 : 2;\n \n \t\tci->ren1_other.path = pair1->one->path;\n \t\toidcpy(&ci->ren1_other.oid, &src_entry1->stages[ostage1].oid);\n \t\tci->ren1_other.mode = src_entry1->stages[ostage1].mode;\n+\t}\n+\n+\tif (rename_type == RENAME_TWO_FILES_TO_ONE) {\n+\t\tostage2 = ostage1 ^ 1;\n \n \t\tci->ren2_other.path = pair2->one->path;\n \t\toidcpy(&ci->ren2_other.oid, &src_entry2->stages[ostage2].oid);\n@@ -1559,7 +1565,6 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,\n \treturn target;\n }\n \n-#if 0 // #if-0-ing avoids unused function warning; will make live in next commit\n static int handle_file_collision(struct merge_options *o,\n \t\t\t\t const char *collide_path,\n \t\t\t\t const char *prev_path1,\n@@ -1575,6 +1580,20 @@ static int handle_file_collision(struct merge_options *o,\n \tchar *alt_path = NULL;\n \tconst char *update_path = collide_path;\n \n+\t/*\n+\t * It's easiest to get the correct things into stage 2 and 3, and\n+\t * to make sure that the content merge puts HEAD before the other\n+\t * branch if we just ensure that branch1 == o->branch1.  So, simply\n+\t * flip arguments around if we don't have that.\n+\t */\n+\tif (branch1 != o->branch1) {\n+\t\treturn handle_file_collision(o, collide_path,\n+\t\t\t\t\t     prev_path2, prev_path1,\n+\t\t\t\t\t     branch2, branch1,\n+\t\t\t\t\t     b_oid, b_mode,\n+\t\t\t\t\t     a_oid, a_mode);\n+\t}\n+\n \t/*\n \t * In the recursive case, we just opt to undo renames\n \t */\n@@ -1678,7 +1697,38 @@ static int handle_file_collision(struct merge_options *o,\n \t */\n \treturn mfi.clean;\n }\n-#endif\n+\n+static int handle_rename_add(struct merge_options *o,\n+\t\t\t     struct rename_conflict_info *ci)\n+{\n+\t/* a was renamed to c, and a separate c was added. */\n+\tstruct diff_filespec *a = ci->pair1->one;\n+\tstruct diff_filespec *c = ci->pair1->two;\n+\tchar *path = c->path;\n+\tchar *prev_path_desc;\n+\tstruct merge_file_info mfi;\n+\n+\tint other_stage = (ci->branch1 == o->branch1 ? 3 : 2);\n+\n+\toutput(o, 1, _(\"CONFLICT (rename/add): \"\n+\t       \"Rename %s->%s in %s.  Added %s in %s\"),\n+\t       a->path, c->path, ci->branch1,\n+\t       c->path, ci->branch2);\n+\n+\tprev_path_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n+\tif (merge_mode_and_contents(o, a, c, &ci->ren1_other, prev_path_desc,\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    1 + o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tfree(prev_path_desc);\n+\n+\treturn handle_file_collision(o,\n+\t\t\t\t     c->path, a->path, NULL,\n+\t\t\t\t     ci->branch1, ci->branch2,\n+\t\t\t\t     &mfi.oid, mfi.mode,\n+\t\t\t\t     &ci->dst_entry1->stages[other_stage].oid,\n+\t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n+}\n \n static int handle_file(struct merge_options *o,\n \t\t\tstruct diff_filespec *rename,\n@@ -2860,47 +2910,23 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t      0  /* update_wd    */))\n \t\t\t\t\tclean_merge = -1;\n \t\t\t} else if (!oid_eq(&dst_other.oid, &null_oid)) {\n-\t\t\t\tclean_merge = 0;\n-\t\t\t\ttry_merge = 1;\n-\t\t\t\toutput(o, 1, _(\"CONFLICT (rename/add): Rename %s->%s in %s. \"\n-\t\t\t\t       \"%s added in %s\"),\n-\t\t\t\t       ren1_src, ren1_dst, branch1,\n-\t\t\t\t       ren1_dst, branch2);\n-\t\t\t\tif (o->call_depth) {\n-\t\t\t\t\tstruct merge_file_info mfi;\n-\t\t\t\t\tstruct diff_filespec one, a, b;\n-\n-\t\t\t\t\toidcpy(&one.oid, &null_oid);\n-\t\t\t\t\tone.mode = 0;\n-\t\t\t\t\tone.path = ren1->pair->two->path;\n-\n-\t\t\t\t\toidcpy(&a.oid, &ren1->pair->two->oid);\n-\t\t\t\t\ta.mode = ren1->pair->two->mode;\n-\t\t\t\t\ta.path = one.path;\n-\n-\t\t\t\t\toidcpy(&b.oid, &dst_other.oid);\n-\t\t\t\t\tb.mode = dst_other.mode;\n-\t\t\t\t\tb.path = one.path;\n-\n-\t\t\t\t\tif (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,\n-\t\t\t\t\t\t\t\t    branch1, branch2,\n-\t\t\t\t\t\t\t\t    o->call_depth * 2, &mfi)) {\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\t\tgoto cleanup_and_return;\n-\t\t\t\t\t}\n-\t\t\t\t\toutput(o, 1, _(\"Adding merged %s\"), ren1_dst);\n-\t\t\t\t\tif (update_file(o, 0, &mfi.oid,\n-\t\t\t\t\t\t\tmfi.mode, ren1_dst))\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\ttry_merge = 0;\n-\t\t\t\t} else {\n-\t\t\t\t\tchar *new_path = unique_path(o, ren1_dst, branch2);\n-\t\t\t\t\toutput(o, 1, _(\"Adding as %s instead\"), new_path);\n-\t\t\t\t\tif (update_file(o, 0, &dst_other.oid,\n-\t\t\t\t\t\t\tdst_other.mode, new_path))\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\tfree(new_path);\n-\t\t\t\t}\n+\t\t\t\t/*\n+\t\t\t\t * Probably not a clean merge, but it's\n+\t\t\t\t * premature to set clean_merge to 0 here,\n+\t\t\t\t * because if the rename merges cleanly and\n+\t\t\t\t * the merge exactly matches the newly added\n+\t\t\t\t * file, then the merge will be clean.\n+\t\t\t\t */\n+\t\t\t\tsetup_rename_conflict_info(RENAME_ADD,\n+\t\t\t\t\t\t\t   ren1->pair,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   branch1,\n+\t\t\t\t\t\t\t   branch2,\n+\t\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   o,\n+\t\t\t\t\t\t\t   ren1->src_entry,\n+\t\t\t\t\t\t\t   NULL);\n \t\t\t} else\n \t\t\t\ttry_merge = 1;\n \n@@ -3312,6 +3338,15 @@ static int process_entry(struct merge_options *o,\n \t\t\t\t\t\t  conflict_info->branch2))\n \t\t\t\tclean_merge = -1;\n \t\t\tbreak;\n+\t\tcase RENAME_ADD:\n+\t\t\t/*\n+\t\t\t * Probably unclean merge, but if the renamed file\n+\t\t\t * merges cleanly and the result can then be\n+\t\t\t * two-way merged cleanly with the added file, I\n+\t\t\t * guess it's a clean merge?\n+\t\t\t */\n+\t\t\tclean_merge = handle_rename_add(o, conflict_info);\n+\t\t\tbreak;\n \t\tcase RENAME_DELETE:\n \t\t\tclean_merge = 0;\n \t\t\tif (handle_rename_delete(o,\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 32fd0a2995..44807603e3 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -185,7 +185,7 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '\n \t\tgit branch B &&\n \t\tgit checkout -b C &&\n \t\techo 10 >>a &&\n-\t\techo \"other content\" >>new_a &&\n+\t\ttest_write_lines 0 1 2 3 4 5 6 7 foobar >new_a &&\n \t\tgit add a new_a &&\n \t\ttest_tick && git commit -m C &&\n \n@@ -195,14 +195,14 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '\n \n \t\tgit checkout B^0 &&\n \t\ttest_must_fail git merge C &&\n-\t\tgit clean -f &&\n+\t\tgit show :2:new_a >new_a &&\n+\t\tgit add new_a &&\n \t\ttest_tick && git commit -m D &&\n \t\tgit tag D &&\n \n \t\tgit checkout C^0 &&\n \t\ttest_must_fail git merge B &&\n-\t\trm new_a~HEAD new_a &&\n-\t\tprintf \"Incorrectly merged content\" >>new_a &&\n+\t\ttest_write_lines 0 1 2 3 4 5 6 7 bad_merge >new_a &&\n \t\tgit add -u &&\n \t\ttest_tick && git commit -m E &&\n \t\tgit tag E\n@@ -225,21 +225,23 @@ test_expect_success 'git detects differently handled merges conflict' '\n \t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tD:new_a  E:new_a &&\n+\t\t\tC:new_a  D:new_a  E:new_a &&\n \t\tgit rev-parse   >actual     \\\n-\t\t\t:2:new_a :3:new_a &&\n+\t\t\t:1:new_a :2:new_a :3:new_a &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit cat-file -p C:new_a >ours &&\n-\t\tgit cat-file -p B:new_a >theirs &&\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p D:new_a >ours &&\n+\t\tgit cat-file -p E:new_a >theirs &&\n \t\t>empty &&\n \t\ttest_must_fail git merge-file \\\n-\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"HEAD\" \\\n \t\t\t-L \"\" \\\n-\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\t-L \"E^0\" \\\n \t\t\tours empty theirs &&\n \t\tsed -e \"s/^\\([<=>]\\)/\\1\\1\\1/\" ours >expect &&\n-\t\tgit cat-file -p :1:new_a >actual &&\n+\t\tgit hash-object new_a >actual &&\n+\t\tgit hash-object ours  >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 5e69e60b48..f1c4a06dd6 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -1095,8 +1095,8 @@ test_conflicts_with_adds_and_renames() {\n }\n \n test_conflicts_with_adds_and_renames rename rename failure\n-test_conflicts_with_adds_and_renames rename add    failure\n-test_conflicts_with_adds_and_renames add    rename failure\n+test_conflicts_with_adds_and_renames rename add    success\n+test_conflicts_with_adds_and_renames add    rename success\n test_conflicts_with_adds_and_renames add    add    success\n \n # Setup:\n-- \n2.19.1.1079.gae8ff35a65\n\n"},{"id":"360976","messageId":"20181019193111.12051-8-newren@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-1-newren@gmail.com","subject":"[PATCH v3 7/8] merge-recursive: use handle_file_collision for add/add conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-19T19:31:10Z","receivedAt":"2018-10-19T19:31:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This results in no-net change of behavior, it simply ensures that all\nfile-collision conflict handling types are being handled the same by\ncalling the same function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 29 +++++++++++++++++++++--------\n 1 file changed, 21 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ead6054a75..c78b347112 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3355,14 +3355,27 @@ static int process_entry(struct merge_options *o,\n \t\t\t\tclean_merge = -1;\n \t\t}\n \t} else if (a_oid && b_oid) {\n-\t\t/* Case C: Added in both (check for same permissions) and */\n-\t\t/* case D: Modified in both, but differently. */\n-\t\tint is_dirty = 0; /* unpack_trees would have bailed if dirty */\n-\t\tclean_merge = handle_content_merge(o, path, is_dirty,\n-\t\t\t\t\t\t   o_oid, o_mode,\n-\t\t\t\t\t\t   a_oid, a_mode,\n-\t\t\t\t\t\t   b_oid, b_mode,\n-\t\t\t\t\t\t   NULL);\n+\t\tif (!o_oid) {\n+\t\t\t/* Case C: Added in both (check for same permissions) */\n+\t\t\toutput(o, 1,\n+\t\t\t       _(\"CONFLICT (add/add): Merge conflict in %s\"),\n+\t\t\t       path);\n+\t\t\tclean_merge = handle_file_collision(o,\n+\t\t\t\t\t\t\t    path, NULL, NULL,\n+\t\t\t\t\t\t\t    o->branch1,\n+\t\t\t\t\t\t\t    o->branch2,\n+\t\t\t\t\t\t\t    a_oid, a_mode,\n+\t\t\t\t\t\t\t    b_oid, b_mode);\n+\t\t} else {\n+\t\t\t/* case D: Modified in both, but differently. */\n+\t\t\tint is_dirty = 0; /* unpack_trees would have bailed if dirty */\n+\t\t\tclean_merge = handle_content_merge(o, path,\n+\t\t\t\t\t\t\t   is_dirty,\n+\t\t\t\t\t\t\t   o_oid, o_mode,\n+\t\t\t\t\t\t\t   a_oid, a_mode,\n+\t\t\t\t\t\t\t   b_oid, b_mode,\n+\t\t\t\t\t\t\t   NULL);\n+\t\t}\n \t} else if (!o_oid && !a_oid && !b_oid) {\n \t\t/*\n \t\t * this entry was deleted altogether. a_mode == 0 means\n-- \n2.19.1.1079.gae8ff35a65\n\n"},{"id":"360977","messageId":"20181019193111.12051-9-newren@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-1-newren@gmail.com","subject":"[PATCH v3 8/8] merge-recursive: improve rename/rename(1to2)/add[/add] handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-10-19T19:31:11Z","receivedAt":"2018-10-19T19:31:27Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When we have a rename/rename(1to2) conflict, each of the renames can\ncollide with a file addition.  Each of these rename/add conflicts suffered\nfrom the same kinds of problems that normal rename/add suffered from.\nMake the code use handle_file_conflicts() as well so that we get all the\nsame fixes and consistent behavior between the different conflict types.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 154 +++++++++++++--------------\n t/t6042-merge-rename-corner-cases.sh |  29 +++--\n t/t6043-merge-rename-directories.sh  |  24 +++--\n 3 files changed, 113 insertions(+), 94 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c78b347112..59811116b6 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1709,80 +1709,17 @@ static int handle_rename_add(struct merge_options *o,\n \t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n }\n \n-static int handle_file(struct merge_options *o,\n-\t\t\tstruct diff_filespec *rename,\n-\t\t\tint stage,\n-\t\t\tstruct rename_conflict_info *ci)\n-{\n-\tchar *dst_name = rename->path;\n-\tstruct stage_data *dst_entry;\n-\tconst char *cur_branch, *other_branch;\n-\tstruct diff_filespec other;\n-\tstruct diff_filespec *add;\n-\tint ret;\n-\n-\tif (stage == 2) {\n-\t\tdst_entry = ci->dst_entry1;\n-\t\tcur_branch = ci->branch1;\n-\t\tother_branch = ci->branch2;\n-\t} else {\n-\t\tdst_entry = ci->dst_entry2;\n-\t\tcur_branch = ci->branch2;\n-\t\tother_branch = ci->branch1;\n-\t}\n-\n-\tadd = filespec_from_entry(&other, dst_entry, stage ^ 1);\n-\tif (add) {\n-\t\tint ren_src_was_dirty = was_dirty(o, rename->path);\n-\t\tchar *add_name = unique_path(o, rename->path, other_branch);\n-\t\tif (update_file(o, 0, &add->oid, add->mode, add_name))\n-\t\t\treturn -1;\n-\n-\t\tif (ren_src_was_dirty) {\n-\t\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n-\t\t\t       rename->path);\n-\t\t}\n-\t\t/*\n-\t\t * Because the double negatives somehow keep confusing me...\n-\t\t *    1) update_wd iff !ren_src_was_dirty.\n-\t\t *    2) no_wd iff !update_wd\n-\t\t *    3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty\n-\t\t */\n-\t\tremove_file(o, 0, rename->path, ren_src_was_dirty);\n-\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t} else {\n-\t\tif (dir_in_way(rename->path, !o->call_depth, 0)) {\n-\t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t\t\toutput(o, 1, _(\"%s is a directory in %s adding as %s instead\"),\n-\t\t\t       rename->path, other_branch, dst_name);\n-\t\t} else if (!o->call_depth &&\n-\t\t\t   would_lose_untracked(rename->path)) {\n-\t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t\t\toutput(o, 1, _(\"Refusing to lose untracked file at %s; \"\n-\t\t\t\t       \"adding as %s instead\"),\n-\t\t\t       rename->path, dst_name);\n-\t\t}\n-\t}\n-\tif ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))\n-\t\t; /* fall through, do allow dst_name to be released */\n-\telse if (stage == 2)\n-\t\tret = update_stages(o, rename->path, NULL, rename, add);\n-\telse\n-\t\tret = update_stages(o, rename->path, NULL, add, rename);\n-\n-\tif (dst_name != rename->path)\n-\t\tfree(dst_name);\n-\n-\treturn ret;\n-}\n-\n static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t     struct rename_conflict_info *ci)\n {\n \t/* One file was renamed in both branches, but to different names. */\n+\tstruct merge_file_info mfi;\n+\tstruct diff_filespec other;\n+\tstruct diff_filespec *add;\n \tstruct diff_filespec *one = ci->pair1->one;\n \tstruct diff_filespec *a = ci->pair1->two;\n \tstruct diff_filespec *b = ci->pair2->two;\n+\tchar *path_desc;\n \n \toutput(o, 1, _(\"CONFLICT (rename/rename): \"\n \t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n@@ -1790,15 +1727,16 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t       one->path, a->path, ci->branch1,\n \t       one->path, b->path, ci->branch2,\n \t       o->call_depth ? _(\" (left unresolved)\") : \"\");\n-\tif (o->call_depth) {\n-\t\tstruct merge_file_info mfi;\n-\t\tstruct diff_filespec other;\n-\t\tstruct diff_filespec *add;\n-\t\tif (merge_mode_and_contents(o, one, a, b, one->path,\n-\t\t\t\t\t    ci->branch1, ci->branch2,\n-\t\t\t\t\t    o->call_depth * 2, &mfi))\n-\t\t\treturn -1;\n \n+\tpath_desc = xstrfmt(\"%s and %s, both renamed from %s\",\n+\t\t\t    a->path, b->path, one->path);\n+\tif (merge_mode_and_contents(o, one, a, b, path_desc,\n+\t\t\t\t    ci->branch1, ci->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tfree(path_desc);\n+\n+\tif (o->call_depth) {\n \t\t/*\n \t\t * FIXME: For rename/add-source conflicts (if we could detect\n \t\t * such), this is wrong.  We should instead find a unique\n@@ -1830,8 +1768,70 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t}\n \t\telse\n \t\t\tremove_file_from_cache(b->path);\n-\t} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))\n-\t\treturn -1;\n+\t} else {\n+\t\t/*\n+\t\t * For each destination path, we need to see if there is a\n+\t\t * rename/add collision.  If not, we can write the file out\n+\t\t * to the specified location.\n+\t\t */\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);\n+\t\tif (add) {\n+\t\t\tif (handle_file_collision(o, a->path,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  ci->branch1, ci->branch2,\n+\t\t\t\t\t\t  &mfi.oid, mfi.mode,\n+\t\t\t\t\t\t  &add->oid, add->mode) < 0)\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tchar *new_path = NULL;\n+\t\t\tif (dir_in_way(a->path, !o->call_depth, 0)) {\n+\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n+\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t\t\t       \"as %s instead\"),\n+\t\t\t\t       a->path, ci->branch2, new_path);\n+\t\t\t} else if (would_lose_untracked(a->path)) {\n+\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n+\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t\t\t       a->path, new_path);\n+\t\t\t}\n+\n+\t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))\n+\t\t\t\treturn -1;\n+\t\t\tfree(new_path);\n+\t\t\tif (update_stages(o, a->path, NULL, a, NULL))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);\n+\t\tif (add) {\n+\t\t\tif (handle_file_collision(o, b->path,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  ci->branch1, ci->branch2,\n+\t\t\t\t\t\t  &add->oid, add->mode,\n+\t\t\t\t\t\t  &mfi.oid, mfi.mode) < 0)\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tchar *new_path = NULL;\n+\t\t\tif (dir_in_way(b->path, !o->call_depth, 0)) {\n+\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n+\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t\t\t       \"as %s instead\"),\n+\t\t\t\t       b->path, ci->branch1, new_path);\n+\t\t\t} else if (would_lose_untracked(b->path)) {\n+\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n+\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t\t\t       b->path, new_path);\n+\t\t\t}\n+\n+\t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))\n+\t\t\t\treturn -1;\n+\t\t\tfree(new_path);\n+\t\t\tif (update_stages(o, b->path, NULL, NULL, b))\n+\t\t\t\treturn -1;\n+\t\t}\n+\t}\n \n \treturn 0;\n }\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 4cfcde3082..534f7b55a1 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -684,7 +684,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t\tgit ls-files -u c >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 5 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect               \\\n \t\t\tA:a   C:b   B:b   C:c   B:c &&\n@@ -692,14 +692,27 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t\t\t:1:a  :2:b  :3:b  :2:c  :3:c &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit rev-parse >expect               \\\n-\t\t\tC:c     B:c     C:b     B:b &&\n-\t\tgit hash-object >actual                \\\n-\t\t\tc~HEAD  c~B\\^0  b~HEAD  b~B\\^0 &&\n-\t\ttest_cmp expect actual &&\n+\t\t# Record some contents for re-doing merges\n+\t\tgit cat-file -p A:a >stuff &&\n+\t\tgit cat-file -p C:b >important_info &&\n+\t\tgit cat-file -p B:c >precious_data &&\n+\t\t>empty &&\n \n-\t\ttest_path_is_missing b &&\n-\t\ttest_path_is_missing c\n+\t\t# Test the merge in b\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\timportant_info empty stuff &&\n+\t\ttest_cmp important_info b &&\n+\n+\t\t# Test the merge in c\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\tstuff empty precious_data &&\n+\t\ttest_cmp stuff c\n \t)\n '\n \ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex fedaeafc55..5c01a0c14a 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -1078,7 +1078,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 6 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/b :0:y/c :0:y/e &&\n@@ -1094,9 +1094,9 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam\n \t\ttest_cmp expect actual &&\n \n \t\tgit hash-object >actual \\\n-\t\t\tw/d~HEAD w/d~B^0 z/d &&\n+\t\t\tz/d &&\n \t\tgit rev-parse >expect \\\n-\t\t\tO:x/d    B:w/d   O:x/d &&\n+\t\t\tO:x/d &&\n \t\ttest_cmp expect actual &&\n \t\ttest_path_is_missing x/d &&\n \t\ttest_path_is_file y/d &&\n@@ -3672,7 +3672,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 4 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\techo different >expected &&\n \t\techo mods >>expected &&\n@@ -3684,11 +3684,17 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n \t\t\t O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  A:y/c  O:x/c &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit hash-object >actual \\\n-\t\t\ty/c~B^0 y/c~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/c   A:y/c &&\n-\t\ttest_cmp expect actual\n+\t\t# See if y/c~merged has expected contents; requires manually\n+\t\t# doing the expected file merge\n+\t\tgit cat-file -p A:y/c >c1 &&\n+\t\tgit cat-file -p B:z/c >c2 &&\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\tc1 empty c2 &&\n+\t\ttest_cmp c1 y/c~merged\n \t)\n '\n \n-- \n2.19.1.1079.gae8ff35a65\n\n"},{"id":"362105","messageId":"0b84c95b-38bc-e39e-a033-81eb3e7b8202@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-5-newren@gmail.com","subject":"Re: [PATCH v3 4/8] merge-recursive: new function for better colliding conflict resolutions","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2018-10-31T13:53:40Z","receivedAt":"2018-10-31T13:53:43Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/19/2018 3:31 PM, Elijah Newren wrote:\n> +#if 0 // #if-0-ing avoids unused function warning; will make live in next commit\n> +static int handle_file_collision(struct merge_options *o,\n> +\t\t\t\t const char *collide_path,\n> +\t\t\t\t const char *prev_path1,\n> +\t\t\t\t const char *prev_path2,\n> +\t\t\t\t const char *branch1, const char *branch2,\n> +\t\t\t\t const struct object_id *a_oid,\n> +\t\t\t\t unsigned int a_mode,\n> +\t\t\t\t const struct object_id *b_oid,\n> +\t\t\t\t unsigned int b_mode)\n> +{\n> +\tstruct merge_file_info mfi;\n> +\tstruct diff_filespec null, a, b;\n> +\tchar *alt_path = NULL;\n> +\tconst char *update_path = collide_path;\n> +\n> +\t/*\n> +\t * In the recursive case, we just opt to undo renames\n> +\t */\n> +\tif (o->call_depth && (prev_path1 || prev_path2)) {\n> +\t\t/* Put first file (a_oid, a_mode) in its original spot */\n> +\t\tif (prev_path1) {\n> +\t\t\tif (update_file(o, 1, a_oid, a_mode, prev_path1))\n> +\t\t\t\treturn -1;\n> +\t\t} else {\n> +\t\t\tif (update_file(o, 1, a_oid, a_mode, collide_path))\n\nThe latest test coverage report [1] shows this if statement is never run, so\nit appears that every call to this method in the test suite has either\no->call_depth positive, prev_path1 non-NULL, or both prev_path1 and \nprev_path2\nNULL.\n\nIs there a way we can add a test case that calls this method with \no->call_depth\npositive, prev_path1 NULL, and prev_path2 non-NULL?\n\n> +\t\t\t\treturn -1;\n> +\t\t}\n> +\n> +\t\t/* Put second file (b_oid, b_mode) in its original spot */\n> +\t\tif (prev_path2) {\n> +\t\t\tif (update_file(o, 1, b_oid, b_mode, prev_path2))\n\nSince this line is covered, we _do_ call the method with prev_path2 \nnon-NULL, but\nprev_path1 must be non-NULL in all cases.\n\nI may have found a reason why this doesn't happen in one of the callers \nyou introduced.\nI'm going to comment on PATCH 8/8 to see if that is the case.\n\nThanks,\n-Stolee\n\n[1] \nhttps://public-inbox.org/git/62f0bcf6-aa73-c192-d804-e6d69cac146f@gmail.com/\n"},{"id":"362106","messageId":"95a236fd-a757-81ad-34aa-b26b3c3b6e85@gmail.com","threadId":"49559","inReplyTo":"0b84c95b-38bc-e39e-a033-81eb3e7b8202@gmail.com","subject":"Re: [PATCH v3 4/8] merge-recursive: new function for better colliding conflict resolutions","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2018-10-31T13:57:37Z","receivedAt":"2018-10-31T13:57:41Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/31/2018 9:53 AM, Derrick Stolee wrote:\n> On 10/19/2018 3:31 PM, Elijah Newren wrote:\n>> +#if 0 // #if-0-ing avoids unused function warning; will make live in \n>> next commit\n>> +static int handle_file_collision(struct merge_options *o,\n>> +                 const char *collide_path,\n>> +                 const char *prev_path1,\n>> +                 const char *prev_path2,\n>> +                 const char *branch1, const char *branch2,\n>> +                 const struct object_id *a_oid,\n>> +                 unsigned int a_mode,\n>> +                 const struct object_id *b_oid,\n>> +                 unsigned int b_mode)\n>> +{\n>> +    struct merge_file_info mfi;\n>> +    struct diff_filespec null, a, b;\n>> +    char *alt_path = NULL;\n>> +    const char *update_path = collide_path;\n>> +\n>> +    /*\n>> +     * In the recursive case, we just opt to undo renames\n>> +     */\n>> +    if (o->call_depth && (prev_path1 || prev_path2)) {\n>> +        /* Put first file (a_oid, a_mode) in its original spot */\n>> +        if (prev_path1) {\n>> +            if (update_file(o, 1, a_oid, a_mode, prev_path1))\n>> +                return -1;\n>> +        } else {\n>> +            if (update_file(o, 1, a_oid, a_mode, collide_path))\n>\n> The latest test coverage report [1] shows this if statement is never \n> run, so\n> it appears that every call to this method in the test suite has either\n> o->call_depth positive, prev_path1 non-NULL, or both prev_path1 and \n> prev_path2\n> NULL.\n>\n> Is there a way we can add a test case that calls this method with \n> o->call_depth\n> positive, prev_path1 NULL, and prev_path2 non-NULL?\n>\n>> +                return -1;\n>> +        }\n>> +\n>> +        /* Put second file (b_oid, b_mode) in its original spot */\n>> +        if (prev_path2) {\n>> +            if (update_file(o, 1, b_oid, b_mode, prev_path2))\n>\n> Since this line is covered, we _do_ call the method with prev_path2 \n> non-NULL, but\n> prev_path1 must be non-NULL in all cases.\n>\n> I may have found a reason why this doesn't happen in one of the \n> callers you introduced.\n> I'm going to comment on PATCH 8/8 to see if that is the case.\n\nNevermind on the PATCH 8/8 situation. I thought I saw you pass (a->path, \nNULL) and\n(b->path, NULL) into the (prev_path1, prev_path2) pairs, but in each \ncase the non-NULL\nparameter is actually for 'collide_path'.\n\nIt is still interesting if we can hit this case. Perhaps we need a \ndifferent kind of\nconflict, like (rename, delete) [but I struggle to make sense of how to \ndo that].\n\nThanks,\n-Stolee\n"},{"id":"362107","messageId":"b14a0c79-5dce-a1dd-cd26-16e061154c43@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-3-newren@gmail.com","subject":"Re: [PATCH v3 2/8] t6036, t6042: testcases for rename collision of already conflicting files","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2018-10-31T14:01:54Z","receivedAt":"2018-10-31T14:01:56Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/19/2018 3:31 PM, Elijah Newren wrote:\n> +test_expect_success \"setup nested conflicts\" '\n\nnit: should these test names be single-quoted? I see you using double-quotes\nin PATCH 1/8 as well, but that seems to be because there are variables in\nthe test names.\n\n\n...\n> +test_expect_failure \"check nested conflicts\" '\n\nSame here.\n\n> +test_expect_success \"setup nested conflicts from rename/rename(2to1)\" '\n\n> +test_expect_failure \"check nested conflicts from rename/rename(2to1)\" '\n\nThanks,\n-Stolee\n"},{"id":"362112","messageId":"d23b4e26-4df4-273d-a75c-a0af915a6e5e@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-9-newren@gmail.com","subject":"Re: [PATCH v3 8/8] merge-recursive: improve rename/rename(1to2)/add[/add] handling","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2018-10-31T15:08:45Z","receivedAt":"2018-10-31T15:08:48Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 10/19/2018 3:31 PM, Elijah Newren wrote:\n> [snip]\n>\n> +\t\t\tchar *new_path = NULL;\n> +\t\t\tif (dir_in_way(b->path, !o->call_depth, 0)) {\n> +\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n> +\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n> +\t\t\t\t\t       \"as %s instead\"),\n> +\t\t\t\t       b->path, ci->branch1, new_path);\n\nI tried really hard, but failed to get a test to cover the block below. \nI was able to\nfind that the \"check handling of differently renamed file with D/F \nconflicts\" test\nin t6022-merge-rename.sh covers the block above. Trying to tweak the \nexample using\nuntracked files seems to hit an error message from unpack-trees.c instead.\n\n> +\t\t\t} else if (would_lose_untracked(b->path)) {\n> +\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n> +\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n> +\t\t\t\t\t       \" at %s; adding as %s instead\"),\n> +\t\t\t\t       b->path, new_path);\n\nIt could also be that I failed because I'm less familiar with this part \nof the\ncodebase. Elijah, do you think it is possible to hit this block?\n\nThanks,\n-Stolee\n"},{"id":"362152","messageId":"CABPp-BGC11LRrSXveKg5+rvNY-29uTB5_Qdiev1-s7abJTNygA@mail.gmail.com","threadId":"49559","inReplyTo":"95a236fd-a757-81ad-34aa-b26b3c3b6e85@gmail.com","subject":"Re: [PATCH v3 4/8] merge-recursive: new function for better colliding conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-01T06:56:24Z","receivedAt":"2018-11-01T06:56:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Oct 31, 2018 at 6:57 AM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 10/31/2018 9:53 AM, Derrick Stolee wrote:\n> > On 10/19/2018 3:31 PM, Elijah Newren wrote:\n> >> +#if 0 // #if-0-ing avoids unused function warning; will make live in\n> >> next commit\n> >> +static int handle_file_collision(struct merge_options *o,\n> >> +                 const char *collide_path,\n> >> +                 const char *prev_path1,\n> >> +                 const char *prev_path2,\n> >> +                 const char *branch1, const char *branch2,\n> >> +                 const struct object_id *a_oid,\n> >> +                 unsigned int a_mode,\n> >> +                 const struct object_id *b_oid,\n> >> +                 unsigned int b_mode)\n> >> +{\n> >> +    struct merge_file_info mfi;\n> >> +    struct diff_filespec null, a, b;\n> >> +    char *alt_path = NULL;\n> >> +    const char *update_path = collide_path;\n> >> +\n> >> +    /*\n> >> +     * In the recursive case, we just opt to undo renames\n> >> +     */\n> >> +    if (o->call_depth && (prev_path1 || prev_path2)) {\n> >> +        /* Put first file (a_oid, a_mode) in its original spot */\n> >> +        if (prev_path1) {\n> >> +            if (update_file(o, 1, a_oid, a_mode, prev_path1))\n> >> +                return -1;\n> >> +        } else {\n> >> +            if (update_file(o, 1, a_oid, a_mode, collide_path))\n> >\n> > The latest test coverage report [1] shows this if statement is never\n> > run, so\n> > it appears that every call to this method in the test suite has either\n> > o->call_depth positive, prev_path1 non-NULL, or both prev_path1 and\n> > prev_path2\n> > NULL.\n> >\n> > Is there a way we can add a test case that calls this method with\n> > o->call_depth\n> > positive, prev_path1 NULL, and prev_path2 non-NULL?\n> >\n> >> +                return -1;\n> >> +        }\n> >> +\n> >> +        /* Put second file (b_oid, b_mode) in its original spot */\n> >> +        if (prev_path2) {\n> >> +            if (update_file(o, 1, b_oid, b_mode, prev_path2))\n> >\n> > Since this line is covered, we _do_ call the method with prev_path2\n> > non-NULL, but\n> > prev_path1 must be non-NULL in all cases.\n> >\n> > I may have found a reason why this doesn't happen in one of the\n> > callers you introduced.\n> > I'm going to comment on PATCH 8/8 to see if that is the case.\n>\n> Nevermind on the PATCH 8/8 situation. I thought I saw you pass (a->path,\n> NULL) and\n> (b->path, NULL) into the (prev_path1, prev_path2) pairs, but in each\n> case the non-NULL\n> parameter is actually for 'collide_path'.\n>\n> It is still interesting if we can hit this case. Perhaps we need a\n> different kind of\n> conflict, like (rename, delete) [but I struggle to make sense of how to\n> do that].\n\nrename/delete conflicts are sent through handle_rename_delete() which\ndo not call into handle_file_collision().  What you'd instead need is\na rename/add conflict, in the virtual merge base, on the appropriate\nside.  The fact that the prev_path2 non-NULL case is covered means\nthere's already a regression test that's probably nearly good enough,\nyou'd just need to edit the committer timestamps of the two merge\nbases so that a different one was older.  I'm pretty sure we can come\nup with one without too much effort.  I'll take a look tomorrow; too\nlate tonight.\n"},{"id":"362153","messageId":"CABPp-BGq1YDdzZ6CqzCp9QXPZy6XxV8sujyRxwBc-EnS+ygSrg@mail.gmail.com","threadId":"49559","inReplyTo":"b14a0c79-5dce-a1dd-cd26-16e061154c43@gmail.com","subject":"Re: [PATCH v3 2/8] t6036, t6042: testcases for rename collision of already conflicting files","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-01T06:57:10Z","receivedAt":"2018-11-01T06:57:24Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Oct 31, 2018 at 7:01 AM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 10/19/2018 3:31 PM, Elijah Newren wrote:\n> > +test_expect_success \"setup nested conflicts\" '\n>\n> nit: should these test names be single-quoted? I see you using double-quotes\n> in PATCH 1/8 as well, but that seems to be because there are variables in\n> the test names.\n>\n>\n> ...\n> > +test_expect_failure \"check nested conflicts\" '\n>\n> Same here.\n>\n> > +test_expect_success \"setup nested conflicts from rename/rename(2to1)\" '\n>\n> > +test_expect_failure \"check nested conflicts from rename/rename(2to1)\" '\n\nI'll fix them up; thanks.\n"},{"id":"362154","messageId":"CABPp-BFZugCFDS_A7FrWdRhp7WO0m8jhsqbSnNCSUWnAVz7mtg@mail.gmail.com","threadId":"49559","inReplyTo":"d23b4e26-4df4-273d-a75c-a0af915a6e5e@gmail.com","subject":"Re: [PATCH v3 8/8] merge-recursive: improve rename/rename(1to2)/add[/add] handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-01T07:01:44Z","receivedAt":"2018-11-01T07:01:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Oct 31, 2018 at 8:08 AM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 10/19/2018 3:31 PM, Elijah Newren wrote:\n> > [snip]\n> >\n> > +                     char *new_path = NULL;\n> > +                     if (dir_in_way(b->path, !o->call_depth, 0)) {\n> > +                             new_path = unique_path(o, b->path, ci->branch2);\n> > +                             output(o, 1, _(\"%s is a directory in %s adding \"\n> > +                                            \"as %s instead\"),\n> > +                                    b->path, ci->branch1, new_path);\n>\n> I tried really hard, but failed to get a test to cover the block below.\n> I was able to\n> find that the \"check handling of differently renamed file with D/F\n> conflicts\" test\n> in t6022-merge-rename.sh covers the block above. Trying to tweak the\n> example using\n> untracked files seems to hit an error message from unpack-trees.c instead.\n>\n> > +                     } else if (would_lose_untracked(b->path)) {\n> > +                             new_path = unique_path(o, b->path, ci->branch2);\n> > +                             output(o, 1, _(\"Refusing to lose untracked file\"\n> > +                                            \" at %s; adding as %s instead\"),\n> > +                                    b->path, new_path);\n>\n> It could also be that I failed because I'm less familiar with this part\n> of the\n> codebase. Elijah, do you think it is possible to hit this block?\n\nYeah, this one's going to be a little harder; the upper block would be\ndone with a D/F, but I think for this block you'd need a directory\nrename so that unpack-trees.c can't tell that the untracked file in\nthe way is actually in the way of anything.  But since this is in the\nrename/rename(1to2) area, I think I had rules around avoiding doing\ndirectory renames if the other side renamed the file to avoid getting\ninto rename/rename(1to3) situations and other weirdness.  So, it might\nrequire a transitive rename (i.e. file renamed on both sides, and on\none side it's renamed into a directory that the other side renamed\naway).\n\nI'll try to take a look at it tomorrow, with everything else.  We'll\nsee how much I can get done.\n\nThanks for digging in to all these and bringing them up.\n"},{"id":"362291","messageId":"CABPp-BFwimcDhwMeFcTJLy-omXiBNzZApkyCnSSRfwXvKi2OUQ@mail.gmail.com","threadId":"49559","inReplyTo":"CABPp-BFZugCFDS_A7FrWdRhp7WO0m8jhsqbSnNCSUWnAVz7mtg@mail.gmail.com","subject":"Re: [PATCH v3 8/8] merge-recursive: improve rename/rename(1to2)/add[/add] handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T17:27:53Z","receivedAt":"2018-11-02T17:28:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Nov 1, 2018 at 12:01 AM Elijah Newren <newren@gmail.com> wrote:\n>\n> On Wed, Oct 31, 2018 at 8:08 AM Derrick Stolee <stolee@gmail.com> wrote:\n> >\n> > On 10/19/2018 3:31 PM, Elijah Newren wrote:\n> > > [snip]\n> > >\n> > > +                     char *new_path = NULL;\n> > > +                     if (dir_in_way(b->path, !o->call_depth, 0)) {\n> > > +                             new_path = unique_path(o, b->path, ci->branch2);\n> > > +                             output(o, 1, _(\"%s is a directory in %s adding \"\n> > > +                                            \"as %s instead\"),\n> > > +                                    b->path, ci->branch1, new_path);\n> >\n> > I tried really hard, but failed to get a test to cover the block below.\n> > I was able to\n> > find that the \"check handling of differently renamed file with D/F\n> > conflicts\" test\n> > in t6022-merge-rename.sh covers the block above. Trying to tweak the\n> > example using\n> > untracked files seems to hit an error message from unpack-trees.c instead.\n> >\n> > > +                     } else if (would_lose_untracked(b->path)) {\n> > > +                             new_path = unique_path(o, b->path, ci->branch2);\n> > > +                             output(o, 1, _(\"Refusing to lose untracked file\"\n> > > +                                            \" at %s; adding as %s instead\"),\n> > > +                                    b->path, new_path);\n> >\n\nSo now I'm confused.  This block was not listed in your coverage\nreport[1].  And, in fact, I think this block IS covered by testcase\n10c of t6043.  However, there is a very similar looking block about 30\nlines up that is uncovered (and which was mentioned in your report):\n\n            } else if (would_lose_untracked(a->path)) {\n                new_path = unique_path(o, a->path, ci->branch1);\n                output(o, 1, _(\"Refusing to lose untracked file\"\n                           \" at %s; adding as %s instead\"),\n                       a->path, new_path);\n\ncovering it, I think, is just a matter of repeating the 10c test with\nthe merge repeated in the other direction (checkout B and merge A\ninstead of checking out A and merging B) -- and touching up the checks\naccordingly.\n\nHowever, now I'm wondering if I'm crazy.  Was it really the block you\nhad highlighted that you were seeing uncovered?\n\nThanks,\nElijah\n\n[1] https://public-inbox.org/git/62f0bcf6-aa73-c192-d804-e6d69cac146f@gmail.com/\n"},{"id":"362292","messageId":"c3b4d1d5-2a7e-b83c-d8cc-bdac6a8835e2@gmail.com","threadId":"49559","inReplyTo":"CABPp-BFwimcDhwMeFcTJLy-omXiBNzZApkyCnSSRfwXvKi2OUQ@mail.gmail.com","subject":"Re: [PATCH v3 8/8] merge-recursive: improve rename/rename(1to2)/add[/add] handling","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2018-11-02T17:30:31Z","receivedAt":"2018-11-02T17:30:35Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/2/2018 1:27 PM, Elijah Newren wrote:\n> On Thu, Nov 1, 2018 at 12:01 AM Elijah Newren <newren@gmail.com> wrote:\n>> On Wed, Oct 31, 2018 at 8:08 AM Derrick Stolee <stolee@gmail.com> wrote:\n>>> On 10/19/2018 3:31 PM, Elijah Newren wrote:\n>>>> [snip]\n>>>>\n>>>> +                     char *new_path = NULL;\n>>>> +                     if (dir_in_way(b->path, !o->call_depth, 0)) {\n>>>> +                             new_path = unique_path(o, b->path, ci->branch2);\n>>>> +                             output(o, 1, _(\"%s is a directory in %s adding \"\n>>>> +                                            \"as %s instead\"),\n>>>> +                                    b->path, ci->branch1, new_path);\n>>> I tried really hard, but failed to get a test to cover the block below.\n>>> I was able to\n>>> find that the \"check handling of differently renamed file with D/F\n>>> conflicts\" test\n>>> in t6022-merge-rename.sh covers the block above. Trying to tweak the\n>>> example using\n>>> untracked files seems to hit an error message from unpack-trees.c instead.\n>>>\n>>>> +                     } else if (would_lose_untracked(b->path)) {\n>>>> +                             new_path = unique_path(o, b->path, ci->branch2);\n>>>> +                             output(o, 1, _(\"Refusing to lose untracked file\"\n>>>> +                                            \" at %s; adding as %s instead\"),\n>>>> +                                    b->path, new_path);\n> So now I'm confused.  This block was not listed in your coverage\n> report[1].  And, in fact, I think this block IS covered by testcase\n> 10c of t6043.  However, there is a very similar looking block about 30\n> lines up that is uncovered (and which was mentioned in your report):\n>\n>              } else if (would_lose_untracked(a->path)) {\n>                  new_path = unique_path(o, a->path, ci->branch1);\n>                  output(o, 1, _(\"Refusing to lose untracked file\"\n>                             \" at %s; adding as %s instead\"),\n>                         a->path, new_path);\n>\n> covering it, I think, is just a matter of repeating the 10c test with\n> the merge repeated in the other direction (checkout B and merge A\n> instead of checking out A and merging B) -- and touching up the checks\n> accordingly.\n>\n> However, now I'm wondering if I'm crazy.  Was it really the block you\n> had highlighted that you were seeing uncovered?\n\nTrust the report (generated by computer) over me (generated by squinting \nat an email, trying to match line numbers).\n\nThanks,\n\n-Stolee\n\n"},{"id":"362297","messageId":"20181102185317.31015-8-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[PATCH v4 07/10] merge-recursive: use handle_file_collision for add/add conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:15Z","receivedAt":"2018-11-02T18:53:31Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This results in no-net change of behavior, it simply ensures that all\nfile-collision conflict handling types are being handled the same by\ncalling the same function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 29 +++++++++++++++++++++--------\n 1 file changed, 21 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ead6054a75..c78b347112 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3355,14 +3355,27 @@ static int process_entry(struct merge_options *o,\n \t\t\t\tclean_merge = -1;\n \t\t}\n \t} else if (a_oid && b_oid) {\n-\t\t/* Case C: Added in both (check for same permissions) and */\n-\t\t/* case D: Modified in both, but differently. */\n-\t\tint is_dirty = 0; /* unpack_trees would have bailed if dirty */\n-\t\tclean_merge = handle_content_merge(o, path, is_dirty,\n-\t\t\t\t\t\t   o_oid, o_mode,\n-\t\t\t\t\t\t   a_oid, a_mode,\n-\t\t\t\t\t\t   b_oid, b_mode,\n-\t\t\t\t\t\t   NULL);\n+\t\tif (!o_oid) {\n+\t\t\t/* Case C: Added in both (check for same permissions) */\n+\t\t\toutput(o, 1,\n+\t\t\t       _(\"CONFLICT (add/add): Merge conflict in %s\"),\n+\t\t\t       path);\n+\t\t\tclean_merge = handle_file_collision(o,\n+\t\t\t\t\t\t\t    path, NULL, NULL,\n+\t\t\t\t\t\t\t    o->branch1,\n+\t\t\t\t\t\t\t    o->branch2,\n+\t\t\t\t\t\t\t    a_oid, a_mode,\n+\t\t\t\t\t\t\t    b_oid, b_mode);\n+\t\t} else {\n+\t\t\t/* case D: Modified in both, but differently. */\n+\t\t\tint is_dirty = 0; /* unpack_trees would have bailed if dirty */\n+\t\t\tclean_merge = handle_content_merge(o, path,\n+\t\t\t\t\t\t\t   is_dirty,\n+\t\t\t\t\t\t\t   o_oid, o_mode,\n+\t\t\t\t\t\t\t   a_oid, a_mode,\n+\t\t\t\t\t\t\t   b_oid, b_mode,\n+\t\t\t\t\t\t\t   NULL);\n+\t\t}\n \t} else if (!o_oid && !a_oid && !b_oid) {\n \t\t/*\n \t\t * this entry was deleted altogether. a_mode == 0 means\n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362298","messageId":"20181102185317.31015-3-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[PATCH v4 02/10] t6036, t6042: testcases for rename collision of already conflicting files","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:10Z","receivedAt":"2018-11-02T18:53:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When a single file is renamed, it can also be modified, yielding the\npossibility of that renamed file having content conflicts.  If two\ndifferent such files are renamed into the same location, then two-way\nmerging those files may result in nested conflicts.  Add a testcase that\nmakes sure we get this case correct, and uses different lengths of\nconflict markers to differentiate between the different nestings.\n\nAlso add another case with an extra (i.e. third) level of conflict\nmarkers due to using merge.conflictstyle=diff3 and the virtual merge\nbase also having conflicts present.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh    | 194 +++++++++++++++++++++++++++\n t/t6042-merge-rename-corner-cases.sh | 118 ++++++++++++++++\n 2 files changed, 312 insertions(+)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex e1cef58f2a..f229d7e47b 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1402,4 +1402,198 @@ test_expect_failure 'check conflicting modes for regular file' '\n \t)\n '\n \n+# Setup:\n+#          L1---L2\n+#         /  \\ /  \\\n+#   master    X    ?\n+#         \\  / \\  /\n+#          R1---R2\n+#\n+# Where:\n+#   master has two files, named 'b' and 'a'\n+#   branches L1 and R1 both modify each of the two files in conflicting ways\n+#\n+#   L2 is a merge of R1 into L1; more on it later.\n+#   R2 is a merge of L1 into R1; more on it later.\n+#\n+#   X is an auto-generated merge-base used when merging L2 and R2.\n+#   since X is a merge of L1 and R1, it has conflicting versions of each file\n+#\n+#   More about L2 and R2:\n+#     - both resolve the conflicts in 'b' and 'a' differently\n+#     - L2 renames 'b' to 'm'\n+#     - R2 renames 'a' to 'm'\n+#\n+#   In the end, in file 'm' we have four different conflicting files (from\n+#   two versions of 'b' and two of 'a').  In addition, if\n+#   merge.conflictstyle is diff3, then the base version also has\n+#   conflict markers of its own, leading to a total of three levels of\n+#   conflict markers.  This is a pretty weird corner case, but we just want\n+#   to ensure that we handle it as well as practical.\n+\n+test_expect_success 'setup nested conflicts' '\n+\ttest_create_repo nested_conflicts &&\n+\t(\n+\t\tcd nested_conflicts &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >initial &&\n+\n+\t\tcp initial b_L1 &&\n+\t\tcp initial b_R1 &&\n+\t\tcp initial b_L2 &&\n+\t\tcp initial b_R2 &&\n+\t\tcp initial a_L1 &&\n+\t\tcp initial a_R1 &&\n+\t\tcp initial a_L2 &&\n+\t\tcp initial a_R2 &&\n+\n+\t\ttest_write_lines b b_L1 >>b_L1 &&\n+\t\ttest_write_lines b b_R1 >>b_R1 &&\n+\t\ttest_write_lines b b_L2 >>b_L2 &&\n+\t\ttest_write_lines b b_R2 >>b_R2 &&\n+\t\ttest_write_lines a a_L1 >>a_L1 &&\n+\t\ttest_write_lines a a_R1 >>a_R1 &&\n+\t\ttest_write_lines a a_L2 >>a_L2 &&\n+\t\ttest_write_lines a a_R2 >>a_R2 &&\n+\n+\t\t# Setup original commit (or merge-base), consisting of\n+\t\t# files named \"b\" and \"a\"\n+\t\tcp initial b &&\n+\t\tcp initial a &&\n+\t\techo b >>b &&\n+\t\techo a >>a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m initial &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Handle the left side\n+\t\tgit checkout L &&\n+\t\tmv -f b_L1 b &&\n+\t\tmv -f a_L1 a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m \"version L1 of files\" &&\n+\t\tgit tag L1 &&\n+\n+\t\t# Handle the right side\n+\t\tgit checkout R &&\n+\t\tmv -f b_R1 b &&\n+\t\tmv -f a_R1 a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m \"verson R1 of files\" &&\n+\t\tgit tag R1 &&\n+\n+\t\t# Create first merge on left side\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git merge R1 &&\n+\t\tmv -f b_L2 b &&\n+\t\tmv -f a_L2 a &&\n+\t\tgit add b a &&\n+\t\tgit mv b m &&\n+\t\ttest_tick && git commit -m \"left merge, rename b->m\" &&\n+\t\tgit tag L2 &&\n+\n+\t\t# Create first merge on right side\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git merge L1 &&\n+\t\tmv -f b_R2 b &&\n+\t\tmv -f a_R2 a &&\n+\t\tgit add b a &&\n+\t\tgit mv a m &&\n+\t\ttest_tick && git commit -m \"right merge, rename a->m\" &&\n+\t\tgit tag R2\n+\t)\n+'\n+\n+test_expect_failure 'check nested conflicts' '\n+\t(\n+\t\tcd nested_conflicts &&\n+\n+\t\tgit clean -f &&\n+\t\tgit checkout L2^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R2^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Create a and b from virtual merge base X\n+\t\tgit cat-file -p master:a >base &&\n+\t\tgit cat-file -p L1:a >ours &&\n+\t\tgit cat-file -p R1:a >theirs &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tours  \\\n+\t\t\tbase  \\\n+\t\t\ttheirs &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >vmb_a &&\n+\n+\t\tgit cat-file -p master:b >base &&\n+\t\tgit cat-file -p L1:b >ours &&\n+\t\tgit cat-file -p R1:b >theirs &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tours  \\\n+\t\t\tbase  \\\n+\t\t\ttheirs &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >vmb_b &&\n+\n+\t\t# Compare :2:m to expected values\n+\t\tgit cat-file -p L2:m >ours &&\n+\t\tgit cat-file -p R2:b >theirs &&\n+\t\ttest_must_fail git merge-file --diff3  \\\n+\t\t\t-L \"HEAD:m\"                    \\\n+\t\t\t-L \"merged common ancestors:b\" \\\n+\t\t\t-L \"R2^0:b\"                    \\\n+\t\t\tours                           \\\n+\t\t\tvmb_b                          \\\n+\t\t\ttheirs                         &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >m_stage_2 &&\n+\t\tgit cat-file -p :2:m >actual &&\n+\t\ttest_cmp m_stage_2 actual &&\n+\n+\t\t# Compare :3:m to expected values\n+\t\tgit cat-file -p L2:a >ours &&\n+\t\tgit cat-file -p R2:m >theirs &&\n+\t\ttest_must_fail git merge-file --diff3  \\\n+\t\t\t-L \"HEAD:a\"                    \\\n+\t\t\t-L \"merged common ancestors:a\" \\\n+\t\t\t-L \"R2^0:m\"                    \\\n+\t\t\tours                           \\\n+\t\t\tvmb_a                          \\\n+\t\t\ttheirs                         &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >m_stage_3 &&\n+\t\tgit cat-file -p :3:m >actual &&\n+\t\ttest_cmp m_stage_3 actual &&\n+\n+\t\t# Compare m to expected contents\n+\t\t>empty &&\n+\t\tcp -a m_stage_2 expected_final_m &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"HEAD\"                     \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"R2^0\"                     \\\n+\t\t\texpected_final_m              \\\n+\t\t\tempty                         \\\n+\t\t\tm_stage_3                     &&\n+\t\ttest_cmp expected_final_m m\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b6fed2cb9a..23c3b6dffa 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -1099,4 +1099,122 @@ test_conflicts_with_adds_and_renames rename add    failure\n test_conflicts_with_adds_and_renames add    rename failure\n test_conflicts_with_adds_and_renames add    add    success\n \n+# Setup:\n+#          L\n+#         / \\\n+#   master   ?\n+#         \\ /\n+#          R\n+#\n+# Where:\n+#   master has two files, named 'one' and 'two'.\n+#   branches L and R both modify 'one', in conflicting ways.\n+#   branches L and R both modify 'two', in conflicting ways.\n+#   branch L also renames 'one' to 'three'.\n+#   branch R also renames 'two' to 'three'.\n+#\n+#   So, we have four different conflicting files that all end up at path\n+#   'three'.\n+test_expect_success 'setup nested conflicts from rename/rename(2to1)' '\n+\ttest_create_repo nested_conflicts_from_rename_rename &&\n+\t(\n+\t\tcd nested_conflicts_from_rename_rename &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >file_v1 &&\n+\n+\t\tcp file_v1 file_v2 &&\n+\t\tcp file_v1 file_v3 &&\n+\t\tcp file_v1 file_v4 &&\n+\t\tcp file_v1 file_v5 &&\n+\t\tcp file_v1 file_v6 &&\n+\n+\t\techo one  >>file_v1 &&\n+\t\techo uno  >>file_v2 &&\n+\t\techo eins >>file_v3 &&\n+\n+\t\techo two  >>file_v4 &&\n+\t\techo dos  >>file_v5 &&\n+\t\techo zwei >>file_v6 &&\n+\n+\t\t# Setup original commit (or merge-base), consisting of\n+\t\t# files named \"one\" and \"two\".\n+\t\tmv file_v1 one &&\n+\t\tmv file_v4 two &&\n+\t\tgit add one two &&\n+\t\ttest_tick && git commit -m english &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Handle the left side\n+\t\tgit checkout L &&\n+\t\tgit mv one three &&\n+\t\tmv -f file_v2 three &&\n+\t\tmv -f file_v5 two &&\n+\t\tgit add two three &&\n+\t\ttest_tick && git commit -m spanish &&\n+\n+\t\t# Handle the right side\n+\t\tgit checkout R &&\n+\t\tgit mv two three &&\n+\t\tmv -f file_v3 one &&\n+\t\tmv -f file_v6 three &&\n+\t\tgit add one three &&\n+\t\ttest_tick && git commit -m german\n+\t)\n+'\n+\n+test_expect_failure 'check nested conflicts from rename/rename(2to1)' '\n+\t(\n+\t\tcd nested_conflicts_from_rename_rename &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git merge -s recursive R^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Compare :2:three to expected values\n+\t\tgit cat-file -p master:one >base &&\n+\t\tgit cat-file -p L:three >ours &&\n+\t\tgit cat-file -p R:one >theirs &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD:three\"  -L \"\"  -L \"R^0:one\" \\\n+\t\t\tours             base   theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1/\" ours >L-three &&\n+\t\tgit cat-file -p :2:three >expect &&\n+\t\ttest_cmp expect L-three &&\n+\n+\t\t# Compare :2:three to expected values\n+\t\tgit cat-file -p master:two >base &&\n+\t\tgit cat-file -p L:two >ours &&\n+\t\tgit cat-file -p R:three >theirs &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD:two\"  -L \"\"  -L \"R^0:three\" \\\n+\t\t\tours           base   theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1/\" ours >R-three &&\n+\t\tgit cat-file -p :3:three >expect &&\n+\t\ttest_cmp expect R-three &&\n+\n+\t\t# Compare three to expected contents\n+\t\t>empty &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD\"  -L \"\"  -L \"R^0\" \\\n+\t\t\tL-three    empty  R-three &&\n+\t\ttest_cmp three L-three\n+\t)\n+'\n+\n test_done\n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362299","messageId":"20181102185317.31015-7-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[PATCH v4 06/10] merge-recursive: improve handling for rename/rename(2to1) conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:14Z","receivedAt":"2018-11-02T18:53:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This makes the rename/rename(2to1) conflicts use the new\nhandle_file_collision() function.  Since that function was based\noriginally on the rename/rename(2to1) handling code, the main\ndifferences here are in what was added.  In particular:\n\n  * Instead of storing files at collide_path~HEAD and collide_path~MERGE,\n    the files are two-way merged and recorded at collide_path.\n\n  * Instead of recording the version of the renamed file that existed\n    on the renamed side in the index (thus ignoring any changes that\n    were made to the file on the side of history without the rename),\n    we do a three-way content merge on the renamed path, then store\n    that at either stage 2 or stage 3.\n\n  * Note that since the content merge for each rename may have conflicts,\n    and then we have to merge the two renamed files, we can end up with\n    nested conflict markers.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 104 ++++-----------------------\n t/t6036-recursive-corner-cases.sh    |  12 +---\n t/t6042-merge-rename-corner-cases.sh |  38 ++++++----\n t/t6043-merge-rename-directories.sh  |  83 ++++++++++++---------\n 4 files changed, 89 insertions(+), 148 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0805095168..ead6054a75 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -696,27 +696,6 @@ static int update_stages(struct merge_options *opt, const char *path,\n \treturn 0;\n }\n \n-static int update_stages_for_stage_data(struct merge_options *opt,\n-\t\t\t\t\tconst char *path,\n-\t\t\t\t\tconst struct stage_data *stage_data)\n-{\n-\tstruct diff_filespec o, a, b;\n-\n-\to.mode = stage_data->stages[1].mode;\n-\toidcpy(&o.oid, &stage_data->stages[1].oid);\n-\n-\ta.mode = stage_data->stages[2].mode;\n-\toidcpy(&a.oid, &stage_data->stages[2].oid);\n-\n-\tb.mode = stage_data->stages[3].mode;\n-\toidcpy(&b.oid, &stage_data->stages[3].oid);\n-\n-\treturn update_stages(opt, path,\n-\t\t\t     is_null_oid(&o.oid) ? NULL : &o,\n-\t\t\t     is_null_oid(&a.oid) ? NULL : &a,\n-\t\t\t     is_null_oid(&b.oid) ? NULL : &b);\n-}\n-\n static void update_entry(struct stage_data *entry,\n \t\t\t struct diff_filespec *o,\n \t\t\t struct diff_filespec *a,\n@@ -1870,7 +1849,6 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \tchar *path_side_2_desc;\n \tstruct merge_file_info mfi_c1;\n \tstruct merge_file_info mfi_c2;\n-\tint ret;\n \n \toutput(o, 1, _(\"CONFLICT (rename/rename): \"\n \t       \"Rename %s->%s in %s. \"\n@@ -1878,81 +1856,22 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \t       a->path, c1->path, ci->branch1,\n \t       b->path, c2->path, ci->branch2);\n \n-\tremove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));\n-\tremove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));\n-\n \tpath_side_1_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n \tpath_side_2_desc = xstrfmt(\"version of %s from %s\", path, b->path);\n \tif (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,\n \t\t\t\t    o->branch1, o->branch2,\n-\t\t\t\t    o->call_depth * 2, &mfi_c1) ||\n+\t\t\t\t    1 + o->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,\n \t\t\t\t    o->branch1, o->branch2,\n-\t\t\t\t    o->call_depth * 2, &mfi_c2))\n+\t\t\t\t    1 + o->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n \n-\tif (o->call_depth) {\n-\t\t/*\n-\t\t * If mfi_c1.clean && mfi_c2.clean, then it might make\n-\t\t * sense to do a two-way merge of those results.  But, I\n-\t\t * think in all cases, it makes sense to have the virtual\n-\t\t * merge base just undo the renames; they can be detected\n-\t\t * again later for the non-recursive merge.\n-\t\t */\n-\t\tremove_file(o, 0, path, 0);\n-\t\tret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);\n-\t\tif (!ret)\n-\t\t\tret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,\n-\t\t\t\t\t  b->path);\n-\t} else {\n-\t\tchar *new_path1 = unique_path(o, path, ci->branch1);\n-\t\tchar *new_path2 = unique_path(o, path, ci->branch2);\n-\t\toutput(o, 1, _(\"Renaming %s to %s and %s to %s instead\"),\n-\t\t       a->path, new_path1, b->path, new_path2);\n-\t\tif (was_dirty(o, path))\n-\t\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n-\t\t\t       path);\n-\t\telse if (would_lose_untracked(path))\n-\t\t\t/*\n-\t\t\t * Only way we get here is if both renames were from\n-\t\t\t * a directory rename AND user had an untracked file\n-\t\t\t * at the location where both files end up after the\n-\t\t\t * two directory renames.  See testcase 10d of t6043.\n-\t\t\t */\n-\t\t\toutput(o, 1, _(\"Refusing to lose untracked file at \"\n-\t\t\t\t       \"%s, even though it's in the way.\"),\n-\t\t\t       path);\n-\t\telse\n-\t\t\tremove_file(o, 0, path, 0);\n-\t\tret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);\n-\t\tif (!ret)\n-\t\t\tret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,\n-\t\t\t\t\t  new_path2);\n-\t\t/*\n-\t\t * unpack_trees() actually populates the index for us for\n-\t\t * \"normal\" rename/rename(2to1) situtations so that the\n-\t\t * correct entries are at the higher stages, which would\n-\t\t * make the call below to update_stages_for_stage_data\n-\t\t * unnecessary.  However, if either of the renames came\n-\t\t * from a directory rename, then unpack_trees() will not\n-\t\t * have gotten the right data loaded into the index, so we\n-\t\t * need to do so now.  (While it'd be tempting to move this\n-\t\t * call to update_stages_for_stage_data() to\n-\t\t * apply_directory_rename_modifications(), that would break\n-\t\t * our intermediate calls to would_lose_untracked() since\n-\t\t * those rely on the current in-memory index.  See also the\n-\t\t * big \"NOTE\" in update_stages()).\n-\t\t */\n-\t\tif (update_stages_for_stage_data(o, path, ci->dst_entry1))\n-\t\t\tret = -1;\n-\n-\t\tfree(new_path2);\n-\t\tfree(new_path1);\n-\t}\n-\n-\treturn ret;\n+\treturn handle_file_collision(o, path, a->path, b->path,\n+\t\t\t\t     ci->branch1, ci->branch2,\n+\t\t\t\t     &mfi_c1.oid, mfi_c1.mode,\n+\t\t\t\t     &mfi_c2.oid, mfi_c2.mode);\n }\n \n /*\n@@ -3361,9 +3280,14 @@ static int process_entry(struct merge_options *o,\n \t\t\t\tclean_merge = -1;\n \t\t\tbreak;\n \t\tcase RENAME_TWO_FILES_TO_ONE:\n-\t\t\tclean_merge = 0;\n-\t\t\tif (handle_rename_rename_2to1(o, conflict_info))\n-\t\t\t\tclean_merge = -1;\n+\t\t\t/*\n+\t\t\t * Probably unclean merge, but if the two renamed\n+\t\t\t * files merge cleanly and the two resulting files\n+\t\t\t * can then be two-way merged cleanly, I guess it's\n+\t\t\t * a clean merge?\n+\t\t\t */\n+\t\t\tclean_merge = handle_rename_rename_2to1(o,\n+\t\t\t\t\t\t\t\tconflict_info);\n \t\t\tbreak;\n \t\tdefault:\n \t\t\tentry->processed = 0;\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex d6b7e568f5..99cddb05af 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -64,15 +64,12 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tL2:three   R2:three \\\n \t\t\tL2:three   R2:three &&\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n-\t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -140,15 +137,12 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tL2:three   R2:three \\\n \t\t\tL2:three   R2:three &&\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n-\t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -1512,7 +1506,7 @@ test_expect_success 'setup nested conflicts' '\n \t)\n '\n \n-test_expect_failure 'check nested conflicts' '\n+test_expect_success 'check nested conflicts' '\n \t(\n \t\tcd nested_conflicts &&\n \ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 3e934ae89e..b93139f628 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -464,17 +464,28 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' '\n \t\tgit ls-files -u c >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\ttest_path_is_missing a &&\n \t\ttest_path_is_missing b &&\n-\t\ttest_path_is_file c~HEAD &&\n-\t\ttest_path_is_file c~C^0 &&\n \n-\t\tgit rev-parse >expect   \\\n-\t\t\tC:a     B:b     &&\n-\t\tgit hash-object >actual \\\n-\t\t\tc~HEAD  c~C^0   &&\n+\t\tgit rev-parse >expect  \\\n+\t\t\tC:a     B:b    &&\n+\t\tgit rev-parse >actual  \\\n+\t\t\t:2:c    :3:c   &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p :2:c >>ours &&\n+\t\tgit cat-file -p :3:c >>theirs &&\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 \"C^0\" \\\n+\t\t\tours empty theirs &&\n+\t\tgit hash-object c >actual &&\n+\t\tgit hash-object ours >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -940,7 +951,6 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename\n test_conflicts_with_adds_and_renames() {\n \tsideL=$1\n \tsideR=$2\n-\texpect=$3\n \n \t# Setup:\n \t#          L\n@@ -1048,7 +1058,7 @@ test_conflicts_with_adds_and_renames() {\n \t\t)\n \t'\n \n-\ttest_expect_$expect \"check simple $sideL/$sideR conflict\" '\n+\ttest_expect_success \"check simple $sideL/$sideR conflict\" '\n \t\t(\n \t\t\tcd simple_${sideL}_${sideR} &&\n \n@@ -1094,10 +1104,10 @@ test_conflicts_with_adds_and_renames() {\n \t'\n }\n \n-test_conflicts_with_adds_and_renames rename rename failure\n-test_conflicts_with_adds_and_renames rename add    success\n-test_conflicts_with_adds_and_renames add    rename success\n-test_conflicts_with_adds_and_renames add    add    success\n+test_conflicts_with_adds_and_renames rename rename\n+test_conflicts_with_adds_and_renames rename add\n+test_conflicts_with_adds_and_renames add    rename\n+test_conflicts_with_adds_and_renames add    add\n \n # Setup:\n #          L\n@@ -1168,7 +1178,7 @@ test_expect_success 'setup nested conflicts from rename/rename(2to1)' '\n \t)\n '\n \n-test_expect_failure 'check nested conflicts from rename/rename(2to1)' '\n+test_expect_success 'check nested conflicts from rename/rename(2to1)' '\n \t(\n \t\tcd nested_conflicts_from_rename_rename &&\n \ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex 4a71f17edd..fedaeafc55 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -278,7 +278,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n &&\n@@ -293,15 +293,16 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con\n \t\t\t A:y/wham  B:z/wham &&\n \t\ttest_cmp expect actual &&\n \n-\t\ttest_path_is_missing x/wham &&\n-\t\ttest_path_is_file x/wham~HEAD &&\n-\t\ttest_path_is_file x/wham~B^0 &&\n-\n-\t\tgit hash-object >actual \\\n-\t\t\tx/wham~HEAD x/wham~B^0 &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tA:y/wham    B:z/wham &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in x/wham is as expected\n+\t\tgit cat-file -p :2:x/wham >expect &&\n+\t\tgit cat-file -p :3:x/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect x/wham\n \t)\n '\n \n@@ -1670,7 +1671,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/b :0:y/c :2:y/d :3:y/d &&\n@@ -1678,15 +1679,16 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r\n \t\t\t O:z/b  O:z/c  O:w/d  O:x/d &&\n \t\ttest_cmp expect actual &&\n \n-\t\ttest_path_is_missing y/d &&\n-\t\ttest_path_is_file y/d~HEAD &&\n-\t\ttest_path_is_file y/d~B^0 &&\n-\n-\t\tgit hash-object >actual \\\n-\t\t\ty/d~HEAD y/d~B^0 &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:w/d    O:x/d &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in y/d is as expected\n+\t\tgit cat-file -p :2:y/d >expect &&\n+\t\tgit cat-file -p :3:y/d >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/d\n \t)\n '\n \n@@ -3165,7 +3167,7 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)\n #   Commit O: z/{a,b,c_1},        x/{d,e,f_2}\n #   Commit A: y/{a,b},            x/{d,e,f_2,wham_1} + untracked y/wham\n #   Commit B: z/{a,b,c_1,wham_2}, y/{d,e}\n-#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+\n+#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~merged}+\n #             CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham\n #             ERROR_MSG(Refusing to lose untracked file at y/wham)\n \n@@ -3219,7 +3221,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham &&\n@@ -3232,11 +3234,16 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '\n \t\techo important >expect &&\n \t\ttest_cmp expect y/wham &&\n \n-\t\tgit hash-object >actual \\\n-\t\t\ty/wham~B^0 y/wham~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/f      O:z/c &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in y/wham~merged is as expected\n+\t\tgit cat-file -p :2:y/wham >expect &&\n+\t\tgit cat-file -p :3:y/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/wham~merged\n \t)\n '\n \n@@ -3689,7 +3696,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n #   Commit O: z/{a,b},        x/{c_1,d_2}\n #   Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods\n #   Commit B: z/{a,b,wham_2}, x/c_1\n-#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} +\n+#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~merged} +\n #             y/wham with dirty changes from before merge +\n #             CONFLICT(rename/rename) x/c vs x/d -> y/wham\n #             ERROR_MSG(Refusing to lose dirty file at y/wham)\n@@ -3741,24 +3748,30 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\ttest_seq 1 10 >expected &&\n \t\techo important >>expected &&\n \t\ttest_cmp expected y/wham &&\n \n \t\ttest_must_fail git rev-parse :1:y/wham &&\n-\t\tgit hash-object >actual \\\n-\t\t\ty/wham~B^0 y/wham~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/d      O:x/c &&\n-\t\ttest_cmp expect actual &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/a :0:y/b :2:y/wham :3:y/wham &&\n \t\tgit rev-parse >expect \\\n \t\t\t O:z/a  O:z/b  O:x/c     O:x/d &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in y/wham~merged is as expected\n+\t\tgit cat-file -p :2:y/wham >expect &&\n+\t\tgit cat-file -p :3:y/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/wham~merged\n \t)\n '\n \n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362300","messageId":"20181102185317.31015-1-newren@gmail.com","threadId":"49559","inReplyTo":"20181019193111.12051-1-newren@gmail.com","subject":"[PATCH v4 00/10] Improve path collision conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:08Z","receivedAt":"2018-11-02T18:53:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This series depends on en/merge-cleanup-more and is built on that\nseries.  (It merges cleanly to master, next, and pu -- well, as long\nas v3 of this series is excluded from pu, that is).\n\nThis series makes all the \"file collision\" conflict types be handled\nconsistently; making them all behave like add/add (as suggested by\nJonathan[1] and Junio[2]).  These types are:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n  * each rename/add piece of a rename/rename(1to2)/add[/add] conflict\n\n[1] https://public-inbox.org/git/20180312213521.GB58506@aiede.svl.corp.google.com/\n[2] https://public-inbox.org/git/CAPc5daVu8vv9RdGON8JiXEO3ycDVqQ38ySzZc-cpo+AQcAKXjA@mail.gmail.com\n\nChanges since v3:\n  * Fixed test names to be surrounded by single quotes instead of double\n    quotes, as suggested by Derrick.\n  * Two more (RFC) patches add a couple testcases to cover previously\n    uncovered code, as pointed out by Derrick and his test coverage report.\n  * Full range-diff below.\n\nMajor question:\n  * You'll note that I edited the last two patches to mark them as RFC.\n    To be honest, I'm not sure what to do with these.  They improve code\n    coverage of new code, but the same gaps existed in the old code;\n    they only show up in the coverage-diff because I essentially moved\n    code around to a new improved function.  Since the new code doesn't\n    really add new abilities but rather just shifts the handling of\n    these conflicts to a common function, they shouldn't need any more\n    testcases than previously and modifying the existing patches thus\n    feels slightly misleading.  That line of thought leads me to believe\n    that perhaps putting them in a separate combined patch of their own\n    with a decent commit message is the right way to go.  On the other\n    hand, squashing them to the commits they're marked as fixups for\n    shows which logical part of the code the tests are related to, which\n    seems like a good thing.  So what's the right way to handle these?\n\n\n 1:  1be9e213db !  1:  0fa67d6109 t6036, t6042: testcases for rename collision of already conflicting files\n    @@ -51,7 +51,7 @@\n     +#   conflict markers.  This is a pretty weird corner case, but we just want\n     +#   to ensure that we handle it as well as practical.\n     +\n    -+test_expect_success \"setup nested conflicts\" '\n    ++test_expect_success 'setup nested conflicts' '\n     +\ttest_create_repo nested_conflicts &&\n     +\t(\n     +\t\tcd nested_conflicts &&\n    @@ -130,7 +130,7 @@\n     +\t)\n     +'\n     +\n    -+test_expect_failure \"check nested conflicts\" '\n    ++test_expect_failure 'check nested conflicts' '\n     +\t(\n     +\t\tcd nested_conflicts &&\n     +\n    @@ -241,7 +241,7 @@\n     +#\n     +#   So, we have four different conflicting files that all end up at path\n     +#   'three'.\n    -+test_expect_success \"setup nested conflicts from rename/rename(2to1)\" '\n    ++test_expect_success 'setup nested conflicts from rename/rename(2to1)' '\n     +\ttest_create_repo nested_conflicts_from_rename_rename &&\n     +\t(\n     +\t\tcd nested_conflicts_from_rename_rename &&\n    @@ -294,7 +294,7 @@\n     +\t)\n     +'\n     +\n    -+test_expect_failure \"check nested conflicts from rename/rename(2to1)\" '\n    ++test_expect_failure 'check nested conflicts from rename/rename(2to1)' '\n     +\t(\n     +\t\tcd nested_conflicts_from_rename_rename &&\n     +\n 2:  d3356ff525 !  2:  9f5f0105d0 merge-recursive: increase marker length with depth of recursion\n    @@ -179,7 +179,7 @@\n     +#   nested conflict markers from X2 in the base version -- that means we\n     +#   have three levels of conflict markers.  Can we distinguish all three?\n     +\n    -+test_expect_success \"setup virtual merge base with nested conflicts\" '\n    ++test_expect_success 'setup virtual merge base with nested conflicts' '\n     +\ttest_create_repo virtual_merge_base_has_nested_conflicts &&\n     +\t(\n     +\t\tcd virtual_merge_base_has_nested_conflicts &&\n    @@ -241,7 +241,7 @@\n     +\t)\n     +'\n     +\n    -+test_expect_success \"check virtual merge base with nested conflicts\" '\n    ++test_expect_success 'check virtual merge base with nested conflicts' '\n     +\t(\n     +\t\tcd virtual_merge_base_has_nested_conflicts &&\n     +\n 3:  aa68e3d675 =  3:  5922c40fa7 merge-recursive: new function for better colliding conflict resolutions\n 4:  f046ba6362 =  4:  dcf88dd363 merge-recursive: fix rename/add conflict handling\n 5:  37742bdefd !  5:  1d11288be4 merge-recursive: improve handling for rename/rename(2to1) conflicts\n    @@ -209,8 +209,8 @@\n      \t)\n      '\n      \n    --test_expect_failure \"check nested conflicts\" '\n    -+test_expect_success \"check nested conflicts\" '\n    +-test_expect_failure 'check nested conflicts' '\n    ++test_expect_success 'check nested conflicts' '\n      \t(\n      \t\tcd nested_conflicts &&\n      \n    @@ -290,8 +290,8 @@\n      \t)\n      '\n      \n    --test_expect_failure \"check nested conflicts from rename/rename(2to1)\" '\n    -+test_expect_success \"check nested conflicts from rename/rename(2to1)\" '\n    +-test_expect_failure 'check nested conflicts from rename/rename(2to1)' '\n    ++test_expect_success 'check nested conflicts from rename/rename(2to1)' '\n      \t(\n      \t\tcd nested_conflicts_from_rename_rename &&\n      \n 6:  776dff8bc4 =  6:  1fad3428a4 merge-recursive: use handle_file_collision for add/add conflicts\n 7:  45940724d5 =  7:  e7ac0d894e merge-recursive: improve rename/rename(1to2)/add[/add] handling\n -:  ---------- >  8:  9328f66ed1 fixup! merge-recursive: fix rename/add conflict handling\n -:  ---------- >  9:  d061509573 fixup! merge-recursive: improve rename/rename(1to2)/add[/add] handling\n  \nElijah Newren (10):\n  Add testcases for consistency in file collision conflict handling\n  t6036, t6042: testcases for rename collision of already conflicting\n    files\n  merge-recursive: increase marker length with depth of recursion\n  merge-recursive: new function for better colliding conflict\n    resolutions\n  merge-recursive: fix rename/add conflict handling\n  merge-recursive: improve handling for rename/rename(2to1) conflicts\n  merge-recursive: use handle_file_collision for add/add conflicts\n  merge-recursive: improve rename/rename(1to2)/add[/add] handling\n  fixup! merge-recursive: fix rename/add conflict handling\n  fixup! merge-recursive: improve rename/rename(1to2)/add[/add] handling\n\n ll-merge.c                           |   4 +-\n ll-merge.h                           |   1 +\n merge-recursive.c                    | 528 ++++++++++++++++-----------\n t/t6036-recursive-corner-cases.sh    | 430 +++++++++++++++++++++-\n t/t6042-merge-rename-corner-cases.sh | 333 ++++++++++++++++-\n t/t6043-merge-rename-directories.sh  | 144 +++++---\n 6 files changed, 1148 insertions(+), 292 deletions(-)\n\n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362301","messageId":"20181102185317.31015-9-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[PATCH v4 08/10] merge-recursive: improve rename/rename(1to2)/add[/add] handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:16Z","receivedAt":"2018-11-02T18:53:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When we have a rename/rename(1to2) conflict, each of the renames can\ncollide with a file addition.  Each of these rename/add conflicts suffered\nfrom the same kinds of problems that normal rename/add suffered from.\nMake the code use handle_file_conflicts() as well so that we get all the\nsame fixes and consistent behavior between the different conflict types.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 154 +++++++++++++--------------\n t/t6042-merge-rename-corner-cases.sh |  29 +++--\n t/t6043-merge-rename-directories.sh  |  24 +++--\n 3 files changed, 113 insertions(+), 94 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c78b347112..59811116b6 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1709,80 +1709,17 @@ static int handle_rename_add(struct merge_options *o,\n \t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n }\n \n-static int handle_file(struct merge_options *o,\n-\t\t\tstruct diff_filespec *rename,\n-\t\t\tint stage,\n-\t\t\tstruct rename_conflict_info *ci)\n-{\n-\tchar *dst_name = rename->path;\n-\tstruct stage_data *dst_entry;\n-\tconst char *cur_branch, *other_branch;\n-\tstruct diff_filespec other;\n-\tstruct diff_filespec *add;\n-\tint ret;\n-\n-\tif (stage == 2) {\n-\t\tdst_entry = ci->dst_entry1;\n-\t\tcur_branch = ci->branch1;\n-\t\tother_branch = ci->branch2;\n-\t} else {\n-\t\tdst_entry = ci->dst_entry2;\n-\t\tcur_branch = ci->branch2;\n-\t\tother_branch = ci->branch1;\n-\t}\n-\n-\tadd = filespec_from_entry(&other, dst_entry, stage ^ 1);\n-\tif (add) {\n-\t\tint ren_src_was_dirty = was_dirty(o, rename->path);\n-\t\tchar *add_name = unique_path(o, rename->path, other_branch);\n-\t\tif (update_file(o, 0, &add->oid, add->mode, add_name))\n-\t\t\treturn -1;\n-\n-\t\tif (ren_src_was_dirty) {\n-\t\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n-\t\t\t       rename->path);\n-\t\t}\n-\t\t/*\n-\t\t * Because the double negatives somehow keep confusing me...\n-\t\t *    1) update_wd iff !ren_src_was_dirty.\n-\t\t *    2) no_wd iff !update_wd\n-\t\t *    3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty\n-\t\t */\n-\t\tremove_file(o, 0, rename->path, ren_src_was_dirty);\n-\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t} else {\n-\t\tif (dir_in_way(rename->path, !o->call_depth, 0)) {\n-\t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t\t\toutput(o, 1, _(\"%s is a directory in %s adding as %s instead\"),\n-\t\t\t       rename->path, other_branch, dst_name);\n-\t\t} else if (!o->call_depth &&\n-\t\t\t   would_lose_untracked(rename->path)) {\n-\t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t\t\toutput(o, 1, _(\"Refusing to lose untracked file at %s; \"\n-\t\t\t\t       \"adding as %s instead\"),\n-\t\t\t       rename->path, dst_name);\n-\t\t}\n-\t}\n-\tif ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))\n-\t\t; /* fall through, do allow dst_name to be released */\n-\telse if (stage == 2)\n-\t\tret = update_stages(o, rename->path, NULL, rename, add);\n-\telse\n-\t\tret = update_stages(o, rename->path, NULL, add, rename);\n-\n-\tif (dst_name != rename->path)\n-\t\tfree(dst_name);\n-\n-\treturn ret;\n-}\n-\n static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t     struct rename_conflict_info *ci)\n {\n \t/* One file was renamed in both branches, but to different names. */\n+\tstruct merge_file_info mfi;\n+\tstruct diff_filespec other;\n+\tstruct diff_filespec *add;\n \tstruct diff_filespec *one = ci->pair1->one;\n \tstruct diff_filespec *a = ci->pair1->two;\n \tstruct diff_filespec *b = ci->pair2->two;\n+\tchar *path_desc;\n \n \toutput(o, 1, _(\"CONFLICT (rename/rename): \"\n \t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n@@ -1790,15 +1727,16 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t       one->path, a->path, ci->branch1,\n \t       one->path, b->path, ci->branch2,\n \t       o->call_depth ? _(\" (left unresolved)\") : \"\");\n-\tif (o->call_depth) {\n-\t\tstruct merge_file_info mfi;\n-\t\tstruct diff_filespec other;\n-\t\tstruct diff_filespec *add;\n-\t\tif (merge_mode_and_contents(o, one, a, b, one->path,\n-\t\t\t\t\t    ci->branch1, ci->branch2,\n-\t\t\t\t\t    o->call_depth * 2, &mfi))\n-\t\t\treturn -1;\n \n+\tpath_desc = xstrfmt(\"%s and %s, both renamed from %s\",\n+\t\t\t    a->path, b->path, one->path);\n+\tif (merge_mode_and_contents(o, one, a, b, path_desc,\n+\t\t\t\t    ci->branch1, ci->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tfree(path_desc);\n+\n+\tif (o->call_depth) {\n \t\t/*\n \t\t * FIXME: For rename/add-source conflicts (if we could detect\n \t\t * such), this is wrong.  We should instead find a unique\n@@ -1830,8 +1768,70 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t}\n \t\telse\n \t\t\tremove_file_from_cache(b->path);\n-\t} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))\n-\t\treturn -1;\n+\t} else {\n+\t\t/*\n+\t\t * For each destination path, we need to see if there is a\n+\t\t * rename/add collision.  If not, we can write the file out\n+\t\t * to the specified location.\n+\t\t */\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);\n+\t\tif (add) {\n+\t\t\tif (handle_file_collision(o, a->path,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  ci->branch1, ci->branch2,\n+\t\t\t\t\t\t  &mfi.oid, mfi.mode,\n+\t\t\t\t\t\t  &add->oid, add->mode) < 0)\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tchar *new_path = NULL;\n+\t\t\tif (dir_in_way(a->path, !o->call_depth, 0)) {\n+\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n+\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t\t\t       \"as %s instead\"),\n+\t\t\t\t       a->path, ci->branch2, new_path);\n+\t\t\t} else if (would_lose_untracked(a->path)) {\n+\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n+\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t\t\t       a->path, new_path);\n+\t\t\t}\n+\n+\t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))\n+\t\t\t\treturn -1;\n+\t\t\tfree(new_path);\n+\t\t\tif (update_stages(o, a->path, NULL, a, NULL))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);\n+\t\tif (add) {\n+\t\t\tif (handle_file_collision(o, b->path,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  ci->branch1, ci->branch2,\n+\t\t\t\t\t\t  &add->oid, add->mode,\n+\t\t\t\t\t\t  &mfi.oid, mfi.mode) < 0)\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tchar *new_path = NULL;\n+\t\t\tif (dir_in_way(b->path, !o->call_depth, 0)) {\n+\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n+\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t\t\t       \"as %s instead\"),\n+\t\t\t\t       b->path, ci->branch1, new_path);\n+\t\t\t} else if (would_lose_untracked(b->path)) {\n+\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n+\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t\t\t       b->path, new_path);\n+\t\t\t}\n+\n+\t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))\n+\t\t\t\treturn -1;\n+\t\t\tfree(new_path);\n+\t\t\tif (update_stages(o, b->path, NULL, NULL, b))\n+\t\t\t\treturn -1;\n+\t\t}\n+\t}\n \n \treturn 0;\n }\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b93139f628..7cc34e7579 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -684,7 +684,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t\tgit ls-files -u c >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 5 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect               \\\n \t\t\tA:a   C:b   B:b   C:c   B:c &&\n@@ -692,14 +692,27 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t\t\t:1:a  :2:b  :3:b  :2:c  :3:c &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit rev-parse >expect               \\\n-\t\t\tC:c     B:c     C:b     B:b &&\n-\t\tgit hash-object >actual                \\\n-\t\t\tc~HEAD  c~B\\^0  b~HEAD  b~B\\^0 &&\n-\t\ttest_cmp expect actual &&\n+\t\t# Record some contents for re-doing merges\n+\t\tgit cat-file -p A:a >stuff &&\n+\t\tgit cat-file -p C:b >important_info &&\n+\t\tgit cat-file -p B:c >precious_data &&\n+\t\t>empty &&\n \n-\t\ttest_path_is_missing b &&\n-\t\ttest_path_is_missing c\n+\t\t# Test the merge in b\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\timportant_info empty stuff &&\n+\t\ttest_cmp important_info b &&\n+\n+\t\t# Test the merge in c\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\tstuff empty precious_data &&\n+\t\ttest_cmp stuff c\n \t)\n '\n \ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex fedaeafc55..5c01a0c14a 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -1078,7 +1078,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 6 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/b :0:y/c :0:y/e &&\n@@ -1094,9 +1094,9 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam\n \t\ttest_cmp expect actual &&\n \n \t\tgit hash-object >actual \\\n-\t\t\tw/d~HEAD w/d~B^0 z/d &&\n+\t\t\tz/d &&\n \t\tgit rev-parse >expect \\\n-\t\t\tO:x/d    B:w/d   O:x/d &&\n+\t\t\tO:x/d &&\n \t\ttest_cmp expect actual &&\n \t\ttest_path_is_missing x/d &&\n \t\ttest_path_is_file y/d &&\n@@ -3672,7 +3672,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 4 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\techo different >expected &&\n \t\techo mods >>expected &&\n@@ -3684,11 +3684,17 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n \t\t\t O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  A:y/c  O:x/c &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit hash-object >actual \\\n-\t\t\ty/c~B^0 y/c~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/c   A:y/c &&\n-\t\ttest_cmp expect actual\n+\t\t# See if y/c~merged has expected contents; requires manually\n+\t\t# doing the expected file merge\n+\t\tgit cat-file -p A:y/c >c1 &&\n+\t\tgit cat-file -p B:z/c >c2 &&\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\tc1 empty c2 &&\n+\t\ttest_cmp c1 y/c~merged\n \t)\n '\n \n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362302","messageId":"20181102185317.31015-6-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[PATCH v4 05/10] merge-recursive: fix rename/add conflict handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:13Z","receivedAt":"2018-11-02T18:53:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This makes the rename/add conflict handling make use of the new\nhandle_file_collision() function, which fixes several bugs and improves\nthings for the rename/add case significantly.  Previously, rename/add\nwould:\n\n  * Not leave any higher order stage entries in the index, making it\n    appear as if there were no conflict.\n  * Would place the rename file at the colliding path, and move the\n    added file elsewhere, which combined with the lack of higher order\n    stage entries felt really odd.  It's not clear to me why the\n    rename should take precedence over the add; if one should be moved\n    out of the way, they both probably should.\n  * In the recursive case, it would do a two way merge of the added\n    file and the version of the renamed file on the renamed side,\n    completely excluding modifications to the renamed file on the\n    unrenamed side of history.\n\nUse the new handle_file_collision() to fix all of these issues.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 137 +++++++++++++++++----------\n t/t6036-recursive-corner-cases.sh    |  24 ++---\n t/t6042-merge-rename-corner-cases.sh |   4 +-\n 3 files changed, 101 insertions(+), 64 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 24d979022e..0805095168 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -186,6 +186,7 @@ static int oid_eq(const struct object_id *a, const struct object_id *b)\n enum rename_type {\n \tRENAME_NORMAL = 0,\n \tRENAME_VIA_DIR,\n+\tRENAME_ADD,\n \tRENAME_DELETE,\n \tRENAME_ONE_FILE_TO_ONE,\n \tRENAME_ONE_FILE_TO_TWO,\n@@ -228,6 +229,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\t\t\t\t      struct stage_data *src_entry1,\n \t\t\t\t\t      struct stage_data *src_entry2)\n {\n+\tint ostage1 = 0, ostage2;\n \tstruct rename_conflict_info *ci;\n \n \t/*\n@@ -264,18 +266,22 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\tdst_entry2->rename_conflict_info = ci;\n \t}\n \n-\tif (rename_type == RENAME_TWO_FILES_TO_ONE) {\n-\t\t/*\n-\t\t * For each rename, there could have been\n-\t\t * modifications on the side of history where that\n-\t\t * file was not renamed.\n-\t\t */\n-\t\tint ostage1 = o->branch1 == branch1 ? 3 : 2;\n-\t\tint ostage2 = ostage1 ^ 1;\n+\t/*\n+\t * For each rename, there could have been\n+\t * modifications on the side of history where that\n+\t * file was not renamed.\n+\t */\n+\tif (rename_type == RENAME_ADD ||\n+\t    rename_type == RENAME_TWO_FILES_TO_ONE) {\n+\t\tostage1 = o->branch1 == branch1 ? 3 : 2;\n \n \t\tci->ren1_other.path = pair1->one->path;\n \t\toidcpy(&ci->ren1_other.oid, &src_entry1->stages[ostage1].oid);\n \t\tci->ren1_other.mode = src_entry1->stages[ostage1].mode;\n+\t}\n+\n+\tif (rename_type == RENAME_TWO_FILES_TO_ONE) {\n+\t\tostage2 = ostage1 ^ 1;\n \n \t\tci->ren2_other.path = pair2->one->path;\n \t\toidcpy(&ci->ren2_other.oid, &src_entry2->stages[ostage2].oid);\n@@ -1559,7 +1565,6 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,\n \treturn target;\n }\n \n-#if 0 // #if-0-ing avoids unused function warning; will make live in next commit\n static int handle_file_collision(struct merge_options *o,\n \t\t\t\t const char *collide_path,\n \t\t\t\t const char *prev_path1,\n@@ -1575,6 +1580,20 @@ static int handle_file_collision(struct merge_options *o,\n \tchar *alt_path = NULL;\n \tconst char *update_path = collide_path;\n \n+\t/*\n+\t * It's easiest to get the correct things into stage 2 and 3, and\n+\t * to make sure that the content merge puts HEAD before the other\n+\t * branch if we just ensure that branch1 == o->branch1.  So, simply\n+\t * flip arguments around if we don't have that.\n+\t */\n+\tif (branch1 != o->branch1) {\n+\t\treturn handle_file_collision(o, collide_path,\n+\t\t\t\t\t     prev_path2, prev_path1,\n+\t\t\t\t\t     branch2, branch1,\n+\t\t\t\t\t     b_oid, b_mode,\n+\t\t\t\t\t     a_oid, a_mode);\n+\t}\n+\n \t/*\n \t * In the recursive case, we just opt to undo renames\n \t */\n@@ -1678,7 +1697,38 @@ static int handle_file_collision(struct merge_options *o,\n \t */\n \treturn mfi.clean;\n }\n-#endif\n+\n+static int handle_rename_add(struct merge_options *o,\n+\t\t\t     struct rename_conflict_info *ci)\n+{\n+\t/* a was renamed to c, and a separate c was added. */\n+\tstruct diff_filespec *a = ci->pair1->one;\n+\tstruct diff_filespec *c = ci->pair1->two;\n+\tchar *path = c->path;\n+\tchar *prev_path_desc;\n+\tstruct merge_file_info mfi;\n+\n+\tint other_stage = (ci->branch1 == o->branch1 ? 3 : 2);\n+\n+\toutput(o, 1, _(\"CONFLICT (rename/add): \"\n+\t       \"Rename %s->%s in %s.  Added %s in %s\"),\n+\t       a->path, c->path, ci->branch1,\n+\t       c->path, ci->branch2);\n+\n+\tprev_path_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n+\tif (merge_mode_and_contents(o, a, c, &ci->ren1_other, prev_path_desc,\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    1 + o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tfree(prev_path_desc);\n+\n+\treturn handle_file_collision(o,\n+\t\t\t\t     c->path, a->path, NULL,\n+\t\t\t\t     ci->branch1, ci->branch2,\n+\t\t\t\t     &mfi.oid, mfi.mode,\n+\t\t\t\t     &ci->dst_entry1->stages[other_stage].oid,\n+\t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n+}\n \n static int handle_file(struct merge_options *o,\n \t\t\tstruct diff_filespec *rename,\n@@ -2860,47 +2910,23 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t      0  /* update_wd    */))\n \t\t\t\t\tclean_merge = -1;\n \t\t\t} else if (!oid_eq(&dst_other.oid, &null_oid)) {\n-\t\t\t\tclean_merge = 0;\n-\t\t\t\ttry_merge = 1;\n-\t\t\t\toutput(o, 1, _(\"CONFLICT (rename/add): Rename %s->%s in %s. \"\n-\t\t\t\t       \"%s added in %s\"),\n-\t\t\t\t       ren1_src, ren1_dst, branch1,\n-\t\t\t\t       ren1_dst, branch2);\n-\t\t\t\tif (o->call_depth) {\n-\t\t\t\t\tstruct merge_file_info mfi;\n-\t\t\t\t\tstruct diff_filespec one, a, b;\n-\n-\t\t\t\t\toidcpy(&one.oid, &null_oid);\n-\t\t\t\t\tone.mode = 0;\n-\t\t\t\t\tone.path = ren1->pair->two->path;\n-\n-\t\t\t\t\toidcpy(&a.oid, &ren1->pair->two->oid);\n-\t\t\t\t\ta.mode = ren1->pair->two->mode;\n-\t\t\t\t\ta.path = one.path;\n-\n-\t\t\t\t\toidcpy(&b.oid, &dst_other.oid);\n-\t\t\t\t\tb.mode = dst_other.mode;\n-\t\t\t\t\tb.path = one.path;\n-\n-\t\t\t\t\tif (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,\n-\t\t\t\t\t\t\t\t    branch1, branch2,\n-\t\t\t\t\t\t\t\t    o->call_depth * 2, &mfi)) {\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\t\tgoto cleanup_and_return;\n-\t\t\t\t\t}\n-\t\t\t\t\toutput(o, 1, _(\"Adding merged %s\"), ren1_dst);\n-\t\t\t\t\tif (update_file(o, 0, &mfi.oid,\n-\t\t\t\t\t\t\tmfi.mode, ren1_dst))\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\ttry_merge = 0;\n-\t\t\t\t} else {\n-\t\t\t\t\tchar *new_path = unique_path(o, ren1_dst, branch2);\n-\t\t\t\t\toutput(o, 1, _(\"Adding as %s instead\"), new_path);\n-\t\t\t\t\tif (update_file(o, 0, &dst_other.oid,\n-\t\t\t\t\t\t\tdst_other.mode, new_path))\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\tfree(new_path);\n-\t\t\t\t}\n+\t\t\t\t/*\n+\t\t\t\t * Probably not a clean merge, but it's\n+\t\t\t\t * premature to set clean_merge to 0 here,\n+\t\t\t\t * because if the rename merges cleanly and\n+\t\t\t\t * the merge exactly matches the newly added\n+\t\t\t\t * file, then the merge will be clean.\n+\t\t\t\t */\n+\t\t\t\tsetup_rename_conflict_info(RENAME_ADD,\n+\t\t\t\t\t\t\t   ren1->pair,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   branch1,\n+\t\t\t\t\t\t\t   branch2,\n+\t\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   o,\n+\t\t\t\t\t\t\t   ren1->src_entry,\n+\t\t\t\t\t\t\t   NULL);\n \t\t\t} else\n \t\t\t\ttry_merge = 1;\n \n@@ -3312,6 +3338,15 @@ static int process_entry(struct merge_options *o,\n \t\t\t\t\t\t  conflict_info->branch2))\n \t\t\t\tclean_merge = -1;\n \t\t\tbreak;\n+\t\tcase RENAME_ADD:\n+\t\t\t/*\n+\t\t\t * Probably unclean merge, but if the renamed file\n+\t\t\t * merges cleanly and the result can then be\n+\t\t\t * two-way merged cleanly with the added file, I\n+\t\t\t * guess it's a clean merge?\n+\t\t\t */\n+\t\t\tclean_merge = handle_rename_add(o, conflict_info);\n+\t\t\tbreak;\n \t\tcase RENAME_DELETE:\n \t\t\tclean_merge = 0;\n \t\t\tif (handle_rename_delete(o,\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 3091cbc06a..d6b7e568f5 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -185,7 +185,7 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '\n \t\tgit branch B &&\n \t\tgit checkout -b C &&\n \t\techo 10 >>a &&\n-\t\techo \"other content\" >>new_a &&\n+\t\ttest_write_lines 0 1 2 3 4 5 6 7 foobar >new_a &&\n \t\tgit add a new_a &&\n \t\ttest_tick && git commit -m C &&\n \n@@ -195,14 +195,14 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '\n \n \t\tgit checkout B^0 &&\n \t\ttest_must_fail git merge C &&\n-\t\tgit clean -f &&\n+\t\tgit show :2:new_a >new_a &&\n+\t\tgit add new_a &&\n \t\ttest_tick && git commit -m D &&\n \t\tgit tag D &&\n \n \t\tgit checkout C^0 &&\n \t\ttest_must_fail git merge B &&\n-\t\trm new_a~HEAD new_a &&\n-\t\tprintf \"Incorrectly merged content\" >>new_a &&\n+\t\ttest_write_lines 0 1 2 3 4 5 6 7 bad_merge >new_a &&\n \t\tgit add -u &&\n \t\ttest_tick && git commit -m E &&\n \t\tgit tag E\n@@ -225,21 +225,23 @@ test_expect_success 'git detects differently handled merges conflict' '\n \t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tD:new_a  E:new_a &&\n+\t\t\tC:new_a  D:new_a  E:new_a &&\n \t\tgit rev-parse   >actual     \\\n-\t\t\t:2:new_a :3:new_a &&\n+\t\t\t:1:new_a :2:new_a :3:new_a &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit cat-file -p C:new_a >ours &&\n-\t\tgit cat-file -p B:new_a >theirs &&\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p D:new_a >ours &&\n+\t\tgit cat-file -p E:new_a >theirs &&\n \t\t>empty &&\n \t\ttest_must_fail git merge-file \\\n-\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"HEAD\" \\\n \t\t\t-L \"\" \\\n-\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\t-L \"E^0\" \\\n \t\t\tours empty theirs &&\n \t\tsed -e \"s/^\\([<=>]\\)/\\1\\1\\1/\" ours >expect &&\n-\t\tgit cat-file -p :1:new_a >actual &&\n+\t\tgit hash-object new_a >actual &&\n+\t\tgit hash-object ours  >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 23c3b6dffa..3e934ae89e 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -1095,8 +1095,8 @@ test_conflicts_with_adds_and_renames() {\n }\n \n test_conflicts_with_adds_and_renames rename rename failure\n-test_conflicts_with_adds_and_renames rename add    failure\n-test_conflicts_with_adds_and_renames add    rename failure\n+test_conflicts_with_adds_and_renames rename add    success\n+test_conflicts_with_adds_and_renames add    rename success\n test_conflicts_with_adds_and_renames add    add    success\n \n # Setup:\n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362303","messageId":"20181102185317.31015-10-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[RFC PATCH v4 09/10] fixup! merge-recursive: fix rename/add conflict handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:17Z","receivedAt":"2018-11-02T18:53:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"NOTE: This test added solely to improve code coverage of new code added in\nthis series.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh | 51 +++++++++++++++++++++++++++++++\n 1 file changed, 51 insertions(+)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 99cddb05af..b7488b00c0 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -240,6 +240,57 @@ test_expect_success 'git detects differently handled merges conflict' '\n \t)\n '\n \n+# Repeat the above testcase with precisely the same setup, other than with\n+# the two merge bases having different orderings of commit timestamps so\n+# that they are reversed in the order they are provided to merge-recursive,\n+# so that we can improve code coverage.\n+test_expect_success 'git detects differently handled merges conflict, swapped' '\n+\t(\n+\t\tcd rename-add &&\n+\n+\t\t# Difference #1: Do cleanup from previous testrun\n+\t\tgit reset --hard &&\n+\t\tgit clean -fdqx &&\n+\n+\t\t# Difference #2: Change commit timestamps\n+\t\tbtime=$(git log --no-walk --date=raw --format=%cd B | awk \"{print \\$1}\") &&\n+\t\tctime=$(git log --no-walk --date=raw --format=%cd C | awk \"{print \\$1}\") &&\n+\t\tnewctime=$(($btime+1)) &&\n+\t\tgit fast-export --no-data --all | sed -e s/$ctime/$newctime/ | git fast-import --force --quiet &&\n+\t\t# End of differences; rest is copy-paste of last test\n+\n+\t\tgit checkout D^0 &&\n+\t\ttest_must_fail git merge -s recursive E^0 &&\n+\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\tgit rev-parse >expect       \\\n+\t\t\tC:new_a  D:new_a  E:new_a &&\n+\t\tgit rev-parse   >actual     \\\n+\t\t\t:1:new_a :2:new_a :3:new_a &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p D:new_a >ours &&\n+\t\tgit cat-file -p E:new_a >theirs &&\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 \"E^0\" \\\n+\t\t\tours empty theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1\\1/\" ours >expect &&\n+\t\tgit hash-object new_a >actual &&\n+\t\tgit hash-object ours  >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n #\n # criss-cross + modify/delete:\n #\n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362304","messageId":"20181102185317.31015-5-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[PATCH v4 04/10] merge-recursive: new function for better colliding conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:12Z","receivedAt":"2018-11-02T18:58:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There are three conflict types that represent two (possibly entirely\nunrelated) files colliding at the same location:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n\nThese three conflict types already share more similarity than might be\nimmediately apparent from their description: (1) the handling of the\nrename variants already involves removing any entries from the index\ncorresponding to the original file names[*], thus only leaving entries\nin the index for the colliding path; (2) likewise, any trace of the\noriginal file name in the working tree is also removed.  So, in all\nthree cases we're left with how to represent two colliding files in both\nthe index and the working copy.\n\n[*] Technically, this isn't quite true because rename/rename(2to1)\nconflicts in the recursive (o->call_depth > 0) case do an \"unrename\"\nsince about seven years ago.  But even in that case, Junio felt\ncompelled to explain that my decision to \"unrename\" wasn't necessarily\nthe only or right answer -- search for \"Comment from Junio\" in t6036 for\ndetails.\n\nMy initial motivation for looking at these three conflict types was that\nif the handling of these three conflict types is the same, at least in\nthe limited set of cases where a renamed file is unmodified on the side\nof history where the file is not renamed, then a significant performance\nimprovement for rename detection during merges is possible.  However,\nwhile that served as motivation to look at these three types of\nconflicts, the actual goal of this new function is to try to improve the\nhandling for all three cases, not to merely make them the same as each\nother in that special circumstance.\n\n=== Handling the working tree ===\n\nThe previous behavior for these conflict types in regards to the\nworking tree (assuming the file collision occurs at 'foo') was:\n  * add/add does a two-way merge of the two files and records it as 'foo'.\n  * rename/rename(2to1) records the two different files into two new\n    uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'\n    from the working tree.\n  * rename/add records the two different files into two different\n    locations, recording the add at foo~$SIDE and, oddly, recording\n    the rename at foo (why is the rename more important than the add?)\n\nSo, the question for what to write to the working tree boils down to\nwhether the two colliding files should be two-way merged and recorded in\nplace, or recorded into separate files.  As per discussion on the git\nmailing lit, two-way merging was deemed to always be preferred, as that\nmakes these cases all more like content conflicts that users can handle\nfrom within their favorite editor, IDE, or merge tool.  Note that since\nrenames already involve a content merge, rename/add and\nrename/rename(2to1) conflicts could result in nested conflict markers.\n\n=== Handling of the index ===\n\nFor a typical rename, unpack_trees() would set up the index in the\nfollowing fashion:\n           old_path  new_path\n   stage1: 5ca1ab1e  00000000\n   stage2: f005ba11  00000000\n   stage3: 00000000  b0a710ad\nAnd merge-recursive would rewrite this to\n           new_path\n   stage1: 5ca1ab1e\n   stage2: f005ba11\n   stage3: b0a710ad\nRemoving old_path from the index means the user won't have to `git rm\nold_path` manually every time a renamed path has a content conflict.\nIt also means they can use `git checkout [--ours|--theirs|--conflict|-m]\nnew_path`, `git diff [--ours|--theirs]` and various other commands that\nwould be difficult otherwise.\n\nThis strategy becomes a problem when we have a rename/add or\nrename/rename(2to1) conflict, however, because then we have only three\nslots to store blob sha1s and we need either four or six.  Previously,\nthis was handled by continuing to delete old_path from the index, and\njust outright ignoring any blob shas from old_path.  That had the\ndownside of deleting any trace of changes made to old_path on the other\nside of history.  This function instead does a three-way content merge of\nthe renamed file, and stores the blob sha1 for that at either stage2 or\nstage3 for new_path (depending on which side the rename came from).  That\nhas the advantage of bringing information about changes on both sides and\nstill allows for easy resolution (no need to git rm old_path, etc.), but\ndoes have the downside that if the content merge had conflict markers,\nthen what we store in the index is the sha1 of a blob with conflict\nmarkers.  While that is a downside, it seems less problematic than the\ndownsides of any obvious alternatives, and certainly makes more sense\nthan the previous handling.  Further, it has a precedent in that when we\ndo recursive merges, we may accept a file with conflict markers as the\nresolution for the merge of the merge-bases, which will then show up in\nthe index of the outer merge at stage 1 if a conflict exists at the outer\nlevel.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 121 insertions(+)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f795c92a69..24d979022e 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1559,6 +1559,127 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,\n \treturn target;\n }\n \n+#if 0 // #if-0-ing avoids unused function warning; will make live in next commit\n+static int handle_file_collision(struct merge_options *o,\n+\t\t\t\t const char *collide_path,\n+\t\t\t\t const char *prev_path1,\n+\t\t\t\t const char *prev_path2,\n+\t\t\t\t const char *branch1, const char *branch2,\n+\t\t\t\t const struct object_id *a_oid,\n+\t\t\t\t unsigned int a_mode,\n+\t\t\t\t const struct object_id *b_oid,\n+\t\t\t\t unsigned int b_mode)\n+{\n+\tstruct merge_file_info mfi;\n+\tstruct diff_filespec null, a, b;\n+\tchar *alt_path = NULL;\n+\tconst char *update_path = collide_path;\n+\n+\t/*\n+\t * In the recursive case, we just opt to undo renames\n+\t */\n+\tif (o->call_depth && (prev_path1 || prev_path2)) {\n+\t\t/* Put first file (a_oid, a_mode) in its original spot */\n+\t\tif (prev_path1) {\n+\t\t\tif (update_file(o, 1, a_oid, a_mode, prev_path1))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tif (update_file(o, 1, a_oid, a_mode, collide_path))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Put second file (b_oid, b_mode) in its original spot */\n+\t\tif (prev_path2) {\n+\t\t\tif (update_file(o, 1, b_oid, b_mode, prev_path2))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tif (update_file(o, 1, b_oid, b_mode, collide_path))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Don't leave something at collision path if unrenaming both */\n+\t\tif (prev_path1 && prev_path2)\n+\t\t\tremove_file(o, 1, collide_path, 0);\n+\n+\t\treturn 0;\n+\t}\n+\n+\t/* Remove rename sources if rename/add or rename/rename(2to1) */\n+\tif (prev_path1)\n+\t\tremove_file(o, 1, prev_path1,\n+\t\t\t    o->call_depth || would_lose_untracked(prev_path1));\n+\tif (prev_path2)\n+\t\tremove_file(o, 1, prev_path2,\n+\t\t\t    o->call_depth || would_lose_untracked(prev_path2));\n+\n+\t/*\n+\t * Remove the collision path, if it wouldn't cause dirty contents\n+\t * or an untracked file to get lost.  We'll either overwrite with\n+\t * merged contents, or just write out to differently named files.\n+\t */\n+\tif (was_dirty(o, collide_path)) {\n+\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n+\t\t       collide_path);\n+\t\tupdate_path = alt_path = unique_path(o, collide_path, \"merged\");\n+\t} else if (would_lose_untracked(collide_path)) {\n+\t\t/*\n+\t\t * Only way we get here is if both renames were from\n+\t\t * a directory rename AND user had an untracked file\n+\t\t * at the location where both files end up after the\n+\t\t * two directory renames.  See testcase 10d of t6043.\n+\t\t */\n+\t\toutput(o, 1, _(\"Refusing to lose untracked file at \"\n+\t\t\t       \"%s, even though it's in the way.\"),\n+\t\t       collide_path);\n+\t\tupdate_path = alt_path = unique_path(o, collide_path, \"merged\");\n+\t} else {\n+\t\t/*\n+\t\t * FIXME: It's possible that the two files are identical\n+\t\t * and that the current working copy happens to match, in\n+\t\t * which case we are unnecessarily touching the working\n+\t\t * tree file.  It's not a likely enough scenario that I\n+\t\t * want to code up the checks for it and a better fix is\n+\t\t * available if we restructure how unpack_trees() and\n+\t\t * merge-recursive interoperate anyway, so punting for\n+\t\t * now...\n+\t\t */\n+\t\tremove_file(o, 0, collide_path, 0);\n+\t}\n+\n+\t/* Store things in diff_filespecs for functions that need it */\n+\tmemset(&a, 0, sizeof(struct diff_filespec));\n+\tmemset(&b, 0, sizeof(struct diff_filespec));\n+\tnull.path = a.path = b.path = (char *)collide_path;\n+\toidcpy(&null.oid, &null_oid);\n+\tnull.mode = 0;\n+\toidcpy(&a.oid, a_oid);\n+\ta.mode = a_mode;\n+\ta.oid_valid = 1;\n+\toidcpy(&b.oid, b_oid);\n+\tb.mode = b_mode;\n+\tb.oid_valid = 1;\n+\n+\tif (merge_mode_and_contents(o, &null, &a, &b, collide_path,\n+\t\t\t\t    branch1, branch2, o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tmfi.clean &= !alt_path;\n+\tif (update_file(o, mfi.clean, &mfi.oid, mfi.mode, update_path))\n+\t\treturn -1;\n+\tif (!mfi.clean && !o->call_depth &&\n+\t    update_stages(o, collide_path, NULL, &a, &b))\n+\t\treturn -1;\n+\tfree(alt_path);\n+\t/*\n+\t * FIXME: If both a & b both started with conflicts (only possible\n+\t * if they came from a rename/rename(2to1)), but had IDENTICAL\n+\t * contents including those conflicts, then in the next line we claim\n+\t * it was clean.  If someone cares about this case, we should have the\n+\t * caller notify us if we started with conflicts.\n+\t */\n+\treturn mfi.clean;\n+}\n+#endif\n+\n static int handle_file(struct merge_options *o,\n \t\t\tstruct diff_filespec *rename,\n \t\t\tint stage,\n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362305","messageId":"20181102185317.31015-4-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[PATCH v4 03/10] merge-recursive: increase marker length with depth of recursion","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:11Z","receivedAt":"2018-11-02T18:58:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Later patches in this series will modify file collision conflict\nhandling (e.g. from rename/add and rename/rename(2to1) conflicts) so\nthat multiply nested conflict markers can arise even before considering\nconflicts in the virtual merge base.  Including the virtual merge base\nwill provide a way to get triply (or higher) nested conflict markers.\nThis new way to get nested conflict markers will force the need for a\nmore general mechanism to extend the length of conflict markers in order\nto differentiate between different nestings.\n\nAlong with this change to conflict marker length handling, we want to\nmake sure that we don't regress handling for other types of conflicts\nwith nested conflict markers.  Add a more involved testcase using\nmerge.conflictstyle=diff3, where not only does the virtual merge base\ncontain conflicts, but its virtual merge base does as well (i.e. a case\nwith triply nested conflict markers).  While there are multiple\nreasonable ways to handle nested conflict markers in the virtual merge\nbase for this type of situation, the easiest approach that dovetails\nwell with the new needs for the file collision conflict handling is to\nrequire that the length of the conflict markers increase with each\nsubsequent nesting.\n\nSubsequent patches which change the rename/add and rename/rename(2to1)\nconflict handling will modify the extra_marker_size flag appropriately\nfor their new needs.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n ll-merge.c                        |   4 +-\n ll-merge.h                        |   1 +\n merge-recursive.c                 |  25 +++--\n t/t6036-recursive-corner-cases.sh | 151 ++++++++++++++++++++++++++++++\n 4 files changed, 172 insertions(+), 9 deletions(-)\n\ndiff --git a/ll-merge.c b/ll-merge.c\nindex 0e2800f7bb..aabc1b5c2e 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -384,7 +384,9 @@ int ll_merge(mmbuffer_t *result_buf,\n \tif (opts->virtual_ancestor) {\n \t\tif (driver->recursive)\n \t\t\tdriver = find_ll_merge_driver(driver->recursive);\n-\t\tmarker_size += 2;\n+\t}\n+\tif (opts->extra_marker_size) {\n+\t\tmarker_size += opts->extra_marker_size;\n \t}\n \treturn driver->fn(driver, result_buf, path, ancestor, ancestor_label,\n \t\t\t  ours, our_label, theirs, their_label,\ndiff --git a/ll-merge.h b/ll-merge.h\nindex b72b19921e..5b4e158502 100644\n--- a/ll-merge.h\n+++ b/ll-merge.h\n@@ -11,6 +11,7 @@ struct ll_merge_options {\n \tunsigned virtual_ancestor : 1;\n \tunsigned variant : 2;\t/* favor ours, favor theirs, or union merge */\n \tunsigned renormalize : 1;\n+\tunsigned extra_marker_size;\n \tlong xdl_opts;\n };\n \ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 73b5710386..f795c92a69 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1058,7 +1058,8 @@ static int merge_3way(struct merge_options *o,\n \t\t      const struct diff_filespec *a,\n \t\t      const struct diff_filespec *b,\n \t\t      const char *branch1,\n-\t\t      const char *branch2)\n+\t\t      const char *branch2,\n+\t\t      const int extra_marker_size)\n {\n \tmmfile_t orig, src1, src2;\n \tstruct ll_merge_options ll_opts = {0};\n@@ -1066,6 +1067,7 @@ static int merge_3way(struct merge_options *o,\n \tint merge_status;\n \n \tll_opts.renormalize = o->renormalize;\n+\tll_opts.extra_marker_size = extra_marker_size;\n \tll_opts.xdl_opts = o->xdl_opts;\n \n \tif (o->call_depth) {\n@@ -1300,6 +1302,7 @@ static int merge_mode_and_contents(struct merge_options *o,\n \t\t\t\t   const char *filename,\n \t\t\t\t   const char *branch1,\n \t\t\t\t   const char *branch2,\n+\t\t\t\t   const int extra_marker_size,\n \t\t\t\t   struct merge_file_info *result)\n {\n \tif (o->branch1 != branch1) {\n@@ -1310,7 +1313,8 @@ static int merge_mode_and_contents(struct merge_options *o,\n \t\t */\n \t\treturn merge_mode_and_contents(o, one, b, a,\n \t\t\t\t\t       filename,\n-\t\t\t\t\t       branch2, branch1, result);\n+\t\t\t\t\t       branch2, branch1,\n+\t\t\t\t\t       extra_marker_size, result);\n \t}\n \n \tresult->merge = 0;\n@@ -1351,7 +1355,8 @@ static int merge_mode_and_contents(struct merge_options *o,\n \t\t\tint ret = 0, merge_status;\n \n \t\t\tmerge_status = merge_3way(o, &result_buf, one, a, b,\n-\t\t\t\t\t\t  branch1, branch2);\n+\t\t\t\t\t\t  branch1, branch2,\n+\t\t\t\t\t\t  extra_marker_size);\n \n \t\t\tif ((merge_status < 0) || !result_buf.ptr)\n \t\t\t\tret = err(o, _(\"Failed to execute internal merge\"));\n@@ -1640,7 +1645,8 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\tstruct diff_filespec other;\n \t\tstruct diff_filespec *add;\n \t\tif (merge_mode_and_contents(o, one, a, b, one->path,\n-\t\t\t\t\t    ci->branch1, ci->branch2, &mfi))\n+\t\t\t\t\t    ci->branch1, ci->branch2,\n+\t\t\t\t\t    o->call_depth * 2, &mfi))\n \t\t\treturn -1;\n \n \t\t/*\n@@ -1707,9 +1713,11 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \tpath_side_1_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n \tpath_side_2_desc = xstrfmt(\"version of %s from %s\", path, b->path);\n \tif (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,\n-\t\t\t\t    o->branch1, o->branch2, &mfi_c1) ||\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,\n-\t\t\t\t    o->branch1, o->branch2, &mfi_c2))\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n@@ -2755,7 +2763,7 @@ static int process_renames(struct merge_options *o,\n \n \t\t\t\t\tif (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,\n \t\t\t\t\t\t\t\t    branch1, branch2,\n-\t\t\t\t\t\t\t\t    &mfi)) {\n+\t\t\t\t\t\t\t\t    o->call_depth * 2, &mfi)) {\n \t\t\t\t\t\tclean_merge = -1;\n \t\t\t\t\t\tgoto cleanup_and_return;\n \t\t\t\t\t}\n@@ -3052,7 +3060,8 @@ static int handle_content_merge(struct merge_options *o,\n \t\t\tdf_conflict_remains = 1;\n \t}\n \tif (merge_mode_and_contents(o, &one, &a, &b, path,\n-\t\t\t\t    o->branch1, o->branch2, &mfi))\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi))\n \t\treturn -1;\n \n \t/*\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex f229d7e47b..3091cbc06a 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1596,4 +1596,155 @@ test_expect_failure 'check nested conflicts' '\n \t)\n '\n \n+# Setup:\n+#          L1---L2---L3\n+#         /  \\ /  \\ /  \\\n+#   master    X1   X2   ?\n+#         \\  / \\  / \\  /\n+#          R1---R2---R3\n+#\n+# Where:\n+#   master has one file named 'content'\n+#   branches L1 and R1 both modify each of the two files in conflicting ways\n+#\n+#   L<n> (n>1) is a merge of R<n-1> into L<n-1>\n+#   R<n> (n>1) is a merge of L<n-1> into R<n-1>\n+#   L<n> and R<n> resolve the conflicts differently.\n+#\n+#   X<n> is an auto-generated merge-base used when merging L<n+1> and R<n+1>.\n+#   By construction, X1 has conflict markers due to conflicting versions.\n+#   X2, due to using merge.conflictstyle=3, has nested conflict markers.\n+#\n+#   So, merging R3 into L3 using merge.conflictstyle=3 should show the\n+#   nested conflict markers from X2 in the base version -- that means we\n+#   have three levels of conflict markers.  Can we distinguish all three?\n+\n+test_expect_success 'setup virtual merge base with nested conflicts' '\n+\ttest_create_repo virtual_merge_base_has_nested_conflicts &&\n+\t(\n+\t\tcd virtual_merge_base_has_nested_conflicts &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >content &&\n+\n+\t\t# Setup original commit\n+\t\tgit add content &&\n+\t\ttest_tick && git commit -m initial &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Create L1\n+\t\tgit checkout L &&\n+\t\techo left >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick && git commit -m \"version L1 of content\" &&\n+\t\tgit tag L1 &&\n+\n+\t\t# Create R1\n+\t\tgit checkout R &&\n+\t\techo right >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick && git commit -m \"verson R1 of content\" &&\n+\t\tgit tag R1 &&\n+\n+\t\t# Create L2\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge R1 &&\n+\t\tgit checkout L1 content &&\n+\t\ttest_tick && git commit -m \"version L2 of content\" &&\n+\t\tgit tag L2 &&\n+\n+\t\t# Create R2\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge L1 &&\n+\t\tgit checkout R1 content &&\n+\t\ttest_tick && git commit -m \"version R2 of content\" &&\n+\t\tgit tag R2 &&\n+\n+\t\t# Create L3\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge R2 &&\n+\t\tgit checkout L1 content &&\n+\t\ttest_tick && git commit -m \"version L3 of content\" &&\n+\t\tgit tag L3 &&\n+\n+\t\t# Create R3\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge L2 &&\n+\t\tgit checkout R1 content &&\n+\t\ttest_tick && git commit -m \"version R3 of content\" &&\n+\t\tgit tag R3\n+\t)\n+'\n+\n+test_expect_success 'check virtual merge base with nested conflicts' '\n+\t(\n+\t\tcd virtual_merge_base_has_nested_conflicts &&\n+\n+\t\tgit checkout L3^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R3^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Compare :[23]:content to expected values\n+\t\tgit rev-parse L1:content R1:content >expect &&\n+\t\tgit rev-parse :2:content :3:content >actual &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Imitate X1 merge base, except without long enough conflict\n+\t\t# markers because a subsequent sed will modify them.  Put\n+\t\t# result into vmb.\n+\t\tgit cat-file -p master:content >base &&\n+\t\tgit cat-file -p L:content >left &&\n+\t\tgit cat-file -p R:content >right &&\n+\t\tcp left merged-once &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tmerged-once \\\n+\t\t\tbase        \\\n+\t\t\tright       &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1\\1/\" merged-once >vmb &&\n+\n+\t\t# Imitate X2 merge base, overwriting vmb.  Note that we\n+\t\t# extend both sets of conflict markers to make them longer\n+\t\t# with the sed command.\n+\t\tcp left merged-twice &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tmerged-twice \\\n+\t\t\tvmb          \\\n+\t\t\tright        &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1\\1/\" merged-twice >vmb &&\n+\n+\t\t# Compare :1:content to expected value\n+\t\tgit cat-file -p :1:content >actual &&\n+\t\ttest_cmp vmb actual &&\n+\n+\t\t# Determine expected content in final outer merge, compare to\n+\t\t# what the merge generated.\n+\t\tcp -f left expect &&\n+\t\ttest_must_fail git merge-file --diff3                      \\\n+\t\t\t-L \"HEAD\"  -L \"merged common ancestors\"  -L \"R3^0\" \\\n+\t\t\texpect     vmb                           right     &&\n+\t\ttest_cmp expect content\n+\t)\n+'\n+\n test_done\n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362306","messageId":"20181102185317.31015-2-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[PATCH v4 01/10] Add testcases for consistency in file collision conflict handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T18:53:09Z","receivedAt":"2018-11-02T18:58:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Add testcases dealing with file collisions for the following types of\nconflicts:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n\nAll these conflict types simplify down to two files \"colliding\"\nand should thus be handled similarly.  This means that rename/add and\nrename/rename(2to1) conflicts need to be modified to behave the same as\nadd/add conflicts currently do: the colliding files should be two-way\nmerged (instead of the current behavior of writing the two colliding\nfiles out to separate temporary unique pathnames).  Add testcases which\ncheck this; subsequent commits will fix the conflict handling to make\nthese tests pass.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6042-merge-rename-corner-cases.sh | 162 +++++++++++++++++++++++++++\n 1 file changed, 162 insertions(+)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b97aca7fa2..b6fed2cb9a 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -937,4 +937,166 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename\n \t)\n '\n \n+test_conflicts_with_adds_and_renames() {\n+\tsideL=$1\n+\tsideR=$2\n+\texpect=$3\n+\n+\t# Setup:\n+\t#          L\n+\t#         / \\\n+\t#   master   ?\n+\t#         \\ /\n+\t#          R\n+\t#\n+\t# Where:\n+\t#   Both L and R have files named 'three' which collide.  Each of\n+\t#   the colliding files could have been involved in a rename, in\n+\t#   which case there was a file named 'one' or 'two' that was\n+\t#   modified on the opposite side of history and renamed into the\n+\t#   collision on this side of history.\n+\t#\n+\t# Questions:\n+\t#   1) The index should contain both a stage 2 and stage 3 entry\n+\t#      for the colliding file.  Does it?\n+\t#   2) When renames are involved, the content merges are clean, so\n+\t#      the index should reflect the content merges, not merely the\n+\t#      version of the colliding file from the prior commit.  Does\n+\t#      it?\n+\t#   3) There should be a file in the worktree named 'three'\n+\t#      containing the two-way merged contents of the content-merged\n+\t#      versions of 'three' from each of the two colliding\n+\t#      files.  Is it present?\n+\t#   4) There should not be any three~* files in the working\n+\t#      tree\n+\ttest_expect_success \"setup simple $sideL/$sideR conflict\" '\n+\t\ttest_create_repo simple_${sideL}_${sideR} &&\n+\t\t(\n+\t\t\tcd simple_${sideL}_${sideR} &&\n+\n+\t\t\t# Create some related files now\n+\t\t\tfor i in $(test_seq 1 10)\n+\t\t\tdo\n+\t\t\t\techo Random base content line $i\n+\t\t\tdone >file_v1 &&\n+\t\t\tcp file_v1 file_v2 &&\n+\t\t\techo modification >>file_v2 &&\n+\n+\t\t\tcp file_v1 file_v3 &&\n+\t\t\techo more stuff >>file_v3 &&\n+\t\t\tcp file_v3 file_v4 &&\n+\t\t\techo yet more stuff >>file_v4 &&\n+\n+\t\t\t# Use a tag to record both these files for simple\n+\t\t\t# access, and clean out these untracked files\n+\t\t\tgit tag file_v1 $(git hash-object -w file_v1) &&\n+\t\t\tgit tag file_v2 $(git hash-object -w file_v2) &&\n+\t\t\tgit tag file_v3 $(git hash-object -w file_v3) &&\n+\t\t\tgit tag file_v4 $(git hash-object -w file_v4) &&\n+\t\t\tgit clean -f &&\n+\n+\t\t\t# Setup original commit (or merge-base), consisting of\n+\t\t\t# files named \"one\" and \"two\" if renames were involved.\n+\t\t\ttouch irrelevant_file &&\n+\t\t\tgit add irrelevant_file &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v1 >one &&\n+\t\t\t\tgit add one\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v3 >two &&\n+\t\t\t\tgit add two\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m initial &&\n+\n+\t\t\tgit branch L &&\n+\t\t\tgit branch R &&\n+\n+\t\t\t# Handle the left side\n+\t\t\tgit checkout L &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit mv one three\n+\t\t\telse\n+\t\t\t\tgit show file_v2 >three &&\n+\t\t\t\tgit add three\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v4 >two &&\n+\t\t\t\tgit add two\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m L &&\n+\n+\t\t\t# Handle the right side\n+\t\t\tgit checkout R &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v2 >one &&\n+\t\t\t\tgit add one\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit mv two three\n+\t\t\telse\n+\t\t\t\tgit show file_v4 >three &&\n+\t\t\t\tgit add three\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m R\n+\t\t)\n+\t'\n+\n+\ttest_expect_$expect \"check simple $sideL/$sideR conflict\" '\n+\t\t(\n+\t\t\tcd simple_${sideL}_${sideR} &&\n+\n+\t\t\tgit checkout L^0 &&\n+\n+\t\t\t# Merge must fail; there is a conflict\n+\t\t\ttest_must_fail git merge -s recursive R^0 &&\n+\n+\t\t\t# Make sure the index has the right number of entries\n+\t\t\tgit ls-files -s >out &&\n+\t\t\ttest_line_count = 3 out &&\n+\t\t\tgit ls-files -u >out &&\n+\t\t\ttest_line_count = 2 out &&\n+\t\t\t# Ensure we have the correct number of untracked files\n+\t\t\tgit ls-files -o >out &&\n+\t\t\ttest_line_count = 1 out &&\n+\n+\t\t\t# Nothing should have touched irrelevant_file\n+\t\t\tgit rev-parse >actual      \\\n+\t\t\t\t:0:irrelevant_file \\\n+\t\t\t\t:2:three           \\\n+\t\t\t\t:3:three           &&\n+\t\t\tgit rev-parse >expected        \\\n+\t\t\t\tmaster:irrelevant_file \\\n+\t\t\t\tfile_v2                \\\n+\t\t\t\tfile_v4                &&\n+\t\t\ttest_cmp expected actual &&\n+\n+\t\t\t# Make sure we have the correct merged contents for\n+\t\t\t# three\n+\t\t\tgit show file_v1 >expected &&\n+\t\t\tcat <<-\\EOF >>expected &&\n+\t\t\t<<<<<<< HEAD\n+\t\t\tmodification\n+\t\t\t=======\n+\t\t\tmore stuff\n+\t\t\tyet more stuff\n+\t\t\t>>>>>>> R^0\n+\t\t\tEOF\n+\n+\t\t\ttest_cmp expected three\n+\t\t)\n+\t'\n+}\n+\n+test_conflicts_with_adds_and_renames rename rename failure\n+test_conflicts_with_adds_and_renames rename add    failure\n+test_conflicts_with_adds_and_renames add    rename failure\n+test_conflicts_with_adds_and_renames add    add    success\n+\n test_done\n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362308","messageId":"20181102190500.31603-1-newren@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"[PATCH v4 10/10] fixup! merge-recursive: improve rename/rename(1to2)/add[/add] handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T19:05:00Z","receivedAt":"2018-11-02T19:05:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"NOTE: This test added solely to improve code coverage of new code added in\nthis series.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6043-merge-rename-directories.sh | 37 +++++++++++++++++++++++++++++\n 1 file changed, 37 insertions(+)\n\ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex 5c01a0c14a..62c564707b 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -3163,6 +3163,43 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)\n \t)\n '\n \n+test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2), other direction' '\n+\t(\n+\t\tcd 10c &&\n+\n+\t\tgit reset --hard &&\n+\t\tgit clean -fdqx &&\n+\n+\t\tgit checkout B^0 &&\n+\t\tmkdir y &&\n+\t\techo important >y/c &&\n+\n+\t\ttest_must_fail git merge -s recursive A^0 >out 2>err &&\n+\t\ttest_i18ngrep \"CONFLICT (rename/rename)\" out &&\n+\t\ttest_i18ngrep \"Refusing to lose untracked file at y/c; adding as y/c~HEAD instead\" out &&\n+\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 6 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 3 out &&\n+\n+\t\tgit rev-parse >actual \\\n+\t\t\t:0:y/a :0:y/b :0:x/d :1:x/c :3:w/c :2:y/c &&\n+\t\tgit rev-parse >expect \\\n+\t\t\t O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  O:x/c &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit hash-object y/c~HEAD >actual &&\n+\t\tgit rev-parse O:x/c >expect &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\techo important >expect &&\n+\t\ttest_cmp expect y/c\n+\t)\n+'\n+\n # Testcase 10d, Delete untracked w/ dir rename/rename(2to1)\n #   Commit O: z/{a,b,c_1},        x/{d,e,f_2}\n #   Commit A: y/{a,b},            x/{d,e,f_2,wham_1} + untracked y/wham\n-- \n2.19.0.232.gd14c2061fc\n\n"},{"id":"362309","messageId":"232094ce-6872-4039-06a6-af40c709cac0@gmail.com","threadId":"49559","inReplyTo":"20181102185317.31015-1-newren@gmail.com","subject":"Re: [PATCH v4 00/10] Improve path collision conflict resolutions","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2018-11-02T19:09:25Z","receivedAt":"2018-11-02T19:09:29Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"On 11/2/2018 2:53 PM, Elijah Newren wrote:\n> Major question:\n>    * You'll note that I edited the last two patches to mark them as RFC.\n>      To be honest, I'm not sure what to do with these.  They improve code\n>      coverage of new code, but the same gaps existed in the old code;\n>      they only show up in the coverage-diff because I essentially moved\n>      code around to a new improved function.  Since the new code doesn't\n>      really add new abilities but rather just shifts the handling of\n>      these conflicts to a common function, they shouldn't need any more\n>      testcases than previously and modifying the existing patches thus\n>      feels slightly misleading.  That line of thought leads me to believe\n>      that perhaps putting them in a separate combined patch of their own\n>      with a decent commit message is the right way to go.  On the other\n>      hand, squashing them to the commits they're marked as fixups for\n>      shows which logical part of the code the tests are related to, which\n>      seems like a good thing.  So what's the right way to handle these?\n\nI appreciate the effort you made to improve test coverage! It's \nunfortunate that this portion wasn't covered earlier, because we could \nhave broken it and not noticed until a release.\n\nI think making them separate commits is fine, and the comment on the \ntest case is helpful. The fact that you only had to change the commit \ntimestamps in order to get the coverage makes me reexamine the code and \nrealize that maybe the \"right\" thing to do is to reduce our code clones. \n(This is also how I was looking at the wrong block of the patch when \ntalking about it not being covered.) I'll look at the patch and see if I \ncan contribute a concrete code suggestion there.\n\nAside: I hope that I am not being annoying by poking around with the \ntest coverage reports. It does give me a way to target my review \nefforts, especially into changes that touch areas outside my expertise \n(like this one).\n\nThanks,\n\n-Stolee\n\n"},{"id":"362311","messageId":"20181102200127.127356-1-dstolee@microsoft.com","threadId":"49559","inReplyTo":"20181102185317.31015-9-newren@gmail.com","subject":"[PATCH] merge-recursive: combine error handling","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2018-11-02T20:01:27Z","receivedAt":"2018-11-02T20:01:33Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"In handle_rename_rename_1to2(), we have duplicated error handling\naround colliding paths. Specifically, when we want to write out\nthe file and there is a directory or untracked file in the way,\nwe need to create a temporary file to hold the contents. This has\nsome special output to alert the user, and this output is\nduplicated for each side of the conflict.\n\nSimplify the call by generating this new path in a helper\nfunction.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n\nElijah,\n\nHere is a patch that combines the logic to avoid code clones, and also\nmore easily covers code blocks. Of course, your additional test helps\nthe branch coverage.\n\nThanks,\n-Stolee\n\n merge-recursive.c | 53 ++++++++++++++++++++++++-----------------------\n 1 file changed, 27 insertions(+), 26 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 59811116b6..5e36bef162 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1709,6 +1709,27 @@ static int handle_rename_add(struct merge_options *o,\n \t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n }\n \n+static char *find_path_for_conflict(struct merge_options *o,\n+\t\t\t\t    const char *path,\n+\t\t\t\t    const char *branch1,\n+\t\t\t\t    const char *branch2)\n+{\n+\tchar *new_path = NULL;\n+\tif (dir_in_way(path, !o->call_depth, 0)) {\n+\t\tnew_path = unique_path(o, path, branch1);\n+\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t       \"as %s instead\"),\n+\t\t       path, branch2, new_path);\n+\t} else if (would_lose_untracked(path)) {\n+\t\tnew_path = unique_path(o, path, branch1);\n+\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t       path, new_path);\n+\t}\n+\n+\treturn new_path;\n+}\n+\n static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t     struct rename_conflict_info *ci)\n {\n@@ -1783,19 +1804,9 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t\t\t  &add->oid, add->mode) < 0)\n \t\t\t\treturn -1;\n \t\t} else {\n-\t\t\tchar *new_path = NULL;\n-\t\t\tif (dir_in_way(a->path, !o->call_depth, 0)) {\n-\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n-\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n-\t\t\t\t\t       \"as %s instead\"),\n-\t\t\t\t       a->path, ci->branch2, new_path);\n-\t\t\t} else if (would_lose_untracked(a->path)) {\n-\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n-\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n-\t\t\t\t\t       \" at %s; adding as %s instead\"),\n-\t\t\t\t       a->path, new_path);\n-\t\t\t}\n-\n+\t\t\tchar *new_path = find_path_for_conflict(o, a->path,\n+\t\t\t\t\t\t\t\tci->branch1,\n+\t\t\t\t\t\t\t\tci->branch2);\n \t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))\n \t\t\t\treturn -1;\n \t\t\tfree(new_path);\n@@ -1812,19 +1823,9 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t\t\t  &mfi.oid, mfi.mode) < 0)\n \t\t\t\treturn -1;\n \t\t} else {\n-\t\t\tchar *new_path = NULL;\n-\t\t\tif (dir_in_way(b->path, !o->call_depth, 0)) {\n-\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n-\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n-\t\t\t\t\t       \"as %s instead\"),\n-\t\t\t\t       b->path, ci->branch1, new_path);\n-\t\t\t} else if (would_lose_untracked(b->path)) {\n-\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n-\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n-\t\t\t\t\t       \" at %s; adding as %s instead\"),\n-\t\t\t\t       b->path, new_path);\n-\t\t\t}\n-\n+\t\t\tchar *new_path = find_path_for_conflict(o, b->path,\n+\t\t\t\t\t\t\t\tci->branch2,\n+\t\t\t\t\t\t\t\tci->branch1);\n \t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))\n \t\t\t\treturn -1;\n \t\t\tfree(new_path);\n-- \n2.19.1.1235.gbda564b1a2\n\n"},{"id":"362312","messageId":"CABPp-BG2rFEeKVe8ok+a-jLFvPBfnZs1b3Mp2Jfi2JgNZcO8gA@mail.gmail.com","threadId":"49559","inReplyTo":"232094ce-6872-4039-06a6-af40c709cac0@gmail.com","subject":"Re: [PATCH v4 00/10] Improve path collision conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-02T20:06:32Z","receivedAt":"2018-11-02T20:06:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Nov 2, 2018 at 12:09 PM Derrick Stolee <stolee@gmail.com> wrote:\n>\n> On 11/2/2018 2:53 PM, Elijah Newren wrote:\n> > Major question:\n> >    * You'll note that I edited the last two patches to mark them as RFC.\n> >      To be honest, I'm not sure what to do with these.  They improve code\n> >      coverage of new code, but the same gaps existed in the old code;\n> >      they only show up in the coverage-diff because I essentially moved\n> >      code around to a new improved function.  Since the new code doesn't\n> >      really add new abilities but rather just shifts the handling of\n> >      these conflicts to a common function, they shouldn't need any more\n> >      testcases than previously and modifying the existing patches thus\n> >      feels slightly misleading.  That line of thought leads me to believe\n> >      that perhaps putting them in a separate combined patch of their own\n> >      with a decent commit message is the right way to go.  On the other\n> >      hand, squashing them to the commits they're marked as fixups for\n> >      shows which logical part of the code the tests are related to, which\n> >      seems like a good thing.  So what's the right way to handle these?\n>\n> I appreciate the effort you made to improve test coverage! It's\n> unfortunate that this portion wasn't covered earlier, because we could\n> have broken it and not noticed until a release.\n\nYes, I agree...except that I think we might not have noticed until a\ncouple releases down the road; these things tend to not come up a lot\nin practice.  (Which may make it even more important to pay attention\nto code coverage.)\n\n> I think making them separate commits is fine, and the comment on the\n> test case is helpful. The fact that you only had to change the commit\n> timestamps in order to get the coverage makes me reexamine the code and\n> realize that maybe the \"right\" thing to do is to reduce our code clones.\n> (This is also how I was looking at the wrong block of the patch when\n> talking about it not being covered.) I'll look at the patch and see if I\n> can contribute a concrete code suggestion there.\n\nYeah, I had the same feeling, _again_, while re-looking at the tests\nand code as well.  I think the history of how we got here goes\nsomething like this:\n\n  * there is some fairly simple code to handle these rename/rename\n(1to2 and 2to1) cases, with logic for handling each side being a\nneary-copy of each other.\n  * someone does some analysis about trying to remove duplication and\nnotes that there are 3-4 pieces that change; adding logic to change\nout those pieces and rewrite it as a loop adds some complexity, which\nisn't worth it given the simple code\n  * additional issues are discovered, such as D/F conflicts or\ninappropriate handling of untracked or dirty files, and due to\nmerge-recursive's bad design[1], the fixes have to be sprinkled\n*everywhere* throughout the whole code base.  Lather, rinse, repeat a\nfew times.\n  * Now, although the original analysis of removing the duplication\nwas correct given the amount of code that exited at the time, the\nweights have changed as new code was added to both codepaths.  But the\noriginal analysis is long since forgotten, the code is more complex,\nand we have to think about whether fixing it now is worth it if we're\ngoing to rewrite it all anyway to fix that fundamental design flaw[2].\n\n[1] https://public-inbox.org/git/xmqqd147kpdm.fsf@gitster.mtv.corp.google.com/\n[2] https://public-inbox.org/git/xmqqk1ydkbx0.fsf@gitster.mtv.corp.google.com/\n\n\n> Aside: I hope that I am not being annoying by poking around with the\n> test coverage reports. It does give me a way to target my review\n> efforts, especially into changes that touch areas outside my expertise\n> (like this one).\n\nNot annoying at all; I think it's a very valuable thing you're doing.\nAnd I think these tests make things better (there have definitely been\ncases in the past where a merge one way would work and a merge the\nother way would have funny bugs).  I was just unsure about what the\nbest way to present it amongst the other changes was.\n"},{"id":"362699","messageId":"20181108044031.25885-1-newren@gmail.com","threadId":"49559","inReplyTo":"CABPp-BG2rFEeKVe8ok+a-jLFvPBfnZs1b3Mp2Jfi2JgNZcO8gA@mail.gmail.com","subject":"[PATCH v5 00/10] Improve path collision conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:21Z","receivedAt":"2018-11-08T04:40:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This series applies cleanly on master, since en/merge-cleanup-more has\nnow merged down.\n\nThis series makes all the \"file collision\" conflict types be handled\nconsistently; making them all behave like add/add (as suggested by\nJonathan[1] and Junio[2]).  These types are:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n  * each rename/add piece of a rename/rename(1to2)/add[/add] conflict\n\n[1] https://public-inbox.org/git/20180312213521.GB58506@aiede.svl.corp.google.com/\n[2] https://public-inbox.org/git/CAPc5daVu8vv9RdGON8JiXEO3ycDVqQ38ySzZc-cpo+AQcAKXjA@mail.gmail.com\n\nChanges since v4:\n  * Merged the two RFC patches into a single patch.\n  * Added patch submitted by Stolee at the end.\n  * (The addition of what are the last two patches are also the only\n     difference since v3)\n\n\nDerrick Stolee (1):\n  merge-recursive: combine error handling\n\nElijah Newren (9):\n  Add testcases for consistency in file collision conflict handling\n  t6036, t6042: testcases for rename collision of already conflicting\n    files\n  merge-recursive: increase marker length with depth of recursion\n  merge-recursive: new function for better colliding conflict\n    resolutions\n  merge-recursive: fix rename/add conflict handling\n  merge-recursive: improve handling for rename/rename(2to1) conflicts\n  merge-recursive: use handle_file_collision for add/add conflicts\n  merge-recursive: improve rename/rename(1to2)/add[/add] handling\n  t6036, t6043: increase code coverage for file collision handling\n\n ll-merge.c                           |   4 +-\n ll-merge.h                           |   1 +\n merge-recursive.c                    | 529 ++++++++++++++++-----------\n t/t6036-recursive-corner-cases.sh    | 430 +++++++++++++++++++++-\n t/t6042-merge-rename-corner-cases.sh | 333 ++++++++++++++++-\n t/t6043-merge-rename-directories.sh  | 144 +++++---\n 6 files changed, 1149 insertions(+), 292 deletions(-)\n\n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362700","messageId":"20181108044031.25885-2-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 01/10] Add testcases for consistency in file collision conflict handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:22Z","receivedAt":"2018-11-08T04:40:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Add testcases dealing with file collisions for the following types of\nconflicts:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n\nAll these conflict types simplify down to two files \"colliding\"\nand should thus be handled similarly.  This means that rename/add and\nrename/rename(2to1) conflicts need to be modified to behave the same as\nadd/add conflicts currently do: the colliding files should be two-way\nmerged (instead of the current behavior of writing the two colliding\nfiles out to separate temporary unique pathnames).  Add testcases which\ncheck this; subsequent commits will fix the conflict handling to make\nthese tests pass.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6042-merge-rename-corner-cases.sh | 162 +++++++++++++++++++++++++++\n 1 file changed, 162 insertions(+)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b97aca7fa2..b6fed2cb9a 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -937,4 +937,166 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename\n \t)\n '\n \n+test_conflicts_with_adds_and_renames() {\n+\tsideL=$1\n+\tsideR=$2\n+\texpect=$3\n+\n+\t# Setup:\n+\t#          L\n+\t#         / \\\n+\t#   master   ?\n+\t#         \\ /\n+\t#          R\n+\t#\n+\t# Where:\n+\t#   Both L and R have files named 'three' which collide.  Each of\n+\t#   the colliding files could have been involved in a rename, in\n+\t#   which case there was a file named 'one' or 'two' that was\n+\t#   modified on the opposite side of history and renamed into the\n+\t#   collision on this side of history.\n+\t#\n+\t# Questions:\n+\t#   1) The index should contain both a stage 2 and stage 3 entry\n+\t#      for the colliding file.  Does it?\n+\t#   2) When renames are involved, the content merges are clean, so\n+\t#      the index should reflect the content merges, not merely the\n+\t#      version of the colliding file from the prior commit.  Does\n+\t#      it?\n+\t#   3) There should be a file in the worktree named 'three'\n+\t#      containing the two-way merged contents of the content-merged\n+\t#      versions of 'three' from each of the two colliding\n+\t#      files.  Is it present?\n+\t#   4) There should not be any three~* files in the working\n+\t#      tree\n+\ttest_expect_success \"setup simple $sideL/$sideR conflict\" '\n+\t\ttest_create_repo simple_${sideL}_${sideR} &&\n+\t\t(\n+\t\t\tcd simple_${sideL}_${sideR} &&\n+\n+\t\t\t# Create some related files now\n+\t\t\tfor i in $(test_seq 1 10)\n+\t\t\tdo\n+\t\t\t\techo Random base content line $i\n+\t\t\tdone >file_v1 &&\n+\t\t\tcp file_v1 file_v2 &&\n+\t\t\techo modification >>file_v2 &&\n+\n+\t\t\tcp file_v1 file_v3 &&\n+\t\t\techo more stuff >>file_v3 &&\n+\t\t\tcp file_v3 file_v4 &&\n+\t\t\techo yet more stuff >>file_v4 &&\n+\n+\t\t\t# Use a tag to record both these files for simple\n+\t\t\t# access, and clean out these untracked files\n+\t\t\tgit tag file_v1 $(git hash-object -w file_v1) &&\n+\t\t\tgit tag file_v2 $(git hash-object -w file_v2) &&\n+\t\t\tgit tag file_v3 $(git hash-object -w file_v3) &&\n+\t\t\tgit tag file_v4 $(git hash-object -w file_v4) &&\n+\t\t\tgit clean -f &&\n+\n+\t\t\t# Setup original commit (or merge-base), consisting of\n+\t\t\t# files named \"one\" and \"two\" if renames were involved.\n+\t\t\ttouch irrelevant_file &&\n+\t\t\tgit add irrelevant_file &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v1 >one &&\n+\t\t\t\tgit add one\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v3 >two &&\n+\t\t\t\tgit add two\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m initial &&\n+\n+\t\t\tgit branch L &&\n+\t\t\tgit branch R &&\n+\n+\t\t\t# Handle the left side\n+\t\t\tgit checkout L &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit mv one three\n+\t\t\telse\n+\t\t\t\tgit show file_v2 >three &&\n+\t\t\t\tgit add three\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v4 >two &&\n+\t\t\t\tgit add two\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m L &&\n+\n+\t\t\t# Handle the right side\n+\t\t\tgit checkout R &&\n+\t\t\tif [ $sideL = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit show file_v2 >one &&\n+\t\t\t\tgit add one\n+\t\t\tfi &&\n+\t\t\tif [ $sideR = \"rename\" ]\n+\t\t\tthen\n+\t\t\t\tgit mv two three\n+\t\t\telse\n+\t\t\t\tgit show file_v4 >three &&\n+\t\t\t\tgit add three\n+\t\t\tfi &&\n+\t\t\ttest_tick && git commit -m R\n+\t\t)\n+\t'\n+\n+\ttest_expect_$expect \"check simple $sideL/$sideR conflict\" '\n+\t\t(\n+\t\t\tcd simple_${sideL}_${sideR} &&\n+\n+\t\t\tgit checkout L^0 &&\n+\n+\t\t\t# Merge must fail; there is a conflict\n+\t\t\ttest_must_fail git merge -s recursive R^0 &&\n+\n+\t\t\t# Make sure the index has the right number of entries\n+\t\t\tgit ls-files -s >out &&\n+\t\t\ttest_line_count = 3 out &&\n+\t\t\tgit ls-files -u >out &&\n+\t\t\ttest_line_count = 2 out &&\n+\t\t\t# Ensure we have the correct number of untracked files\n+\t\t\tgit ls-files -o >out &&\n+\t\t\ttest_line_count = 1 out &&\n+\n+\t\t\t# Nothing should have touched irrelevant_file\n+\t\t\tgit rev-parse >actual      \\\n+\t\t\t\t:0:irrelevant_file \\\n+\t\t\t\t:2:three           \\\n+\t\t\t\t:3:three           &&\n+\t\t\tgit rev-parse >expected        \\\n+\t\t\t\tmaster:irrelevant_file \\\n+\t\t\t\tfile_v2                \\\n+\t\t\t\tfile_v4                &&\n+\t\t\ttest_cmp expected actual &&\n+\n+\t\t\t# Make sure we have the correct merged contents for\n+\t\t\t# three\n+\t\t\tgit show file_v1 >expected &&\n+\t\t\tcat <<-\\EOF >>expected &&\n+\t\t\t<<<<<<< HEAD\n+\t\t\tmodification\n+\t\t\t=======\n+\t\t\tmore stuff\n+\t\t\tyet more stuff\n+\t\t\t>>>>>>> R^0\n+\t\t\tEOF\n+\n+\t\t\ttest_cmp expected three\n+\t\t)\n+\t'\n+}\n+\n+test_conflicts_with_adds_and_renames rename rename failure\n+test_conflicts_with_adds_and_renames rename add    failure\n+test_conflicts_with_adds_and_renames add    rename failure\n+test_conflicts_with_adds_and_renames add    add    success\n+\n test_done\n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362701","messageId":"20181108044031.25885-6-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 05/10] merge-recursive: fix rename/add conflict handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:26Z","receivedAt":"2018-11-08T04:40:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This makes the rename/add conflict handling make use of the new\nhandle_file_collision() function, which fixes several bugs and improves\nthings for the rename/add case significantly.  Previously, rename/add\nwould:\n\n  * Not leave any higher order stage entries in the index, making it\n    appear as if there were no conflict.\n  * Would place the rename file at the colliding path, and move the\n    added file elsewhere, which combined with the lack of higher order\n    stage entries felt really odd.  It's not clear to me why the\n    rename should take precedence over the add; if one should be moved\n    out of the way, they both probably should.\n  * In the recursive case, it would do a two way merge of the added\n    file and the version of the renamed file on the renamed side,\n    completely excluding modifications to the renamed file on the\n    unrenamed side of history.\n\nUse the new handle_file_collision() to fix all of these issues.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 137 +++++++++++++++++----------\n t/t6036-recursive-corner-cases.sh    |  24 ++---\n t/t6042-merge-rename-corner-cases.sh |   4 +-\n 3 files changed, 101 insertions(+), 64 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 16ba425c2d..c0aa93e3df 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -186,6 +186,7 @@ static int oid_eq(const struct object_id *a, const struct object_id *b)\n enum rename_type {\n \tRENAME_NORMAL = 0,\n \tRENAME_VIA_DIR,\n+\tRENAME_ADD,\n \tRENAME_DELETE,\n \tRENAME_ONE_FILE_TO_ONE,\n \tRENAME_ONE_FILE_TO_TWO,\n@@ -228,6 +229,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\t\t\t\t      struct stage_data *src_entry1,\n \t\t\t\t\t      struct stage_data *src_entry2)\n {\n+\tint ostage1 = 0, ostage2;\n \tstruct rename_conflict_info *ci;\n \n \t/*\n@@ -264,18 +266,22 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\tdst_entry2->rename_conflict_info = ci;\n \t}\n \n-\tif (rename_type == RENAME_TWO_FILES_TO_ONE) {\n-\t\t/*\n-\t\t * For each rename, there could have been\n-\t\t * modifications on the side of history where that\n-\t\t * file was not renamed.\n-\t\t */\n-\t\tint ostage1 = o->branch1 == branch1 ? 3 : 2;\n-\t\tint ostage2 = ostage1 ^ 1;\n+\t/*\n+\t * For each rename, there could have been\n+\t * modifications on the side of history where that\n+\t * file was not renamed.\n+\t */\n+\tif (rename_type == RENAME_ADD ||\n+\t    rename_type == RENAME_TWO_FILES_TO_ONE) {\n+\t\tostage1 = o->branch1 == branch1 ? 3 : 2;\n \n \t\tci->ren1_other.path = pair1->one->path;\n \t\toidcpy(&ci->ren1_other.oid, &src_entry1->stages[ostage1].oid);\n \t\tci->ren1_other.mode = src_entry1->stages[ostage1].mode;\n+\t}\n+\n+\tif (rename_type == RENAME_TWO_FILES_TO_ONE) {\n+\t\tostage2 = ostage1 ^ 1;\n \n \t\tci->ren2_other.path = pair2->one->path;\n \t\toidcpy(&ci->ren2_other.oid, &src_entry2->stages[ostage2].oid);\n@@ -1560,7 +1566,6 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,\n \treturn target;\n }\n \n-#if 0 // #if-0-ing avoids unused function warning; will make live in next commit\n static int handle_file_collision(struct merge_options *o,\n \t\t\t\t const char *collide_path,\n \t\t\t\t const char *prev_path1,\n@@ -1576,6 +1581,20 @@ static int handle_file_collision(struct merge_options *o,\n \tchar *alt_path = NULL;\n \tconst char *update_path = collide_path;\n \n+\t/*\n+\t * It's easiest to get the correct things into stage 2 and 3, and\n+\t * to make sure that the content merge puts HEAD before the other\n+\t * branch if we just ensure that branch1 == o->branch1.  So, simply\n+\t * flip arguments around if we don't have that.\n+\t */\n+\tif (branch1 != o->branch1) {\n+\t\treturn handle_file_collision(o, collide_path,\n+\t\t\t\t\t     prev_path2, prev_path1,\n+\t\t\t\t\t     branch2, branch1,\n+\t\t\t\t\t     b_oid, b_mode,\n+\t\t\t\t\t     a_oid, a_mode);\n+\t}\n+\n \t/*\n \t * In the recursive case, we just opt to undo renames\n \t */\n@@ -1679,7 +1698,38 @@ static int handle_file_collision(struct merge_options *o,\n \t */\n \treturn mfi.clean;\n }\n-#endif\n+\n+static int handle_rename_add(struct merge_options *o,\n+\t\t\t     struct rename_conflict_info *ci)\n+{\n+\t/* a was renamed to c, and a separate c was added. */\n+\tstruct diff_filespec *a = ci->pair1->one;\n+\tstruct diff_filespec *c = ci->pair1->two;\n+\tchar *path = c->path;\n+\tchar *prev_path_desc;\n+\tstruct merge_file_info mfi;\n+\n+\tint other_stage = (ci->branch1 == o->branch1 ? 3 : 2);\n+\n+\toutput(o, 1, _(\"CONFLICT (rename/add): \"\n+\t       \"Rename %s->%s in %s.  Added %s in %s\"),\n+\t       a->path, c->path, ci->branch1,\n+\t       c->path, ci->branch2);\n+\n+\tprev_path_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n+\tif (merge_mode_and_contents(o, a, c, &ci->ren1_other, prev_path_desc,\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    1 + o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tfree(prev_path_desc);\n+\n+\treturn handle_file_collision(o,\n+\t\t\t\t     c->path, a->path, NULL,\n+\t\t\t\t     ci->branch1, ci->branch2,\n+\t\t\t\t     &mfi.oid, mfi.mode,\n+\t\t\t\t     &ci->dst_entry1->stages[other_stage].oid,\n+\t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n+}\n \n static int handle_file(struct merge_options *o,\n \t\t\tstruct diff_filespec *rename,\n@@ -2861,47 +2911,23 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t      0  /* update_wd    */))\n \t\t\t\t\tclean_merge = -1;\n \t\t\t} else if (!oid_eq(&dst_other.oid, &null_oid)) {\n-\t\t\t\tclean_merge = 0;\n-\t\t\t\ttry_merge = 1;\n-\t\t\t\toutput(o, 1, _(\"CONFLICT (rename/add): Rename %s->%s in %s. \"\n-\t\t\t\t       \"%s added in %s\"),\n-\t\t\t\t       ren1_src, ren1_dst, branch1,\n-\t\t\t\t       ren1_dst, branch2);\n-\t\t\t\tif (o->call_depth) {\n-\t\t\t\t\tstruct merge_file_info mfi;\n-\t\t\t\t\tstruct diff_filespec one, a, b;\n-\n-\t\t\t\t\toidcpy(&one.oid, &null_oid);\n-\t\t\t\t\tone.mode = 0;\n-\t\t\t\t\tone.path = ren1->pair->two->path;\n-\n-\t\t\t\t\toidcpy(&a.oid, &ren1->pair->two->oid);\n-\t\t\t\t\ta.mode = ren1->pair->two->mode;\n-\t\t\t\t\ta.path = one.path;\n-\n-\t\t\t\t\toidcpy(&b.oid, &dst_other.oid);\n-\t\t\t\t\tb.mode = dst_other.mode;\n-\t\t\t\t\tb.path = one.path;\n-\n-\t\t\t\t\tif (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,\n-\t\t\t\t\t\t\t\t    branch1, branch2,\n-\t\t\t\t\t\t\t\t    o->call_depth * 2, &mfi)) {\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\t\tgoto cleanup_and_return;\n-\t\t\t\t\t}\n-\t\t\t\t\toutput(o, 1, _(\"Adding merged %s\"), ren1_dst);\n-\t\t\t\t\tif (update_file(o, 0, &mfi.oid,\n-\t\t\t\t\t\t\tmfi.mode, ren1_dst))\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\ttry_merge = 0;\n-\t\t\t\t} else {\n-\t\t\t\t\tchar *new_path = unique_path(o, ren1_dst, branch2);\n-\t\t\t\t\toutput(o, 1, _(\"Adding as %s instead\"), new_path);\n-\t\t\t\t\tif (update_file(o, 0, &dst_other.oid,\n-\t\t\t\t\t\t\tdst_other.mode, new_path))\n-\t\t\t\t\t\tclean_merge = -1;\n-\t\t\t\t\tfree(new_path);\n-\t\t\t\t}\n+\t\t\t\t/*\n+\t\t\t\t * Probably not a clean merge, but it's\n+\t\t\t\t * premature to set clean_merge to 0 here,\n+\t\t\t\t * because if the rename merges cleanly and\n+\t\t\t\t * the merge exactly matches the newly added\n+\t\t\t\t * file, then the merge will be clean.\n+\t\t\t\t */\n+\t\t\t\tsetup_rename_conflict_info(RENAME_ADD,\n+\t\t\t\t\t\t\t   ren1->pair,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   branch1,\n+\t\t\t\t\t\t\t   branch2,\n+\t\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   o,\n+\t\t\t\t\t\t\t   ren1->src_entry,\n+\t\t\t\t\t\t\t   NULL);\n \t\t\t} else\n \t\t\t\ttry_merge = 1;\n \n@@ -3313,6 +3339,15 @@ static int process_entry(struct merge_options *o,\n \t\t\t\t\t\t  conflict_info->branch2))\n \t\t\t\tclean_merge = -1;\n \t\t\tbreak;\n+\t\tcase RENAME_ADD:\n+\t\t\t/*\n+\t\t\t * Probably unclean merge, but if the renamed file\n+\t\t\t * merges cleanly and the result can then be\n+\t\t\t * two-way merged cleanly with the added file, I\n+\t\t\t * guess it's a clean merge?\n+\t\t\t */\n+\t\t\tclean_merge = handle_rename_add(o, conflict_info);\n+\t\t\tbreak;\n \t\tcase RENAME_DELETE:\n \t\t\tclean_merge = 0;\n \t\t\tif (handle_rename_delete(o,\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 3091cbc06a..d6b7e568f5 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -185,7 +185,7 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '\n \t\tgit branch B &&\n \t\tgit checkout -b C &&\n \t\techo 10 >>a &&\n-\t\techo \"other content\" >>new_a &&\n+\t\ttest_write_lines 0 1 2 3 4 5 6 7 foobar >new_a &&\n \t\tgit add a new_a &&\n \t\ttest_tick && git commit -m C &&\n \n@@ -195,14 +195,14 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '\n \n \t\tgit checkout B^0 &&\n \t\ttest_must_fail git merge C &&\n-\t\tgit clean -f &&\n+\t\tgit show :2:new_a >new_a &&\n+\t\tgit add new_a &&\n \t\ttest_tick && git commit -m D &&\n \t\tgit tag D &&\n \n \t\tgit checkout C^0 &&\n \t\ttest_must_fail git merge B &&\n-\t\trm new_a~HEAD new_a &&\n-\t\tprintf \"Incorrectly merged content\" >>new_a &&\n+\t\ttest_write_lines 0 1 2 3 4 5 6 7 bad_merge >new_a &&\n \t\tgit add -u &&\n \t\ttest_tick && git commit -m E &&\n \t\tgit tag E\n@@ -225,21 +225,23 @@ test_expect_success 'git detects differently handled merges conflict' '\n \t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tD:new_a  E:new_a &&\n+\t\t\tC:new_a  D:new_a  E:new_a &&\n \t\tgit rev-parse   >actual     \\\n-\t\t\t:2:new_a :3:new_a &&\n+\t\t\t:1:new_a :2:new_a :3:new_a &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit cat-file -p C:new_a >ours &&\n-\t\tgit cat-file -p B:new_a >theirs &&\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p D:new_a >ours &&\n+\t\tgit cat-file -p E:new_a >theirs &&\n \t\t>empty &&\n \t\ttest_must_fail git merge-file \\\n-\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"HEAD\" \\\n \t\t\t-L \"\" \\\n-\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\t-L \"E^0\" \\\n \t\t\tours empty theirs &&\n \t\tsed -e \"s/^\\([<=>]\\)/\\1\\1\\1/\" ours >expect &&\n-\t\tgit cat-file -p :1:new_a >actual &&\n+\t\tgit hash-object new_a >actual &&\n+\t\tgit hash-object ours  >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 23c3b6dffa..3e934ae89e 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -1095,8 +1095,8 @@ test_conflicts_with_adds_and_renames() {\n }\n \n test_conflicts_with_adds_and_renames rename rename failure\n-test_conflicts_with_adds_and_renames rename add    failure\n-test_conflicts_with_adds_and_renames add    rename failure\n+test_conflicts_with_adds_and_renames rename add    success\n+test_conflicts_with_adds_and_renames add    rename success\n test_conflicts_with_adds_and_renames add    add    success\n \n # Setup:\n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362702","messageId":"20181108044031.25885-5-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 04/10] merge-recursive: new function for better colliding conflict resolutions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:25Z","receivedAt":"2018-11-08T04:40:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There are three conflict types that represent two (possibly entirely\nunrelated) files colliding at the same location:\n  * add/add\n  * rename/add\n  * rename/rename(2to1)\n\nThese three conflict types already share more similarity than might be\nimmediately apparent from their description: (1) the handling of the\nrename variants already involves removing any entries from the index\ncorresponding to the original file names[*], thus only leaving entries\nin the index for the colliding path; (2) likewise, any trace of the\noriginal file name in the working tree is also removed.  So, in all\nthree cases we're left with how to represent two colliding files in both\nthe index and the working copy.\n\n[*] Technically, this isn't quite true because rename/rename(2to1)\nconflicts in the recursive (o->call_depth > 0) case do an \"unrename\"\nsince about seven years ago.  But even in that case, Junio felt\ncompelled to explain that my decision to \"unrename\" wasn't necessarily\nthe only or right answer -- search for \"Comment from Junio\" in t6036 for\ndetails.\n\nMy initial motivation for looking at these three conflict types was that\nif the handling of these three conflict types is the same, at least in\nthe limited set of cases where a renamed file is unmodified on the side\nof history where the file is not renamed, then a significant performance\nimprovement for rename detection during merges is possible.  However,\nwhile that served as motivation to look at these three types of\nconflicts, the actual goal of this new function is to try to improve the\nhandling for all three cases, not to merely make them the same as each\nother in that special circumstance.\n\n=== Handling the working tree ===\n\nThe previous behavior for these conflict types in regards to the\nworking tree (assuming the file collision occurs at 'foo') was:\n  * add/add does a two-way merge of the two files and records it as 'foo'.\n  * rename/rename(2to1) records the two different files into two new\n    uniquely named files (foo~HEAD and foo~$MERGE), while removing 'foo'\n    from the working tree.\n  * rename/add records the two different files into two different\n    locations, recording the add at foo~$SIDE and, oddly, recording\n    the rename at foo (why is the rename more important than the add?)\n\nSo, the question for what to write to the working tree boils down to\nwhether the two colliding files should be two-way merged and recorded in\nplace, or recorded into separate files.  As per discussion on the git\nmailing lit, two-way merging was deemed to always be preferred, as that\nmakes these cases all more like content conflicts that users can handle\nfrom within their favorite editor, IDE, or merge tool.  Note that since\nrenames already involve a content merge, rename/add and\nrename/rename(2to1) conflicts could result in nested conflict markers.\n\n=== Handling of the index ===\n\nFor a typical rename, unpack_trees() would set up the index in the\nfollowing fashion:\n           old_path  new_path\n   stage1: 5ca1ab1e  00000000\n   stage2: f005ba11  00000000\n   stage3: 00000000  b0a710ad\nAnd merge-recursive would rewrite this to\n           new_path\n   stage1: 5ca1ab1e\n   stage2: f005ba11\n   stage3: b0a710ad\nRemoving old_path from the index means the user won't have to `git rm\nold_path` manually every time a renamed path has a content conflict.\nIt also means they can use `git checkout [--ours|--theirs|--conflict|-m]\nnew_path`, `git diff [--ours|--theirs]` and various other commands that\nwould be difficult otherwise.\n\nThis strategy becomes a problem when we have a rename/add or\nrename/rename(2to1) conflict, however, because then we have only three\nslots to store blob sha1s and we need either four or six.  Previously,\nthis was handled by continuing to delete old_path from the index, and\njust outright ignoring any blob shas from old_path.  That had the\ndownside of deleting any trace of changes made to old_path on the other\nside of history.  This function instead does a three-way content merge of\nthe renamed file, and stores the blob sha1 for that at either stage2 or\nstage3 for new_path (depending on which side the rename came from).  That\nhas the advantage of bringing information about changes on both sides and\nstill allows for easy resolution (no need to git rm old_path, etc.), but\ndoes have the downside that if the content merge had conflict markers,\nthen what we store in the index is the sha1 of a blob with conflict\nmarkers.  While that is a downside, it seems less problematic than the\ndownsides of any obvious alternatives, and certainly makes more sense\nthan the previous handling.  Further, it has a precedent in that when we\ndo recursive merges, we may accept a file with conflict markers as the\nresolution for the merge of the merge-bases, which will then show up in\nthe index of the outer merge at stage 1 if a conflict exists at the outer\nlevel.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 121 insertions(+)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f35e3b5f95..16ba425c2d 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1560,6 +1560,127 @@ static struct diff_filespec *filespec_from_entry(struct diff_filespec *target,\n \treturn target;\n }\n \n+#if 0 // #if-0-ing avoids unused function warning; will make live in next commit\n+static int handle_file_collision(struct merge_options *o,\n+\t\t\t\t const char *collide_path,\n+\t\t\t\t const char *prev_path1,\n+\t\t\t\t const char *prev_path2,\n+\t\t\t\t const char *branch1, const char *branch2,\n+\t\t\t\t const struct object_id *a_oid,\n+\t\t\t\t unsigned int a_mode,\n+\t\t\t\t const struct object_id *b_oid,\n+\t\t\t\t unsigned int b_mode)\n+{\n+\tstruct merge_file_info mfi;\n+\tstruct diff_filespec null, a, b;\n+\tchar *alt_path = NULL;\n+\tconst char *update_path = collide_path;\n+\n+\t/*\n+\t * In the recursive case, we just opt to undo renames\n+\t */\n+\tif (o->call_depth && (prev_path1 || prev_path2)) {\n+\t\t/* Put first file (a_oid, a_mode) in its original spot */\n+\t\tif (prev_path1) {\n+\t\t\tif (update_file(o, 1, a_oid, a_mode, prev_path1))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tif (update_file(o, 1, a_oid, a_mode, collide_path))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Put second file (b_oid, b_mode) in its original spot */\n+\t\tif (prev_path2) {\n+\t\t\tif (update_file(o, 1, b_oid, b_mode, prev_path2))\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tif (update_file(o, 1, b_oid, b_mode, collide_path))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\t/* Don't leave something at collision path if unrenaming both */\n+\t\tif (prev_path1 && prev_path2)\n+\t\t\tremove_file(o, 1, collide_path, 0);\n+\n+\t\treturn 0;\n+\t}\n+\n+\t/* Remove rename sources if rename/add or rename/rename(2to1) */\n+\tif (prev_path1)\n+\t\tremove_file(o, 1, prev_path1,\n+\t\t\t    o->call_depth || would_lose_untracked(prev_path1));\n+\tif (prev_path2)\n+\t\tremove_file(o, 1, prev_path2,\n+\t\t\t    o->call_depth || would_lose_untracked(prev_path2));\n+\n+\t/*\n+\t * Remove the collision path, if it wouldn't cause dirty contents\n+\t * or an untracked file to get lost.  We'll either overwrite with\n+\t * merged contents, or just write out to differently named files.\n+\t */\n+\tif (was_dirty(o, collide_path)) {\n+\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n+\t\t       collide_path);\n+\t\tupdate_path = alt_path = unique_path(o, collide_path, \"merged\");\n+\t} else if (would_lose_untracked(collide_path)) {\n+\t\t/*\n+\t\t * Only way we get here is if both renames were from\n+\t\t * a directory rename AND user had an untracked file\n+\t\t * at the location where both files end up after the\n+\t\t * two directory renames.  See testcase 10d of t6043.\n+\t\t */\n+\t\toutput(o, 1, _(\"Refusing to lose untracked file at \"\n+\t\t\t       \"%s, even though it's in the way.\"),\n+\t\t       collide_path);\n+\t\tupdate_path = alt_path = unique_path(o, collide_path, \"merged\");\n+\t} else {\n+\t\t/*\n+\t\t * FIXME: It's possible that the two files are identical\n+\t\t * and that the current working copy happens to match, in\n+\t\t * which case we are unnecessarily touching the working\n+\t\t * tree file.  It's not a likely enough scenario that I\n+\t\t * want to code up the checks for it and a better fix is\n+\t\t * available if we restructure how unpack_trees() and\n+\t\t * merge-recursive interoperate anyway, so punting for\n+\t\t * now...\n+\t\t */\n+\t\tremove_file(o, 0, collide_path, 0);\n+\t}\n+\n+\t/* Store things in diff_filespecs for functions that need it */\n+\tmemset(&a, 0, sizeof(struct diff_filespec));\n+\tmemset(&b, 0, sizeof(struct diff_filespec));\n+\tnull.path = a.path = b.path = (char *)collide_path;\n+\toidcpy(&null.oid, &null_oid);\n+\tnull.mode = 0;\n+\toidcpy(&a.oid, a_oid);\n+\ta.mode = a_mode;\n+\ta.oid_valid = 1;\n+\toidcpy(&b.oid, b_oid);\n+\tb.mode = b_mode;\n+\tb.oid_valid = 1;\n+\n+\tif (merge_mode_and_contents(o, &null, &a, &b, collide_path,\n+\t\t\t\t    branch1, branch2, o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tmfi.clean &= !alt_path;\n+\tif (update_file(o, mfi.clean, &mfi.oid, mfi.mode, update_path))\n+\t\treturn -1;\n+\tif (!mfi.clean && !o->call_depth &&\n+\t    update_stages(o, collide_path, NULL, &a, &b))\n+\t\treturn -1;\n+\tfree(alt_path);\n+\t/*\n+\t * FIXME: If both a & b both started with conflicts (only possible\n+\t * if they came from a rename/rename(2to1)), but had IDENTICAL\n+\t * contents including those conflicts, then in the next line we claim\n+\t * it was clean.  If someone cares about this case, we should have the\n+\t * caller notify us if we started with conflicts.\n+\t */\n+\treturn mfi.clean;\n+}\n+#endif\n+\n static int handle_file(struct merge_options *o,\n \t\t\tstruct diff_filespec *rename,\n \t\t\tint stage,\n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362703","messageId":"20181108044031.25885-8-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 07/10] merge-recursive: use handle_file_collision for add/add conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:28Z","receivedAt":"2018-11-08T04:40:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This results in no-net change of behavior, it simply ensures that all\nfile-collision conflict handling types are being handled the same by\ncalling the same function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 29 +++++++++++++++++++++--------\n 1 file changed, 21 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 62eab9e4cc..88e9e1166a 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3356,14 +3356,27 @@ static int process_entry(struct merge_options *o,\n \t\t\t\tclean_merge = -1;\n \t\t}\n \t} else if (a_oid && b_oid) {\n-\t\t/* Case C: Added in both (check for same permissions) and */\n-\t\t/* case D: Modified in both, but differently. */\n-\t\tint is_dirty = 0; /* unpack_trees would have bailed if dirty */\n-\t\tclean_merge = handle_content_merge(o, path, is_dirty,\n-\t\t\t\t\t\t   o_oid, o_mode,\n-\t\t\t\t\t\t   a_oid, a_mode,\n-\t\t\t\t\t\t   b_oid, b_mode,\n-\t\t\t\t\t\t   NULL);\n+\t\tif (!o_oid) {\n+\t\t\t/* Case C: Added in both (check for same permissions) */\n+\t\t\toutput(o, 1,\n+\t\t\t       _(\"CONFLICT (add/add): Merge conflict in %s\"),\n+\t\t\t       path);\n+\t\t\tclean_merge = handle_file_collision(o,\n+\t\t\t\t\t\t\t    path, NULL, NULL,\n+\t\t\t\t\t\t\t    o->branch1,\n+\t\t\t\t\t\t\t    o->branch2,\n+\t\t\t\t\t\t\t    a_oid, a_mode,\n+\t\t\t\t\t\t\t    b_oid, b_mode);\n+\t\t} else {\n+\t\t\t/* case D: Modified in both, but differently. */\n+\t\t\tint is_dirty = 0; /* unpack_trees would have bailed if dirty */\n+\t\t\tclean_merge = handle_content_merge(o, path,\n+\t\t\t\t\t\t\t   is_dirty,\n+\t\t\t\t\t\t\t   o_oid, o_mode,\n+\t\t\t\t\t\t\t   a_oid, a_mode,\n+\t\t\t\t\t\t\t   b_oid, b_mode,\n+\t\t\t\t\t\t\t   NULL);\n+\t\t}\n \t} else if (!o_oid && !a_oid && !b_oid) {\n \t\t/*\n \t\t * this entry was deleted altogether. a_mode == 0 means\n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362704","messageId":"20181108044031.25885-10-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 09/10] t6036, t6043: increase code coverage for file collision handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:30Z","receivedAt":"2018-11-08T04:40:46Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Stolee's coverage reports found a few code blocks for file collision\nconflicts that had not previously been covered by testcases; add a few\nmore testcases to cover those too.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh   | 51 +++++++++++++++++++++++++++++\n t/t6043-merge-rename-directories.sh | 37 +++++++++++++++++++++\n 2 files changed, 88 insertions(+)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 99cddb05af..b7488b00c0 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -240,6 +240,57 @@ test_expect_success 'git detects differently handled merges conflict' '\n \t)\n '\n \n+# Repeat the above testcase with precisely the same setup, other than with\n+# the two merge bases having different orderings of commit timestamps so\n+# that they are reversed in the order they are provided to merge-recursive,\n+# so that we can improve code coverage.\n+test_expect_success 'git detects differently handled merges conflict, swapped' '\n+\t(\n+\t\tcd rename-add &&\n+\n+\t\t# Difference #1: Do cleanup from previous testrun\n+\t\tgit reset --hard &&\n+\t\tgit clean -fdqx &&\n+\n+\t\t# Difference #2: Change commit timestamps\n+\t\tbtime=$(git log --no-walk --date=raw --format=%cd B | awk \"{print \\$1}\") &&\n+\t\tctime=$(git log --no-walk --date=raw --format=%cd C | awk \"{print \\$1}\") &&\n+\t\tnewctime=$(($btime+1)) &&\n+\t\tgit fast-export --no-data --all | sed -e s/$ctime/$newctime/ | git fast-import --force --quiet &&\n+\t\t# End of differences; rest is copy-paste of last test\n+\n+\t\tgit checkout D^0 &&\n+\t\ttest_must_fail git merge -s recursive E^0 &&\n+\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\tgit rev-parse >expect       \\\n+\t\t\tC:new_a  D:new_a  E:new_a &&\n+\t\tgit rev-parse   >actual     \\\n+\t\t\t:1:new_a :2:new_a :3:new_a &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p D:new_a >ours &&\n+\t\tgit cat-file -p E:new_a >theirs &&\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 \"E^0\" \\\n+\t\t\tours empty theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1\\1/\" ours >expect &&\n+\t\tgit hash-object new_a >actual &&\n+\t\tgit hash-object ours  >expect &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n #\n # criss-cross + modify/delete:\n #\ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex 5c01a0c14a..62c564707b 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -3163,6 +3163,43 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)\n \t)\n '\n \n+test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2), other direction' '\n+\t(\n+\t\tcd 10c &&\n+\n+\t\tgit reset --hard &&\n+\t\tgit clean -fdqx &&\n+\n+\t\tgit checkout B^0 &&\n+\t\tmkdir y &&\n+\t\techo important >y/c &&\n+\n+\t\ttest_must_fail git merge -s recursive A^0 >out 2>err &&\n+\t\ttest_i18ngrep \"CONFLICT (rename/rename)\" out &&\n+\t\ttest_i18ngrep \"Refusing to lose untracked file at y/c; adding as y/c~HEAD instead\" out &&\n+\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 6 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 3 out &&\n+\n+\t\tgit rev-parse >actual \\\n+\t\t\t:0:y/a :0:y/b :0:x/d :1:x/c :3:w/c :2:y/c &&\n+\t\tgit rev-parse >expect \\\n+\t\t\t O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  O:x/c &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\tgit hash-object y/c~HEAD >actual &&\n+\t\tgit rev-parse O:x/c >expect &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\techo important >expect &&\n+\t\ttest_cmp expect y/c\n+\t)\n+'\n+\n # Testcase 10d, Delete untracked w/ dir rename/rename(2to1)\n #   Commit O: z/{a,b,c_1},        x/{d,e,f_2}\n #   Commit A: y/{a,b},            x/{d,e,f_2,wham_1} + untracked y/wham\n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362705","messageId":"20181108044031.25885-11-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 10/10] merge-recursive: combine error handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:31Z","receivedAt":"2018-11-08T04:41:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Derrick Stolee <stolee@gmail.com>\n\nIn handle_rename_rename_1to2(), we have duplicated error handling\naround colliding paths. Specifically, when we want to write out\nthe file and there is a directory or untracked file in the way,\nwe need to create a temporary file to hold the contents. This has\nsome special output to alert the user, and this output is\nduplicated for each side of the conflict.\n\nSimplify the call by generating this new path in a helper\nfunction.\n\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c | 53 ++++++++++++++++++++++++-----------------------\n 1 file changed, 27 insertions(+), 26 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 3e2a63d094..ecf8db0b71 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1710,6 +1710,27 @@ static int handle_rename_add(struct merge_options *o,\n \t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n }\n \n+static char *find_path_for_conflict(struct merge_options *o,\n+\t\t\t\t    const char *path,\n+\t\t\t\t    const char *branch1,\n+\t\t\t\t    const char *branch2)\n+{\n+\tchar *new_path = NULL;\n+\tif (dir_in_way(path, !o->call_depth, 0)) {\n+\t\tnew_path = unique_path(o, path, branch1);\n+\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t       \"as %s instead\"),\n+\t\t       path, branch2, new_path);\n+\t} else if (would_lose_untracked(path)) {\n+\t\tnew_path = unique_path(o, path, branch1);\n+\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t       path, new_path);\n+\t}\n+\n+\treturn new_path;\n+}\n+\n static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t     struct rename_conflict_info *ci)\n {\n@@ -1784,19 +1805,9 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t\t\t  &add->oid, add->mode) < 0)\n \t\t\t\treturn -1;\n \t\t} else {\n-\t\t\tchar *new_path = NULL;\n-\t\t\tif (dir_in_way(a->path, !o->call_depth, 0)) {\n-\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n-\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n-\t\t\t\t\t       \"as %s instead\"),\n-\t\t\t\t       a->path, ci->branch2, new_path);\n-\t\t\t} else if (would_lose_untracked(a->path)) {\n-\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n-\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n-\t\t\t\t\t       \" at %s; adding as %s instead\"),\n-\t\t\t\t       a->path, new_path);\n-\t\t\t}\n-\n+\t\t\tchar *new_path = find_path_for_conflict(o, a->path,\n+\t\t\t\t\t\t\t\tci->branch1,\n+\t\t\t\t\t\t\t\tci->branch2);\n \t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))\n \t\t\t\treturn -1;\n \t\t\tfree(new_path);\n@@ -1813,19 +1824,9 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t\t\t  &mfi.oid, mfi.mode) < 0)\n \t\t\t\treturn -1;\n \t\t} else {\n-\t\t\tchar *new_path = NULL;\n-\t\t\tif (dir_in_way(b->path, !o->call_depth, 0)) {\n-\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n-\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n-\t\t\t\t\t       \"as %s instead\"),\n-\t\t\t\t       b->path, ci->branch1, new_path);\n-\t\t\t} else if (would_lose_untracked(b->path)) {\n-\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n-\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n-\t\t\t\t\t       \" at %s; adding as %s instead\"),\n-\t\t\t\t       b->path, new_path);\n-\t\t\t}\n-\n+\t\t\tchar *new_path = find_path_for_conflict(o, b->path,\n+\t\t\t\t\t\t\t\tci->branch2,\n+\t\t\t\t\t\t\t\tci->branch1);\n \t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))\n \t\t\t\treturn -1;\n \t\t\tfree(new_path);\n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362706","messageId":"20181108044031.25885-7-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 06/10] merge-recursive: improve handling for rename/rename(2to1) conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:27Z","receivedAt":"2018-11-08T04:41:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This makes the rename/rename(2to1) conflicts use the new\nhandle_file_collision() function.  Since that function was based\noriginally on the rename/rename(2to1) handling code, the main\ndifferences here are in what was added.  In particular:\n\n  * Instead of storing files at collide_path~HEAD and collide_path~MERGE,\n    the files are two-way merged and recorded at collide_path.\n\n  * Instead of recording the version of the renamed file that existed\n    on the renamed side in the index (thus ignoring any changes that\n    were made to the file on the side of history without the rename),\n    we do a three-way content merge on the renamed path, then store\n    that at either stage 2 or stage 3.\n\n  * Note that since the content merge for each rename may have conflicts,\n    and then we have to merge the two renamed files, we can end up with\n    nested conflict markers.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 104 ++++-----------------------\n t/t6036-recursive-corner-cases.sh    |  12 +---\n t/t6042-merge-rename-corner-cases.sh |  38 ++++++----\n t/t6043-merge-rename-directories.sh  |  83 ++++++++++++---------\n 4 files changed, 89 insertions(+), 148 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c0aa93e3df..62eab9e4cc 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -696,27 +696,6 @@ static int update_stages(struct merge_options *opt, const char *path,\n \treturn 0;\n }\n \n-static int update_stages_for_stage_data(struct merge_options *opt,\n-\t\t\t\t\tconst char *path,\n-\t\t\t\t\tconst struct stage_data *stage_data)\n-{\n-\tstruct diff_filespec o, a, b;\n-\n-\to.mode = stage_data->stages[1].mode;\n-\toidcpy(&o.oid, &stage_data->stages[1].oid);\n-\n-\ta.mode = stage_data->stages[2].mode;\n-\toidcpy(&a.oid, &stage_data->stages[2].oid);\n-\n-\tb.mode = stage_data->stages[3].mode;\n-\toidcpy(&b.oid, &stage_data->stages[3].oid);\n-\n-\treturn update_stages(opt, path,\n-\t\t\t     is_null_oid(&o.oid) ? NULL : &o,\n-\t\t\t     is_null_oid(&a.oid) ? NULL : &a,\n-\t\t\t     is_null_oid(&b.oid) ? NULL : &b);\n-}\n-\n static void update_entry(struct stage_data *entry,\n \t\t\t struct diff_filespec *o,\n \t\t\t struct diff_filespec *a,\n@@ -1871,7 +1850,6 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \tchar *path_side_2_desc;\n \tstruct merge_file_info mfi_c1;\n \tstruct merge_file_info mfi_c2;\n-\tint ret;\n \n \toutput(o, 1, _(\"CONFLICT (rename/rename): \"\n \t       \"Rename %s->%s in %s. \"\n@@ -1879,81 +1857,22 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \t       a->path, c1->path, ci->branch1,\n \t       b->path, c2->path, ci->branch2);\n \n-\tremove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));\n-\tremove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));\n-\n \tpath_side_1_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n \tpath_side_2_desc = xstrfmt(\"version of %s from %s\", path, b->path);\n \tif (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,\n \t\t\t\t    o->branch1, o->branch2,\n-\t\t\t\t    o->call_depth * 2, &mfi_c1) ||\n+\t\t\t\t    1 + o->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,\n \t\t\t\t    o->branch1, o->branch2,\n-\t\t\t\t    o->call_depth * 2, &mfi_c2))\n+\t\t\t\t    1 + o->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n \n-\tif (o->call_depth) {\n-\t\t/*\n-\t\t * If mfi_c1.clean && mfi_c2.clean, then it might make\n-\t\t * sense to do a two-way merge of those results.  But, I\n-\t\t * think in all cases, it makes sense to have the virtual\n-\t\t * merge base just undo the renames; they can be detected\n-\t\t * again later for the non-recursive merge.\n-\t\t */\n-\t\tremove_file(o, 0, path, 0);\n-\t\tret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);\n-\t\tif (!ret)\n-\t\t\tret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,\n-\t\t\t\t\t  b->path);\n-\t} else {\n-\t\tchar *new_path1 = unique_path(o, path, ci->branch1);\n-\t\tchar *new_path2 = unique_path(o, path, ci->branch2);\n-\t\toutput(o, 1, _(\"Renaming %s to %s and %s to %s instead\"),\n-\t\t       a->path, new_path1, b->path, new_path2);\n-\t\tif (was_dirty(o, path))\n-\t\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n-\t\t\t       path);\n-\t\telse if (would_lose_untracked(path))\n-\t\t\t/*\n-\t\t\t * Only way we get here is if both renames were from\n-\t\t\t * a directory rename AND user had an untracked file\n-\t\t\t * at the location where both files end up after the\n-\t\t\t * two directory renames.  See testcase 10d of t6043.\n-\t\t\t */\n-\t\t\toutput(o, 1, _(\"Refusing to lose untracked file at \"\n-\t\t\t\t       \"%s, even though it's in the way.\"),\n-\t\t\t       path);\n-\t\telse\n-\t\t\tremove_file(o, 0, path, 0);\n-\t\tret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);\n-\t\tif (!ret)\n-\t\t\tret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,\n-\t\t\t\t\t  new_path2);\n-\t\t/*\n-\t\t * unpack_trees() actually populates the index for us for\n-\t\t * \"normal\" rename/rename(2to1) situtations so that the\n-\t\t * correct entries are at the higher stages, which would\n-\t\t * make the call below to update_stages_for_stage_data\n-\t\t * unnecessary.  However, if either of the renames came\n-\t\t * from a directory rename, then unpack_trees() will not\n-\t\t * have gotten the right data loaded into the index, so we\n-\t\t * need to do so now.  (While it'd be tempting to move this\n-\t\t * call to update_stages_for_stage_data() to\n-\t\t * apply_directory_rename_modifications(), that would break\n-\t\t * our intermediate calls to would_lose_untracked() since\n-\t\t * those rely on the current in-memory index.  See also the\n-\t\t * big \"NOTE\" in update_stages()).\n-\t\t */\n-\t\tif (update_stages_for_stage_data(o, path, ci->dst_entry1))\n-\t\t\tret = -1;\n-\n-\t\tfree(new_path2);\n-\t\tfree(new_path1);\n-\t}\n-\n-\treturn ret;\n+\treturn handle_file_collision(o, path, a->path, b->path,\n+\t\t\t\t     ci->branch1, ci->branch2,\n+\t\t\t\t     &mfi_c1.oid, mfi_c1.mode,\n+\t\t\t\t     &mfi_c2.oid, mfi_c2.mode);\n }\n \n /*\n@@ -3362,9 +3281,14 @@ static int process_entry(struct merge_options *o,\n \t\t\t\tclean_merge = -1;\n \t\t\tbreak;\n \t\tcase RENAME_TWO_FILES_TO_ONE:\n-\t\t\tclean_merge = 0;\n-\t\t\tif (handle_rename_rename_2to1(o, conflict_info))\n-\t\t\t\tclean_merge = -1;\n+\t\t\t/*\n+\t\t\t * Probably unclean merge, but if the two renamed\n+\t\t\t * files merge cleanly and the two resulting files\n+\t\t\t * can then be two-way merged cleanly, I guess it's\n+\t\t\t * a clean merge?\n+\t\t\t */\n+\t\t\tclean_merge = handle_rename_rename_2to1(o,\n+\t\t\t\t\t\t\t\tconflict_info);\n \t\t\tbreak;\n \t\tdefault:\n \t\t\tentry->processed = 0;\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex d6b7e568f5..99cddb05af 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -64,15 +64,12 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tL2:three   R2:three \\\n \t\t\tL2:three   R2:three &&\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n-\t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -140,15 +137,12 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect       \\\n-\t\t\tL2:three   R2:three \\\n \t\t\tL2:three   R2:three &&\n \t\tgit rev-parse   >actual     \\\n \t\t\t:2:three   :3:three &&\n-\t\tgit hash-object >>actual    \\\n-\t\t\tthree~HEAD three~R2^0 &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -1512,7 +1506,7 @@ test_expect_success 'setup nested conflicts' '\n \t)\n '\n \n-test_expect_failure 'check nested conflicts' '\n+test_expect_success 'check nested conflicts' '\n \t(\n \t\tcd nested_conflicts &&\n \ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 3e934ae89e..b93139f628 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -464,17 +464,28 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' '\n \t\tgit ls-files -u c >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\ttest_path_is_missing a &&\n \t\ttest_path_is_missing b &&\n-\t\ttest_path_is_file c~HEAD &&\n-\t\ttest_path_is_file c~C^0 &&\n \n-\t\tgit rev-parse >expect   \\\n-\t\t\tC:a     B:b     &&\n-\t\tgit hash-object >actual \\\n-\t\t\tc~HEAD  c~C^0   &&\n+\t\tgit rev-parse >expect  \\\n+\t\t\tC:a     B:b    &&\n+\t\tgit rev-parse >actual  \\\n+\t\t\t:2:c    :3:c   &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in new_a is as expected\n+\t\tgit cat-file -p :2:c >>ours &&\n+\t\tgit cat-file -p :3:c >>theirs &&\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 \"C^0\" \\\n+\t\t\tours empty theirs &&\n+\t\tgit hash-object c >actual &&\n+\t\tgit hash-object ours >expect &&\n \t\ttest_cmp expect actual\n \t)\n '\n@@ -940,7 +951,6 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename\n test_conflicts_with_adds_and_renames() {\n \tsideL=$1\n \tsideR=$2\n-\texpect=$3\n \n \t# Setup:\n \t#          L\n@@ -1048,7 +1058,7 @@ test_conflicts_with_adds_and_renames() {\n \t\t)\n \t'\n \n-\ttest_expect_$expect \"check simple $sideL/$sideR conflict\" '\n+\ttest_expect_success \"check simple $sideL/$sideR conflict\" '\n \t\t(\n \t\t\tcd simple_${sideL}_${sideR} &&\n \n@@ -1094,10 +1104,10 @@ test_conflicts_with_adds_and_renames() {\n \t'\n }\n \n-test_conflicts_with_adds_and_renames rename rename failure\n-test_conflicts_with_adds_and_renames rename add    success\n-test_conflicts_with_adds_and_renames add    rename success\n-test_conflicts_with_adds_and_renames add    add    success\n+test_conflicts_with_adds_and_renames rename rename\n+test_conflicts_with_adds_and_renames rename add\n+test_conflicts_with_adds_and_renames add    rename\n+test_conflicts_with_adds_and_renames add    add\n \n # Setup:\n #          L\n@@ -1168,7 +1178,7 @@ test_expect_success 'setup nested conflicts from rename/rename(2to1)' '\n \t)\n '\n \n-test_expect_failure 'check nested conflicts from rename/rename(2to1)' '\n+test_expect_success 'check nested conflicts from rename/rename(2to1)' '\n \t(\n \t\tcd nested_conflicts_from_rename_rename &&\n \ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex 4a71f17edd..fedaeafc55 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -278,7 +278,7 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:x/b :0:x/c :0:x/d :0:x/e :0:x/m :0:x/n &&\n@@ -293,15 +293,16 @@ test_expect_success '1d-check: Directory renames cause a rename/rename(2to1) con\n \t\t\t A:y/wham  B:z/wham &&\n \t\ttest_cmp expect actual &&\n \n-\t\ttest_path_is_missing x/wham &&\n-\t\ttest_path_is_file x/wham~HEAD &&\n-\t\ttest_path_is_file x/wham~B^0 &&\n-\n-\t\tgit hash-object >actual \\\n-\t\t\tx/wham~HEAD x/wham~B^0 &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tA:y/wham    B:z/wham &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in x/wham is as expected\n+\t\tgit cat-file -p :2:x/wham >expect &&\n+\t\tgit cat-file -p :3:x/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect x/wham\n \t)\n '\n \n@@ -1670,7 +1671,7 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/b :0:y/c :2:y/d :3:y/d &&\n@@ -1678,15 +1679,16 @@ test_expect_success '7b-check: rename/rename(2to1), but only due to transitive r\n \t\t\t O:z/b  O:z/c  O:w/d  O:x/d &&\n \t\ttest_cmp expect actual &&\n \n-\t\ttest_path_is_missing y/d &&\n-\t\ttest_path_is_file y/d~HEAD &&\n-\t\ttest_path_is_file y/d~B^0 &&\n-\n-\t\tgit hash-object >actual \\\n-\t\t\ty/d~HEAD y/d~B^0 &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:w/d    O:x/d &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in y/d is as expected\n+\t\tgit cat-file -p :2:y/d >expect &&\n+\t\tgit cat-file -p :3:y/d >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/d\n \t)\n '\n \n@@ -3165,7 +3167,7 @@ test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)\n #   Commit O: z/{a,b,c_1},        x/{d,e,f_2}\n #   Commit A: y/{a,b},            x/{d,e,f_2,wham_1} + untracked y/wham\n #   Commit B: z/{a,b,c_1,wham_2}, y/{d,e}\n-#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~B^0,wham~HEAD}+\n+#   Expected: Failed Merge; y/{a,b,d,e} + untracked y/{wham,wham~merged}+\n #             CONFLICT(rename/rename) z/c_1 vs x/f_2 -> y/wham\n #             ERROR_MSG(Refusing to lose untracked file at y/wham)\n \n@@ -3219,7 +3221,7 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/a :0:y/b :0:y/d :0:y/e :2:y/wham :3:y/wham &&\n@@ -3232,11 +3234,16 @@ test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '\n \t\techo important >expect &&\n \t\ttest_cmp expect y/wham &&\n \n-\t\tgit hash-object >actual \\\n-\t\t\ty/wham~B^0 y/wham~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/f      O:z/c &&\n-\t\ttest_cmp expect actual\n+\t\t# Test that the two-way merge in y/wham~merged is as expected\n+\t\tgit cat-file -p :2:y/wham >expect &&\n+\t\tgit cat-file -p :3:y/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/wham~merged\n \t)\n '\n \n@@ -3689,7 +3696,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n #   Commit O: z/{a,b},        x/{c_1,d_2}\n #   Commit A: y/{a,b,wham_1}, x/d_2, except y/wham has uncommitted mods\n #   Commit B: z/{a,b,wham_2}, x/c_1\n-#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~B^0,wham~B^HEAD} +\n+#   Expected: Failed Merge; y/{a,b} + untracked y/{wham~merged} +\n #             y/wham with dirty changes from before merge +\n #             CONFLICT(rename/rename) x/c vs x/d -> y/wham\n #             ERROR_MSG(Refusing to lose dirty file at y/wham)\n@@ -3741,24 +3748,30 @@ test_expect_success '11f-check: Avoid deleting not-uptodate with dir rename/rena\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\ttest_seq 1 10 >expected &&\n \t\techo important >>expected &&\n \t\ttest_cmp expected y/wham &&\n \n \t\ttest_must_fail git rev-parse :1:y/wham &&\n-\t\tgit hash-object >actual \\\n-\t\t\ty/wham~B^0 y/wham~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/d      O:x/c &&\n-\t\ttest_cmp expect actual &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/a :0:y/b :2:y/wham :3:y/wham &&\n \t\tgit rev-parse >expect \\\n \t\t\t O:z/a  O:z/b  O:x/c     O:x/d &&\n-\t\ttest_cmp expect actual\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Test that the two-way merge in y/wham~merged is as expected\n+\t\tgit cat-file -p :2:y/wham >expect &&\n+\t\tgit cat-file -p :3:y/wham >other &&\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\texpect empty other &&\n+\t\ttest_cmp expect y/wham~merged\n \t)\n '\n \n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362707","messageId":"20181108044031.25885-3-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 02/10] t6036, t6042: testcases for rename collision of already conflicting files","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:23Z","receivedAt":"2018-11-08T04:41:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When a single file is renamed, it can also be modified, yielding the\npossibility of that renamed file having content conflicts.  If two\ndifferent such files are renamed into the same location, then two-way\nmerging those files may result in nested conflicts.  Add a testcase that\nmakes sure we get this case correct, and uses different lengths of\nconflict markers to differentiate between the different nestings.\n\nAlso add another case with an extra (i.e. third) level of conflict\nmarkers due to using merge.conflictstyle=diff3 and the virtual merge\nbase also having conflicts present.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh    | 194 +++++++++++++++++++++++++++\n t/t6042-merge-rename-corner-cases.sh | 118 ++++++++++++++++\n 2 files changed, 312 insertions(+)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex e1cef58f2a..f229d7e47b 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1402,4 +1402,198 @@ test_expect_failure 'check conflicting modes for regular file' '\n \t)\n '\n \n+# Setup:\n+#          L1---L2\n+#         /  \\ /  \\\n+#   master    X    ?\n+#         \\  / \\  /\n+#          R1---R2\n+#\n+# Where:\n+#   master has two files, named 'b' and 'a'\n+#   branches L1 and R1 both modify each of the two files in conflicting ways\n+#\n+#   L2 is a merge of R1 into L1; more on it later.\n+#   R2 is a merge of L1 into R1; more on it later.\n+#\n+#   X is an auto-generated merge-base used when merging L2 and R2.\n+#   since X is a merge of L1 and R1, it has conflicting versions of each file\n+#\n+#   More about L2 and R2:\n+#     - both resolve the conflicts in 'b' and 'a' differently\n+#     - L2 renames 'b' to 'm'\n+#     - R2 renames 'a' to 'm'\n+#\n+#   In the end, in file 'm' we have four different conflicting files (from\n+#   two versions of 'b' and two of 'a').  In addition, if\n+#   merge.conflictstyle is diff3, then the base version also has\n+#   conflict markers of its own, leading to a total of three levels of\n+#   conflict markers.  This is a pretty weird corner case, but we just want\n+#   to ensure that we handle it as well as practical.\n+\n+test_expect_success 'setup nested conflicts' '\n+\ttest_create_repo nested_conflicts &&\n+\t(\n+\t\tcd nested_conflicts &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >initial &&\n+\n+\t\tcp initial b_L1 &&\n+\t\tcp initial b_R1 &&\n+\t\tcp initial b_L2 &&\n+\t\tcp initial b_R2 &&\n+\t\tcp initial a_L1 &&\n+\t\tcp initial a_R1 &&\n+\t\tcp initial a_L2 &&\n+\t\tcp initial a_R2 &&\n+\n+\t\ttest_write_lines b b_L1 >>b_L1 &&\n+\t\ttest_write_lines b b_R1 >>b_R1 &&\n+\t\ttest_write_lines b b_L2 >>b_L2 &&\n+\t\ttest_write_lines b b_R2 >>b_R2 &&\n+\t\ttest_write_lines a a_L1 >>a_L1 &&\n+\t\ttest_write_lines a a_R1 >>a_R1 &&\n+\t\ttest_write_lines a a_L2 >>a_L2 &&\n+\t\ttest_write_lines a a_R2 >>a_R2 &&\n+\n+\t\t# Setup original commit (or merge-base), consisting of\n+\t\t# files named \"b\" and \"a\"\n+\t\tcp initial b &&\n+\t\tcp initial a &&\n+\t\techo b >>b &&\n+\t\techo a >>a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m initial &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Handle the left side\n+\t\tgit checkout L &&\n+\t\tmv -f b_L1 b &&\n+\t\tmv -f a_L1 a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m \"version L1 of files\" &&\n+\t\tgit tag L1 &&\n+\n+\t\t# Handle the right side\n+\t\tgit checkout R &&\n+\t\tmv -f b_R1 b &&\n+\t\tmv -f a_R1 a &&\n+\t\tgit add b a &&\n+\t\ttest_tick && git commit -m \"verson R1 of files\" &&\n+\t\tgit tag R1 &&\n+\n+\t\t# Create first merge on left side\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git merge R1 &&\n+\t\tmv -f b_L2 b &&\n+\t\tmv -f a_L2 a &&\n+\t\tgit add b a &&\n+\t\tgit mv b m &&\n+\t\ttest_tick && git commit -m \"left merge, rename b->m\" &&\n+\t\tgit tag L2 &&\n+\n+\t\t# Create first merge on right side\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git merge L1 &&\n+\t\tmv -f b_R2 b &&\n+\t\tmv -f a_R2 a &&\n+\t\tgit add b a &&\n+\t\tgit mv a m &&\n+\t\ttest_tick && git commit -m \"right merge, rename a->m\" &&\n+\t\tgit tag R2\n+\t)\n+'\n+\n+test_expect_failure 'check nested conflicts' '\n+\t(\n+\t\tcd nested_conflicts &&\n+\n+\t\tgit clean -f &&\n+\t\tgit checkout L2^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R2^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Create a and b from virtual merge base X\n+\t\tgit cat-file -p master:a >base &&\n+\t\tgit cat-file -p L1:a >ours &&\n+\t\tgit cat-file -p R1:a >theirs &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tours  \\\n+\t\t\tbase  \\\n+\t\t\ttheirs &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >vmb_a &&\n+\n+\t\tgit cat-file -p master:b >base &&\n+\t\tgit cat-file -p L1:b >ours &&\n+\t\tgit cat-file -p R1:b >theirs &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tours  \\\n+\t\t\tbase  \\\n+\t\t\ttheirs &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >vmb_b &&\n+\n+\t\t# Compare :2:m to expected values\n+\t\tgit cat-file -p L2:m >ours &&\n+\t\tgit cat-file -p R2:b >theirs &&\n+\t\ttest_must_fail git merge-file --diff3  \\\n+\t\t\t-L \"HEAD:m\"                    \\\n+\t\t\t-L \"merged common ancestors:b\" \\\n+\t\t\t-L \"R2^0:b\"                    \\\n+\t\t\tours                           \\\n+\t\t\tvmb_b                          \\\n+\t\t\ttheirs                         &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >m_stage_2 &&\n+\t\tgit cat-file -p :2:m >actual &&\n+\t\ttest_cmp m_stage_2 actual &&\n+\n+\t\t# Compare :3:m to expected values\n+\t\tgit cat-file -p L2:a >ours &&\n+\t\tgit cat-file -p R2:m >theirs &&\n+\t\ttest_must_fail git merge-file --diff3  \\\n+\t\t\t-L \"HEAD:a\"                    \\\n+\t\t\t-L \"merged common ancestors:a\" \\\n+\t\t\t-L \"R2^0:m\"                    \\\n+\t\t\tours                           \\\n+\t\t\tvmb_a                          \\\n+\t\t\ttheirs                         &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1/\" ours >m_stage_3 &&\n+\t\tgit cat-file -p :3:m >actual &&\n+\t\ttest_cmp m_stage_3 actual &&\n+\n+\t\t# Compare m to expected contents\n+\t\t>empty &&\n+\t\tcp -a m_stage_2 expected_final_m &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"HEAD\"                     \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"R2^0\"                     \\\n+\t\t\texpected_final_m              \\\n+\t\t\tempty                         \\\n+\t\t\tm_stage_3                     &&\n+\t\ttest_cmp expected_final_m m\n+\t)\n+'\n+\n test_done\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b6fed2cb9a..23c3b6dffa 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -1099,4 +1099,122 @@ test_conflicts_with_adds_and_renames rename add    failure\n test_conflicts_with_adds_and_renames add    rename failure\n test_conflicts_with_adds_and_renames add    add    success\n \n+# Setup:\n+#          L\n+#         / \\\n+#   master   ?\n+#         \\ /\n+#          R\n+#\n+# Where:\n+#   master has two files, named 'one' and 'two'.\n+#   branches L and R both modify 'one', in conflicting ways.\n+#   branches L and R both modify 'two', in conflicting ways.\n+#   branch L also renames 'one' to 'three'.\n+#   branch R also renames 'two' to 'three'.\n+#\n+#   So, we have four different conflicting files that all end up at path\n+#   'three'.\n+test_expect_success 'setup nested conflicts from rename/rename(2to1)' '\n+\ttest_create_repo nested_conflicts_from_rename_rename &&\n+\t(\n+\t\tcd nested_conflicts_from_rename_rename &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >file_v1 &&\n+\n+\t\tcp file_v1 file_v2 &&\n+\t\tcp file_v1 file_v3 &&\n+\t\tcp file_v1 file_v4 &&\n+\t\tcp file_v1 file_v5 &&\n+\t\tcp file_v1 file_v6 &&\n+\n+\t\techo one  >>file_v1 &&\n+\t\techo uno  >>file_v2 &&\n+\t\techo eins >>file_v3 &&\n+\n+\t\techo two  >>file_v4 &&\n+\t\techo dos  >>file_v5 &&\n+\t\techo zwei >>file_v6 &&\n+\n+\t\t# Setup original commit (or merge-base), consisting of\n+\t\t# files named \"one\" and \"two\".\n+\t\tmv file_v1 one &&\n+\t\tmv file_v4 two &&\n+\t\tgit add one two &&\n+\t\ttest_tick && git commit -m english &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Handle the left side\n+\t\tgit checkout L &&\n+\t\tgit mv one three &&\n+\t\tmv -f file_v2 three &&\n+\t\tmv -f file_v5 two &&\n+\t\tgit add two three &&\n+\t\ttest_tick && git commit -m spanish &&\n+\n+\t\t# Handle the right side\n+\t\tgit checkout R &&\n+\t\tgit mv two three &&\n+\t\tmv -f file_v3 one &&\n+\t\tmv -f file_v6 three &&\n+\t\tgit add one three &&\n+\t\ttest_tick && git commit -m german\n+\t)\n+'\n+\n+test_expect_failure 'check nested conflicts from rename/rename(2to1)' '\n+\t(\n+\t\tcd nested_conflicts_from_rename_rename &&\n+\n+\t\tgit checkout L^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git merge -s recursive R^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 2 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Compare :2:three to expected values\n+\t\tgit cat-file -p master:one >base &&\n+\t\tgit cat-file -p L:three >ours &&\n+\t\tgit cat-file -p R:one >theirs &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD:three\"  -L \"\"  -L \"R^0:one\" \\\n+\t\t\tours             base   theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1/\" ours >L-three &&\n+\t\tgit cat-file -p :2:three >expect &&\n+\t\ttest_cmp expect L-three &&\n+\n+\t\t# Compare :2:three to expected values\n+\t\tgit cat-file -p master:two >base &&\n+\t\tgit cat-file -p L:two >ours &&\n+\t\tgit cat-file -p R:three >theirs &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD:two\"  -L \"\"  -L \"R^0:three\" \\\n+\t\t\tours           base   theirs &&\n+\t\tsed -e \"s/^\\([<=>]\\)/\\1\\1/\" ours >R-three &&\n+\t\tgit cat-file -p :3:three >expect &&\n+\t\ttest_cmp expect R-three &&\n+\n+\t\t# Compare three to expected contents\n+\t\t>empty &&\n+\t\ttest_must_fail git merge-file    \\\n+\t\t\t-L \"HEAD\"  -L \"\"  -L \"R^0\" \\\n+\t\t\tL-three    empty  R-three &&\n+\t\ttest_cmp three L-three\n+\t)\n+'\n+\n test_done\n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362708","messageId":"20181108044031.25885-9-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 08/10] merge-recursive: improve rename/rename(1to2)/add[/add] handling","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:29Z","receivedAt":"2018-11-08T04:41:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When we have a rename/rename(1to2) conflict, each of the renames can\ncollide with a file addition.  Each of these rename/add conflicts suffered\nfrom the same kinds of problems that normal rename/add suffered from.\nMake the code use handle_file_conflicts() as well so that we get all the\nsame fixes and consistent behavior between the different conflict types.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    | 154 +++++++++++++--------------\n t/t6042-merge-rename-corner-cases.sh |  29 +++--\n t/t6043-merge-rename-directories.sh  |  24 +++--\n 3 files changed, 113 insertions(+), 94 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 88e9e1166a..3e2a63d094 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1710,80 +1710,17 @@ static int handle_rename_add(struct merge_options *o,\n \t\t\t\t     ci->dst_entry1->stages[other_stage].mode);\n }\n \n-static int handle_file(struct merge_options *o,\n-\t\t\tstruct diff_filespec *rename,\n-\t\t\tint stage,\n-\t\t\tstruct rename_conflict_info *ci)\n-{\n-\tchar *dst_name = rename->path;\n-\tstruct stage_data *dst_entry;\n-\tconst char *cur_branch, *other_branch;\n-\tstruct diff_filespec other;\n-\tstruct diff_filespec *add;\n-\tint ret;\n-\n-\tif (stage == 2) {\n-\t\tdst_entry = ci->dst_entry1;\n-\t\tcur_branch = ci->branch1;\n-\t\tother_branch = ci->branch2;\n-\t} else {\n-\t\tdst_entry = ci->dst_entry2;\n-\t\tcur_branch = ci->branch2;\n-\t\tother_branch = ci->branch1;\n-\t}\n-\n-\tadd = filespec_from_entry(&other, dst_entry, stage ^ 1);\n-\tif (add) {\n-\t\tint ren_src_was_dirty = was_dirty(o, rename->path);\n-\t\tchar *add_name = unique_path(o, rename->path, other_branch);\n-\t\tif (update_file(o, 0, &add->oid, add->mode, add_name))\n-\t\t\treturn -1;\n-\n-\t\tif (ren_src_was_dirty) {\n-\t\t\toutput(o, 1, _(\"Refusing to lose dirty file at %s\"),\n-\t\t\t       rename->path);\n-\t\t}\n-\t\t/*\n-\t\t * Because the double negatives somehow keep confusing me...\n-\t\t *    1) update_wd iff !ren_src_was_dirty.\n-\t\t *    2) no_wd iff !update_wd\n-\t\t *    3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty\n-\t\t */\n-\t\tremove_file(o, 0, rename->path, ren_src_was_dirty);\n-\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t} else {\n-\t\tif (dir_in_way(rename->path, !o->call_depth, 0)) {\n-\t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t\t\toutput(o, 1, _(\"%s is a directory in %s adding as %s instead\"),\n-\t\t\t       rename->path, other_branch, dst_name);\n-\t\t} else if (!o->call_depth &&\n-\t\t\t   would_lose_untracked(rename->path)) {\n-\t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n-\t\t\toutput(o, 1, _(\"Refusing to lose untracked file at %s; \"\n-\t\t\t\t       \"adding as %s instead\"),\n-\t\t\t       rename->path, dst_name);\n-\t\t}\n-\t}\n-\tif ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))\n-\t\t; /* fall through, do allow dst_name to be released */\n-\telse if (stage == 2)\n-\t\tret = update_stages(o, rename->path, NULL, rename, add);\n-\telse\n-\t\tret = update_stages(o, rename->path, NULL, add, rename);\n-\n-\tif (dst_name != rename->path)\n-\t\tfree(dst_name);\n-\n-\treturn ret;\n-}\n-\n static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t     struct rename_conflict_info *ci)\n {\n \t/* One file was renamed in both branches, but to different names. */\n+\tstruct merge_file_info mfi;\n+\tstruct diff_filespec other;\n+\tstruct diff_filespec *add;\n \tstruct diff_filespec *one = ci->pair1->one;\n \tstruct diff_filespec *a = ci->pair1->two;\n \tstruct diff_filespec *b = ci->pair2->two;\n+\tchar *path_desc;\n \n \toutput(o, 1, _(\"CONFLICT (rename/rename): \"\n \t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n@@ -1791,15 +1728,16 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t       one->path, a->path, ci->branch1,\n \t       one->path, b->path, ci->branch2,\n \t       o->call_depth ? _(\" (left unresolved)\") : \"\");\n-\tif (o->call_depth) {\n-\t\tstruct merge_file_info mfi;\n-\t\tstruct diff_filespec other;\n-\t\tstruct diff_filespec *add;\n-\t\tif (merge_mode_and_contents(o, one, a, b, one->path,\n-\t\t\t\t\t    ci->branch1, ci->branch2,\n-\t\t\t\t\t    o->call_depth * 2, &mfi))\n-\t\t\treturn -1;\n \n+\tpath_desc = xstrfmt(\"%s and %s, both renamed from %s\",\n+\t\t\t    a->path, b->path, one->path);\n+\tif (merge_mode_and_contents(o, one, a, b, path_desc,\n+\t\t\t\t    ci->branch1, ci->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi))\n+\t\treturn -1;\n+\tfree(path_desc);\n+\n+\tif (o->call_depth) {\n \t\t/*\n \t\t * FIXME: For rename/add-source conflicts (if we could detect\n \t\t * such), this is wrong.  We should instead find a unique\n@@ -1831,8 +1769,70 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\t}\n \t\telse\n \t\t\tremove_file_from_cache(b->path);\n-\t} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))\n-\t\treturn -1;\n+\t} else {\n+\t\t/*\n+\t\t * For each destination path, we need to see if there is a\n+\t\t * rename/add collision.  If not, we can write the file out\n+\t\t * to the specified location.\n+\t\t */\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);\n+\t\tif (add) {\n+\t\t\tif (handle_file_collision(o, a->path,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  ci->branch1, ci->branch2,\n+\t\t\t\t\t\t  &mfi.oid, mfi.mode,\n+\t\t\t\t\t\t  &add->oid, add->mode) < 0)\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tchar *new_path = NULL;\n+\t\t\tif (dir_in_way(a->path, !o->call_depth, 0)) {\n+\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n+\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t\t\t       \"as %s instead\"),\n+\t\t\t\t       a->path, ci->branch2, new_path);\n+\t\t\t} else if (would_lose_untracked(a->path)) {\n+\t\t\t\tnew_path = unique_path(o, a->path, ci->branch1);\n+\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t\t\t       a->path, new_path);\n+\t\t\t}\n+\n+\t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))\n+\t\t\t\treturn -1;\n+\t\t\tfree(new_path);\n+\t\t\tif (update_stages(o, a->path, NULL, a, NULL))\n+\t\t\t\treturn -1;\n+\t\t}\n+\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);\n+\t\tif (add) {\n+\t\t\tif (handle_file_collision(o, b->path,\n+\t\t\t\t\t\t  NULL, NULL,\n+\t\t\t\t\t\t  ci->branch1, ci->branch2,\n+\t\t\t\t\t\t  &add->oid, add->mode,\n+\t\t\t\t\t\t  &mfi.oid, mfi.mode) < 0)\n+\t\t\t\treturn -1;\n+\t\t} else {\n+\t\t\tchar *new_path = NULL;\n+\t\t\tif (dir_in_way(b->path, !o->call_depth, 0)) {\n+\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n+\t\t\t\toutput(o, 1, _(\"%s is a directory in %s adding \"\n+\t\t\t\t\t       \"as %s instead\"),\n+\t\t\t\t       b->path, ci->branch1, new_path);\n+\t\t\t} else if (would_lose_untracked(b->path)) {\n+\t\t\t\tnew_path = unique_path(o, b->path, ci->branch2);\n+\t\t\t\toutput(o, 1, _(\"Refusing to lose untracked file\"\n+\t\t\t\t\t       \" at %s; adding as %s instead\"),\n+\t\t\t\t       b->path, new_path);\n+\t\t\t}\n+\n+\t\t\tif (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))\n+\t\t\t\treturn -1;\n+\t\t\tfree(new_path);\n+\t\t\tif (update_stages(o, b->path, NULL, NULL, b))\n+\t\t\t\treturn -1;\n+\t\t}\n+\t}\n \n \treturn 0;\n }\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b93139f628..7cc34e7579 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -684,7 +684,7 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t\tgit ls-files -u c >out &&\n \t\ttest_line_count = 2 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 5 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >expect               \\\n \t\t\tA:a   C:b   B:b   C:c   B:c &&\n@@ -692,14 +692,27 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \t\t\t:1:a  :2:b  :3:b  :2:c  :3:c &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit rev-parse >expect               \\\n-\t\t\tC:c     B:c     C:b     B:b &&\n-\t\tgit hash-object >actual                \\\n-\t\t\tc~HEAD  c~B\\^0  b~HEAD  b~B\\^0 &&\n-\t\ttest_cmp expect actual &&\n+\t\t# Record some contents for re-doing merges\n+\t\tgit cat-file -p A:a >stuff &&\n+\t\tgit cat-file -p C:b >important_info &&\n+\t\tgit cat-file -p B:c >precious_data &&\n+\t\t>empty &&\n \n-\t\ttest_path_is_missing b &&\n-\t\ttest_path_is_missing c\n+\t\t# Test the merge in b\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\timportant_info empty stuff &&\n+\t\ttest_cmp important_info b &&\n+\n+\t\t# Test the merge in c\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\tstuff empty precious_data &&\n+\t\ttest_cmp stuff c\n \t)\n '\n \ndiff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh\nindex fedaeafc55..5c01a0c14a 100755\n--- a/t/t6043-merge-rename-directories.sh\n+++ b/t/t6043-merge-rename-directories.sh\n@@ -1078,7 +1078,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 6 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 3 out &&\n+\t\ttest_line_count = 1 out &&\n \n \t\tgit rev-parse >actual \\\n \t\t\t:0:y/b :0:y/c :0:y/e &&\n@@ -1094,9 +1094,9 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam\n \t\ttest_cmp expect actual &&\n \n \t\tgit hash-object >actual \\\n-\t\t\tw/d~HEAD w/d~B^0 z/d &&\n+\t\t\tz/d &&\n \t\tgit rev-parse >expect \\\n-\t\t\tO:x/d    B:w/d   O:x/d &&\n+\t\t\tO:x/d &&\n \t\ttest_cmp expect actual &&\n \t\ttest_path_is_missing x/d &&\n \t\ttest_path_is_file y/d &&\n@@ -3672,7 +3672,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n \t\tgit ls-files -u >out &&\n \t\ttest_line_count = 4 out &&\n \t\tgit ls-files -o >out &&\n-\t\ttest_line_count = 4 out &&\n+\t\ttest_line_count = 3 out &&\n \n \t\techo different >expected &&\n \t\techo mods >>expected &&\n@@ -3684,11 +3684,17 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena\n \t\t\t O:z/a  O:z/b  O:x/d  O:x/c  O:x/c  A:y/c  O:x/c &&\n \t\ttest_cmp expect actual &&\n \n-\t\tgit hash-object >actual \\\n-\t\t\ty/c~B^0 y/c~HEAD &&\n-\t\tgit rev-parse >expect \\\n-\t\t\tO:x/c   A:y/c &&\n-\t\ttest_cmp expect actual\n+\t\t# See if y/c~merged has expected contents; requires manually\n+\t\t# doing the expected file merge\n+\t\tgit cat-file -p A:y/c >c1 &&\n+\t\tgit cat-file -p B:z/c >c2 &&\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\tc1 empty c2 &&\n+\t\ttest_cmp c1 y/c~merged\n \t)\n '\n \n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362709","messageId":"20181108044031.25885-4-newren@gmail.com","threadId":"49559","inReplyTo":"20181108044031.25885-1-newren@gmail.com","subject":"[PATCH v5 03/10] merge-recursive: increase marker length with depth of recursion","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2018-11-08T04:40:24Z","receivedAt":"2018-11-08T04:41:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Later patches in this series will modify file collision conflict\nhandling (e.g. from rename/add and rename/rename(2to1) conflicts) so\nthat multiply nested conflict markers can arise even before considering\nconflicts in the virtual merge base.  Including the virtual merge base\nwill provide a way to get triply (or higher) nested conflict markers.\nThis new way to get nested conflict markers will force the need for a\nmore general mechanism to extend the length of conflict markers in order\nto differentiate between different nestings.\n\nAlong with this change to conflict marker length handling, we want to\nmake sure that we don't regress handling for other types of conflicts\nwith nested conflict markers.  Add a more involved testcase using\nmerge.conflictstyle=diff3, where not only does the virtual merge base\ncontain conflicts, but its virtual merge base does as well (i.e. a case\nwith triply nested conflict markers).  While there are multiple\nreasonable ways to handle nested conflict markers in the virtual merge\nbase for this type of situation, the easiest approach that dovetails\nwell with the new needs for the file collision conflict handling is to\nrequire that the length of the conflict markers increase with each\nsubsequent nesting.\n\nSubsequent patches which change the rename/add and rename/rename(2to1)\nconflict handling will modify the extra_marker_size flag appropriately\nfor their new needs.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n ll-merge.c                        |   4 +-\n ll-merge.h                        |   1 +\n merge-recursive.c                 |  25 +++--\n t/t6036-recursive-corner-cases.sh | 151 ++++++++++++++++++++++++++++++\n 4 files changed, 172 insertions(+), 9 deletions(-)\n\ndiff --git a/ll-merge.c b/ll-merge.c\nindex 3c8fb917e9..5b8d46aede 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -384,7 +384,9 @@ int ll_merge(mmbuffer_t *result_buf,\n \tif (opts->virtual_ancestor) {\n \t\tif (driver->recursive)\n \t\t\tdriver = find_ll_merge_driver(driver->recursive);\n-\t\tmarker_size += 2;\n+\t}\n+\tif (opts->extra_marker_size) {\n+\t\tmarker_size += opts->extra_marker_size;\n \t}\n \treturn driver->fn(driver, result_buf, path, ancestor, ancestor_label,\n \t\t\t  ours, our_label, theirs, their_label,\ndiff --git a/ll-merge.h b/ll-merge.h\nindex 6c6e22e40d..b9e2af1c88 100644\n--- a/ll-merge.h\n+++ b/ll-merge.h\n@@ -13,6 +13,7 @@ struct ll_merge_options {\n \tunsigned virtual_ancestor : 1;\n \tunsigned variant : 2;\t/* favor ours, favor theirs, or union merge */\n \tunsigned renormalize : 1;\n+\tunsigned extra_marker_size;\n \tlong xdl_opts;\n };\n \ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex acc2f64a4e..f35e3b5f95 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1058,7 +1058,8 @@ static int merge_3way(struct merge_options *o,\n \t\t      const struct diff_filespec *a,\n \t\t      const struct diff_filespec *b,\n \t\t      const char *branch1,\n-\t\t      const char *branch2)\n+\t\t      const char *branch2,\n+\t\t      const int extra_marker_size)\n {\n \tmmfile_t orig, src1, src2;\n \tstruct ll_merge_options ll_opts = {0};\n@@ -1066,6 +1067,7 @@ static int merge_3way(struct merge_options *o,\n \tint merge_status;\n \n \tll_opts.renormalize = o->renormalize;\n+\tll_opts.extra_marker_size = extra_marker_size;\n \tll_opts.xdl_opts = o->xdl_opts;\n \n \tif (o->call_depth) {\n@@ -1301,6 +1303,7 @@ static int merge_mode_and_contents(struct merge_options *o,\n \t\t\t\t   const char *filename,\n \t\t\t\t   const char *branch1,\n \t\t\t\t   const char *branch2,\n+\t\t\t\t   const int extra_marker_size,\n \t\t\t\t   struct merge_file_info *result)\n {\n \tif (o->branch1 != branch1) {\n@@ -1311,7 +1314,8 @@ static int merge_mode_and_contents(struct merge_options *o,\n \t\t */\n \t\treturn merge_mode_and_contents(o, one, b, a,\n \t\t\t\t\t       filename,\n-\t\t\t\t\t       branch2, branch1, result);\n+\t\t\t\t\t       branch2, branch1,\n+\t\t\t\t\t       extra_marker_size, result);\n \t}\n \n \tresult->merge = 0;\n@@ -1352,7 +1356,8 @@ static int merge_mode_and_contents(struct merge_options *o,\n \t\t\tint ret = 0, merge_status;\n \n \t\t\tmerge_status = merge_3way(o, &result_buf, one, a, b,\n-\t\t\t\t\t\t  branch1, branch2);\n+\t\t\t\t\t\t  branch1, branch2,\n+\t\t\t\t\t\t  extra_marker_size);\n \n \t\t\tif ((merge_status < 0) || !result_buf.ptr)\n \t\t\t\tret = err(o, _(\"Failed to execute internal merge\"));\n@@ -1641,7 +1646,8 @@ static int handle_rename_rename_1to2(struct merge_options *o,\n \t\tstruct diff_filespec other;\n \t\tstruct diff_filespec *add;\n \t\tif (merge_mode_and_contents(o, one, a, b, one->path,\n-\t\t\t\t\t    ci->branch1, ci->branch2, &mfi))\n+\t\t\t\t\t    ci->branch1, ci->branch2,\n+\t\t\t\t\t    o->call_depth * 2, &mfi))\n \t\t\treturn -1;\n \n \t\t/*\n@@ -1708,9 +1714,11 @@ static int handle_rename_rename_2to1(struct merge_options *o,\n \tpath_side_1_desc = xstrfmt(\"version of %s from %s\", path, a->path);\n \tpath_side_2_desc = xstrfmt(\"version of %s from %s\", path, b->path);\n \tif (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,\n-\t\t\t\t    o->branch1, o->branch2, &mfi_c1) ||\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi_c1) ||\n \t    merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,\n-\t\t\t\t    o->branch1, o->branch2, &mfi_c2))\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi_c2))\n \t\treturn -1;\n \tfree(path_side_1_desc);\n \tfree(path_side_2_desc);\n@@ -2756,7 +2764,7 @@ static int process_renames(struct merge_options *o,\n \n \t\t\t\t\tif (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,\n \t\t\t\t\t\t\t\t    branch1, branch2,\n-\t\t\t\t\t\t\t\t    &mfi)) {\n+\t\t\t\t\t\t\t\t    o->call_depth * 2, &mfi)) {\n \t\t\t\t\t\tclean_merge = -1;\n \t\t\t\t\t\tgoto cleanup_and_return;\n \t\t\t\t\t}\n@@ -3053,7 +3061,8 @@ static int handle_content_merge(struct merge_options *o,\n \t\t\tdf_conflict_remains = 1;\n \t}\n \tif (merge_mode_and_contents(o, &one, &a, &b, path,\n-\t\t\t\t    o->branch1, o->branch2, &mfi))\n+\t\t\t\t    o->branch1, o->branch2,\n+\t\t\t\t    o->call_depth * 2, &mfi))\n \t\treturn -1;\n \n \t/*\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex f229d7e47b..3091cbc06a 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1596,4 +1596,155 @@ test_expect_failure 'check nested conflicts' '\n \t)\n '\n \n+# Setup:\n+#          L1---L2---L3\n+#         /  \\ /  \\ /  \\\n+#   master    X1   X2   ?\n+#         \\  / \\  / \\  /\n+#          R1---R2---R3\n+#\n+# Where:\n+#   master has one file named 'content'\n+#   branches L1 and R1 both modify each of the two files in conflicting ways\n+#\n+#   L<n> (n>1) is a merge of R<n-1> into L<n-1>\n+#   R<n> (n>1) is a merge of L<n-1> into R<n-1>\n+#   L<n> and R<n> resolve the conflicts differently.\n+#\n+#   X<n> is an auto-generated merge-base used when merging L<n+1> and R<n+1>.\n+#   By construction, X1 has conflict markers due to conflicting versions.\n+#   X2, due to using merge.conflictstyle=3, has nested conflict markers.\n+#\n+#   So, merging R3 into L3 using merge.conflictstyle=3 should show the\n+#   nested conflict markers from X2 in the base version -- that means we\n+#   have three levels of conflict markers.  Can we distinguish all three?\n+\n+test_expect_success 'setup virtual merge base with nested conflicts' '\n+\ttest_create_repo virtual_merge_base_has_nested_conflicts &&\n+\t(\n+\t\tcd virtual_merge_base_has_nested_conflicts &&\n+\n+\t\t# Create some related files now\n+\t\tfor i in $(test_seq 1 10)\n+\t\tdo\n+\t\t\techo Random base content line $i\n+\t\tdone >content &&\n+\n+\t\t# Setup original commit\n+\t\tgit add content &&\n+\t\ttest_tick && git commit -m initial &&\n+\n+\t\tgit branch L &&\n+\t\tgit branch R &&\n+\n+\t\t# Create L1\n+\t\tgit checkout L &&\n+\t\techo left >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick && git commit -m \"version L1 of content\" &&\n+\t\tgit tag L1 &&\n+\n+\t\t# Create R1\n+\t\tgit checkout R &&\n+\t\techo right >>content &&\n+\t\tgit add content &&\n+\t\ttest_tick && git commit -m \"verson R1 of content\" &&\n+\t\tgit tag R1 &&\n+\n+\t\t# Create L2\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge R1 &&\n+\t\tgit checkout L1 content &&\n+\t\ttest_tick && git commit -m \"version L2 of content\" &&\n+\t\tgit tag L2 &&\n+\n+\t\t# Create R2\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge L1 &&\n+\t\tgit checkout R1 content &&\n+\t\ttest_tick && git commit -m \"version R2 of content\" &&\n+\t\tgit tag R2 &&\n+\n+\t\t# Create L3\n+\t\tgit checkout L &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge R2 &&\n+\t\tgit checkout L1 content &&\n+\t\ttest_tick && git commit -m \"version L3 of content\" &&\n+\t\tgit tag L3 &&\n+\n+\t\t# Create R3\n+\t\tgit checkout R &&\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge L2 &&\n+\t\tgit checkout R1 content &&\n+\t\ttest_tick && git commit -m \"version R3 of content\" &&\n+\t\tgit tag R3\n+\t)\n+'\n+\n+test_expect_success 'check virtual merge base with nested conflicts' '\n+\t(\n+\t\tcd virtual_merge_base_has_nested_conflicts &&\n+\n+\t\tgit checkout L3^0 &&\n+\n+\t\t# Merge must fail; there is a conflict\n+\t\ttest_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R3^0 &&\n+\n+\t\t# Make sure the index has the right number of entries\n+\t\tgit ls-files -s >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\tgit ls-files -u >out &&\n+\t\ttest_line_count = 3 out &&\n+\t\t# Ensure we have the correct number of untracked files\n+\t\tgit ls-files -o >out &&\n+\t\ttest_line_count = 1 out &&\n+\n+\t\t# Compare :[23]:content to expected values\n+\t\tgit rev-parse L1:content R1:content >expect &&\n+\t\tgit rev-parse :2:content :3:content >actual &&\n+\t\ttest_cmp expect actual &&\n+\n+\t\t# Imitate X1 merge base, except without long enough conflict\n+\t\t# markers because a subsequent sed will modify them.  Put\n+\t\t# result into vmb.\n+\t\tgit cat-file -p master:content >base &&\n+\t\tgit cat-file -p L:content >left &&\n+\t\tgit cat-file -p R:content >right &&\n+\t\tcp left merged-once &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tmerged-once \\\n+\t\t\tbase        \\\n+\t\t\tright       &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1\\1/\" merged-once >vmb &&\n+\n+\t\t# Imitate X2 merge base, overwriting vmb.  Note that we\n+\t\t# extend both sets of conflict markers to make them longer\n+\t\t# with the sed command.\n+\t\tcp left merged-twice &&\n+\t\ttest_must_fail git merge-file --diff3 \\\n+\t\t\t-L \"Temporary merge branch 1\" \\\n+\t\t\t-L \"merged common ancestors\"  \\\n+\t\t\t-L \"Temporary merge branch 2\" \\\n+\t\t\tmerged-twice \\\n+\t\t\tvmb          \\\n+\t\t\tright        &&\n+\t\tsed -e \"s/^\\([<|=>]\\)/\\1\\1\\1/\" merged-twice >vmb &&\n+\n+\t\t# Compare :1:content to expected value\n+\t\tgit cat-file -p :1:content >actual &&\n+\t\ttest_cmp vmb actual &&\n+\n+\t\t# Determine expected content in final outer merge, compare to\n+\t\t# what the merge generated.\n+\t\tcp -f left expect &&\n+\t\ttest_must_fail git merge-file --diff3                      \\\n+\t\t\t-L \"HEAD\"  -L \"merged common ancestors\"  -L \"R3^0\" \\\n+\t\t\texpect     vmb                           right     &&\n+\t\ttest_cmp expect content\n+\t)\n+'\n+\n test_done\n-- \n2.19.1.858.g526e8fe740.dirty\n\n"},{"id":"362716","messageId":"xmqq8t2487jv.fsf@gitster-ct.c.googlers.com","threadId":"49559","inReplyTo":"20181108044031.25885-11-newren@gmail.com","subject":"Re: [PATCH v5 10/10] merge-recursive: combine error handling","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2018-11-08T05:25:24Z","receivedAt":"2018-11-08T05:25:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> From: Derrick Stolee <stolee@gmail.com>\n>\n> In handle_rename_rename_1to2(), we have duplicated error handling\n> around colliding paths. Specifically, when we want to write out\n> the file and there is a directory or untracked file in the way,\n> we need to create a temporary file to hold the contents. This has\n> some special output to alert the user, and this output is\n> duplicated for each side of the conflict.\n>\n> Simplify the call by generating this new path in a helper\n> function.\n>\n> Signed-off-by: Derrick Stolee <dstolee@microsoft.com>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c | 53 ++++++++++++++++++++++++-----------------------\n>  1 file changed, 27 insertions(+), 26 deletions(-)\n\nThanks, both.\n\nLet's advance these patches to 'next' soonish.\n"}]}