{"thread":{"id":"25162","subject":"[PATCH 00/37] Audit and fix corner case bugs in recursive merge","startedAt":"2010-09-20T08:28:33Z","lastAt":"2010-09-22T04:41:17Z","messageCount":46,"participants":["Elijah Newren","Johannes Sixt","Jakub Narebski","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":37},"messages":[{"id":"151053","messageId":"1284971350-30590-1-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":null,"subject":"[PATCH 00/37] Audit and fix corner case bugs in recursive merge","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:33Z","receivedAt":"2010-09-20T08:28:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"I may want to add another test or two, and tweak some things here or\nthere, but I finally have this series working enough that others could\ntest or comment on it now...\n\nThis patch series serves as an audit and fix of a number of corner\ncase bugs in the recursive merge algorithm.  The initial thrust for\nthe audit came from this exchange (from\nhttp://thread.gmane.org/gmane.comp.version-control.git/155770/focus=155917):\n\nOn Thu, Sep 9, 2010 at 6:26 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Elijah Newren <newren@gmail.com> writes:\n>> ... git can't know that there's a conflict\n>> until after it's tried resolving paths involving newsub/newfile to see if\n>> they are still in the way at the end (and if newsub/newfile is not in the\n>> way at the end, there should be no conflict at all, which did not hold with\n>> git previously).\n>\n> I'll queue this patch to 'pu', but anybody who wrote the above to\n> correctly arriave at the crux of the issue in his analysis would know that\n> this is another band-aid on top of band-aid.  The approach merge-recursive\n> takes to first grab the set of directories and files fundamentally is\n> wrong---it should be resolving the paths in-core first and then look at\n> the result to ignore a directory that has become empty.\n\nJunio is right, of course -- on all counts.  Inspection of the current\nband-aids shows that there are still a number of bugs in the recursive\nmerge algorithm in corner cases with renames and directory/file (D/F)\nconflicts.  My investigation also turned up a bug not involving\nrenames but dealing with D/F conflicts in combination with\nmodify/delete conflicts.  I also happened to find a bug not involving\nD/F conflicts where git could incorrectly resolve a merge involving\nprior criss-cross merges (in some cases silently accepting one side of\nthe merge as the final resolution without detecting a relevant\nconflict).\n\nThis patch series is predominantly about doing exactly as Junio\nsuggests: resolving paths in-core first and then looking at the result\nto see if the D/F conflicts still have paths in the way of each other\nat the end.  However, it also includes the creation of several\ntestcases showing how and where the current algorithm is insufficient,\nand addresses the undetected merge conflict found as well.\n\nThis series is based on next as it modifies/builds-on en/rename-d-f;\nit cannot be played directly on top of that series as it also depends\non ks/recursive-rename-add-identical from master.\n\nThis series also includes three patches previously posted on the list\nbut which have not yet been picked up in pu or elsewhere: One from\nJunio to allow creation of new process_*() functions and modifying\nprocess_df_entry() to work with such chaining, one from Ken Schalk to\nadd a testcase for resolving a rename/add conflict involving symlinks,\nand a previous submission of mine to rename/clarify the 'stage'\nvariable in process_renames().\n\nThe series ended up being bigger than I realized when I started.\nAlso, since it's whole purpose is to handle corner cases, I know that\nit may be harder to review than other series.  I've tried to break it\ndown to address that, but I'm not sure if I have split the patches too\nfinely or too coarsely, or whether I've added sufficient explanation.\nLet me know.\n\n\nSchalk, Ken (1):\n  t3030: Add a testcase for resolvable rename/add conflict with\n    symlinks\n\nJunio C Hamano (1):\n  merge-recursive: Restructure showing how to chain more process_*\n    functions\n\nElijah Newren (35):\n  t6032: Add a test checking for excessive output from merge\n  t6022: Add test combinations of {content conflict?, D/F conflict\n    remains?}\n  t6022: Add tests for reversing order of merges when D/F conflicts\n    present\n  t6022: Add tests with both rename source & dest involved in D/F\n    conflicts\n  t6022: Add paired rename+D/F conflict: (two/file, one/file) -> (one,\n    two)\n  t6022: Add tests for rename/rename combined with D/F conflicts\n  t6020: Modernize style a bit\n  t6020: Add a testcase for modify/delete + directory/file conflict\n  t6036: Test index and worktree state, not just that merge fails\n  t6036: Add a second testcase similar to the first but with content\n    changes\n  t6036: Add testcase for undetected conflict\n\nThe above patches are all about adding testcases both to extend our\ncoverage of different cases, and to expose corner case bugs.\n\n  merge-recursive: Small code clarification -- variable name and\n    comments\n  merge-recursive: Rename conflict_rename_rename*() for clarity\n  merge-recursive: Nuke rename/directory conflict detection\n\nThese three patches are just small code cleanups.  No bug fixes yet.\n\n  merge-recursive: Move rename/delete handling into dedicated function\n  merge-recursive: Move delete/modify handling into dedicated function\n  merge-recursive: Move process_entry's content merging into a function\n\nThese three patches are just moving code into functions which will\nlater be called from multiple places.  Still no bug fixes.\n\n  merge-recursive: New data structures for deferring of D/F conflicts\n  merge-recursive: New function to assist resolving renames in-core\n    only\n  merge-recursive: Have process_entry() skip D/F or rename entries\n  merge-recursive: Structure process_df_entry() to handle more cases\n  merge-recursive: Update conflict_rename_rename_1to2() call signature\n  merge-recursive: Update merge_content() call signature\n\nThese patches introduce new data structures and update the call\nsignatures of various functions to make it so I can pass additional\nD/F conflict information to them.  Still no bug fixes yet.\n\n  merge-recursive: Avoid doubly merging rename/add conflict contents\n\nThis patch is for what I belive to be the most concerning of the\ncorner case bugs I found (since git will under some circumstances\ncreate and report a clean merge when it should have detected a\nconflict), and the only one that doesn't involve D/F conflicts.\n\n  merge-recursive: Move handling of double rename of one file to two\n  merge-recursive: Move handling of double rename of one file to other\n    file\n  merge-recursive: Delay handling of rename/delete conflicts\n  merge-recursive: Delay content merging for renames\n  merge-recursive: Delay modify/delete conflicts if D/F conflict\n    present\n\nThese patches simply move anything related to D/F conflicts from\nprocess_renames() and process_entry() into process_df_entry().  Just\nmoving the code without further changes already fixes a couple bugs.\n\n  conflict_rename_delete(): Check whether D/F conflicts are still\n    present\n  conflict_rename_rename_1to2(): Fix checks for presence of D/F\n    conflicts\n  merge_content(): Check whether D/F conflicts are still present\n  handle_delete_modify(): Check whether D/F conflicts are still present\n  merge-recursive: Make room for directories in D/F conflicts\n\nThese patches are where the majority of the corner case bugs involving\nD/F conflicts get fixed, now that the appropriate structure is in place.\n\n  merge-recursive: Remove redundant path clearing for D/F conflicts\n\n...and one final clean up patch, due to the fact that keeping all the\ntests passing in intermediate commits meant using ad-hoc methods to\nmake room for directories in D/F conflicts.\n\n\n merge-recursive.c                 |  649 ++++++++++++++++++++++++-------------\n t/t3030-merge-recursive.sh        |   37 ++-\n t/t6020-merge-df.sh               |   82 ++++-\n t/t6022-merge-rename.sh           |  366 +++++++++++++++++++++\n t/t6032-merge-large-rename.sh     |   30 ++\n t/t6036-recursive-corner-cases.sh |  185 +++++++++++-\n 6 files changed, 1108 insertions(+), 241 deletions(-)\n\n-- \n1.7.3.271.g16009\n"},{"id":"151070","messageId":"1284971350-30590-2-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 01/37] t3030: Add a testcase for resolvable rename/add conflict with symlinks","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:34Z","receivedAt":"2010-09-20T08:28:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Schalk, Ken <ken.schalk@intel.com>\n\nd5af510 (RE: [PATCH] Avoid rename/add conflict when contents are identical\n2010-09-01) avoided erroring out in a rename/add conflict when the contents\nwere identical.  A simpler fix could have handled that particular testcase,\nbut it would not correctly handle the case where a symlink is involved.\nAdd another testcase using symlinks, to avoid breaking that case.\n\nSigned-off-by: Ken Schalk <ken.schalk@intel.com>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t3030-merge-recursive.sh |   37 ++++++++++++++++++++++++++++++++++++-\n 1 files changed, 36 insertions(+), 1 deletions(-)\n\ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex e66e550..3935c4b 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -25,6 +25,10 @@ test_expect_success 'setup 1' '\n \tgit branch submod &&\n \tgit branch copy &&\n \tgit branch rename &&\n+\tif test_have_prereq SYMLINKS\n+\tthen\n+\t\tgit branch rename-ln\n+\tfi &&\n \n \techo hello >>a &&\n \tcp a d/e &&\n@@ -255,7 +259,16 @@ test_expect_success 'setup 8' '\n \tgit mv a e &&\n \tgit add e &&\n \ttest_tick &&\n-\tgit commit -m \"rename a->e\"\n+\tgit commit -m \"rename a->e\" &&\n+\tif test_have_prereq SYMLINKS\n+\tthen\n+\t\tgit checkout rename-ln &&\n+\t\tgit mv a e &&\n+\t\tln -s e a &&\n+\t\tgit add a e &&\n+\t\ttest_tick &&\n+\t\tgit commit -m \"rename a->e, symlink a->e\"\n+\tfi\n '\n \n test_expect_success 'setup 9' '\n@@ -615,4 +628,26 @@ test_expect_success 'merge-recursive copy vs. rename' '\n \ttest_cmp expected actual\n '\n \n+if test_have_prereq SYMLINKS\n+then\n+\ttest_expect_success 'merge-recursive rename vs. rename/symlink' '\n+\n+\t\tgit checkout -f rename &&\n+\t\tgit merge rename-ln &&\n+\t\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n+\t\t(\n+\t\t\techo \"100644 blob $o0\tb\"\n+\t\t\techo \"100644 blob $o0\tc\"\n+\t\t\techo \"100644 blob $o0\td/e\"\n+\t\t\techo \"100644 blob $o0\te\"\n+\t\t\techo \"100644 $o0 0\tb\"\n+\t\t\techo \"100644 $o0 0\tc\"\n+\t\t\techo \"100644 $o0 0\td/e\"\n+\t\t\techo \"100644 $o0 0\te\"\n+\t\t) >expected &&\n+\t\ttest_cmp expected actual\n+\t'\n+fi\n+\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151087","messageId":"1284971350-30590-3-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 02/37] merge-recursive: Restructure showing how to chain more process_* functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:35Z","receivedAt":"2010-09-20T08:28:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Junio C Hamano <gitster@pobox.com>\n\nIn 3734893 (merge-recursive: Fix D/F conflicts 2010-07-09),\nprocess_df_entry() was added to process_renames() and process_entry() but\nin a somewhat restrictive manner.  Modify the code slightly to make it\nclearer how we could chain more such functions if necessary, and alter\nprocess_df_entry() to handle such chaining.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   11 ++++++++---\n 1 files changed, 8 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex bf611ae..4d9c165 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1260,9 +1260,8 @@ static int process_df_entry(struct merge_options *o,\n \tconst char *conf;\n \tstruct stat st;\n \n-\t/* We currently only handle D->F cases */\n-\tassert((!o_sha && a_sha && !b_sha) ||\n-\t       (!o_sha && !a_sha && b_sha));\n+\tif (! ((!o_sha && a_sha && !b_sha) || (!o_sha && !a_sha && b_sha)))\n+\t\treturn 1; /* we don't handle non D-F cases */\n \n \tentry->processed = 1;\n \n@@ -1351,6 +1350,12 @@ int merge_trees(struct merge_options *o,\n \t\t\t\t&& !process_df_entry(o, path, e))\n \t\t\t\tclean = 0;\n \t\t}\n+\t\tfor (i = 0; i < entries->nr; i++) {\n+\t\t\tstruct stage_data *e = entries->items[i].util;\n+\t\t\tif (!e->processed)\n+\t\t\t\tdie(\"Unprocessed path??? %s\",\n+\t\t\t\t    entries->items[i].string);\n+\t\t}\n \n \t\tstring_list_clear(re_merge, 0);\n \t\tstring_list_clear(re_head, 0);\n-- \n1.7.3.271.g16009\n"},{"id":"151054","messageId":"1284971350-30590-4-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 03/37] t6032: Add a test checking for excessive output from merge","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:36Z","receivedAt":"2010-09-20T08:28:36Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Previous D/F fixes I submitted (5a2580d and ae74548) had caused merge to\nbecome excessively spammy, which was fixed in 96ecac6 (merge-recursive:\nAvoid excessive output for and reprocessing of renames 2010-08-20).  Add a\nnew test to avoid repeating that mistake with my several upcoming changes.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6032-merge-large-rename.sh |   30 ++++++++++++++++++++++++++++++\n 1 files changed, 30 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6032-merge-large-rename.sh b/t/t6032-merge-large-rename.sh\nindex eac5eba..fdb6c25 100755\n--- a/t/t6032-merge-large-rename.sh\n+++ b/t/t6032-merge-large-rename.sh\n@@ -70,4 +70,34 @@ test_expect_success 'set merge.renamelimit to 5' '\n test_rename 5 ok\n test_rename 6 fail\n \n+test_expect_success 'setup large simple rename' '\n+\tgit config --unset merge.renamelimit &&\n+\tgit config --unset diff.renamelimit &&\n+\n+\tgit reset --hard initial &&\n+\tfor i in $(count 200); do\n+\t\tmake_text foo bar baz >$i\n+\tdone &&\n+\tgit add . &&\n+\tgit commit -m create-files &&\n+\n+\tgit branch simple-change &&\n+\tgit checkout -b simple-rename &&\n+\n+\tmkdir builtin &&\n+\tgit mv [0-9]* builtin/ &&\n+\tgit commit -m renamed &&\n+\n+\tgit checkout simple-change &&\n+\t>unrelated-change &&\n+\tgit add unrelated-change &&\n+\tgit commit -m unrelated-change\n+'\n+\n+test_expect_success 'massive simple rename does not spam added files' '\n+\tunset GIT_MERGE_VERBOSITY &&\n+\tgit merge --no-stat simple-rename | grep -v Removing >output &&\n+\ttest 5 -gt \"$(wc -l < output)\"\n+'\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151089","messageId":"1284971350-30590-5-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 04/37] t6022: Add test combinations of {content conflict?, D/F conflict remains?}","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:37Z","receivedAt":"2010-09-20T08:28:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Add testing of the various ways that a renamed file to a path involved in\na directory/file conflict may be involved in.  This includes whether or not\nthere are conflicts of the contents of the renamed file (if the file was\nmodified on both sides of history), and whether the directory from the\nother side of the merge will disappear as a result of the merge or not.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6022-merge-rename.sh |  128 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 128 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex b66544b..a992206 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -3,6 +3,11 @@\n test_description='Merge-recursive merging renames'\n . ./test-lib.sh\n \n+modify () {\n+\tsed -e \"$1\" <\"$2\" >\"$2.x\" &&\n+\tmv \"$2.x\" \"$2\"\n+}\n+\n test_expect_success setup \\\n '\n cat >A <<\\EOF &&\n@@ -341,4 +346,127 @@ test_expect_success 'merge of identical changes in a renamed file' '\n \tGIT_MERGE_VERBOSITY=3 git merge change+rename | grep \"^Skipped B\"\n '\n \n+test_expect_success 'setup for rename + d/f conflicts' '\n+\tgit reset --hard &&\n+\tgit checkout --orphan dir-in-way &&\n+\tgit rm -rf . &&\n+\n+\tmkdir sub &&\n+\tmkdir dir &&\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n9\\n10\\n\" >sub/file &&\n+\techo foo >dir/file-in-the-way &&\n+\tgit add -A &&\n+\tgit commit -m \"Common commmit\" &&\n+\n+\techo 11 >>sub/file &&\n+\techo more >>dir/file-in-the-way &&\n+\tgit add -u &&\n+\tgit commit -m \"Commit to merge, with dir in the way\" &&\n+\n+\tgit checkout -b dir-not-in-way &&\n+\tgit reset --soft HEAD^ &&\n+\tgit rm -rf dir &&\n+\tgit commit -m \"Commit to merge, with dir removed\" -- dir sub/file &&\n+\n+\tgit checkout -b renamed-file-has-no-conflicts dir-in-way~1 &&\n+\tgit rm -rf dir &&\n+\tgit rm sub/file &&\n+\tprintf \"1\\n2\\n3\\n4\\n5555\\n6\\n7\\n8\\n9\\n10\\n\" >dir &&\n+\tgit add dir &&\n+\tgit commit -m \"Independent change\" &&\n+\n+\tgit checkout -b renamed-file-has-conflicts dir-in-way~1 &&\n+\tgit rm -rf dir &&\n+\tgit mv sub/file dir &&\n+\techo 12 >>dir &&\n+\tgit add dir &&\n+\tgit commit -m \"Conflicting change\"\n+'\n+\n+printf \"1\\n2\\n3\\n4\\n5555\\n6\\n7\\n8\\n9\\n10\\n11\\n\" >expected\n+\n+test_expect_success 'Rename+D/F conflict; renamed file merges + dir not in way' '\n+\tgit reset --hard &&\n+\tgit checkout -q renamed-file-has-no-conflicts^0 &&\n+\tgit merge --strategy=recursive dir-not-in-way &&\n+\tgit diff --quiet &&\n+\ttest -f dir &&\n+\ttest_cmp expected dir\n+'\n+\n+test_expect_failure 'Rename+D/F conflict; renamed file merges but dir in way' '\n+\tgit reset --hard &&\n+\trm -rf dir~* &&\n+\tgit checkout -q renamed-file-has-no-conflicts^0 &&\n+\ttest_must_fail git merge --strategy=recursive dir-in-way >output &&\n+\n+\tgrep \"CONFLICT (delete/modify): dir/file-in-the-way\" output &&\n+\tgrep \"Auto-merging dir\" output &&\n+\tgrep \"Adding as dir~HEAD instead\" output &&\n+\n+\ttest 2 = \"$(git ls-files -u | wc -l)\" &&\n+\ttest 2 = \"$(git ls-files -u dir/file-in-the-way | wc -l)\" &&\n+\n+\ttest_must_fail git diff --quiet &&\n+\ttest_must_fail git diff --cached --quiet &&\n+\n+\ttest -f dir/file-in-the-way &&\n+\ttest -f dir~HEAD &&\n+\ttest_cmp expected dir~HEAD\n+'\n+\n+cat >expected <<\\EOF &&\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+<<<<<<< HEAD\n+12\n+=======\n+11\n+>>>>>>> dir-not-in-way\n+EOF\n+\n+test_expect_failure 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '\n+\tgit reset --hard &&\n+\trm -rf dir~* &&\n+\tgit checkout -q renamed-file-has-conflicts^0 &&\n+\ttest_must_fail git merge --strategy=recursive dir-not-in-way &&\n+\n+\ttest 3 = \"$(git ls-files -u | wc -l)\" &&\n+\ttest 3 = \"$(git ls-files -u dir | wc -l)\" &&\n+\n+\ttest_must_fail git diff --quiet &&\n+\ttest_must_fail git diff --cached --quiet &&\n+\n+\ttest -f dir &&\n+\ttest_cmp expected dir\n+'\n+\n+test_expect_failure 'Rename+D/F conflict; renamed file cannot merge and dir in the way' '\n+\tmodify s/dir-not-in-way/dir-in-way/ expected &&\n+\n+\tgit reset --hard &&\n+\trm -rf dir~* &&\n+\tgit checkout -q renamed-file-has-conflicts^0 &&\n+\ttest_must_fail git merge --strategy=recursive dir-in-way &&\n+\n+\ttest 5 = \"$(git ls-files -u | wc -l)\" &&\n+\ttest 3 = \"$(git ls-files -u dir | grep -v file-in-the-way | wc -l)\" &&\n+\ttest 2 = \"$(git ls-files -u dir/file-in-the-way | wc -l)\" &&\n+\n+\ttest_must_fail git diff --quiet &&\n+\ttest_must_fail git diff --cached --quiet &&\n+\n+\ttest -f dir/file-in-the-way &&\n+\ttest -f dir~HEAD &&\n+\ttest_cmp expected dir~HEAD\n+'\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151055","messageId":"1284971350-30590-6-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 05/37] t6022: Add tests for reversing order of merges when D/F conflicts present","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:38Z","receivedAt":"2010-09-20T08:28:38Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When merging two branches with some path involved in a D/F conflict, the\nchoice of which branch to merge into the other matters for (at least) two\nreasons: (1) whether the working copy has a directory full of files that\nis in the way of a file, or a file exists that is in the way of a\ndirectory of files, (2) when the directory full of files does not disappear\ndue to the merge, what files at the same paths should be renamed to\n(e.g. filename~HEAD vs. filename~otherbranch).\n\nAdd some tests that reverse the merge order of two other tests, and which\nverify the contents are as expected (namely, that the results are identical\nother than modified-for-uniqueness filenames involving branch names).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6022-merge-rename.sh |   58 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 58 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex a992206..2839dfb 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -415,6 +415,28 @@ test_expect_failure 'Rename+D/F conflict; renamed file merges but dir in way' '\n \ttest_cmp expected dir~HEAD\n '\n \n+test_expect_failure 'Same as previous, but merged other way' '\n+\tgit reset --hard &&\n+\trm -rf dir~* &&\n+\tgit checkout -q dir-in-way^0 &&\n+\ttest_must_fail git merge --strategy=recursive renamed-file-has-no-conflicts >output 2>errors &&\n+\n+\t! grep \"error: refusing to lose untracked file at\" errors &&\n+\tgrep \"CONFLICT (delete/modify): dir/file-in-the-way\" output &&\n+\tgrep \"Auto-merging dir\" output &&\n+\tgrep \"Adding as dir~renamed-file-has-no-conflicts instead\" output &&\n+\n+\ttest 2 = \"$(git ls-files -u | wc -l)\" &&\n+\ttest 2 = \"$(git ls-files -u dir/file-in-the-way | wc -l)\" &&\n+\n+\ttest_must_fail git diff --quiet &&\n+\ttest_must_fail git diff --cached --quiet &&\n+\n+\ttest -f dir/file-in-the-way &&\n+\ttest -f dir~renamed-file-has-no-conflicts &&\n+\ttest_cmp expected dir~renamed-file-has-no-conflicts\n+'\n+\n cat >expected <<\\EOF &&\n 1\n 2\n@@ -469,4 +491,40 @@ test_expect_failure 'Rename+D/F conflict; renamed file cannot merge and dir in t\n \ttest_cmp expected dir~HEAD\n '\n \n+cat >expected <<\\EOF &&\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+<<<<<<< HEAD\n+11\n+=======\n+12\n+>>>>>>> renamed-file-has-conflicts\n+EOF\n+\n+test_expect_failure 'Same as previous, but merged other way' '\n+\tgit reset --hard &&\n+\trm -rf dir~* &&\n+\tgit checkout -q dir-in-way^0 &&\n+\ttest_must_fail git merge --strategy=recursive renamed-file-has-conflicts &&\n+\n+\ttest 5 = \"$(git ls-files -u | wc -l)\" &&\n+\ttest 3 = \"$(git ls-files -u dir | grep -v file-in-the-way | wc -l)\" &&\n+\ttest 2 = \"$(git ls-files -u dir/file-in-the-way | wc -l)\" &&\n+\n+\ttest_must_fail git diff --quiet &&\n+\ttest_must_fail git diff --cached --quiet &&\n+\n+\ttest -f dir/file-in-the-way &&\n+\ttest -f dir~renamed-file-has-conflicts &&\n+\ttest_cmp expected dir~renamed-file-has-conflicts\n+'\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151057","messageId":"1284971350-30590-7-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 06/37] t6022: Add tests with both rename source & dest involved in D/F conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:39Z","receivedAt":"2010-09-20T08:28:39Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Having the source of a rename be involved in a directory/file conflict does\nnot currently pose any difficulties to the current merge-recursive\nalgorithm (in contrast to destinations of renames and D/F conflicts).\nHowever, combining the two seemed like good testcases to include for\ncompleteness.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6022-merge-rename.sh |   38 ++++++++++++++++++++++++++++++++++++++\n 1 files changed, 38 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 2839dfb..2af863c 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -527,4 +527,42 @@ test_expect_failure 'Same as previous, but merged other way' '\n \ttest_cmp expected dir~renamed-file-has-conflicts\n '\n \n+test_expect_success 'setup both rename source and destination involved in D/F conflict' '\n+\tgit reset --hard &&\n+\tgit checkout --orphan rename-dest &&\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\n+\tmkdir one &&\n+\techo stuff >one/file &&\n+\tgit add -A &&\n+\tgit commit -m \"Common commmit\" &&\n+\n+\tgit mv one/file destdir &&\n+\tgit commit -m \"Renamed to destdir\" &&\n+\n+\tgit checkout -b source-conflict HEAD~1 &&\n+\tgit rm -rf one &&\n+\tmkdir destdir &&\n+\ttouch one destdir/foo &&\n+\tgit add -A &&\n+\tgit commit -m \"Conflicts in the way\"\n+'\n+\n+test_expect_failure 'both rename source and destination involved in D/F conflict' '\n+\tgit reset --hard &&\n+\trm -rf dir~* &&\n+\tgit checkout -q rename-dest^0 &&\n+\ttest_must_fail git merge --strategy=recursive source-conflict &&\n+\n+\ttest 1 = \"$(git ls-files -u | wc -l)\" &&\n+\n+\ttest_must_fail git diff --quiet &&\n+\n+\ttest -f destdir/foo &&\n+\ttest -f one &&\n+\ttest -f destdir~HEAD &&\n+\ttest \"stuff\" = \"$(cat destdir~HEAD)\"\n+'\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151056","messageId":"1284971350-30590-8-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 07/37] t6022: Add paired rename+D/F conflict: (two/file, one/file) -> (one, two)","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:40Z","receivedAt":"2010-09-20T08:28:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"An interesting testcase is having two files each in their own subdirectory\ngetting renamed to the toplevel at the directory pathname of the other.\nQuestions arise as to whether the order of operations matters and whether\nthe directories can correctly get out of the way and make room for the\nnew files.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6022-merge-rename.sh |   63 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 63 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 2af863c..a38b383 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -565,4 +565,67 @@ test_expect_failure 'both rename source and destination involved in D/F conflict\n \ttest \"stuff\" = \"$(cat destdir~HEAD)\"\n '\n \n+test_expect_success 'setup pair rename to parent of other (D/F conflicts)' '\n+\tgit reset --hard &&\n+\tgit checkout --orphan rename-two &&\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\n+\tmkdir one &&\n+\tmkdir two &&\n+\techo stuff >one/file &&\n+\techo other >two/file &&\n+\tgit add -A &&\n+\tgit commit -m \"Common commmit\" &&\n+\n+\tgit rm -rf one &&\n+\tgit mv two/file one &&\n+\tgit commit -m \"Rename two/file -> one\" &&\n+\n+\tgit checkout -b rename-one HEAD~1 &&\n+\tgit rm -rf two &&\n+\tgit mv one/file two &&\n+\trm -r one &&\n+\tgit commit -m \"Rename one/file -> two\"\n+'\n+\n+test_expect_failure 'pair rename to parent of other (D/F conflicts) w/ untracked dir' '\n+\tgit checkout -q rename-one^0 &&\n+\tmkdir one &&\n+\ttest_must_fail git merge --strategy=recursive rename-two &&\n+\n+\ttest 2 = \"$(git ls-files -u | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u one | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u two | wc -l)\" &&\n+\n+\ttest_must_fail git diff --quiet &&\n+\n+\ttest 4 = $(find . | grep -v .git | wc -l) &&\n+\n+\ttest -d one &&\n+\ttest -f one~rename-two &&\n+\ttest -f two &&\n+\ttest \"other\" = $(cat one~rename-two) &&\n+\ttest \"stuff\" = $(cat two)\n+'\n+\n+test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean start' '\n+\tgit reset --hard &&\n+\tgit clean -fdqx &&\n+\ttest_must_fail git merge --strategy=recursive rename-two &&\n+\n+\ttest 2 = \"$(git ls-files -u | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u one | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u two | wc -l)\" &&\n+\n+\ttest_must_fail git diff --quiet &&\n+\n+\ttest 3 = $(find . | grep -v .git | wc -l) &&\n+\n+\ttest -f one &&\n+\ttest -f two &&\n+\ttest \"other\" = $(cat one) &&\n+\ttest \"stuff\" = $(cat two)\n+'\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151059","messageId":"1284971350-30590-9-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 08/37] t6022: Add tests for rename/rename combined with D/F conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:41Z","receivedAt":"2010-09-20T08:28:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Add tests where one file is renamed to two different paths in different\nsides of history, and where each of the new files matches the name of a\ndirectory from the opposite side of history.  Include tests for both the\ncase where the merge results in those directories not being cleanly\nremoved, and where those directories are cleanly removed during the merge.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6022-merge-rename.sh |   79 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 79 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex a38b383..02dea16 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -628,4 +628,83 @@ test_expect_success 'pair rename to parent of other (D/F conflicts) w/ clean sta\n \ttest \"stuff\" = $(cat two)\n '\n \n+test_expect_success 'setup rename of one file to two, with directories in the way' '\n+\tgit reset --hard &&\n+\tgit checkout --orphan first-rename &&\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\n+\techo stuff >original &&\n+\tgit add -A &&\n+\tgit commit -m \"Common commmit\" &&\n+\n+\tmkdir two &&\n+\t>two/file &&\n+\tgit add two/file &&\n+\tgit mv original one &&\n+\tgit commit -m \"Put two/file in the way, rename to one\" &&\n+\n+\tgit checkout -b second-rename HEAD~1 &&\n+\tmkdir one &&\n+\t>one/file &&\n+\tgit add one/file &&\n+\tgit mv original two &&\n+\tgit commit -m \"Put one/file in the way, rename to two\"\n+'\n+\n+test_expect_failure 'check handling of differently renamed file with D/F conflicts' '\n+\tgit checkout -q first-rename^0 &&\n+\ttest_must_fail git merge --strategy=recursive second-rename &&\n+\n+\ttest 5 = \"$(git ls-files -s | wc -l)\" &&\n+\ttest 3 = \"$(git ls-files -u | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u one | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u two | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u original | wc -l)\" &&\n+\ttest 2 = \"$(git ls-files -o | wc -l)\" &&\n+\n+\ttest -f one/file &&\n+\ttest -f two/file &&\n+\ttest -f one~HEAD &&\n+\ttest -f two~second-rename &&\n+\t! test -f original\n+'\n+\n+test_expect_success 'setup rename one file to two; directories moving out of the way' '\n+\tgit reset --hard &&\n+\tgit checkout --orphan first-rename-redo &&\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\n+\techo stuff >original &&\n+\tmkdir one two &&\n+\ttouch one/file two/file &&\n+\tgit add -A &&\n+\tgit commit -m \"Common commmit\" &&\n+\n+\tgit rm -rf one &&\n+\tgit mv original one &&\n+\tgit commit -m \"Rename to one\" &&\n+\n+\tgit checkout -b second-rename-redo HEAD~1 &&\n+\tgit rm -rf two &&\n+\tgit mv original two &&\n+\tgit commit -m \"Rename to two\"\n+'\n+\n+test_expect_failure 'check handling of differently renamed file with D/F conflicts' '\n+\tgit checkout -q first-rename-redo^0 &&\n+\ttest_must_fail git merge --strategy=recursive second-rename-redo &&\n+\n+\ttest 3 = \"$(git ls-files -u | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u one | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u two | wc -l)\" &&\n+\ttest 1 = \"$(git ls-files -u original | wc -l)\" &&\n+\ttest 0 = \"$(git ls-files -o | wc -l)\" &&\n+\n+\ttest -f one &&\n+\ttest -f two &&\n+\t! test -f original\n+'\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151058","messageId":"1284971350-30590-10-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 09/37] t6020: Modernize style a bit","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:42Z","receivedAt":"2010-09-20T08:28:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6020-merge-df.sh |   35 ++++++++++++++++++++---------------\n 1 files changed, 20 insertions(+), 15 deletions(-)\n\ndiff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh\nindex 490d397..8662207 100755\n--- a/t/t6020-merge-df.sh\n+++ b/t/t6020-merge-df.sh\n@@ -6,21 +6,26 @@\n test_description='Test merge with directory/file conflicts'\n . ./test-lib.sh\n \n-test_expect_success 'prepare repository' \\\n-'echo \"Hello\" > init &&\n-git add init &&\n-git commit -m \"Initial commit\" &&\n-git branch B &&\n-mkdir dir &&\n-echo \"foo\" > dir/foo &&\n-git add dir/foo &&\n-git commit -m \"File: dir/foo\" &&\n-git checkout B &&\n-echo \"file dir\" > dir &&\n-git add dir &&\n-git commit -m \"File: dir\"'\n-\n-test_expect_code 1 'Merge with d/f conflicts' 'git merge \"merge msg\" B master'\n+test_expect_success 'prepare repository' '\n+\techo Hello >init &&\n+\tgit add init &&\n+\tgit commit -m initial &&\n+\n+\tgit branch B &&\n+\tmkdir dir &&\n+\techo foo >dir/foo &&\n+\tgit add dir/foo &&\n+\tgit commit -m \"File: dir/foo\" &&\n+\n+\tgit checkout B &&\n+\techo file dir >dir &&\n+\tgit add dir &&\n+\tgit commit -m \"File: dir\"\n+'\n+\n+test_expect_success 'Merge with d/f conflicts' '\n+\ttest_must_fail git merge master\n+'\n \n test_expect_success 'F/D conflict' '\n \tgit reset --hard &&\n-- \n1.7.3.271.g16009\n"},{"id":"151073","messageId":"1284971350-30590-11-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 10/37] t6020: Add a testcase for modify/delete + directory/file conflict","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:43Z","receivedAt":"2010-09-20T08:28:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6020-merge-df.sh |   47 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 47 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh\nindex 8662207..bc9db1a 100755\n--- a/t/t6020-merge-df.sh\n+++ b/t/t6020-merge-df.sh\n@@ -50,4 +50,51 @@ test_expect_success 'F/D conflict' '\n \tgit merge master\n '\n \n+test_expect_success 'setup modify/delete + directory/file conflict' '\n+\tgit checkout --orphan modify &&\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\n+\tprintf \"a\\nb\\nc\\nd\\ne\\nf\\ng\\nh\\n\" >letters &&\n+\tgit add letters &&\n+\tgit commit -m initial &&\n+\n+\techo i >>letters &&\n+\tgit add letters &&\n+\tgit commit -m modified &&\n+\n+\tgit checkout -b delete HEAD^ &&\n+\tgit rm letters &&\n+\tmkdir letters &&\n+\t>letters/file &&\n+\tgit add letters &&\n+\tgit commit -m deleted\n+'\n+\n+test_expect_failure 'modify/delete + directory/file conflict' '\n+\tgit checkout delete^0 &&\n+\ttest_must_fail git merge modify &&\n+\n+\ttest 3 = $(git ls-files -s | wc -l) &&\n+\ttest 2 = $(git ls-files -u | wc -l) &&\n+\ttest 1 = $(git ls-files -o | wc -l) &&\n+\n+\ttest -f letters/file &&\n+\ttest -f letters~modify\n+'\n+\n+test_expect_failure 'modify/delete + directory/file conflict; other way' '\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\tgit checkout modify^0 &&\n+\ttest_must_fail git merge delete &&\n+\n+\ttest 3 = $(git ls-files -s | wc -l) &&\n+\ttest 2 = $(git ls-files -u | wc -l) &&\n+\ttest 1 = $(git ls-files -o | wc -l) &&\n+\n+\ttest -f letters/file &&\n+\ttest -f letters~HEAD\n+'\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151063","messageId":"1284971350-30590-12-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 11/37] t6036: Test index and worktree state, not just that merge fails","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:44Z","receivedAt":"2010-09-20T08:28:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"c94736a (merge-recursive: don't segfault while handling rename clashes\n2009-07-30) added this testcase with an interesting corner case test,\nwhich previously had cased git to segfault.  This test ensures that the\nsegfault does not return and that the merge correctly fails; just add\nsome checks that verify the state of the index and worktree after the merge\nare correct.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh |   24 +++++++++++++++++++++---\n 1 files changed, 21 insertions(+), 3 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex b874141..1755073 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -14,7 +14,7 @@ test_description='recursive merge corner cases'\n #  R1  R2\n #\n \n-test_expect_success setup '\n+test_expect_success 'setup basic criss-cross + rename with no modifications' '\n \tten=\"0 1 2 3 4 5 6 7 8 9\"\n \tfor i in $ten\n \tdo\n@@ -45,11 +45,29 @@ test_expect_success setup '\n \tgit tag R2\n '\n \n-test_expect_success merge '\n+test_expect_success 'merge simple rename+criss-cross with no modifications' '\n \tgit reset --hard &&\n \tgit checkout L2^0 &&\n \n-\ttest_must_fail git merge -s recursive R2^0\n+\ttest_must_fail git merge -s recursive R2^0 &&\n+\n+\ttest 5 = $(git ls-files -s | wc -l) &&\n+\ttest 3 = $(git ls-files -u | wc -l) &&\n+\ttest 0 = $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse :0:one) = $(git rev-parse L2:one) &&\n+\ttest $(git rev-parse :0:two) = $(git rev-parse R2:two) &&\n+\ttest $(git rev-parse :2:three) = $(git rev-parse L2:three) &&\n+\ttest $(git rev-parse :3:three) = $(git rev-parse R2:three) &&\n+\n+\tcp two merged &&\n+\t>empty &&\n+\ttest_must_fail git merge-file \\\n+\t\t-L \"Temporary merge branch 2\" \\\n+\t\t-L \"\" \\\n+\t\t-L \"Temporary merge branch 1\" \\\n+\t\tmerged empty one &&\n+\ttest $(git rev-parse :1:three) = $(git hash-object merged)\n '\n \n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151062","messageId":"1284971350-30590-13-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 12/37] t6036: Add a second testcase similar to the first but with content changes","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:45Z","receivedAt":"2010-09-20T08:28:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"c94736a (merge-recursive: don't segfault while handling rename clashes\n2009-07-30) added t6036 with a testcase that involved dual renames and a\ncriss-cross merge.  Add a test that is nearly identical, but which also\ninvolves content modification -- a case git currently does not merge\ncorrectly.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh |   76 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 76 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 1755073..9206c22 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -70,4 +70,80 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '\n \ttest $(git rev-parse :1:three) = $(git hash-object merged)\n '\n \n+#\n+# Same as before, but modify L1 slightly:\n+#\n+#  L1m L2\n+#   o---o\n+#  / \\ / \\\n+# o   X   ?\n+#  \\ / \\ /\n+#   o---o\n+#  R1  R2\n+#\n+\n+test_expect_success 'setup criss-cross + rename merges with basic modification' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tten=\"0 1 2 3 4 5 6 7 8 9\"\n+\tfor i in $ten\n+\tdo\n+\t\techo line $i in a sample file\n+\tdone >one &&\n+\tfor i in $ten\n+\tdo\n+\t\techo line $i in another sample file\n+\tdone >two &&\n+\tgit add one two &&\n+\ttest_tick && git commit -m initial &&\n+\n+\tgit branch L1 &&\n+\tgit checkout -b R1 &&\n+\tgit mv one three &&\n+\techo more >>two &&\n+\tgit add two &&\n+\ttest_tick && git commit -m R1 &&\n+\n+\tgit checkout L1 &&\n+\tgit mv two three &&\n+\ttest_tick && git commit -m L1 &&\n+\n+\tgit checkout L1^0 &&\n+\ttest_tick && git merge -s ours R1 &&\n+\tgit tag L2 &&\n+\n+\tgit checkout R1^0 &&\n+\ttest_tick && git merge -s ours L1 &&\n+\tgit tag R2\n+'\n+\n+test_expect_failure 'merge criss-cross + rename merges with basic modification' '\n+\tgit reset --hard &&\n+\tgit checkout L2^0 &&\n+\n+\ttest_must_fail git merge -s recursive R2^0 &&\n+\n+\ttest 5 = $(git ls-files -s | wc -l) &&\n+\ttest 3 = $(git ls-files -u | wc -l) &&\n+\ttest 0 = $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse :0:one) = $(git rev-parse L2:one) &&\n+\ttest $(git rev-parse :0:two) = $(git rev-parse R2:two) &&\n+\ttest $(git rev-parse :2:three) = $(git rev-parse L2:three) &&\n+\ttest $(git rev-parse :3:three) = $(git rev-parse R2:three) &&\n+\n+\thead -n 10 two >merged &&\n+\tcp one merge-me &&\n+\t>empty &&\n+\ttest_must_fail git merge-file \\\n+\t\t-L \"Temporary merge branch 2\" \\\n+\t\t-L \"\" \\\n+\t\t-L \"Temporary merge branch 1\" \\\n+\t\tmerged empty merge-me &&\n+\ttest $(git rev-parse :1:three) = $(git hash-object merged)\n+'\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151065","messageId":"1284971350-30590-14-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 13/37] t6036: Add testcase for undetected conflict","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:46Z","receivedAt":"2010-09-20T08:28:46Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If merging two lines of development involves a rename/add conflict, and two\ndifferent people make such a merge but resolve it differently, and then\nsomeone tries to merge the resulting two merges, then they should clearly\nget a conflict due to the different resolutions from the previous\ndevelopers.  However, in some such cases the conflict would not be detected\nand git would silently accept one of the two versions being merged as the\nfinal merge resolution.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh |   85 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 85 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 9206c22..6c2b2bf 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -146,4 +146,89 @@ test_expect_failure 'merge criss-cross + rename merges with basic modification'\n \ttest $(git rev-parse :1:three) = $(git hash-object merged)\n '\n \n+#\n+# For the next test, we start with three commits in two lines of development\n+# which setup a rename/add conflict:\n+#   Commit A: File 'a' exists\n+#   Commit B: Rename 'a' -> 'new_a'\n+#   Commit C: Modify 'a', create different 'new_a'\n+# Later, two different people merge and resolve differently:\n+#   Commit D: Merge B & C, ignoring separately created 'new_a'\n+#   Commit E: Merge B & C making use of some piece of secondary 'new_a'\n+# Finally, someone goes to merge D & E.  Does git detect the conflict?\n+#\n+#      B   D\n+#      o---o\n+#     / \\ / \\\n+#  A o   X   ? F\n+#     \\ / \\ /\n+#      o---o\n+#      C   E\n+#\n+\n+test_expect_success 'setup differently handled merges of rename/add conflict' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tprintf \"0\\n1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n9\\n\" >a &&\n+\tgit add a &&\n+\ttest_tick && git commit -m A &&\n+\n+\tgit branch B &&\n+\tgit checkout -b C &&\n+\techo 10 >>a &&\n+\techo \"other content\" >>new_a &&\n+\tgit add a new_a &&\n+\ttest_tick && git commit -m C &&\n+\n+\tgit checkout B &&\n+\tgit mv a new_a &&\n+\ttest_tick && git commit -m B &&\n+\n+\tgit checkout B^0 &&\n+\ttest_must_fail git merge C &&\n+\tgit clean -f &&\n+\ttest_tick && git commit -m D &&\n+\tgit tag D &&\n+\n+\tgit checkout C^0 &&\n+\ttest_must_fail git merge B &&\n+\trm new_a~HEAD new_a &&\n+\tprintf \"Incorrectly merged content\" >>new_a &&\n+\tgit add -u &&\n+\ttest_tick && git commit -m E &&\n+\tgit tag E\n+'\n+\n+test_expect_failure 'git detects differently handled merges conflict' '\n+\tgit reset --hard &&\n+\tgit checkout D^0 &&\n+\n+\tgit merge -s recursive E^0 && {\n+\t\techo \"BAD: should have conflicted\"\n+\t\ttest \"Incorrectly merged content\" = \"$(cat new_a)\" &&\n+\t\t\techo \"BAD: Silently accepted wrong content\"\n+\t\treturn 1\n+\t}\n+\n+\ttest 3 = $(git ls-files -s | wc -l) &&\n+\ttest 3 = $(git ls-files -u | wc -l) &&\n+\ttest 0 = $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse :2:new_a) = $(git rev-parse D:new_a) &&\n+\ttest $(git rev-parse :3:new_a) = $(git rev-parse E:new_a) &&\n+\n+\tgit cat-file -p B:new_a >>merged &&\n+\tgit cat-file -p C:new_a >>merge-me &&\n+\t>empty &&\n+\ttest_must_fail git merge-file \\\n+\t\t-L \"Temporary merge branch 2\" \\\n+\t\t-L \"\" \\\n+\t\t-L \"Temporary merge branch 1\" \\\n+\t\tmerged empty merge-me &&\n+\ttest $(git rev-parse :1:new_a) = $(git hash-object merged)\n+'\n+\n test_done\n-- \n1.7.3.271.g16009\n"},{"id":"151060","messageId":"1284971350-30590-15-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 14/37] merge-recursive: Small code clarification -- variable name and comments","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:47Z","receivedAt":"2010-09-20T08:28:47Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"process_renames() had a variable named \"stage\" and derived variables\nsrc_other and dst_other whose purpose was not immediately obvious; also,\nI want to extend the scope of this variable and use it later, so it should\nhave a more descriptive name.  Do so, and add a brief comment explaining\nhow it is used and what it relates to.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   20 ++++++++++++++------\n 1 files changed, 14 insertions(+), 6 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 4d9c165..8f45cec 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -925,15 +925,23 @@ static int process_renames(struct merge_options *o,\n \t\t\tstruct string_list_item *item;\n \t\t\t/* we only use sha1 and mode of these */\n \t\t\tstruct diff_filespec src_other, dst_other;\n-\t\t\tint try_merge, stage = a_renames == renames1 ? 3: 2;\n+\t\t\tint try_merge;\n \n-\t\t\tremove_file(o, 1, ren1_src, o->call_depth || stage == 3);\n+\t\t\t/*\n+\t\t\t * unpack_trees loads entries from common-commit\n+\t\t\t * into stage 1, from head-commit into stage 2, and\n+\t\t\t * from merge-commit into stage 3.  We keep track\n+\t\t\t * of which side corresponds to the rename.\n+\t\t\t */\n+\t\t\tint renamed_stage = a_renames == renames1 ? 2 : 3;\n+\t\t\tint other_stage =   a_renames == renames1 ? 3 : 2;\n \n-\t\t\thashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha);\n-\t\t\tsrc_other.mode = ren1->src_entry->stages[stage].mode;\n-\t\t\thashcpy(dst_other.sha1, ren1->dst_entry->stages[stage].sha);\n-\t\t\tdst_other.mode = ren1->dst_entry->stages[stage].mode;\n+\t\t\tremove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);\n \n+\t\t\thashcpy(src_other.sha1, ren1->src_entry->stages[other_stage].sha);\n+\t\t\tsrc_other.mode = ren1->src_entry->stages[other_stage].mode;\n+\t\t\thashcpy(dst_other.sha1, ren1->dst_entry->stages[other_stage].sha);\n+\t\t\tdst_other.mode = ren1->dst_entry->stages[other_stage].mode;\n \t\t\ttry_merge = 0;\n \n \t\t\tif (sha_eq(src_other.sha1, null_sha1)) {\n-- \n1.7.3.271.g16009\n"},{"id":"151064","messageId":"1284971350-30590-16-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 15/37] merge-recursive: Rename conflict_rename_rename*() for clarity","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:48Z","receivedAt":"2010-09-20T08:28:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"The names conflict_rename_rename and conflict_rename_rename_2 did not make\nit clear what they were handling.  Since the first of these handles one\nfile being renamed in both branches to different files, while the latter\nhandles two different files being renamed to the same thing, add a little\n'1to2' and '2to1' suffix on these and an explanatory comment to make their\nintent clearer.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   26 ++++++++++++++------------\n 1 files changed, 14 insertions(+), 12 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 8f45cec..cc1ab92 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -731,12 +731,13 @@ static struct merge_file_info merge_file(struct merge_options *o,\n \treturn result;\n }\n \n-static void conflict_rename_rename(struct merge_options *o,\n-\t\t\t\t   struct rename *ren1,\n-\t\t\t\t   const char *branch1,\n-\t\t\t\t   struct rename *ren2,\n-\t\t\t\t   const char *branch2)\n+static void conflict_rename_rename_1to2(struct merge_options *o,\n+\t\t\t\t\tstruct rename *ren1,\n+\t\t\t\t\tconst char *branch1,\n+\t\t\t\t\tstruct rename *ren2,\n+\t\t\t\t\tconst char *branch2)\n {\n+\t/* One file was renamed in both branches, but to different names. */\n \tchar *del[2];\n \tint delp = 0;\n \tconst char *ren1_dst = ren1->pair->two->path;\n@@ -783,12 +784,13 @@ static void conflict_rename_dir(struct merge_options *o,\n \tfree(new_path);\n }\n \n-static void conflict_rename_rename_2(struct merge_options *o,\n-\t\t\t\t     struct rename *ren1,\n-\t\t\t\t     const char *branch1,\n-\t\t\t\t     struct rename *ren2,\n-\t\t\t\t     const char *branch2)\n+static void conflict_rename_rename_2to1(struct merge_options *o,\n+\t\t\t\t\tstruct rename *ren1,\n+\t\t\t\t\tconst char *branch1,\n+\t\t\t\t\tstruct rename *ren2,\n+\t\t\t\t\tconst char *branch2)\n {\n+\t/* Two files were renamed to the same thing. */\n \tchar *new_path1 = unique_path(o, ren1->pair->two->path, branch1);\n \tchar *new_path2 = unique_path(o, ren2->pair->two->path, branch2);\n \toutput(o, 1, \"Renaming %s to %s and %s to %s instead\",\n@@ -890,7 +892,7 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\tupdate_file(o, 0, ren1->pair->one->sha1,\n \t\t\t\t\t\t    ren1->pair->one->mode, src);\n \t\t\t\t}\n-\t\t\t\tconflict_rename_rename(o, ren1, branch1, ren2, branch2);\n+\t\t\t\tconflict_rename_rename_1to2(o, ren1, branch1, ren2, branch2);\n \t\t\t} else {\n \t\t\t\tstruct merge_file_info mfi;\n \t\t\t\tremove_file(o, 1, ren1_src, 1);\n@@ -1005,7 +1007,7 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t       \"Rename %s->%s in %s\",\n \t\t\t\t       ren1_src, ren1_dst, branch1,\n \t\t\t\t       ren2->pair->one->path, ren2->pair->two->path, branch2);\n-\t\t\t\tconflict_rename_rename_2(o, ren1, branch1, ren2, branch2);\n+\t\t\t\tconflict_rename_rename_2to1(o, ren1, branch1, ren2, branch2);\n \t\t\t} else\n \t\t\t\ttry_merge = 1;\n \n-- \n1.7.3.271.g16009\n"},{"id":"151069","messageId":"1284971350-30590-17-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 16/37] merge-recursive: Nuke rename/directory conflict detection","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:49Z","receivedAt":"2010-09-20T08:28:49Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Since we want to resolve merges in-core and then detect at the end whether\nD/F conflicts remain in the way, we should just apply renames in-core and\nlet logic elsewhere check for D/F conflicts.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   18 ------------------\n 1 files changed, 0 insertions(+), 18 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex cc1ab92..f7591a3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -773,17 +773,6 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t\tfree(del[delp]);\n }\n \n-static void conflict_rename_dir(struct merge_options *o,\n-\t\t\t\tstruct rename *ren1,\n-\t\t\t\tconst char *branch1)\n-{\n-\tchar *new_path = unique_path(o, ren1->pair->two->path, branch1);\n-\toutput(o, 1, \"Renaming %s to %s instead\", ren1->pair->one->path, new_path);\n-\tremove_file(o, 0, ren1->pair->two->path, 0);\n-\tupdate_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path);\n-\tfree(new_path);\n-}\n-\n static void conflict_rename_rename_2to1(struct merge_options *o,\n \t\t\t\t\tstruct rename *ren1,\n \t\t\t\t\tconst char *branch1,\n@@ -1044,13 +1033,6 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\tif (!ren1->dst_entry->stages[2].mode !=\n \t\t\t\t\t    !ren1->dst_entry->stages[3].mode)\n \t\t\t\t\t\tren1->dst_entry->processed = 0;\n-\t\t\t\t} else if (string_list_has_string(&o->current_directory_set, ren1_dst)) {\n-\t\t\t\t\tclean_merge = 0;\n-\t\t\t\t\toutput(o, 1, \"CONFLICT (rename/directory): Rename %s->%s in %s \"\n-\t\t\t\t\t       \" directory %s added in %s\",\n-\t\t\t\t\t       ren1_src, ren1_dst, branch1,\n-\t\t\t\t\t       ren1_dst, branch2);\n-\t\t\t\t\tconflict_rename_dir(o, ren1, branch1);\n \t\t\t\t} else {\n \t\t\t\t\tif (mfi.merge || !mfi.clean)\n \t\t\t\t\t\toutput(o, 1, \"Renaming %s => %s\", ren1_src, ren1_dst);\n-- \n1.7.3.271.g16009\n"},{"id":"151088","messageId":"1284971350-30590-18-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 17/37] merge-recursive: Move rename/delete handling into dedicated function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:50Z","receivedAt":"2010-09-20T08:28:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This move is in preparation for the function growing and being called from\nmultiple places in order to handle D/F conflicts.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   31 ++++++++++++++++++++-----------\n 1 files changed, 20 insertions(+), 11 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f7591a3..87be24c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -731,6 +731,25 @@ static struct merge_file_info merge_file(struct merge_options *o,\n \treturn result;\n }\n \n+static void conflict_rename_delete(struct merge_options *o,\n+\t\t\t\t   struct diff_filepair *pair,\n+\t\t\t\t   const char *rename_branch,\n+\t\t\t\t   const char *other_branch)\n+{\n+\tchar *dest_name = pair->two->path;\n+\n+\toutput(o, 1, \"CONFLICT (rename/delete): Rename %s->%s in %s \"\n+\t       \"and deleted in %s\",\n+\t       pair->one->path, pair->two->path, rename_branch,\n+\t       other_branch);\n+\tif (!o->call_depth)\n+\t\tupdate_stages(dest_name, NULL,\n+\t\t\t      rename_branch == o->branch1 ? pair->two : NULL,\n+\t\t\t      rename_branch == o->branch1 ? NULL : pair->two,\n+\t\t\t      1);\n+\tupdate_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);\n+}\n+\n static void conflict_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t\tstruct rename *ren1,\n \t\t\t\t\tconst char *branch1,\n@@ -937,17 +956,7 @@ static int process_renames(struct merge_options *o,\n \n \t\t\tif (sha_eq(src_other.sha1, null_sha1)) {\n \t\t\t\tclean_merge = 0;\n-\t\t\t\toutput(o, 1, \"CONFLICT (rename/delete): Rename %s->%s in %s \"\n-\t\t\t\t       \"and deleted in %s\",\n-\t\t\t\t       ren1_src, ren1_dst, branch1,\n-\t\t\t\t       branch2);\n-\t\t\t\tupdate_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);\n-\t\t\t\tif (!o->call_depth)\n-\t\t\t\t\tupdate_stages(ren1_dst, NULL,\n-\t\t\t\t\t\t\tbranch1 == o->branch1 ?\n-\t\t\t\t\t\t\tren1->pair->two : NULL,\n-\t\t\t\t\t\t\tbranch1 == o->branch1 ?\n-\t\t\t\t\t\t\tNULL : ren1->pair->two, 1);\n+\t\t\t\tconflict_rename_delete(o, ren1->pair, branch1, branch2);\n \t\t\t} else if ((dst_other.mode == ren1->pair->two->mode) &&\n \t\t\t\t   sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {\n \t\t\t\t/* Added file on the other side\n-- \n1.7.3.271.g16009\n"},{"id":"151066","messageId":"1284971350-30590-19-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 18/37] merge-recursive: Move delete/modify handling into dedicated function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:51Z","receivedAt":"2010-09-20T08:28:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This move is in preparation for the function being called from multiple\nplaces in order to handle D/F conflicts.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   35 ++++++++++++++++++++++-------------\n 1 files changed, 22 insertions(+), 13 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 87be24c..a6da2cc 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1119,6 +1119,26 @@ error_return:\n \treturn ret;\n }\n \n+static void handle_delete_modify(struct merge_options *o,\n+\t\t\t\t const char *path,\n+\t\t\t\t unsigned char *a_sha, int a_mode,\n+\t\t\t\t unsigned char *b_sha, int b_mode)\n+{\n+\tif (!a_sha) {\n+\t\toutput(o, 1, \"CONFLICT (delete/modify): %s deleted in %s \"\n+\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n+\t\t       path, o->branch1,\n+\t\t       o->branch2, o->branch2, path);\n+\t\tupdate_file(o, 0, b_sha, b_mode, path);\n+\t} else {\n+\t\toutput(o, 1, \"CONFLICT (delete/modify): %s deleted in %s \"\n+\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n+\t\t       path, o->branch2,\n+\t\t       o->branch1, o->branch1, path);\n+\t\tupdate_file(o, 0, a_sha, a_mode, path);\n+\t}\n+}\n+\n /* Per entry merge function */\n static int process_entry(struct merge_options *o,\n \t\t\t const char *path, struct stage_data *entry)\n@@ -1151,19 +1171,8 @@ static int process_entry(struct merge_options *o,\n \t\t} else {\n \t\t\t/* Deleted in one and changed in the other */\n \t\t\tclean_merge = 0;\n-\t\t\tif (!a_sha) {\n-\t\t\t\toutput(o, 1, \"CONFLICT (delete/modify): %s deleted in %s \"\n-\t\t\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n-\t\t\t\t       path, o->branch1,\n-\t\t\t\t       o->branch2, o->branch2, path);\n-\t\t\t\tupdate_file(o, 0, b_sha, b_mode, path);\n-\t\t\t} else {\n-\t\t\t\toutput(o, 1, \"CONFLICT (delete/modify): %s deleted in %s \"\n-\t\t\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n-\t\t\t\t       path, o->branch2,\n-\t\t\t\t       o->branch1, o->branch1, path);\n-\t\t\t\tupdate_file(o, 0, a_sha, a_mode, path);\n-\t\t\t}\n+\t\t\thandle_delete_modify(o, path,\n+\t\t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n \t\t}\n \n \t} else if ((!o_sha && a_sha && !b_sha) ||\n-- \n1.7.3.271.g16009\n"},{"id":"151061","messageId":"1284971350-30590-20-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 19/37] merge-recursive: Move process_entry's content merging into a function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:52Z","receivedAt":"2010-09-20T08:28:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This move is in preparation for merge_content growing and being called from\nmultiple places in order to handle D/F conflicts.\n\nI also snuck in a small change to the output in the case that the merged\ncontent for the file matches the current file contents, to make it better\nmatch (and thus more able to take over) how other merge_file() calls in\nprocess_renames() are handled.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   71 ++++++++++++++++++++++++++++++++---------------------\n 1 files changed, 43 insertions(+), 28 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex a6da2cc..1f8b2d5 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1139,6 +1139,47 @@ static void handle_delete_modify(struct merge_options *o,\n \t}\n }\n \n+\n+static int merge_content(struct merge_options *o,\n+\t\t\t const char *path,\n+\t\t\t unsigned char *o_sha, int o_mode,\n+\t\t\t unsigned char *a_sha, int a_mode,\n+\t\t\t unsigned char *b_sha, int b_mode)\n+{\n+\tconst char *reason = \"content\";\n+\tstruct merge_file_info mfi;\n+\tstruct diff_filespec one, a, b;\n+\n+\tif (!o_sha) {\n+\t\treason = \"add/add\";\n+\t\to_sha = (unsigned char *)null_sha1;\n+\t}\n+\tone.path = a.path = b.path = (char *)path;\n+\thashcpy(one.sha1, o_sha);\n+\tone.mode = o_mode;\n+\thashcpy(a.sha1, a_sha);\n+\ta.mode = a_mode;\n+\thashcpy(b.sha1, b_sha);\n+\tb.mode = b_mode;\n+\n+\tmfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);\n+\tif (mfi.clean && sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)\n+\t\toutput(o, 3, \"Skipped %s (merged same as existing)\", path);\n+\telse\n+\t\toutput(o, 2, \"Auto-merging %s\", path);\n+\n+\tif (!mfi.clean) {\n+\t\tif (S_ISGITLINK(mfi.mode))\n+\t\t\treason = \"submodule\";\n+\t\toutput(o, 1, \"CONFLICT (%s): Merge conflict in %s\",\n+\t\t\t\treason, path);\n+\t}\n+\n+\tupdate_file(o, mfi.clean, mfi.sha, mfi.mode, path);\n+\treturn mfi.clean;\n+\n+}\n+\n /* Per entry merge function */\n static int process_entry(struct merge_options *o,\n \t\t\t const char *path, struct stage_data *entry)\n@@ -1207,34 +1248,8 @@ static int process_entry(struct merge_options *o,\n \t} else if (a_sha && b_sha) {\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\tconst char *reason = \"content\";\n-\t\tstruct merge_file_info mfi;\n-\t\tstruct diff_filespec one, a, b;\n-\n-\t\tif (!o_sha) {\n-\t\t\treason = \"add/add\";\n-\t\t\to_sha = (unsigned char *)null_sha1;\n-\t\t}\n-\t\toutput(o, 2, \"Auto-merging %s\", path);\n-\t\tone.path = a.path = b.path = (char *)path;\n-\t\thashcpy(one.sha1, o_sha);\n-\t\tone.mode = o_mode;\n-\t\thashcpy(a.sha1, a_sha);\n-\t\ta.mode = a_mode;\n-\t\thashcpy(b.sha1, b_sha);\n-\t\tb.mode = b_mode;\n-\n-\t\tmfi = merge_file(o, &one, &a, &b,\n-\t\t\t\t o->branch1, o->branch2);\n-\n-\t\tclean_merge = mfi.clean;\n-\t\tif (!mfi.clean) {\n-\t\t\tif (S_ISGITLINK(mfi.mode))\n-\t\t\t\treason = \"submodule\";\n-\t\t\toutput(o, 1, \"CONFLICT (%s): Merge conflict in %s\",\n-\t\t\t\t\treason, path);\n-\t\t}\n-\t\tupdate_file(o, mfi.clean, mfi.sha, mfi.mode, path);\n+\t\tclean_merge = merge_content(o, path,\n+\t\t\t\t\t    o_sha, o_mode, a_sha, a_mode, b_sha, b_mode);\n \t} else if (!o_sha && !a_sha && !b_sha) {\n \t\t/*\n \t\t * this entry was deleted altogether. a_mode == 0 means\n-- \n1.7.3.271.g16009\n"},{"id":"151078","messageId":"1284971350-30590-21-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 20/37] merge-recursive: New data structures for deferring of D/F conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:53Z","receivedAt":"2010-09-20T08:28:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Since we need to resolve paths (including renames) in-core first and defer\nchecking of D/F conflicts (namely waiting to see if directories are still\nin the way after all paths are resolved) before updating files involved in\nD/F conflicts, we will need to first process_renames, then record some\ninformation about the rename needed at D/F resolution time, and then make\nuse of that information when resolving D/F conflicts at the end.\n\nThis commit adds some relevant data structures for storing the necessary\ninformation.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   45 +++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 45 insertions(+), 0 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 1f8b2d5..f15291e 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -63,6 +63,23 @@ static int sha_eq(const unsigned char *a, const unsigned char *b)\n \treturn a && b && hashcmp(a, b) == 0;\n }\n \n+enum rename_type {\n+\tRENAME_NORMAL = 0,\n+\tRENAME_DELETE,\n+\tRENAME_ONE_FILE_TO_TWO\n+};\n+\n+struct rename_df_conflict_info\n+{\n+\tenum rename_type rename_type;\n+\tstruct diff_filepair *pair1;\n+\tstruct diff_filepair *pair2;\n+\tconst char *branch1;\n+\tconst char *branch2;\n+\tstruct stage_data *dst_entry1;\n+\tstruct stage_data *dst_entry2;\n+};\n+\n /*\n  * Since we want to write the index eventually, we cannot reuse the index\n  * for these (temporary) data.\n@@ -74,9 +91,37 @@ struct stage_data\n \t\tunsigned mode;\n \t\tunsigned char sha[20];\n \t} stages[4];\n+\tstruct rename_df_conflict_info *rename_df_conflict_info;\n \tunsigned processed:1;\n };\n \n+static inline void setup_rename_df_conflict_info(enum rename_type rename_type,\n+\t\t\t\t\t\t struct diff_filepair *pair1,\n+\t\t\t\t\t\t struct diff_filepair *pair2,\n+\t\t\t\t\t\t const char *branch1,\n+\t\t\t\t\t\t const char *branch2,\n+\t\t\t\t\t\t struct stage_data *dst_entry1,\n+\t\t\t\t\t\t struct stage_data *dst_entry2)\n+{\n+\tstruct rename_df_conflict_info *ci = xcalloc(1, sizeof(struct rename_df_conflict_info));\n+\tci->rename_type = rename_type;\n+\tci->pair1 = pair1;\n+\tci->branch1 = branch1;\n+\tci->branch2 = branch2;\n+\n+\tci->dst_entry1 = dst_entry1;\n+\tdst_entry1->rename_df_conflict_info = ci;\n+\tdst_entry1->processed = 0;\n+\n+\tassert(!pair2 == !dst_entry2);\n+\tif (dst_entry2) {\n+\t\tci->dst_entry2 = dst_entry2;\n+\t\tci->pair2 = pair2;\n+\t\tdst_entry2->rename_df_conflict_info = ci;\n+\t\tdst_entry2->processed = 0;\n+\t}\n+}\n+\n static int show(struct merge_options *o, int v)\n {\n \treturn (!o->call_depth && o->verbosity >= v) || o->verbosity >= 5;\n-- \n1.7.3.271.g16009\n"},{"id":"151074","messageId":"1284971350-30590-22-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 21/37] merge-recursive: New function to assist resolving renames in-core only","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:54Z","receivedAt":"2010-09-20T08:28:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"process_renames() and process_entry() have nearly identical code for\ndoing three-way file merging to resolve content changes.  Since we are\nalready deferring some of the current rename handling in order to better\nhandle D/F conflicts, it seems to make sense to defer content merging as\nwell and remove the (nearly) duplicated code sections for handling this\nmerging.\n\nTo facilitate this process, add a new update_stages_and_entry() function\nwhich will map the higher stage index entries from two files involved in a\nrename into the resulting rename destination's index entries, and update\nthe associated stage_data structure.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   31 ++++++++++++++++++++++++++++---\n 1 files changed, 28 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f15291e..ec47d56 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -418,11 +418,10 @@ static struct string_list *get_renames(struct merge_options *o,\n \treturn renames;\n }\n \n-static int update_stages(const char *path, struct diff_filespec *o,\n+static int update_stages_options(const char *path, struct diff_filespec *o,\n \t\t\t struct diff_filespec *a, struct diff_filespec *b,\n-\t\t\t int clear)\n+\t\t\t int clear, int options)\n {\n-\tint options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n \tif (clear)\n \t\tif (remove_file_from_cache(path))\n \t\t\treturn -1;\n@@ -438,6 +437,32 @@ static int update_stages(const char *path, struct diff_filespec *o,\n \treturn 0;\n }\n \n+static int update_stages(const char *path, struct diff_filespec *o,\n+\t\t\t struct diff_filespec *a, struct diff_filespec *b,\n+\t\t\t int clear)\n+{\n+\tint options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE;\n+\treturn update_stages_options(path, o, a, b, clear, options);\n+}\n+\n+static int update_stages_and_entry(const char *path,\n+\t\t\t\t   struct stage_data *entry,\n+\t\t\t\t   struct diff_filespec *o,\n+\t\t\t\t   struct diff_filespec *a,\n+\t\t\t\t   struct diff_filespec *b,\n+\t\t\t\t   int clear)\n+{\n+\tentry->processed = 0;\n+\tentry->stages[1].mode = o->mode;\n+\tentry->stages[2].mode = a->mode;\n+\tentry->stages[3].mode = b->mode;\n+\thashcpy(entry->stages[1].sha, o->sha1);\n+\thashcpy(entry->stages[2].sha, a->sha1);\n+\thashcpy(entry->stages[3].sha, b->sha1);\n+\tint options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;\n+\treturn update_stages_options(path, o, a, b, clear, options);\n+}\n+\n static int remove_file(struct merge_options *o, int clean,\n \t\t       const char *path, int no_wd)\n {\n-- \n1.7.3.271.g16009\n"},{"id":"151071","messageId":"1284971350-30590-23-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 22/37] merge-recursive: Have process_entry() skip D/F or rename entries","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:55Z","receivedAt":"2010-09-20T08:28:55Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If an entry has an associated rename_df_conflict_info, skip it and allow\nit to be processed by process_df_entry().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |    3 +++\n 1 files changed, 3 insertions(+), 0 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ec47d56..5326f5e 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1267,6 +1267,9 @@ static int process_entry(struct merge_options *o,\n \tunsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);\n \tunsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);\n \n+\tif (entry->rename_df_conflict_info)\n+\t\treturn 1; /* Such cases are handled elsewhere. */\n+\n \tentry->processed = 1;\n \tif (o_sha && (!a_sha || !b_sha)) {\n \t\t/* Case A: Deleted in one */\n-- \n1.7.3.271.g16009\n"},{"id":"151086","messageId":"1284971350-30590-24-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 23/37] merge-recursive: Structure process_df_entry() to handle more cases","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:56Z","receivedAt":"2010-09-20T08:28:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Modify process_df_entry() (mostly just indentation level changes) to\nget it ready for handling more D/F conflict type cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   83 ++++++++++++++++++++++++++++++-----------------------\n 1 files changed, 47 insertions(+), 36 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 5326f5e..b8222ad 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1336,13 +1336,19 @@ static int process_entry(struct merge_options *o,\n }\n \n /*\n- * Per entry merge function for D/F conflicts, to be called only after\n- * all files below dir have been processed.  We do this because in the\n- * cases we can cleanly resolve D/F conflicts, process_entry() can clean\n- * out all the files below the directory for us.\n+ * Per entry merge function for D/F (and/or rename) conflicts.  In the\n+ * cases we can cleanly resolve D/F conflicts, process_entry() can\n+ * clean out all the files below the directory for us.  All D/F\n+ * conflict cases must be handled here at the end to make sure any\n+ * directories that can be cleaned out, are.\n+ *\n+ * Some rename conflicts may also be handled here that don't necessarily\n+ * involve D/F conflicts, since the code to handle them is generic enough\n+ * to handle those rename conflicts with or without D/F conflicts also\n+ * being involved.\n  */\n static int process_df_entry(struct merge_options *o,\n-\t\t\t const char *path, struct stage_data *entry)\n+\t\t\t    const char *path, struct stage_data *entry)\n {\n \tint clean_merge = 1;\n \tunsigned o_mode = entry->stages[1].mode;\n@@ -1351,42 +1357,47 @@ static int process_df_entry(struct merge_options *o,\n \tunsigned char *o_sha = stage_sha(entry->stages[1].sha, o_mode);\n \tunsigned char *a_sha = stage_sha(entry->stages[2].sha, a_mode);\n \tunsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);\n-\tconst char *add_branch;\n-\tconst char *other_branch;\n-\tunsigned mode;\n-\tconst unsigned char *sha;\n-\tconst char *conf;\n \tstruct stat st;\n \n-\tif (! ((!o_sha && a_sha && !b_sha) || (!o_sha && !a_sha && b_sha)))\n-\t\treturn 1; /* we don't handle non D-F cases */\n-\n \tentry->processed = 1;\n+\tif (entry->rename_df_conflict_info) {\n+\t\tdie (\"Not yet implemented.\");\n+\t} else if (!o_sha && !!a_sha != !!b_sha) {\n+\t\t/* directory -> (directory, file) */\n+\t\tconst char *add_branch;\n+\t\tconst char *other_branch;\n+\t\tunsigned mode;\n+\t\tconst unsigned char *sha;\n+\t\tconst char *conf;\n \n-\tif (a_sha) {\n-\t\tadd_branch = o->branch1;\n-\t\tother_branch = o->branch2;\n-\t\tmode = a_mode;\n-\t\tsha = a_sha;\n-\t\tconf = \"file/directory\";\n-\t} else {\n-\t\tadd_branch = o->branch2;\n-\t\tother_branch = o->branch1;\n-\t\tmode = b_mode;\n-\t\tsha = b_sha;\n-\t\tconf = \"directory/file\";\n-\t}\n-\tif (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {\n-\t\tconst char *new_path = unique_path(o, path, add_branch);\n-\t\tclean_merge = 0;\n-\t\toutput(o, 1, \"CONFLICT (%s): There is a directory with name %s in %s. \"\n-\t\t       \"Adding %s as %s\",\n-\t\t       conf, path, other_branch, path, new_path);\n-\t\tremove_file(o, 0, path, 0);\n-\t\tupdate_file(o, 0, sha, mode, new_path);\n+\t\tif (a_sha) {\n+\t\t\tadd_branch = o->branch1;\n+\t\t\tother_branch = o->branch2;\n+\t\t\tmode = a_mode;\n+\t\t\tsha = a_sha;\n+\t\t\tconf = \"file/directory\";\n+\t\t} else {\n+\t\t\tadd_branch = o->branch2;\n+\t\t\tother_branch = o->branch1;\n+\t\t\tmode = b_mode;\n+\t\t\tsha = b_sha;\n+\t\t\tconf = \"directory/file\";\n+\t\t}\n+\t\tif (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {\n+\t\t\tconst char *new_path = unique_path(o, path, add_branch);\n+\t\t\tclean_merge = 0;\n+\t\t\toutput(o, 1, \"CONFLICT (%s): There is a directory with name %s in %s. \"\n+\t\t\t       \"Adding %s as %s\",\n+\t\t\t       conf, path, other_branch, path, new_path);\n+\t\t\tremove_file(o, 0, path, 0);\n+\t\t\tupdate_file(o, 0, sha, mode, new_path);\n+\t\t} else {\n+\t\t\toutput(o, 2, \"Adding %s\", path);\n+\t\t\tupdate_file(o, 1, sha, mode, path);\n+\t\t}\n \t} else {\n-\t\toutput(o, 2, \"Adding %s\", path);\n-\t\tupdate_file(o, 1, sha, mode, path);\n+\t\tentry->processed = 0;\n+\t\treturn 1; /* not handled; assume clean until processed */\n \t}\n \n \treturn clean_merge;\n-- \n1.7.3.271.g16009\n"},{"id":"151068","messageId":"1284971350-30590-25-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 24/37] merge-recursive: Update conflict_rename_rename_1to2() call signature","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:57Z","receivedAt":"2010-09-20T08:28:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"To facilitate having this function called later using information stored\nin a rename_df_conflict_info struct, accept a diff_filepair instead of a\nrename.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   18 +++++++++---------\n 1 files changed, 9 insertions(+), 9 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b8222ad..237d1ac 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -821,16 +821,16 @@ static void conflict_rename_delete(struct merge_options *o,\n }\n \n static void conflict_rename_rename_1to2(struct merge_options *o,\n-\t\t\t\t\tstruct rename *ren1,\n+\t\t\t\t\tstruct diff_filepair *pair1,\n \t\t\t\t\tconst char *branch1,\n-\t\t\t\t\tstruct rename *ren2,\n+\t\t\t\t\tstruct diff_filepair *pair2,\n \t\t\t\t\tconst char *branch2)\n {\n \t/* One file was renamed in both branches, but to different names. */\n \tchar *del[2];\n \tint delp = 0;\n-\tconst char *ren1_dst = ren1->pair->two->path;\n-\tconst char *ren2_dst = ren2->pair->two->path;\n+\tconst char *ren1_dst = pair1->two->path;\n+\tconst char *ren2_dst = pair2->two->path;\n \tconst char *dst_name1 = ren1_dst;\n \tconst char *dst_name2 = ren2_dst;\n \tif (string_list_has_string(&o->current_directory_set, ren1_dst)) {\n@@ -851,12 +851,12 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t\t/*\n \t\t * Uncomment to leave the conflicting names in the resulting tree\n \t\t *\n-\t\t * update_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, dst_name1);\n-\t\t * update_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, dst_name2);\n+\t\t * update_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);\n+\t\t * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);\n \t\t */\n \t} else {\n-\t\tupdate_stages(dst_name1, NULL, ren1->pair->two, NULL, 1);\n-\t\tupdate_stages(dst_name2, NULL, NULL, ren2->pair->two, 1);\n+\t\tupdate_stages(dst_name1, NULL, pair1->two, NULL, 1);\n+\t\tupdate_stages(dst_name2, NULL, NULL, pair2->two, 1);\n \t}\n \twhile (delp--)\n \t\tfree(del[delp]);\n@@ -970,7 +970,7 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\tupdate_file(o, 0, ren1->pair->one->sha1,\n \t\t\t\t\t\t    ren1->pair->one->mode, src);\n \t\t\t\t}\n-\t\t\t\tconflict_rename_rename_1to2(o, ren1, branch1, ren2, branch2);\n+\t\t\t\tconflict_rename_rename_1to2(o, ren1->pair, branch1, ren2->pair, branch2);\n \t\t\t} else {\n \t\t\t\tstruct merge_file_info mfi;\n \t\t\t\tremove_file(o, 1, ren1_src, 1);\n-- \n1.7.3.271.g16009\n"},{"id":"151067","messageId":"1284971350-30590-26-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 25/37] merge-recursive: Update merge_content() call signature","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:58Z","receivedAt":"2010-09-20T08:28:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Enable calling merge_content() and providing more information about renames\nand D/F conflicts (which we will want to do from process_df_entry()).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |    6 ++++--\n 1 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 237d1ac..5f528c1 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1214,7 +1214,8 @@ static int merge_content(struct merge_options *o,\n \t\t\t const char *path,\n \t\t\t unsigned char *o_sha, int o_mode,\n \t\t\t unsigned char *a_sha, int a_mode,\n-\t\t\t unsigned char *b_sha, int b_mode)\n+\t\t\t unsigned char *b_sha, int b_mode,\n+\t\t\t const char *df_rename_conflict_branch)\n {\n \tconst char *reason = \"content\";\n \tstruct merge_file_info mfi;\n@@ -1322,7 +1323,8 @@ static int process_entry(struct merge_options *o,\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\tclean_merge = merge_content(o, path,\n-\t\t\t\t\t    o_sha, o_mode, a_sha, a_mode, b_sha, b_mode);\n+\t\t\t\t\t    o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,\n+\t\t\t\t\t    NULL);\n \t} else if (!o_sha && !a_sha && !b_sha) {\n \t\t/*\n \t\t * this entry was deleted altogether. a_mode == 0 means\n-- \n1.7.3.271.g16009\n"},{"id":"151083","messageId":"1284971350-30590-27-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 26/37] merge-recursive: Avoid doubly merging rename/add conflict contents","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:28:59Z","receivedAt":"2010-09-20T08:28:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When a commit moves A to B while another commit created B (or moved C to\nB), and these two different commits serve as different merge-bases for a\nlater merge, c94736a (merge-recursive: don't segfault while handling\nrename clashes 2009-07-30) added some special code to avoid segfaults.\nSince that commit, the two versions of B are merged in place (which could\nbe potentially conflicting) and the intermediate result is used as the\nvirtual ancestor.\n\nHowever, right before this special merge, try_merge was turned on, meaning\nthat process_renames() would try an alternative merge that ignores the\n'add' part of the conflict, and, if the merge is clean, store that as the\nnew virtual ancestor.  This could cause incorrect merging of criss-cross\nmerges; it would typically result in just recording a slightly confusing\nmerge base, but in some cases it could cause silent acceptance of one side\nof a merge as the final resolution when a conflict should have been\nflagged.\n\nWhen we do a special merge for such a rename/add conflict between\nmerge-bases, turn try_merge off to avoid an inappropriate second merge.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |    1 +\n t/t6036-recursive-corner-cases.sh |    4 ++--\n 2 files changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 5f528c1..178bbd8 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1061,6 +1061,7 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t    mfi.sha,\n \t\t\t\t\t\t    mfi.mode,\n \t\t\t\t\t\t    ren1_dst);\n+\t\t\t\t\ttry_merge = 0;\n \t\t\t\t} else {\n \t\t\t\t\tnew_path = unique_path(o, ren1_dst, branch2);\n \t\t\t\t\toutput(o, 1, \"Adding as %s instead\", new_path);\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 6c2b2bf..a2e5c5c 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -120,7 +120,7 @@ test_expect_success 'setup criss-cross + rename merges with basic modification'\n \tgit tag R2\n '\n \n-test_expect_failure 'merge criss-cross + rename merges with basic modification' '\n+test_expect_success 'merge criss-cross + rename merges with basic modification' '\n \tgit reset --hard &&\n \tgit checkout L2^0 &&\n \n@@ -202,7 +202,7 @@ test_expect_success 'setup differently handled merges of rename/add conflict' '\n \tgit tag E\n '\n \n-test_expect_failure 'git detects differently handled merges conflict' '\n+test_expect_success 'git detects differently handled merges conflict' '\n \tgit reset --hard &&\n \tgit checkout D^0 &&\n \n-- \n1.7.3.271.g16009\n"},{"id":"151077","messageId":"1284971350-30590-28-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 27/37] merge-recursive: Move handling of double rename of one file to two","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:00Z","receivedAt":"2010-09-20T08:29:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Move the handling of rename/rename conflicts where one file is renamed to\ntwo different files, from process_renames() to process_df_entry().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c       |   57 +++++++++++++++++++++++++++++++++-------------\n t/t6022-merge-rename.sh |    2 +-\n 2 files changed, 42 insertions(+), 17 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 178bbd8..e219d62 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -855,8 +855,11 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t\t * update_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);\n \t\t */\n \t} else {\n-\t\tupdate_stages(dst_name1, NULL, pair1->two, NULL, 1);\n-\t\tupdate_stages(dst_name2, NULL, NULL, pair2->two, 1);\n+\t\tupdate_stages(ren1_dst, NULL, pair1->two, NULL, 1);\n+\t\tupdate_stages(ren2_dst, NULL, NULL, pair2->two, 1);\n+\n+\t\tupdate_file(o, 0, pair1->two->sha1, pair1->two->mode, dst_name1);\n+\t\tupdate_file(o, 0, pair2->two->sha1, pair2->two->mode, dst_name2);\n \t}\n \twhile (delp--)\n \t\tfree(del[delp]);\n@@ -958,19 +961,15 @@ static int process_renames(struct merge_options *o,\n \t\t\tren2->dst_entry->processed = 1;\n \t\t\tren2->processed = 1;\n \t\t\tif (strcmp(ren1_dst, ren2_dst) != 0) {\n-\t\t\t\tclean_merge = 0;\n-\t\t\t\toutput(o, 1, \"CONFLICT (rename/rename): \"\n-\t\t\t\t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n-\t\t\t\t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\",\n-\t\t\t\t       src, ren1_dst, branch1,\n-\t\t\t\t       src, ren2_dst, branch2,\n-\t\t\t\t       o->call_depth ? \" (left unresolved)\": \"\");\n-\t\t\t\tif (o->call_depth) {\n-\t\t\t\t\tremove_file_from_cache(src);\n-\t\t\t\t\tupdate_file(o, 0, ren1->pair->one->sha1,\n-\t\t\t\t\t\t    ren1->pair->one->mode, src);\n-\t\t\t\t}\n-\t\t\t\tconflict_rename_rename_1to2(o, ren1->pair, branch1, ren2->pair, branch2);\n+\t\t\t\tsetup_rename_df_conflict_info(RENAME_ONE_FILE_TO_TWO,\n+\t\t\t\t\t\t\t      ren1->pair,\n+\t\t\t\t\t\t\t      ren2->pair,\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      ren2->dst_entry);\n+\t\t\t\tremove_file(o, 0, ren1_dst, 0);\n+\t\t\t\t/* ren2_dst not in head, so no need to delete */\n \t\t\t} else {\n \t\t\t\tstruct merge_file_info mfi;\n \t\t\t\tremove_file(o, 1, ren1_src, 1);\n@@ -1364,7 +1363,33 @@ static int process_df_entry(struct merge_options *o,\n \n \tentry->processed = 1;\n \tif (entry->rename_df_conflict_info) {\n-\t\tdie (\"Not yet implemented.\");\n+\t\tstruct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;\n+\t\tchar *src;\n+\t\tswitch (conflict_info->rename_type) {\n+\t\tcase RENAME_ONE_FILE_TO_TWO:\n+\t\t\tsrc = conflict_info->pair1->one->path;\n+\t\t\tclean_merge = 0;\n+\t\t\toutput(o, 1, \"CONFLICT (rename/rename): \"\n+\t\t\t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n+\t\t\t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\",\n+\t\t\t       src, conflict_info->pair1->two->path, conflict_info->branch1,\n+\t\t\t       src, conflict_info->pair2->two->path, conflict_info->branch2,\n+\t\t\t       o->call_depth ? \" (left unresolved)\": \"\");\n+\t\t\tif (o->call_depth) {\n+\t\t\t\tremove_file_from_cache(src);\n+\t\t\t\tupdate_file(o, 0, conflict_info->pair1->one->sha1,\n+\t\t\t\t\t    conflict_info->pair1->one->mode, src);\n+\t\t\t}\n+\t\t\tconflict_rename_rename_1to2(o, conflict_info->pair1,\n+\t\t\t\t\t\t    conflict_info->branch1,\n+\t\t\t\t\t\t    conflict_info->pair2,\n+\t\t\t\t\t\t    conflict_info->branch2);\n+\t\t\tconflict_info->dst_entry2->processed = 1;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tentry->processed = 0;\n+\t\t\tbreak;\n+\t\t}\n \t} else if (!o_sha && !!a_sha != !!b_sha) {\n \t\t/* directory -> (directory, file) */\n \t\tconst char *add_branch;\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 02dea16..1f29c0a 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -652,7 +652,7 @@ test_expect_success 'setup rename of one file to two, with directories in the wa\n \tgit commit -m \"Put one/file in the way, rename to two\"\n '\n \n-test_expect_failure 'check handling of differently renamed file with D/F conflicts' '\n+test_expect_success 'check handling of differently renamed file with D/F conflicts' '\n \tgit checkout -q first-rename^0 &&\n \ttest_must_fail git merge --strategy=recursive second-rename &&\n \n-- \n1.7.3.271.g16009\n"},{"id":"151084","messageId":"1284971350-30590-29-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 28/37] merge-recursive: Move handling of double rename of one file to other file","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:01Z","receivedAt":"2010-09-20T08:29:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Move the handling of rename/rename conflicts where one file is renamed on\nboth sides to the same file, from process_renames() to process_entry().\nHere we avoid the three way merge logic by just using\nupdate_stages_and_entry() to move the higher stage entries in the index\nfrom the rename source to the rename destination, and then allow\nprocess_entry() to do its magic.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   32 ++++++--------------------------\n 1 files changed, 6 insertions(+), 26 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e219d62..21b52d4 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -971,33 +971,13 @@ static int process_renames(struct merge_options *o,\n \t\t\t\tremove_file(o, 0, ren1_dst, 0);\n \t\t\t\t/* ren2_dst not in head, so no need to delete */\n \t\t\t} else {\n-\t\t\t\tstruct merge_file_info mfi;\n \t\t\t\tremove_file(o, 1, ren1_src, 1);\n-\t\t\t\tmfi = merge_file(o,\n-\t\t\t\t\t\t ren1->pair->one,\n-\t\t\t\t\t\t ren1->pair->two,\n-\t\t\t\t\t\t ren2->pair->two,\n-\t\t\t\t\t\t branch1,\n-\t\t\t\t\t\t branch2);\n-\t\t\t\tif (mfi.merge || !mfi.clean)\n-\t\t\t\t\toutput(o, 1, \"Renaming %s->%s\", src, ren1_dst);\n-\n-\t\t\t\tif (mfi.merge)\n-\t\t\t\t\toutput(o, 2, \"Auto-merging %s\", ren1_dst);\n-\n-\t\t\t\tif (!mfi.clean) {\n-\t\t\t\t\toutput(o, 1, \"CONFLICT (content): merge conflict in %s\",\n-\t\t\t\t\t       ren1_dst);\n-\t\t\t\t\tclean_merge = 0;\n-\n-\t\t\t\t\tif (!o->call_depth)\n-\t\t\t\t\t\tupdate_stages(ren1_dst,\n-\t\t\t\t\t\t\t      ren1->pair->one,\n-\t\t\t\t\t\t\t      ren1->pair->two,\n-\t\t\t\t\t\t\t      ren2->pair->two,\n-\t\t\t\t\t\t\t      1 /* clear */);\n-\t\t\t\t}\n-\t\t\t\tupdate_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);\n+\t\t\t\tupdate_stages_and_entry(ren1_dst,\n+\t\t\t\t\t\t\tren1->dst_entry,\n+\t\t\t\t\t\t\tren1->pair->one,\n+\t\t\t\t\t\t\tren1->pair->two,\n+\t\t\t\t\t\t\tren2->pair->two,\n+\t\t\t\t\t\t\t1 /* clear */);\n \t\t\t}\n \t\t} else {\n \t\t\t/* Renamed in 1, maybe changed in 2 */\n-- \n1.7.3.271.g16009\n"},{"id":"151072","messageId":"1284971350-30590-30-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 29/37] merge-recursive: Delay handling of rename/delete conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:02Z","receivedAt":"2010-09-20T08:29:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Move the handling of rename/delete conflicts from process_renames() to\nprocess_df_entry().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   22 ++++++++++++++++++++--\n 1 files changed, 20 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 21b52d4..81508b3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1004,8 +1004,20 @@ static int process_renames(struct merge_options *o,\n \t\t\ttry_merge = 0;\n \n \t\t\tif (sha_eq(src_other.sha1, null_sha1)) {\n-\t\t\t\tclean_merge = 0;\n-\t\t\t\tconflict_rename_delete(o, ren1->pair, branch1, branch2);\n+\t\t\t\tif (string_list_has_string(&o->current_directory_set, ren1_dst)) {\n+\t\t\t\t\tren1->dst_entry->processed = 0;\n+\t\t\t\t\tsetup_rename_df_conflict_info(RENAME_DELETE,\n+\t\t\t\t\t\t\t\t      ren1->pair,\n+\t\t\t\t\t\t\t\t      NULL,\n+\t\t\t\t\t\t\t\t      branch1,\n+\t\t\t\t\t\t\t\t      branch2,\n+\t\t\t\t\t\t\t\t      ren1->dst_entry,\n+\t\t\t\t\t\t\t\t      NULL);\n+\t\t\t\t\tremove_file(o, 0, ren1_dst, 0);\n+\t\t\t\t} else {\n+\t\t\t\t\tclean_merge = 0;\n+\t\t\t\t\tconflict_rename_delete(o, ren1->pair, branch1, branch2);\n+\t\t\t\t}\n \t\t\t} else if ((dst_other.mode == ren1->pair->two->mode) &&\n \t\t\t\t   sha_eq(dst_other.sha1, ren1->pair->two->sha1)) {\n \t\t\t\t/* Added file on the other side\n@@ -1346,6 +1358,12 @@ static int process_df_entry(struct merge_options *o,\n \t\tstruct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;\n \t\tchar *src;\n \t\tswitch (conflict_info->rename_type) {\n+\t\tcase RENAME_DELETE:\n+\t\t\tclean_merge = 0;\n+\t\t\tconflict_rename_delete(o, conflict_info->pair1,\n+\t\t\t\t\t       conflict_info->branch1,\n+\t\t\t\t\t       conflict_info->branch2);\n+\t\t\tbreak;\n \t\tcase RENAME_ONE_FILE_TO_TWO:\n \t\t\tsrc = conflict_info->pair1->one->path;\n \t\t\tclean_merge = 0;\n-- \n1.7.3.271.g16009\n"},{"id":"151075","messageId":"1284971350-30590-31-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 30/37] merge-recursive: Delay content merging for renames","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:03Z","receivedAt":"2010-09-20T08:29:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Move the handling of content merging for renames from process_renames() to\nprocess_df_entry().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c       |   51 +++++++++++++---------------------------------\n t/t6022-merge-rename.sh |    2 +-\n 2 files changed, 16 insertions(+), 37 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 81508b3..008cacb 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1073,7 +1073,6 @@ static int process_renames(struct merge_options *o,\n \n \t\t\tif (try_merge) {\n \t\t\t\tstruct diff_filespec *one, *a, *b;\n-\t\t\t\tstruct merge_file_info mfi;\n \t\t\t\tsrc_other.path = (char *)ren1_src;\n \n \t\t\t\tone = ren1->pair->one;\n@@ -1084,41 +1083,16 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\tb = ren1->pair->two;\n \t\t\t\t\ta = &src_other;\n \t\t\t\t}\n-\t\t\t\tmfi = merge_file(o, one, a, b,\n-\t\t\t\t\t\to->branch1, o->branch2);\n-\n-\t\t\t\tif (mfi.clean &&\n-\t\t\t\t    sha_eq(mfi.sha, ren1->pair->two->sha1) &&\n-\t\t\t\t    mfi.mode == ren1->pair->two->mode) {\n-\t\t\t\t\t/*\n-\t\t\t\t\t * This message is part of\n-\t\t\t\t\t * t6022 test. If you change\n-\t\t\t\t\t * it update the test too.\n-\t\t\t\t\t */\n-\t\t\t\t\toutput(o, 3, \"Skipped %s (merged same as existing)\", ren1_dst);\n-\n-\t\t\t\t\t/* There may be higher stage entries left\n-\t\t\t\t\t * in the index (e.g. due to a D/F\n-\t\t\t\t\t * conflict) that need to be resolved.\n-\t\t\t\t\t */\n-\t\t\t\t\tif (!ren1->dst_entry->stages[2].mode !=\n-\t\t\t\t\t    !ren1->dst_entry->stages[3].mode)\n-\t\t\t\t\t\tren1->dst_entry->processed = 0;\n-\t\t\t\t} else {\n-\t\t\t\t\tif (mfi.merge || !mfi.clean)\n-\t\t\t\t\t\toutput(o, 1, \"Renaming %s => %s\", ren1_src, ren1_dst);\n-\t\t\t\t\tif (mfi.merge)\n-\t\t\t\t\t\toutput(o, 2, \"Auto-merging %s\", ren1_dst);\n-\t\t\t\t\tif (!mfi.clean) {\n-\t\t\t\t\t\toutput(o, 1, \"CONFLICT (rename/modify): Merge conflict in %s\",\n-\t\t\t\t\t\t       ren1_dst);\n-\t\t\t\t\t\tclean_merge = 0;\n-\n-\t\t\t\t\t\tif (!o->call_depth)\n-\t\t\t\t\t\t\tupdate_stages(ren1_dst,\n-\t\t\t\t\t\t\t\t      one, a, b, 1);\n-\t\t\t\t\t}\n-\t\t\t\t\tupdate_file(o, mfi.clean, mfi.sha, mfi.mode, ren1_dst);\n+\t\t\t\tupdate_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);\n+\t\t\t\tif (string_list_has_string(&o->current_directory_set, ren1_dst)) {\n+\t\t\t\t\tsetup_rename_df_conflict_info(RENAME_NORMAL,\n+\t\t\t\t\t\t\t\t      ren1->pair,\n+\t\t\t\t\t\t\t\t      NULL,\n+\t\t\t\t\t\t\t\t      branch1,\n+\t\t\t\t\t\t\t\t      NULL,\n+\t\t\t\t\t\t\t\t      ren1->dst_entry,\n+\t\t\t\t\t\t\t\t      NULL);\n+\t\t\t\t\tremove_file(o, 0, ren1_dst, 0);\n \t\t\t\t}\n \t\t\t}\n \t\t}\n@@ -1358,6 +1332,11 @@ static int process_df_entry(struct merge_options *o,\n \t\tstruct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;\n \t\tchar *src;\n \t\tswitch (conflict_info->rename_type) {\n+\t\tcase RENAME_NORMAL:\n+\t\t\tclean_merge = merge_content(o, path,\n+\t\t\t\t\t\t    o_sha, o_mode, a_sha, a_mode, b_sha, b_mode,\n+\t\t\t\t\t\t    conflict_info->branch1);\n+\t\t\tbreak;\n \t\tcase RENAME_DELETE:\n \t\t\tclean_merge = 0;\n \t\t\tconflict_rename_delete(o, conflict_info->pair1,\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 1f29c0a..edbfa47 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -455,7 +455,7 @@ cat >expected <<\\EOF &&\n >>>>>>> dir-not-in-way\n EOF\n \n-test_expect_failure 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '\n+test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '\n \tgit reset --hard &&\n \trm -rf dir~* &&\n \tgit checkout -q renamed-file-has-conflicts^0 &&\n-- \n1.7.3.271.g16009\n"},{"id":"151081","messageId":"1284971350-30590-32-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 31/37] merge-recursive: Delay modify/delete conflicts if D/F conflict present","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:04Z","receivedAt":"2010-09-20T08:29:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When handling merges with modify/delete conflicts, if the modified path is\ninvolved in a D/F conflict, handle the issue in process_df_entry() rather\nthan process_entry().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |    9 +++++++++\n 1 files changed, 9 insertions(+), 0 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 008cacb..d9fcd6d 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1249,6 +1249,10 @@ static int process_entry(struct merge_options *o,\n \t\t\t\toutput(o, 2, \"Removing %s\", path);\n \t\t\t/* do not touch working file if it did not exist */\n \t\t\tremove_file(o, 1, path, !a_sha);\n+\t\t} else if (string_list_has_string(&o->current_directory_set,\n+\t\t\t\t\t\t  path)) {\n+\t\t\tentry->processed = 0;\n+\t\t\treturn 1; /* Assume clean till processed */\n \t\t} else {\n \t\t\t/* Deleted in one and changed in the other */\n \t\t\tclean_merge = 0;\n@@ -1367,6 +1371,11 @@ static int process_df_entry(struct merge_options *o,\n \t\t\tentry->processed = 0;\n \t\t\tbreak;\n \t\t}\n+\t} else if (o_sha && (!a_sha || !b_sha)) {\n+\t\t/* Modify/delete; deleted side may have put a directory in the way */\n+\t\tclean_merge = 0;\n+\t\thandle_delete_modify(o, path,\n+\t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n \t} else if (!o_sha && !!a_sha != !!b_sha) {\n \t\t/* directory -> (directory, file) */\n \t\tconst char *add_branch;\n-- \n1.7.3.271.g16009\n"},{"id":"151080","messageId":"1284971350-30590-33-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 32/37] conflict_rename_delete(): Check whether D/F conflicts are still present","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:05Z","receivedAt":"2010-09-20T08:29:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If all the paths below some directory involved in a D/F conflict were not\nremoved during the rest of the merge, then the contents of the file whose\npath conflicted needs to be recorded in file with an alternative filename.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c       |    8 ++++++++\n t/t6022-merge-rename.sh |    4 ++--\n 2 files changed, 10 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex d9fcd6d..c6a3465 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -807,6 +807,8 @@ static void conflict_rename_delete(struct merge_options *o,\n \t\t\t\t   const char *other_branch)\n {\n \tchar *dest_name = pair->two->path;\n+\tint df_conflict = 0;\n+\tstruct stat st;\n \n \toutput(o, 1, \"CONFLICT (rename/delete): Rename %s->%s in %s \"\n \t       \"and deleted in %s\",\n@@ -817,7 +819,13 @@ static void conflict_rename_delete(struct merge_options *o,\n \t\t\t      rename_branch == o->branch1 ? pair->two : NULL,\n \t\t\t      rename_branch == o->branch1 ? NULL : pair->two,\n \t\t\t      1);\n+\tif (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {\n+\t\tdest_name = unique_path(o, dest_name, rename_branch);\n+\t\tdf_conflict = 1;\n+\t}\n \tupdate_file(o, 0, pair->two->sha1, pair->two->mode, dest_name);\n+\tif (df_conflict)\n+\t\tfree(dest_name);\n }\n \n static void conflict_rename_rename_1to2(struct merge_options *o,\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex edbfa47..9bf190e 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -549,7 +549,7 @@ test_expect_success 'setup both rename source and destination involved in D/F co\n \tgit commit -m \"Conflicts in the way\"\n '\n \n-test_expect_failure 'both rename source and destination involved in D/F conflict' '\n+test_expect_success 'both rename source and destination involved in D/F conflict' '\n \tgit reset --hard &&\n \trm -rf dir~* &&\n \tgit checkout -q rename-dest^0 &&\n@@ -589,7 +589,7 @@ test_expect_success 'setup pair rename to parent of other (D/F conflicts)' '\n \tgit commit -m \"Rename one/file -> two\"\n '\n \n-test_expect_failure 'pair rename to parent of other (D/F conflicts) w/ untracked dir' '\n+test_expect_success 'pair rename to parent of other (D/F conflicts) w/ untracked dir' '\n \tgit checkout -q rename-one^0 &&\n \tmkdir one &&\n \ttest_must_fail git merge --strategy=recursive rename-two &&\n-- \n1.7.3.271.g16009\n"},{"id":"151090","messageId":"1284971350-30590-34-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 33/37] conflict_rename_rename_1to2(): Fix checks for presence of D/F conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:06Z","receivedAt":"2010-09-20T08:29:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This function is called from process_df_entry(), near the end of the merge.\nRather than just checking whether one of the sides of the merge had a\ndirectory at the same path as one of our files, check whether that\ndirectory is still present by this point of our merge.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c       |    7 +++----\n t/t6022-merge-rename.sh |    2 +-\n 2 files changed, 4 insertions(+), 5 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c6a3465..3cc5886 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -841,17 +841,16 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \tconst char *ren2_dst = pair2->two->path;\n \tconst char *dst_name1 = ren1_dst;\n \tconst char *dst_name2 = ren2_dst;\n-\tif (string_list_has_string(&o->current_directory_set, ren1_dst)) {\n+\tstruct stat st;\n+\tif (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) {\n \t\tdst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);\n \t\toutput(o, 1, \"%s is a directory in %s adding as %s instead\",\n \t\t       ren1_dst, branch2, dst_name1);\n-\t\tremove_file(o, 0, ren1_dst, 0);\n \t}\n-\tif (string_list_has_string(&o->current_directory_set, ren2_dst)) {\n+\tif (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) {\n \t\tdst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);\n \t\toutput(o, 1, \"%s is a directory in %s adding as %s instead\",\n \t\t       ren2_dst, branch1, dst_name2);\n-\t\tremove_file(o, 0, ren2_dst, 0);\n \t}\n \tif (o->call_depth) {\n \t\tremove_file_from_cache(dst_name1);\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 9bf190e..0b67002 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -692,7 +692,7 @@ test_expect_success 'setup rename one file to two; directories moving out of the\n \tgit commit -m \"Rename to two\"\n '\n \n-test_expect_failure 'check handling of differently renamed file with D/F conflicts' '\n+test_expect_success 'check handling of differently renamed file with D/F conflicts' '\n \tgit checkout -q first-rename-redo^0 &&\n \ttest_must_fail git merge --strategy=recursive second-rename-redo &&\n \n-- \n1.7.3.271.g16009\n"},{"id":"151076","messageId":"1284971350-30590-35-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 34/37] merge_content(): Check whether D/F conflicts are still present","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:07Z","receivedAt":"2010-09-20T08:29:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If all the paths below some directory involved in a D/F conflict were not\nremoved during the rest of the merge, then the contents of the file whose\npath conflicted needs to be recorded in file with an alternative filename.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c       |   22 ++++++++++++++++++++--\n t/t6022-merge-rename.sh |    8 ++++----\n 2 files changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 3cc5886..a971d59 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1193,6 +1193,8 @@ static int merge_content(struct merge_options *o,\n \tconst char *reason = \"content\";\n \tstruct merge_file_info mfi;\n \tstruct diff_filespec one, a, b;\n+\tstruct stat st;\n+\tunsigned df_conflict_remains = 0;\n \n \tif (!o_sha) {\n \t\treason = \"add/add\";\n@@ -1207,7 +1209,13 @@ static int merge_content(struct merge_options *o,\n \tb.mode = b_mode;\n \n \tmfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);\n-\tif (mfi.clean && sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)\n+\tif (df_rename_conflict_branch &&\n+\t    lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {\n+\t\tdf_conflict_remains = 1;\n+\t}\n+\n+\tif (mfi.clean && !df_conflict_remains &&\n+\t    sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)\n \t\toutput(o, 3, \"Skipped %s (merged same as existing)\", path);\n \telse\n \t\toutput(o, 2, \"Auto-merging %s\", path);\n@@ -1219,7 +1227,17 @@ static int merge_content(struct merge_options *o,\n \t\t\t\treason, path);\n \t}\n \n-\tupdate_file(o, mfi.clean, mfi.sha, mfi.mode, path);\n+\tif (df_conflict_remains) {\n+\t\tconst char *new_path;\n+\t\tupdate_file_flags(o, mfi.sha, mfi.mode, path,\n+\t\t\t\t  o->call_depth || mfi.clean, 0);\n+\t\tnew_path = unique_path(o, path, df_rename_conflict_branch);\n+\t\tmfi.clean = 0;\n+\t\toutput(o, 1, \"Adding as %s instead\", new_path);\n+\t\tupdate_file_flags(o, mfi.sha, mfi.mode, new_path, 0, 1);\n+\t} else {\n+\t\tupdate_file(o, mfi.clean, mfi.sha, mfi.mode, path);\n+\t}\n \treturn mfi.clean;\n \n }\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 0b67002..422092e 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -394,7 +394,7 @@ test_expect_success 'Rename+D/F conflict; renamed file merges + dir not in way'\n \ttest_cmp expected dir\n '\n \n-test_expect_failure 'Rename+D/F conflict; renamed file merges but dir in way' '\n+test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '\n \tgit reset --hard &&\n \trm -rf dir~* &&\n \tgit checkout -q renamed-file-has-no-conflicts^0 &&\n@@ -415,7 +415,7 @@ test_expect_failure 'Rename+D/F conflict; renamed file merges but dir in way' '\n \ttest_cmp expected dir~HEAD\n '\n \n-test_expect_failure 'Same as previous, but merged other way' '\n+test_expect_success 'Same as previous, but merged other way' '\n \tgit reset --hard &&\n \trm -rf dir~* &&\n \tgit checkout -q dir-in-way^0 &&\n@@ -471,7 +471,7 @@ test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in\n \ttest_cmp expected dir\n '\n \n-test_expect_failure 'Rename+D/F conflict; renamed file cannot merge and dir in the way' '\n+test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in the way' '\n \tmodify s/dir-not-in-way/dir-in-way/ expected &&\n \n \tgit reset --hard &&\n@@ -509,7 +509,7 @@ cat >expected <<\\EOF &&\n >>>>>>> renamed-file-has-conflicts\n EOF\n \n-test_expect_failure 'Same as previous, but merged other way' '\n+test_expect_success 'Same as previous, but merged other way' '\n \tgit reset --hard &&\n \trm -rf dir~* &&\n \tgit checkout -q dir-in-way^0 &&\n-- \n1.7.3.271.g16009\n"},{"id":"151079","messageId":"1284971350-30590-36-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 35/37] handle_delete_modify(): Check whether D/F conflicts are still present","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:08Z","receivedAt":"2010-09-20T08:29:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If all the paths below some directory involved in a D/F conflict were not\nremoved during the rest of the merge, then the contents of the file whose\npath conflicted needs to be recorded in file with an alternative filename.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c   |   25 ++++++++++++++++---------\n t/t6020-merge-df.sh |    2 +-\n 2 files changed, 17 insertions(+), 10 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex a971d59..05da47c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1164,25 +1164,29 @@ error_return:\n \n static void handle_delete_modify(struct merge_options *o,\n \t\t\t\t const char *path,\n+\t\t\t\t const char *new_path,\n \t\t\t\t unsigned char *a_sha, int a_mode,\n \t\t\t\t unsigned char *b_sha, int b_mode)\n {\n \tif (!a_sha) {\n \t\toutput(o, 1, \"CONFLICT (delete/modify): %s deleted in %s \"\n-\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n+\t\t       \"and modified in %s. Version %s of %s left in tree%s%s.\",\n \t\t       path, o->branch1,\n-\t\t       o->branch2, o->branch2, path);\n-\t\tupdate_file(o, 0, b_sha, b_mode, path);\n+\t\t       o->branch2, o->branch2, path,\n+\t\t       path == new_path ? \"\" : \" at \",\n+\t\t       path == new_path ? \"\" : new_path);\n+\t\tupdate_file(o, 0, b_sha, b_mode, new_path);\n \t} else {\n \t\toutput(o, 1, \"CONFLICT (delete/modify): %s deleted in %s \"\n-\t\t       \"and modified in %s. Version %s of %s left in tree.\",\n+\t\t       \"and modified in %s. Version %s of %s left in tree%s%s.\",\n \t\t       path, o->branch2,\n-\t\t       o->branch1, o->branch1, path);\n-\t\tupdate_file(o, 0, a_sha, a_mode, path);\n+\t\t       o->branch1, o->branch1, path,\n+\t\t       path == new_path ? \"\" : \" at \",\n+\t\t       path == new_path ? \"\" : new_path);\n+\t\tupdate_file(o, 0, a_sha, a_mode, new_path);\n \t}\n }\n \n-\n static int merge_content(struct merge_options *o,\n \t\t\t const char *path,\n \t\t\t unsigned char *o_sha, int o_mode,\n@@ -1281,7 +1285,7 @@ static int process_entry(struct merge_options *o,\n \t\t} else {\n \t\t\t/* Deleted in one and changed in the other */\n \t\t\tclean_merge = 0;\n-\t\t\thandle_delete_modify(o, path,\n+\t\t\thandle_delete_modify(o, path, path,\n \t\t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n \t\t}\n \n@@ -1398,8 +1402,11 @@ static int process_df_entry(struct merge_options *o,\n \t\t}\n \t} else if (o_sha && (!a_sha || !b_sha)) {\n \t\t/* Modify/delete; deleted side may have put a directory in the way */\n+\t\tconst char *new_path = path;\n+\t\tif (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))\n+\t\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n \t\tclean_merge = 0;\n-\t\thandle_delete_modify(o, path,\n+\t\thandle_delete_modify(o, path, new_path,\n \t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n \t} else if (!o_sha && !!a_sha != !!b_sha) {\n \t\t/* directory -> (directory, file) */\ndiff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh\nindex bc9db1a..7b1ce82 100755\n--- a/t/t6020-merge-df.sh\n+++ b/t/t6020-merge-df.sh\n@@ -71,7 +71,7 @@ test_expect_success 'setup modify/delete + directory/file conflict' '\n \tgit commit -m deleted\n '\n \n-test_expect_failure 'modify/delete + directory/file conflict' '\n+test_expect_success 'modify/delete + directory/file conflict' '\n \tgit checkout delete^0 &&\n \ttest_must_fail git merge modify &&\n \n-- \n1.7.3.271.g16009\n"},{"id":"151082","messageId":"1284971350-30590-37-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 36/37] merge-recursive: Make room for directories in D/F conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:09Z","receivedAt":"2010-09-20T08:29:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When there are unmerged entries present, make sure to check for D/F\nconflicts first and remove any files present in HEAD that would be in the\nway of creating files below the correspondingly named directory.  Such\nfiles will be processed again at the end of the merge in\nprocess_df_entry(); at that time we will be able to tell if we need to\nand can reinstate the file, whether we need to place its contents in a\ndifferent file due to the directory still being present, etc.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c   |   58 +++++++++++++++++++++++++++++++++++++++++++++++++++\n t/t6020-merge-df.sh |    2 +-\n 2 files changed, 59 insertions(+), 1 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 05da47c..821bed8 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -347,6 +347,63 @@ static struct string_list *get_unmerged(void)\n \treturn unmerged;\n }\n \n+static void make_room_for_directories_of_df_conflicts(struct merge_options *o,\n+\t\t\t\t\t\t      struct string_list *entries)\n+{\n+\t/* If there are D/F conflicts, and the paths currently exist\n+\t * in the working copy as a file, we want to remove them to\n+\t * make room for the corresponding directory.  Such paths will\n+\t * later be processed in process_df_entry() at the end.  If\n+\t * the corresponding directory ends up being removed by the\n+\t * merge, then the file will be reinstated at that time;\n+\t * otherwise, if the file is not supposed to be removed by the\n+\t * merge, the contents of the file will be placed in another\n+\t * unique filename.\n+\t *\n+\t * NOTE: This function relies on the fact that entries for a\n+\t * D/F conflict will appear adjacent in the index, with the\n+\t * entries for the file appearing before entries for paths\n+\t * below the corresponding directory.\n+\t */\n+\tconst char *last_file = NULL;\n+\tint last_len;\n+\tstruct stage_data *last_e;\n+\tint i;\n+\n+\tfor (i = 0; i < entries->nr; i++) {\n+\t\tconst char *path = entries->items[i].string;\n+\t\tint len = strlen(path);\n+\t\tstruct stage_data *e = entries->items[i].util;\n+\n+\t\t/*\n+\t\t * Check if last_file & path correspond to a D/F conflict;\n+\t\t * i.e. whether path is last_file+'/'+<something>.\n+\t\t * If so, remove last_file to make room for path and friends.\n+\t\t */\n+\t\tif (last_file &&\n+\t\t    len > last_len &&\n+\t\t    memcmp(path, last_file, last_len) == 0 &&\n+\t\t    path[last_len] == '/') {\n+\t\t\toutput(o, 3, \"Removing %s to make room for subdirectory; may re-add later.\", last_file);\n+\t\t\tunlink(last_file);\n+\t\t}\n+\n+\t\t/*\n+\t\t * Determine whether path could exist as a file in the\n+\t\t * working directory as a possible D/F conflict.  This\n+\t\t * will only occur when it exists in stage 2 as a\n+\t\t * file.\n+\t\t */\n+\t\tif (S_ISREG(e->stages[2].mode) || S_ISLNK(e->stages[2].mode)) {\n+\t\t\tlast_file = path;\n+\t\t\tlast_len = len;\n+\t\t\tlast_e = e;\n+\t\t} else {\n+\t\t\tlast_file = NULL;\n+\t\t}\n+\t}\n+}\n+\n struct rename\n {\n \tstruct diff_filepair *pair;\n@@ -1488,6 +1545,7 @@ int merge_trees(struct merge_options *o,\n \t\tget_files_dirs(o, merge);\n \n \t\tentries = get_unmerged();\n+\t\tmake_room_for_directories_of_df_conflicts(o, entries);\n \t\tre_head  = get_renames(o, head, common, head, merge, entries);\n \t\tre_merge = get_renames(o, merge, common, head, merge, entries);\n \t\tclean = process_renames(o, re_head, re_merge);\ndiff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh\nindex 7b1ce82..b129f1d 100755\n--- a/t/t6020-merge-df.sh\n+++ b/t/t6020-merge-df.sh\n@@ -83,7 +83,7 @@ test_expect_success 'modify/delete + directory/file conflict' '\n \ttest -f letters~modify\n '\n \n-test_expect_failure 'modify/delete + directory/file conflict; other way' '\n+test_expect_success 'modify/delete + directory/file conflict; other way' '\n \tgit reset --hard &&\n \tgit clean -f &&\n \tgit checkout modify^0 &&\n-- \n1.7.3.271.g16009\n"},{"id":"151085","messageId":"1284971350-30590-38-git-send-email-newren@gmail.com","threadId":"25162","inReplyTo":"1284971350-30590-1-git-send-email-newren@gmail.com","subject":"[PATCH 37/37] merge-recursive: Remove redundant path clearing for D/F conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T08:29:10Z","receivedAt":"2010-09-20T08:29:10Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"The code had several places where individual checks were done to remove\nfiles that could be in the way of directories in D/F conflicts.  Not all\nD/F conflicts could have a path cleared for them in such a manner, however,\nleading to the need to create make_room_for_directories_of_df_conflicts()\nas done in the previous patch.  That new function could not have been\nincorporated into the code sooner, since not all relevant code paths had\nbeen deferred to process_df_entry() yet, leading to the creation of even\nmore of these now-redundant path removals.\n\nClean out all of these extra D/F path clearing cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   17 ++---------------\n 1 files changed, 2 insertions(+), 15 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 821bed8..494e15a 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1032,8 +1032,6 @@ static int process_renames(struct merge_options *o,\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      ren2->dst_entry);\n-\t\t\t\tremove_file(o, 0, ren1_dst, 0);\n-\t\t\t\t/* ren2_dst not in head, so no need to delete */\n \t\t\t} else {\n \t\t\t\tremove_file(o, 1, ren1_src, 1);\n \t\t\t\tupdate_stages_and_entry(ren1_dst,\n@@ -1077,7 +1075,6 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t\t\t      branch2,\n \t\t\t\t\t\t\t\t      ren1->dst_entry,\n \t\t\t\t\t\t\t\t      NULL);\n-\t\t\t\t\tremove_file(o, 0, ren1_dst, 0);\n \t\t\t\t} else {\n \t\t\t\t\tclean_merge = 0;\n \t\t\t\t\tconflict_rename_delete(o, ren1->pair, branch1, branch2);\n@@ -1156,7 +1153,6 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t\t\t      NULL,\n \t\t\t\t\t\t\t\t      ren1->dst_entry,\n \t\t\t\t\t\t\t\t      NULL);\n-\t\t\t\t\tremove_file(o, 0, ren1_dst, 0);\n \t\t\t\t}\n \t\t\t}\n \t\t}\n@@ -1338,7 +1334,7 @@ static int process_entry(struct merge_options *o,\n \t\t} else if (string_list_has_string(&o->current_directory_set,\n \t\t\t\t\t\t  path)) {\n \t\t\tentry->processed = 0;\n-\t\t\treturn 1; /* Assume clean till processed */\n+\t\t\treturn 1; /* Assume clean until processed */\n \t\t} else {\n \t\t\t/* Deleted in one and changed in the other */\n \t\t\tclean_merge = 0;\n@@ -1362,15 +1358,7 @@ static int process_entry(struct merge_options *o,\n \t\tif (string_list_has_string(&o->current_directory_set, path)) {\n \t\t\t/* Handle D->F conflicts after all subfiles */\n \t\t\tentry->processed = 0;\n-\t\t\t/* But get any file out of the way now, so conflicted\n-\t\t\t * entries below the directory of the same name can\n-\t\t\t * be put in the working directory.\n-\t\t\t */\n-\t\t\tif (a_sha)\n-\t\t\t\toutput(o, 2, \"Removing %s\", path);\n-\t\t\t/* do not touch working file if it did not exist */\n-\t\t\tremove_file(o, 0, path, !a_sha);\n-\t\t\treturn 1; /* Assume clean till processed */\n+\t\t\treturn 1; /* Assume clean until processed */\n \t\t} else {\n \t\t\toutput(o, 2, \"Adding %s\", path);\n \t\t\tupdate_file(o, 1, sha, mode, path);\n@@ -1492,7 +1480,6 @@ static int process_df_entry(struct merge_options *o,\n \t\t\toutput(o, 1, \"CONFLICT (%s): There is a directory with name %s in %s. \"\n \t\t\t       \"Adding %s as %s\",\n \t\t\t       conf, path, other_branch, path, new_path);\n-\t\t\tremove_file(o, 0, path, 0);\n \t\t\tupdate_file(o, 0, sha, mode, new_path);\n \t\t} else {\n \t\t\toutput(o, 2, \"Adding %s\", path);\n-- \n1.7.3.271.g16009\n"},{"id":"151096","messageId":"4C97262A.9030501@viscovery.net","threadId":"25162","inReplyTo":"1284971350-30590-2-git-send-email-newren@gmail.com","subject":"Re: [PATCH 01/37] t3030: Add a testcase for resolvable rename/add conflict with symlinks","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2010-09-20T09:15:22Z","receivedAt":"2010-09-20T09:15:22Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 9/20/2010 10:28, schrieb Elijah Newren:\n> @@ -255,7 +259,16 @@ test_expect_success 'setup 8' '\n>  \tgit mv a e &&\n>  \tgit add e &&\n>  \ttest_tick &&\n> -\tgit commit -m \"rename a->e\"\n> +\tgit commit -m \"rename a->e\" &&\n> +\tif test_have_prereq SYMLINKS\n> +\tthen\n> +\t\tgit checkout rename-ln &&\n> +\t\tgit mv a e &&\n> +\t\tln -s e a &&\n> +\t\tgit add a e &&\n> +\t\ttest_tick &&\n> +\t\tgit commit -m \"rename a->e, symlink a->e\"\n> +\tfi\n>  '\n\nYou don't need the SYMLINKS prerequisite anywhere. Write this without ln -s:\n\n\tgit commit -m \"rename a->e\" &&\n\tgit checkout rename-ln &&\n\tgit mv a e &&\n\tsha1e=$(printf e | git hash-object -w --stdin) &&\n\tgit update-index --add --cacheinfo 120000 $sha1e a &&\n\ttest_tick &&\n\tgit commit -m \"rename a->e, symlink a->e\"\n\nand the new test case passes even when SYMLINKS are not available!\n\n-- Hannes\n"},{"id":"151098","messageId":"4C972839.9050303@viscovery.net","threadId":"25162","inReplyTo":"1284971350-30590-10-git-send-email-newren@gmail.com","subject":"Re: [PATCH 09/37] t6020: Modernize style a bit","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2010-09-20T09:24:09Z","receivedAt":"2010-09-20T09:24:09Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 9/20/2010 10:28, schrieb Elijah Newren:\n> -test_expect_code 1 'Merge with d/f conflicts' 'git merge \"merge msg\" B master'\n> +test_expect_success 'Merge with d/f conflicts' '\n> +\ttest_must_fail git merge master\n> +'\n\nThe old version requested a particular kind of failure. Are you saying\nthat with modern 'git merge' all non-zero exit codes mean the same kind of\nfailure?\n\n-- Hannes\n"},{"id":"151100","messageId":"m3wrqgkbch.fsf@localhost.localdomain","threadId":"25162","inReplyTo":"1284971350-30590-2-git-send-email-newren@gmail.com","subject":"Re: [PATCH 01/37] t3030: Add a testcase for resolvable rename/add conflict with symlinks","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2010-09-20T09:34:57Z","receivedAt":"2010-09-20T09:34:57Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\nJust an addition to what Johannes Sixt wrote.\n\n> @@ -255,7 +259,16 @@ test_expect_success 'setup 8' '\n>  \tgit mv a e &&\n>  \tgit add e &&\n>  \ttest_tick &&\n> -\tgit commit -m \"rename a->e\"\n> +\tgit commit -m \"rename a->e\" &&\n> +\tif test_have_prereq SYMLINKS\n> +\tthen\n> +\t\tgit checkout rename-ln &&\n> +\t\tgit mv a e &&\n> +\t\tln -s e a &&\n> +\t\tgit add a e &&\n> +\t\ttest_tick &&\n> +\t\tgit commit -m \"rename a->e, symlink a->e\"\n> +\tfi\n\nWouldn't it be better to move it to separate test_expect_success\nentry... if SYMLINK prerequisite would be really needed (Johannes says\nit isn't).\n\n> +if test_have_prereq SYMLINKS\n> +then\n> +\ttest_expect_success 'merge-recursive rename vs. rename/symlink' '\n[...]\n> +fi\n\nIt is better to use\n\n  +test_expect_success SYMLINKS 'merge-recursive rename vs. rename/symlink' '\n\nform, see t/README about three-argument form of test_expect_success.\n\n-- \nJakub Narebski\nPoland\nShadeHawk on #git\n"},{"id":"151111","messageId":"4C97483D.8030605@viscovery.net","threadId":"25162","inReplyTo":"1284971350-30590-37-git-send-email-newren@gmail.com","subject":"Re: [PATCH 36/37] merge-recursive: Make room for directories in D/F conflicts","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2010-09-20T11:40:45Z","receivedAt":"2010-09-20T11:40:45Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 9/20/2010 10:29, schrieb Elijah Newren:\n> +\t * NOTE: This function relies on the fact that entries for a\n> +\t * D/F conflict will appear adjacent in the index, with the\n> +\t * entries for the file appearing before entries for paths\n> +\t * below the corresponding directory.\n\nI don't think that this is a generally valid assumption. There can be\nother entries in between:\n\n   this\n   this.txt\n   this/file\n\n(where your focus is on \"this\").\n\n-- Hannes\n"},{"id":"151127","messageId":"AANLkTin3wXKUJoRtdk4e1KT5Gwo1H1+9H07LU313Ubo1@mail.gmail.com","threadId":"25162","inReplyTo":"4C972839.9050303@viscovery.net","subject":"Re: [PATCH 09/37] t6020: Modernize style a bit","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T16:03:25Z","receivedAt":"2010-09-20T16:03:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Sep 20, 2010 at 3:24 AM, Johannes Sixt <j.sixt@viscovery.net> wrote:\n> Am 9/20/2010 10:28, schrieb Elijah Newren:\n>> -test_expect_code 1 'Merge with d/f conflicts' 'git merge \"merge msg\" B master'\n>> +test_expect_success 'Merge with d/f conflicts' '\n>> +     test_must_fail git merge master\n>> +'\n>\n> The old version requested a particular kind of failure. Are you saying\n> that with modern 'git merge' all non-zero exit codes mean the same kind of\n> failure?\n\nNo, I'm saying that I don't think the test meant to try to distinguish\nbetween exit codes.  I think the test only used test_expect_code\nbecause that's all that existed in 2005 (test_must_fail postdates this\ntest by over 2 years).  There's really only one exit code from git\nmerge[1], so there's really not much point in trying to distinguish\nbetween kinds of failure.\n\n\n[1] Okay, technically you can get code 128 if you hit a die(), but we\ndon't in general try to differentiate between failure due to hitting\ndie() and failure for other reasons when using test_must fail, so I\ndidn't think it was worth checking here either.\n"},{"id":"151128","messageId":"AANLkTin0ra6JL+jbyOwfssvSxh9FB-BCnvFcR45_=ZkS@mail.gmail.com","threadId":"25162","inReplyTo":"4C97483D.8030605@viscovery.net","subject":"Re: [PATCH 36/37] merge-recursive: Make room for directories in D/F conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-20T16:06:00Z","receivedAt":"2010-09-20T16:06:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Sep 20, 2010 at 5:40 AM, Johannes Sixt <j.sixt@viscovery.net> wrote:\n> Am 9/20/2010 10:29, schrieb Elijah Newren:\n>> +      * NOTE: This function relies on the fact that entries for a\n>> +      * D/F conflict will appear adjacent in the index, with the\n>> +      * entries for the file appearing before entries for paths\n>> +      * below the corresponding directory.\n>\n> I don't think that this is a generally valid assumption. There can be\n> other entries in between:\n>\n>   this\n>   this.txt\n>   this/file\n>\n> (where your focus is on \"this\").\n\nAh, this was one of my two big remaining questions about this series\n(the other being whether all the o->call_depth > 0 cases have been\nfixed).  Thanks for the clarification.\n\nWould a sort of the entries string_list using df_name_compare() give\nme what I need?\n\nElijah\n"},{"id":"151270","messageId":"7v7hietuvf.fsf@alter.siamese.dyndns.org","threadId":"25162","inReplyTo":"AANLkTin3wXKUJoRtdk4e1KT5Gwo1H1+9H07LU313Ubo1@mail.gmail.com","subject":"Re: [PATCH 09/37] t6020: Modernize style a bit","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-09-22T01:44:20Z","receivedAt":"2010-09-22T01:44:20Z","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>> The old version requested a particular kind of failure. Are you saying\n>> that with modern 'git merge' all non-zero exit codes mean the same kind of\n>> failure?\n>\n> No, I'm saying that I don't think the test meant to try to distinguish\n> between exit codes.\n\nThe check for status 1 comes from 72d1216 (New test case: merge with\ndirectory/file conflicts, 2005-12-03), and if you read git-merge.sh from\nthat era, you would notice that at least we do not want to see status 2\n(merge wrapper failed in a bad way) from this test.  git-merge was\ndesigned to exit with status 1 when (and only when) the merge request was\nreasonable, it did its best effort and left conflict to be resolved in the\nworking tree.\n"},{"id":"151272","messageId":"AANLkTintJNQKq9Tg0D9g0_Qz9u62mWWR+Pz5OS2f_p2+@mail.gmail.com","threadId":"25162","inReplyTo":"7v7hietuvf.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 09/37] t6020: Modernize style a bit","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2010-09-22T04:41:17Z","receivedAt":"2010-09-22T04:41:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Sep 21, 2010 at 7:44 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Elijah Newren <newren@gmail.com> writes:\n>\n>>> The old version requested a particular kind of failure. Are you saying\n>>> that with modern 'git merge' all non-zero exit codes mean the same kind of\n>>> failure?\n>>\n>> No, I'm saying that I don't think the test meant to try to distinguish\n>> between exit codes.\n>\n> The check for status 1 comes from 72d1216 (New test case: merge with\n> directory/file conflicts, 2005-12-03), and if you read git-merge.sh from\n> that era, you would notice that at least we do not want to see status 2\n> (merge wrapper failed in a bad way) from this test.  git-merge was\n> designed to exit with status 1 when (and only when) the merge request was\n> reasonable, it did its best effort and left conflict to be resolved in the\n> working tree.\n\nIndeed; looks like I didn't check closely enough.\n"}]}