{"thread":{"id":"27574","subject":"[PATCH 00/48] Handling more corner cases in merge-recursive.c","startedAt":"2011-06-08T07:30:30Z","lastAt":"2011-08-09T07:01:41Z","messageCount":97,"participants":["Elijah Newren","Junio C Hamano","Johannes Sixt","Phil Hord"],"isPatch":true,"patchVersion":1,"patchTotal":48},"messages":[{"id":"169535","messageId":"1307518278-23814-1-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":null,"subject":"[PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:30Z","receivedAt":"2011-06-08T07:30:30Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Well, this took me a LOT longer than I expected, but I now have a\n(monster) patch series designed to fix various corner cases in\nmerge-recursive.c.  For those waiting for the 1.7.6 freeze to end,\nreading this patch series makes for some great mental gymnastics.  :-)\n\nYou might be surprised by just how corner cases we have and the types of\nbugs that can be triggered (including deleting untracked files).  This\nseries includes testcases for the unnecessary file update issues[1]\ntriggered by my series from last fall[2] and also a testcase that had\nbeen regressed by my earlier (and now reverted) bandaids designed to fix\nthe unnecessary file update issue[3] (to make sure we fix both those\nissues).  The current series aims to fix these problems using the\nstrategy Junio outlined[4].  However, this series is about a lot more\nthan just those issues.\n\n[1] http://thread.gmane.org/gmane.comp.version-control.git/167800\n[2] http://thread.gmane.org/gmane.comp.version-control.git/156559\n[3] http://thread.gmane.org/gmane.comp.version-control.git/172617\n[4] http://thread.gmane.org/gmane.comp.version-control.git/169209\n\nYou can note that I did not fix all the testcases I added.  I consider\nsome cases either unfixable or not worth fixing (see [5] for mailing\nlist discussion about the main such case).\n\n[5] http://thread.gmane.org/gmane.comp.version-control.git/157591\n\nHowever, there is one large class of problems that I think is fixable,\nI'm just not sure whether it is worth fixing.  git's rename detection\noptimization of only considering files that exist on one side of the\ndiff but not the other causes issues with merges (undetected conflicts,\nspurious conflicts, or merged cleanly but wrongly due to deleting files\nthat should be present).  To fix these cases, we would need some way of\nincluding rewritten files as potential rename candidates, meaning we\nwould also need to somehow check for rewritten files.  (I refer to these\nas rename/add-source situations).  The potential problem with applying\nsuch a fix is that it could make merges significantly more costly in the\ncommon case, in order to fix some relatively rare problems that might\noccur.  I don't know whether it is justified, especially since git's\nmerging can't be expected to know how to merge everything; at some point\nthe user is responsible for checking the result.  Patch 32 does add some\ncomments to the code if anyone does decide they want to look into this,\nthough.\n\nPlease test!  I apologize for the earlier regressions I caused in\nmerge-recursive.  I've tried to be MUCH more careful this time, but the\nchanges are numerous and much more invasive too.  I'm trying to put this\nseries through the testing wringer and will continue to do so, but extra\ntesting from others would be great.\n\nAnyway, if you've read this far...thanks!  \n\n\n\nElijah Newren (48):\n\nIn patches 1-12, I add a bunch of corner case tests.  Since there are so\nmany I want to add and I think putting them all in t6036 would be a bit\nunwieldy, I split the tests.  Since t6036 currently only contains\ntestcases that involve criss-cross merges, I mark it for such usage, and\ncreate t6039 for dealing with corner cases involving renames without\ncriss-cross merges.\n\n  [01] t6039: Add a testcase where git deletes an untracked file\n  [02] t6039: Add failing testcase for rename/modify/add-source conflict\n  [03] t6039: Add a pair of cases where undetected renames cause issues\n  [04] t6039: Add a testcase where undetected rename causes silent file deletion\n  [05] t6039: Add tests for content issues with modify/rename/directory conflicts\n  [06] t6039: Add failing testcases for rename/rename/add-{source,dest} conflicts\n  [07] t6039: Ensure rename/rename conflicts leave index and workdir in sane state\n  [08] t6036: Add differently resolved modify/delete conflict in criss-cross test\n  [09] t6036: criss-cross with weird content can fool git into clean merge\n  [10] t6036: tests for criss-cross merges with various directory/file conflicts\n  [11] t6036: criss-cross w/ rename/rename(1to2)/modify+rename/rename(2to1)/modify\n  [12] t6036: criss-cross + rename/rename(1to2)/add-source + modify/modify\n\nIn patches 13-14, I add two tests that Junio reverted from master\n(6db41050) which we would still like fixed, and a test that my previous\nband-aids had broken to make sure we don't break it again.\n\n  [13] t6022: Remove unnecessary untracked files to make test cleaner\n  [14] t6022: New tests checking for unnecessary updates of files\n  [15] t6022: Add testcase for merging a renamed file with a simple change\n\nIn patches 16-19, I'm just doing minor cleanups.  None of these are\ncritical; I'll be happy to drop any of them.\n\n  [16] merge-recursive: Make BUG message more legible by adding a newline\n  [17] merge-recursive: Correct a comment\n  [18] merge-recursive: Mark some diff_filespec struct arguments const\n  [19] merge-recursive: Remember to free generated unique path names\n\nPatches 20-22 are small but important bug fixes.  Some of these might\nmake sense to be pulled to maint independent of the other changes.\n\n  [20] merge-recursive: Avoid working directory changes during recursive case\n  [21] merge-recursive: Fix recursive case with D/F conflict via add/add conflict\n  [22] merge-recursive: Fix sorting order and directory change assumptions\n\nPatches 23-29 are where I try to implement what I understood Junio\nwanted merge-recursive to do to avoid so many lstat() and other\nunnecessary working directory changes (see link [4] above).\n\n  [23] merge-recursive: Fix code checking for D/F conflicts still being present\n  [24] merge-recursive: Save D/F conflict filenames instead of unlinking them\n  [25] merge-recursive: Split was_tracked() out of would_lose_untracked()\n  [26] merge-recursive: Allow make_room_for_path() to remove D/F entries\n  [27] merge-recursive: Consolidate different update_stages functions\n  [28] merge-recursive: Split update_stages_and_entry; only update stages at end\n  [29] merge-recursive: When we detect we can skip an update, actually skip it\n\nPatch 30 fixes the deletion of an untracked file; I believe it has the\nhighest ratio of commit message length to patch size, since it has to\nexplain why we have modified a certain line of code several times and\nstill didn't have it quite right.\n\n  [30] merge-recursive: Fix deletion of untracked file in rename/delete conflicts\n\nThe commit message for patch 31 seems like there's some good zombie joke\nin there that someone cleverer than myself ought to be able to come up\nwith.  Interestingly, our code for rename/rename (2to1) conflicts was\nnever executed, and would trigger segfaults had there been a way to\nexecute it.\n\n  [31] merge-recursive: Make dead code for rename/rename(2to1) conflicts undead\n\nThere are a few cases where git's rename detection optimization of only\nconsidering files that exist on one side of the diff but not the other\ncauses issues with merges (undetected conflicts, spurious conflicts, or\nmerged cleanly but wrongly due to deleting files that should be\npresent).  To fix these cases, we would need some way of including\nrewritten files as potential rename candidates, meaning we would also\nneed to somehow check for rewritten files.  My series does not address\nthese cases (I'm unsure whether the performance hit would be worth it).\nInstead, I simply have a half-dozen relevant test cases and add some\ncomments to the code about some things that would need to be considered\nif we ever want to tackle these cases.\n\n  [32] merge-recursive: Add comments about handling rename/add-source cases\n\nPatch 33 was specifically written because of a rename/directory conflict\ntestcase, but tries to worry about three-way content merges in\ncombination with renames and D/F conflicts and possible criss-cross\nmerges and getting the appropriate index and working directory contents.\n\n  [33] merge-recursive: Improve handling of rename target vs. directory addition\n\nPatch 34 is an attempted simplification of merge-recursive.c.  We really\ndon't need process_df_entry(), since we can simply iterate through the\nunmerged entries in the order we need.  That can make it easier to keep\ntrack of things, which is particularly important for the rename/rename\nfixes below...\n\n  [34] merge-recursive: Consolidate process_entry() and process_df_entry()\n\nPatches 35-48 try to fix our handling of rename/rename conflicts (both\none file being renamed to two or two files being renamed to one).  In\nsuch cases, we need to consider that the renames may not be the only\nchange; there could also be modifications requiring a three-way content\nmerge.  Further, as with all other rename cases, we need to consider the\nrecursive case (o->call_depth > 0) and potential D/F conflicts.  There\nis also the possibility of rename/add-source issues for each side of the\nrename, though we currently can't detect those anyway.  The\nrename_rename_1to2 case is especially interesting, as we also have to\nworry about each rename potentially also being involved in rename/delete\nand rename/add-dest conflicts.  This all makes handling rename/rename\nconflicts a bit hairy.\n\n  [35] merge-recursive: Cleanup and consolidation of rename_conflict_info\n  [36] merge-recursive: Provide more info in conflict markers with file renames\n  [37] merge-recursive: Fix modify/delete resolution in the recursive case\n  [38] merge-recursive: Introduce a merge_file convenience function\n  [39] merge-recursive: Fix rename/rename(1to2) resolution for virtual merge base\n  [40] merge-recursive: Small cleanups for conflict_rename_rename_1to2\n  [41] merge-recursive: Defer rename/rename(2to1) handling until process_entry\n  [42] merge-recursive: Record more data needed for merging with dual renames\n  [43] merge-recursive: Create function for merging with branchname:file markers\n  [44] merge-recursive: Consider modifications in rename/rename(2to1) conflicts\n  [45] merge-recursive: Make modify/delete handling code reusable\n  [46] merge-recursive: Have conflict_rename_delete reuse modify/delete code\n  [47] merge-recursive: add handling for rename/rename/add-dest/add-dest\n  [48] merge-recursive: Fix working copy handling for rename/rename/add/add\n\n merge-recursive.c                    |  985 +++++++++++++++++++++-------------\n merge-recursive.h                    |    1 +\n t/t6020-merge-df.sh                  |   26 +-\n t/t6022-merge-rename.sh              |  102 ++++-\n t/t6036-recursive-corner-cases.sh    |  502 ++++++++++++++++-\n t/t6039-merge-rename-corner-cases.sh |  556 +++++++++++++++++++\n 6 files changed, 1752 insertions(+), 420 deletions(-)\n create mode 100755 t/t6039-merge-rename-corner-cases.sh\n\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169536","messageId":"1307518278-23814-2-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 01/48] t6039: Add a testcase where git deletes an untracked file","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:31Z","receivedAt":"2011-06-08T07:30:31Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Current git will nuke an untracked file during a rename/delete conflict if\n(a) there is an untracked file whose name matches the source of a rename\nand (b) the merge is done in a certain direction.  Add a simple testcase\ndemonstrating this bug.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh    |    2 +-\n t/t6039-merge-rename-corner-cases.sh |   36 ++++++++++++++++++++++++++++++++++\n 2 files changed, 37 insertions(+), 1 deletions(-)\n create mode 100755 t/t6039-merge-rename-corner-cases.sh\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 871577d..319b6fa 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -1,6 +1,6 @@\n #!/bin/sh\n \n-test_description='recursive merge corner cases'\n+test_description='recursive merge corner cases involving criss-cross merges'\n \n . ./test-lib.sh\n \ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nnew file mode 100755\nindex 0000000..5054459\n--- /dev/null\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -0,0 +1,36 @@\n+#!/bin/sh\n+\n+test_description=\"recursive merge corner cases w/ renames but not criss-crosses\"\n+# t6036 has corner cases that involve both criss-cross merges and renames\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup rename/delete + untracked file' '\n+\techo \"A pretty inscription\" >ring &&\n+\tgit add ring &&\n+\ttest_tick &&\n+\tgit commit -m beginning &&\n+\n+\tgit branch people &&\n+\tgit checkout -b rename-the-ring &&\n+\tgit mv ring one-ring-to-rule-them-all &&\n+\ttest_tick &&\n+\tgit commit -m fullname &&\n+\n+\tgit checkout people &&\n+\tgit rm ring &&\n+\techo gollum >owner &&\n+\tgit add owner &&\n+\ttest_tick &&\n+\tgit commit -m track-people-instead-of-objects &&\n+\techo \"Myyy PRECIOUSSS\" >ring\n+'\n+\n+test_expect_failure \"Does git preserve Gollum's precious artifact?\" '\n+\ttest_must_fail git merge -s recursive rename-the-ring &&\n+\n+\t# Make sure git did not delete an untracked file\n+\ttest -f ring\n+'\n+\n+test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169581","messageId":"1307518278-23814-3-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 02/48] t6039: Add failing testcase for rename/modify/add-source conflict","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:32Z","receivedAt":"2011-06-08T07:30:32Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If there is a cleanly resolvable rename/modify conflict AND there is a new\nfile introduced on the renamed side of the merge whose name happens to\nmatch that of the source of the rename (but is otherwise unrelated to the\nrename), then git fails to cleanly resolve the merge despite the fact that\nthe new file should not cause any problems.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6039-merge-rename-corner-cases.sh |   39 ++++++++++++++++++++++++++++++++++\n 1 files changed, 39 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex 5054459..276d7dd 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -33,4 +33,43 @@ test_expect_failure \"Does git preserve Gollum's precious artifact?\" '\n \ttest -f ring\n '\n \n+# Testcase setup for rename/modify/add-source:\n+#   Commit A: new file: a\n+#   Commit B: modify a slightly\n+#   Commit C: rename a->b, add completely different a\n+#\n+# We should be able to merge B & C cleanly\n+\n+test_expect_success 'setup rename/modify/add-source conflict' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n\" >a &&\n+\tgit add a &&\n+\tgit commit -m A &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\techo 8 >>a &&\n+\tgit add a &&\n+\tgit commit -m B &&\n+\n+\tgit checkout -b C A &&\n+\tgit mv a b &&\n+\techo something completely different >a &&\n+\tgit add a &&\n+\tgit commit -m C\n+'\n+\n+test_expect_failure 'rename/modify/add-source conflict resolvable' '\n+\tgit checkout B^0 &&\n+\n+\tgit merge -s recursive C^0 &&\n+\n+\ttest $(git rev-parse B:a) = $(git rev-parse b) &&\n+\ttest $(git rev-parse C:a) = $(git rev-parse a)\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169538","messageId":"1307518278-23814-4-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 03/48] t6039: Add a pair of cases where undetected renames cause issues","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:33Z","receivedAt":"2011-06-08T07:30:33Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"An undetected rename can cause a silent success where a conflict should\nhave been detected, or can cause an erroneous conflict state where the\nmerge should have been resolvable.  Add testcases for both.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6039-merge-rename-corner-cases.sh |   61 ++++++++++++++++++++++++++++++++++\n 1 files changed, 61 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex 276d7dd..f338fb4 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -72,4 +72,65 @@ test_expect_failure 'rename/modify/add-source conflict resolvable' '\n \ttest $(git rev-parse C:a) = $(git rev-parse a)\n '\n \n+test_expect_success 'setup resolvable conflict missed if rename missed' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n\" >a &&\n+\techo foo >b &&\n+\tgit add a b &&\n+\tgit commit -m A &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\tgit mv a c &&\n+\techo \"Completely different content\" >a &&\n+\tgit add a &&\n+\tgit commit -m B &&\n+\n+\tgit checkout -b C A &&\n+\techo 6 >>a &&\n+\tgit add a &&\n+\tgit commit -m C\n+'\n+\n+test_expect_failure 'conflict caused if rename not detected' '\n+\tgit checkout -q C^0 &&\n+\tgit merge -s recursive B^0 &&\n+\n+\ttest 3 -eq $(git ls-files -s | wc -l) &&\n+\ttest 0 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest 6 -eq $(wc -l < c) &&\n+\ttest $(git rev-parse HEAD:a) = $(git rev-parse B:a) &&\n+\ttest $(git rev-parse HEAD:b) = $(git rev-parse A:b)\n+'\n+\n+test_expect_success 'setup conflict resolved wrong if rename missed' '\n+\tgit reset --hard &&\n+\tgit clean -f &&\n+\n+\tgit checkout -b D A &&\n+\techo 7 >>a &&\n+\tgit add a &&\n+\tgit mv a c &&\n+\techo \"Completely different content\" >a &&\n+\tgit add a &&\n+\tgit commit -m D &&\n+\n+\tgit checkout -b E A &&\n+\tgit rm a &&\n+\techo \"Completely different content\" >>a &&\n+\tgit add a &&\n+\tgit commit -m E\n+'\n+\n+test_expect_failure 'missed conflict if rename not detected' '\n+\tgit checkout -q E^0 &&\n+\ttest_must_fail git merge -s recursive D^0\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169583","messageId":"1307518278-23814-5-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 04/48] t6039: Add a testcase where undetected rename causes silent file deletion","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:34Z","receivedAt":"2011-06-08T07:30:34Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There are cases where history should merge cleanly, and which current git\ndoes merge cleanly despite not detecting a rename; however the merge\ncurrently nukes files that should not be removed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6039-merge-rename-corner-cases.sh |   65 ++++++++++++++++++++++++++++++++++\n 1 files changed, 65 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex f338fb4..db5560c 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -133,4 +133,69 @@ test_expect_failure 'missed conflict if rename not detected' '\n \ttest_must_fail git merge -s recursive D^0\n '\n \n+# Tests for undetected rename/add-source causing a file to erroneously be\n+# deleted (and for mishandled rename/rename(1to1) causing the same issue).\n+#\n+# This test uses a rename/rename(1to1)+add-source conflict (1to1 means the\n+# same file is renamed on both sides to the same thing; it should trigger\n+# the 1to2 logic, which it would do if the add-source didn't cause issues\n+# for git's rename detection):\n+#   Commit A: new file: a\n+#   Commit B: rename a->b\n+#   Commit C: rename a->b, add unrelated a\n+\n+test_expect_success 'setup undetected rename/add-source causes data loss' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n\" >a &&\n+\tgit add a &&\n+\tgit commit -m A &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\tgit mv a b &&\n+\tgit commit -m B &&\n+\n+\tgit checkout -b C A &&\n+\tgit mv a b &&\n+\techo foobar >a &&\n+\tgit add a &&\n+\tgit commit -m C\n+'\n+\n+test_expect_failure 'detect rename/add-source and preserve all data' '\n+\tgit checkout B^0 &&\n+\n+\tgit merge -s recursive C^0 &&\n+\n+\ttest 2 -eq $(git ls-files -s | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest -f a &&\n+\ttest -f b &&\n+\n+\ttest $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&\n+\ttest $(git rev-parse HEAD:a) = $(git rev-parse C:a)\n+'\n+\n+test_expect_failure 'detect rename/add-source and preserve all data, merge other way' '\n+\tgit checkout C^0 &&\n+\n+\tgit merge -s recursive B^0 &&\n+\n+\ttest 2 -eq $(git ls-files -s | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest -f a &&\n+\ttest -f b &&\n+\n+\ttest $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&\n+\ttest $(git rev-parse HEAD:a) = $(git rev-parse C:a)\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169582","messageId":"1307518278-23814-6-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 05/48] t6039: Add tests for content issues with modify/rename/directory conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:35Z","receivedAt":"2011-06-08T07:30:35Z","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/t6039-merge-rename-corner-cases.sh |  141 ++++++++++++++++++++++++++++++++++\n 1 files changed, 141 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex db5560c..b465667 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -198,4 +198,145 @@ test_expect_failure 'detect rename/add-source and preserve all data, merge other\n \ttest $(git rev-parse HEAD:a) = $(git rev-parse C:a)\n '\n \n+test_expect_success 'setup content merge + rename/directory conflict' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n\" >file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m base &&\n+\tgit tag base &&\n+\n+\tgit checkout -b right &&\n+\techo 7 >>file &&\n+\tmkdir newfile &&\n+\techo junk >newfile/realfile &&\n+\tgit add file newfile/realfile &&\n+\ttest_tick &&\n+\tgit commit -m right &&\n+\n+\tgit checkout -b left-conflict base &&\n+\techo 8 >>file &&\n+\tgit add file &&\n+\tgit mv file newfile &&\n+\ttest_tick &&\n+\tgit commit -m left &&\n+\n+\tgit checkout -b left-clean base &&\n+\techo 0 >newfile &&\n+\tcat file >>newfile &&\n+\tgit add newfile &&\n+\tgit rm file &&\n+\ttest_tick &&\n+\tgit commit -m left\n+'\n+\n+test_expect_failure 'rename/directory conflict + clean content merge' '\n+\tgit reset --hard &&\n+\tgit reset --hard &&\n+\tgit clean -fdqx &&\n+\n+\tgit checkout left-clean^0 &&\n+\n+\ttest_must_fail git merge -s recursive right^0 &&\n+\n+\ttest 2 -eq $(git ls-files -s | wc -l) &&\n+\ttest 1 -eq $(git ls-files -u | wc -l) &&\n+\ttest 1 -eq $(git ls-files -o | wc -l) &&\n+\n+\techo 0 >expect &&\n+\tgit cat-file -p base:file >>expect &&\n+\techo 7 >>expect &&\n+\ttest_cmp expect newfile~HEAD &&\n+\n+\ttest $(git rev-parse :2:newfile) = $(git hash-object expect) &&\n+\n+\ttest -f newfile/realfile &&\n+\ttest -f newfile~HEAD\n+'\n+\n+test_expect_failure 'rename/directory conflict + content merge conflict' '\n+\tgit reset --hard &&\n+\tgit reset --hard &&\n+\tgit clean -fdqx &&\n+\n+\tgit checkout left-conflict^0 &&\n+\n+\ttest_must_fail git merge -s recursive right^0 &&\n+\n+\ttest 4 -eq $(git ls-files -s | wc -l) &&\n+\ttest 3 -eq $(git ls-files -u | wc -l) &&\n+\ttest 1 -eq $(git ls-files -o | wc -l) &&\n+\n+\tgit cat-file -p left-conflict:newfile >left &&\n+\tgit cat-file -p base:file    >base &&\n+\tgit cat-file -p right:file   >right &&\n+\ttest_must_fail git merge-file \\\n+\t\t-L \"HEAD:newfile\" \\\n+\t\t-L \"\" \\\n+\t\t-L \"right^0:file\" \\\n+\t\tleft base right &&\n+\ttest_cmp left newfile~HEAD &&\n+\n+\ttest $(git rev-parse :1:newfile) = $(git rev-parse base:file) &&\n+\ttest $(git rev-parse :2:newfile) = $(git rev-parse left-conflict:newfile) &&\n+\ttest $(git rev-parse :3:newfile) = $(git rev-parse right:file) &&\n+\n+\ttest -f newfile/realfile &&\n+\ttest -f newfile~HEAD\n+'\n+\n+test_expect_success 'setup content merge + rename/directory conflict w/ disappearing dir' '\n+\tgit reset --hard &&\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tmkdir sub &&\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n\" >sub/file &&\n+\tgit add sub/file &&\n+\ttest_tick &&\n+\tgit commit -m base &&\n+\tgit tag base &&\n+\n+\tgit checkout -b right &&\n+\techo 7 >>sub/file &&\n+\tgit add sub/file &&\n+\ttest_tick &&\n+\tgit commit -m right &&\n+\n+\tgit checkout -b left base &&\n+\techo 0 >newfile &&\n+\tcat sub/file >>newfile &&\n+\tgit rm sub/file &&\n+\tmv newfile sub &&\n+\tgit add sub &&\n+\ttest_tick &&\n+\tgit commit -m left\n+'\n+\n+test_expect_success 'disappearing dir in rename/directory conflict handled' '\n+\tgit reset --hard &&\n+\tgit clean -fdqx &&\n+\n+\tgit checkout left^0 &&\n+\n+\tgit merge -s recursive right^0 &&\n+\n+\ttest 1 -eq $(git ls-files -s | wc -l) &&\n+\ttest 0 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\techo 0 >expect &&\n+\tgit cat-file -p base:sub/file >>expect &&\n+\techo 7 >>expect &&\n+\ttest_cmp expect sub &&\n+\n+\ttest -f sub\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169537","messageId":"1307518278-23814-7-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 06/48] t6039: Add failing testcases for rename/rename/add-{source,dest} conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:36Z","receivedAt":"2011-06-08T07:30:36Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Add testcases that cover three failures with current git merge, all\ninvolving renaming one file on both sides of history:\n\nCase 1:\nIf a single file is renamed to two different filenames on different sides\nof history, there should be a conflict.  Adding a new file on one of those\nsides of history whose name happens to match the rename source should not\ncause the merge to suddenly succeed.\n\nCase 2:\nIf a single file is renamed on both sides of history but renamed\nidentically, there should not be a conflict.  This works fine.  However,\nif one of those sides also added a new file that happened to match the\nrename source, then that file should be left alone.  Currently, the\nrename/rename conflict handling causes that new file to become untracked.\n\nCase 3:\nIf a single file is renamed to two different filenames on different sides\nof history, there should be a conflict.  This works currently.  However,\nif those renames also involve rename/add conflicts (i.e. there are new\nfiles on one side of history that match the destination of the rename of\nthe other side of history), then the resulting conflict should be recorded\nin the index, showing that there were multiple files with a given filename.\nCurrently, git silently discards one of file versions.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6039-merge-rename-corner-cases.sh |  112 ++++++++++++++++++++++++++++++++++\n 1 files changed, 112 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex b465667..fd8337f 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -339,4 +339,116 @@ test_expect_success 'disappearing dir in rename/directory conflict handled' '\n \ttest -f sub\n '\n \n+# Testcase setup for rename-rename-add-1:\n+#   Commit A: new file: a\n+#   Commit B: rename a->b\n+#   Commit C: rename a->c, add completely different a\n+#\n+# Merging of B & C should NOT be clean; there's a rename/rename conflict\n+\n+test_expect_success 'setup rename/rename(1to2)/add-source conflict' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n\" >a &&\n+\tgit add a &&\n+\tgit commit -m A &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\tgit mv a b &&\n+\tgit commit -m B &&\n+\n+\tgit checkout -b C A &&\n+\tgit mv a c &&\n+\techo something completely different >a &&\n+\tgit add a &&\n+\tgit commit -m C\n+'\n+\n+test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' '\n+\tgit checkout B^0 &&\n+\n+\ttest_must_fail git merge -s recursive C^0 &&\n+\n+\ttest -f a &&\n+\ttest -f b &&\n+\ttest -f c\n+'\n+\n+test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\t>a &&\n+\tgit add a &&\n+\ttest_tick &&\n+\tgit commit -m base &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\tgit mv a b &&\n+\ttest_tick &&\n+\tgit commit -m one &&\n+\n+\tgit checkout -b C A &&\n+\tgit mv a b &&\n+\techo important-info >a &&\n+\ttest_tick &&\n+\tgit commit -m two\n+'\n+\n+test_expect_failure 'rename/rename/add-source still tracks new a file' '\n+\tgit checkout C^0 &&\n+\tgit merge -s recursive B^0 &&\n+\n+\ttest 2 -eq $(git ls-files -s | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l)\n+'\n+\n+test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\techo stuff >a &&\n+\tgit add a &&\n+\ttest_tick &&\n+\tgit commit -m base &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\tgit mv a b &&\n+\techo precious-data >c &&\n+\tgit add c &&\n+\ttest_tick &&\n+\tgit commit -m one &&\n+\n+\tgit checkout -b C A &&\n+\tgit mv a c &&\n+\techo important-info >b &&\n+\tgit add b &&\n+\ttest_tick &&\n+\tgit commit -m two\n+'\n+\n+test_expect_failure 'rename/rename/add-dest merge still knows about conflicting file versions' '\n+\tgit checkout C^0 &&\n+\ttest_must_fail git merge -s recursive B^0 &&\n+\n+\ttest 5 -eq $(git ls-files -s | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u b | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u c | wc -l) &&\n+\n+\ttest $(git rev-parse :2:b) = $(git rev-parse C:b) &&\n+\ttest $(git rev-parse :3:b) = $(git rev-parse B:b) &&\n+\ttest $(git rev-parse :2:c) = $(git rev-parse C:c) &&\n+\ttest $(git rev-parse :3:c) = $(git rev-parse B:c)\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169580","messageId":"1307518278-23814-8-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 07/48] t6039: Ensure rename/rename conflicts leave index and workdir in sane state","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:37Z","receivedAt":"2011-06-08T07:30:37Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"rename/rename conflicts, both with one file being renamed to two different\nfiles and with two files being renamed to the same file, should leave the\nindex and the working copy in a sane state with appropriate conflict\nrecording, auxiliary files, etc.  Git seems to handle one of the two cases\nalright, but has some problems with the two files being renamed to one\ncase.  Add tests for both cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6039-merge-rename-corner-cases.sh |   93 ++++++++++++++++++++++++++++++++++\n 1 files changed, 93 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex fd8337f..06c7ea5 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -451,4 +451,97 @@ test_expect_failure 'rename/rename/add-dest merge still knows about conflicting\n \ttest $(git rev-parse :3:c) = $(git rev-parse B:c)\n '\n \n+test_expect_success 'setup simple rename/rename (1to2) conflict' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\techo stuff >a &&\n+\tgit add a &&\n+\ttest_tick &&\n+\tgit commit -m A &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\tgit mv a b &&\n+\ttest_tick &&\n+\tgit commit -m B &&\n+\n+\tgit checkout -b C A &&\n+\tgit mv a c &&\n+\ttest_tick &&\n+\tgit commit -m C\n+'\n+\n+test_expect_success 'merge has correct working tree contents' '\n+\tgit checkout C^0 &&\n+\n+\ttest_must_fail git merge -s recursive B^0 &&\n+\n+\ttest 3 -eq $(git ls-files -s | wc -l) &&\n+\ttest 3 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest -f b &&\n+\ttest -f c\n+'\n+\n+# Test for all kinds of things that can go wrong with rename/rename (2to1):\n+#   Commit A: new files: a & b\n+#   Commit B: rename a->c, modify b\n+#   Commit C: rename b->c, modify a\n+#\n+# Merging of B & C should NOT be clean.  Questions:\n+#   * Both a & b should be removed by the merge; are they?\n+#   * The two c's should contain modifications to a & b; do they?\n+#   * The index should contain two files, both for c; does it?\n+#   * The working copy should have two files, both of form c~<unique>; does it?\n+#   * Nothing else should be present.  Is anything?\n+\n+test_expect_success 'setup rename/rename (2to1) + modify/modify' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n\" >a &&\n+\tprintf \"5\\n4\\n3\\n2\\n1\\n\" >b &&\n+\tgit add a b &&\n+\tgit commit -m A &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\tgit mv a c &&\n+\techo 0 >>b &&\n+\tgit add b &&\n+\tgit commit -m B &&\n+\n+\tgit checkout -b C A &&\n+\tgit mv b c &&\n+\techo 6 >>a &&\n+\tgit add a &&\n+\tgit commit -m C\n+'\n+\n+test_expect_failure 'handle rename/rename (2to1) conflict correctly' '\n+\tgit checkout B^0 &&\n+\n+\ttest_must_fail git merge -s recursive C^0 >out &&\n+\tgrep \"CONFLICT (rename/rename)\" out &&\n+\n+\ttest 2 -eq $(git ls-files -s | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u c | wc -l) &&\n+\ttest 3 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest ! -f a &&\n+\ttest ! -f b &&\n+\ttest -f c~HEAD &&\n+\ttest -f c~C^0 &&\n+\n+\ttest $(git hash-object c~HEAD) = $(git rev-parse C:a) &&\n+\ttest $(git hash-object c~C^0) = $(git rev-parse B:b)\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169543","messageId":"1307518278-23814-9-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 08/48] t6036: Add differently resolved modify/delete conflict in criss-cross test","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:38Z","receivedAt":"2011-06-08T07:30:38Z","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/t6036-recursive-corner-cases.sh |   83 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 83 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 319b6fa..52d2ecf 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -231,4 +231,87 @@ test_expect_success 'git detects differently handled merges conflict' '\n \ttest $(git rev-parse :1:new_a) = $(git hash-object merged)\n '\n \n+#\n+# criss-cross + modify/delete:\n+#\n+#      B   D\n+#      o---o\n+#     / \\ / \\\n+#  A o   X   ? F\n+#     \\ / \\ /\n+#      o---o\n+#      C   E\n+#\n+#   Commit A: file with contents 'A\\n'\n+#   Commit B: file with contents 'B\\n'\n+#   Commit C: file not present\n+#   Commit D: file with contents 'B\\n'\n+#   Commit E: file not present\n+#\n+# Now, when we merge commits D & E, does git detect the conflict?\n+\n+test_expect_success 'setup criss-cross + modify/delete resolved differently' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\techo A >file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m A &&\n+\n+\tgit branch B &&\n+\tgit checkout -b C &&\n+\tgit rm file &&\n+\ttest_tick &&\n+\tgit commit -m C &&\n+\n+\tgit checkout B &&\n+\techo B >file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m B &&\n+\n+\tgit checkout B^0 &&\n+\ttest_must_fail git merge C &&\n+\techo B >file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m D &&\n+\tgit tag D &&\n+\n+\tgit checkout C^0 &&\n+\ttest_must_fail git merge B &&\n+\tgit rm file &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag E\n+'\n+\n+test_expect_failure 'git detects conflict merging criss-cross+modify/delete' '\n+\tgit checkout D^0 &&\n+\n+\ttest_must_fail git merge -s recursive E^0 &&\n+\n+\ttest 2 -eq $(git ls-files -s | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u | wc -l) &&\n+\n+\ttest $(git rev-parse :1:file) = $(git rev-parse master:file) &&\n+\ttest $(git rev-parse :2:file) = $(git rev-parse B:file)\n+'\n+\n+test_expect_failure 'git detects conflict merging criss-cross+modify/delete, reverse direction' '\n+\tgit reset --hard &&\n+\tgit checkout E^0 &&\n+\n+\ttest_must_fail git merge -s recursive D^0 &&\n+\n+\ttest 2 -eq $(git ls-files -s | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u | wc -l) &&\n+\n+\ttest $(git rev-parse :1:file) = $(git rev-parse master:file) &&\n+\ttest $(git rev-parse :3:file) = $(git rev-parse B:file)\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169579","messageId":"1307518278-23814-10-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 09/48] t6036: criss-cross with weird content can fool git into clean merge","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:39Z","receivedAt":"2011-06-08T07:30:39Z","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/t6036-recursive-corner-cases.sh |   83 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 83 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 52d2ecf..dab52a4 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -314,4 +314,87 @@ test_expect_failure 'git detects conflict merging criss-cross+modify/delete, rev\n \ttest $(git rev-parse :3:file) = $(git rev-parse B:file)\n '\n \n+#\n+# criss-cross + modify/modify with very contrived file contents:\n+#\n+#      B   D\n+#      o---o\n+#     / \\ / \\\n+#  A o   X   ? F\n+#     \\ / \\ /\n+#      o---o\n+#      C   E\n+#\n+#   Commit A: file with contents 'A\\n'\n+#   Commit B: file with contents 'B\\n'\n+#   Commit C: file with contents 'C\\n'\n+#   Commit D: file with contents 'D\\n'\n+#   Commit E: file with contents:\n+#      <<<<<<< Temporary merge branch 1\n+#      C\n+#      =======\n+#      B\n+#      >>>>>>> Temporary merge branch 2\n+#\n+# Now, when we merge commits D & E, does git detect the conflict?\n+\n+test_expect_success 'setup differently handled merges of content conflict' '\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\techo A >file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m A &&\n+\n+\tgit branch B &&\n+\tgit checkout -b C &&\n+\techo C >file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m C &&\n+\n+\tgit checkout B &&\n+\techo B >file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m B &&\n+\n+\tgit checkout B^0 &&\n+\ttest_must_fail git merge C &&\n+\techo D >file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m D &&\n+\tgit tag D &&\n+\n+\tgit checkout C^0 &&\n+\ttest_must_fail git merge B &&\n+\tcat <<EOF >file &&\n+<<<<<<< Temporary merge branch 1\n+C\n+=======\n+B\n+>>>>>>> Temporary merge branch 2\n+EOF\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m E &&\n+\tgit tag E\n+'\n+\n+test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '\n+\tgit checkout D^0 &&\n+\n+\ttest_must_fail git merge -s recursive E^0 &&\n+\n+\ttest 3 -eq $(git ls-files -s | wc -l) &&\n+\ttest 3 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse :2:file) = $(git rev-parse D:file) &&\n+\ttest $(git rev-parse :3:file) = $(git rev-parse E:file)\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169548","messageId":"1307518278-23814-11-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 10/48] t6036: tests for criss-cross merges with various directory/file conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:40Z","receivedAt":"2011-06-08T07:30:40Z","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/t6036-recursive-corner-cases.sh |  149 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 149 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex dab52a4..4993f67 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -397,4 +397,153 @@ test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '\n \ttest $(git rev-parse :3:file) = $(git rev-parse E:file)\n '\n \n+#\n+# criss-cross + d/f conflict via add/add:\n+#   Commit A: Neither file 'a' nor directory 'a/' exist.\n+#   Commit B: Introduce 'a'\n+#   Commit C: Introduce 'a/file'\n+# Two different later cases:\n+#   Commit D1: Merge B & C, keeping 'a' and deleting 'a/'\n+#   Commit E1: Merge B & C, deleting 'a' but keeping 'a/file'\n+#\n+#   Commit D2: Merge B & C, keeping a modified 'a' and deleting 'a/'\n+#   Commit E2: Merge B & C, deleting 'a' but keeping a modified 'a/file'\n+#\n+#   Note: D == D1.\n+# Finally, someone goes to merge D1&E1 or D1&E2 or D2&E1.  What happens?\n+#\n+#      B   D1 or D2\n+#      o---o\n+#     / \\ / \\\n+#  A o   X   ? F\n+#     \\ / \\ /\n+#      o---o\n+#      C   E1 or E2\n+#\n+\n+test_expect_success 'setup differently handled merges of directory/file conflict' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\t>irrelevant-file &&\n+\tgit add irrelevant-file &&\n+\ttest_tick &&\n+\tgit commit -m A &&\n+\n+\tgit branch B &&\n+\tgit checkout -b C &&\n+\tmkdir a &&\n+\techo 10 >a/file &&\n+\tgit add a/file &&\n+\ttest_tick &&\n+\tgit commit -m C &&\n+\n+\tgit checkout B &&\n+\techo 5 >a &&\n+\tgit add a &&\n+\ttest_tick &&\n+\tgit commit -m B &&\n+\n+\tgit checkout B^0 &&\n+\ttest_must_fail git merge C &&\n+\tgit clean -f &&\n+\trm -rf a/ &&\n+\techo 5 >a &&\n+\tgit add a &&\n+\ttest_tick &&\n+\tgit commit -m D &&\n+\tgit tag D &&\n+\n+\tgit checkout C^0 &&\n+\ttest_must_fail git merge B &&\n+\tgit clean -f &&\n+\tgit rm --cached a &&\n+\techo 10 >a/file &&\n+\tgit add a/file &&\n+\ttest_tick &&\n+\tgit commit -m E1 &&\n+\tgit tag E1 &&\n+\n+\tgit checkout C^0 &&\n+\ttest_must_fail git merge B &&\n+\tgit clean -f &&\n+\tgit rm --cached a &&\n+\tprintf \"10\\n11\\n\" >a/file &&\n+\tgit add a/file &&\n+\ttest_tick &&\n+\tgit commit -m E2 &&\n+\tgit tag E2\n+'\n+\n+test_expect_failure 'git detects conflict and handles merge of D & E1 correctly' '\n+\tgit reset --hard &&\n+\tgit reset --hard &&\n+\tgit clean -fdqx &&\n+\tgit checkout D^0 &&\n+\n+\t# FIXME: If merge-base could keep both a and a/file in its tree, then\n+\t# we could this merge would actually be able to succeed.\n+\ttest_must_fail git merge -s recursive E1^0 &&\n+\n+\ttest 2 -eq $(git ls-files -s | wc -l) &&\n+\ttest 1 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse :2:a) = $(git rev-parse B:a)\n+'\n+\n+test_expect_failure 'git detects conflict and handles merge of E1 & D correctly' '\n+\tgit reset --hard &&\n+\tgit reset --hard &&\n+\tgit clean -fdqx &&\n+\tgit checkout E1^0 &&\n+\n+\t# FIXME: If merge-base could keep both a and a/file in its tree, then\n+\t# we could this merge would actually be able to succeed.\n+\ttest_must_fail git merge -s recursive D^0 &&\n+\n+\ttest 2 -eq $(git ls-files -s | wc -l) &&\n+\ttest 1 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse :3:a) = $(git rev-parse B:a)\n+'\n+\n+test_expect_success 'git detects conflict and handles merge of D & E2 correctly' '\n+\tgit reset --hard &&\n+\tgit reset --hard &&\n+\tgit clean -fdqx &&\n+\tgit checkout D^0 &&\n+\n+\ttest_must_fail git merge -s recursive E2^0 &&\n+\n+\ttest 3 -eq $(git ls-files -s | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u | wc -l) &&\n+\ttest 1 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse :2:a) = $(git rev-parse B:a) &&\n+\ttest $(git rev-parse :3:a/file) = $(git rev-parse E1:a/file)\n+\ttest $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)\n+'\n+\n+test_expect_failure 'git detects conflict and handles merge of E2 & D correctly' '\n+\tgit reset --hard &&\n+\tgit reset --hard &&\n+\tgit clean -fdqx &&\n+\tgit checkout E2^0 &&\n+\n+\ttest_must_fail git merge -s recursive D^0 &&\n+\n+\ttest 3 -eq $(git ls-files -s | wc -l) &&\n+\ttest 2 -eq $(git ls-files -u | wc -l) &&\n+\ttest 1 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse :3:a) = $(git rev-parse B:a) &&\n+\ttest $(git rev-parse :2:a/file) = $(git rev-parse E1:a/file)\n+\ttest $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)\n+'\n+\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169576","messageId":"1307518278-23814-12-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 11/48] t6036: criss-cross w/ rename/rename(1to2)/modify+rename/rename(2to1)/modify","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:41Z","receivedAt":"2011-06-08T07:30:41Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This test is mostly just designed for testing optimality of the virtual\nmerge base in the event of a rename/rename(1to2) conflict.  The current\nchoice for resolving this in git seems somewhat confusing and suboptimal.\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 4993f67..eee183e 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -545,5 +545,81 @@ test_expect_failure 'git detects conflict and handles merge of E2 & D correctly'\n \ttest $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)\n '\n \n+#\n+# criss-cross with rename/rename(1to2)/modify followed by\n+# rename/rename(2to1)/modify:\n+#\n+#      B   D\n+#      o---o\n+#     / \\ / \\\n+#  A o   X   ? F\n+#     \\ / \\ /\n+#      o---o\n+#      C   E\n+#\n+#   Commit A: new file: a\n+#   Commit B: rename a->b, modifying by adding a line\n+#   Commit C: rename a->c\n+#   Commit D: merge B&C, resolving conflict by keeping contents in newname\n+#   Commit E: merge B&C, resolving conflict similar to D but adding another line\n+#\n+# There is a conflict merging B & C, but one of filename not of file\n+# content.  Whoever created D and E chose specific resolutions for that\n+# conflict resolution.  Now, since: (1) there is no content conflict\n+# merging B & C, (2) D does not modify that merged content further, and (3)\n+# both D & E resolve the name conflict in the same way, the modification to\n+# newname in E should not cause any conflicts when it is merged with D.\n+# (Note that this can be accomplished by having the virtual merge base have\n+# the merged contents of b and c stored in a file named a, which seems like\n+# the most logical choice anyway.)\n+\n+test_expect_success 'setup rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '\n+\tgit reset --hard &&\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n\" >a &&\n+\tgit add a &&\n+\tgit commit -m A &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\tgit mv a b &&\n+\techo 7 >>b &&\n+\tgit add -u &&\n+\tgit commit -m B &&\n+\n+\tgit checkout -b C A &&\n+\tgit mv a c &&\n+\tgit commit -m C &&\n+\n+\tgit checkout -q B^0 &&\n+\tgit merge --no-commit -s ours C^0 &&\n+\tgit mv b newname &&\n+\tgit commit -m \"Merge commit C^0 into HEAD\" &&\n+\tgit tag D &&\n+\n+\tgit checkout -q C^0 &&\n+\tgit merge --no-commit -s ours B^0 &&\n+\tgit mv c newname &&\n+\tprintf \"7\\n8\\n\" >>newname &&\n+\tgit add -u &&\n+\tgit commit -m \"Merge commit B^0 into HEAD\" &&\n+\tgit tag E\n+'\n+\n+test_expect_failure 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '\n+\tgit checkout D^0 &&\n+\n+\tgit merge -s recursive E^0 &&\n+\n+\ttest 1 -eq $(git ls-files -s | wc -l) &&\n+\ttest 0 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest 8 -eq $(wc -l < newname)\n+'\n \n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169577","messageId":"1307518278-23814-13-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 12/48] t6036: criss-cross + rename/rename(1to2)/add-source + modify/modify","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:42Z","receivedAt":"2011-06-08T07:30:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Now THAT's a corner case.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6036-recursive-corner-cases.sh |   72 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 72 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex eee183e..2fa7c3e 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -622,4 +622,76 @@ test_expect_failure 'handle rename/rename(1to2)/modify followed by what looks li\n \ttest 8 -eq $(wc -l < newname)\n '\n \n+#\n+# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify:\n+#\n+#      B   D\n+#      o---o\n+#     / \\ / \\\n+#  A o   X   ? F\n+#     \\ / \\ /\n+#      o---o\n+#      C   E\n+#\n+#   Commit A: new file: a\n+#   Commit B: rename a->b\n+#   Commit C: rename a->c, add different a\n+#   Commit D: merge B&C, keeping b&c and (new) a modified at beginning\n+#   Commit E: merge B&C, keeping b&c and (new) a modified at end\n+#\n+# Now, when we merge commits D & E, there should be no conflict...\n+\n+test_expect_success 'setup criss-cross + rename/rename/add + modify/modify' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\tprintf \"lots\\nof\\nwords\\nand\\ncontent\\n\" >a &&\n+\tgit add a &&\n+\tgit commit -m A &&\n+\tgit tag A &&\n+\n+\tgit checkout -b B A &&\n+\tgit mv a b &&\n+\tgit commit -m B &&\n+\n+\tgit checkout -b C A &&\n+\tgit mv a c &&\n+\tprintf \"2\\n3\\n4\\n5\\n6\\n7\\n\" >a &&\n+\tgit add a &&\n+\tgit commit -m C &&\n+\n+\tgit checkout B^0 &&\n+\tgit merge --no-commit -s ours C^0 &&\n+\tgit checkout C -- a c &&\n+\tmv a old_a &&\n+\techo 1 >a &&\n+\tcat old_a >>a &&\n+\trm old_a &&\n+\tgit add -u &&\n+\tgit commit -m \"Merge commit C^0 into HEAD\" &&\n+\tgit tag D &&\n+\n+\tgit checkout C^0 &&\n+\tgit merge --no-commit -s ours B^0 &&\n+\tgit checkout B -- b &&\n+\techo 8 >> a &&\n+\tgit add -u &&\n+\tgit commit -m \"Merge commit B^0 into HEAD\" &&\n+\tgit tag E\n+'\n+\n+test_expect_failure 'correctly resolves criss-cross with rename/rename/add and modify/modify conflict' '\n+\tgit checkout D^0 &&\n+\n+\tgit merge -s recursive E^0 &&\n+\n+\ttest 3 -eq $(git ls-files -s | wc -l) &&\n+\ttest 0 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest 6 -eq $(wc -l < a)\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169578","messageId":"1307518278-23814-14-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 13/48] t6022: Remove unnecessary untracked files to make test cleaner","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:43Z","receivedAt":"2011-06-08T07:30:43Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Since this test later does a git add -A, we should clean out unnecessary\nuntracked files as part of our cleanup.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6022-merge-rename.sh |    1 +\n 1 files changed, 1 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 1ed259d..1d1b32e 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -252,6 +252,7 @@ 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+\tgit clean -fdqx &&\n \n \tmkdir sub &&\n \tmkdir dir &&\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169566","messageId":"1307518278-23814-15-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 14/48] t6022: New tests checking for unnecessary updates of files","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:44Z","receivedAt":"2011-06-08T07:30:44Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This testcase was part of en/merge-recursive that was reverted in 6db4105\n(Revert \"Merge branch 'en/merge-recursive'\" 2011-05-19).  While the other\nchanges in that series caused unfortunate breakage, this testcase is still\nuseful; reinstate it.\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 1d1b32e..11c5c60 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -610,4 +610,67 @@ test_expect_success 'check handling of differently renamed file with D/F conflic\n \t! test -f original\n '\n \n+test_expect_success 'setup avoid unnecessary update, normal rename' '\n+\tgit reset --hard &&\n+\tgit checkout --orphan avoid-unnecessary-update-1 &&\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n9\\n10\\n\" >original &&\n+\tgit add -A &&\n+\tgit commit -m \"Common commmit\" &&\n+\n+\tgit mv original rename &&\n+\techo 11 >>rename &&\n+\tgit add -u &&\n+\tgit commit -m \"Renamed and modified\" &&\n+\n+\tgit checkout -b merge-branch-1 HEAD~1 &&\n+\techo \"random content\" >random-file &&\n+\tgit add -A &&\n+\tgit commit -m \"Random, unrelated changes\"\n+'\n+\n+test_expect_failure 'avoid unnecessary update, normal rename' '\n+\tgit checkout -q avoid-unnecessary-update-1^0 &&\n+\ttest-chmtime =1000000000 rename &&\n+\ttest-chmtime -v +0 rename >expect &&\n+\tgit merge merge-branch-1 &&\n+\ttest-chmtime -v +0 rename >actual &&\n+\ttest_cmp expect actual # \"rename\" should have stayed intact\n+'\n+\n+test_expect_success 'setup to test avoiding unnecessary update, with D/F conflict' '\n+\tgit reset --hard &&\n+\tgit checkout --orphan avoid-unnecessary-update-2 &&\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\n+\tmkdir df &&\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n9\\n10\\n\" >df/file &&\n+\tgit add -A &&\n+\tgit commit -m \"Common commmit\" &&\n+\n+\tgit mv df/file temp &&\n+\trm -rf df &&\n+\tgit mv temp df &&\n+\techo 11 >>df &&\n+\tgit add -u &&\n+\tgit commit -m \"Renamed and modified\" &&\n+\n+\tgit checkout -b merge-branch-2 HEAD~1 &&\n+\t>unrelated-change &&\n+\tgit add unrelated-change &&\n+\tgit commit -m \"Only unrelated changes\"\n+'\n+\n+test_expect_failure 'avoid unnecessary update, with D/F conflict' '\n+\tgit checkout -q avoid-unnecessary-update-2^0 &&\n+\ttest-chmtime =1000000000 df &&\n+\ttest-chmtime -v +0 df >expect &&\n+\tgit merge merge-branch-2 &&\n+\ttest-chmtime -v +0 df >actual &&\n+\ttest_cmp expect actual # \"df\" should have stayed intact\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169539","messageId":"1307518278-23814-16-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 15/48] t6022: Add testcase for merging a renamed file with a simple change","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:45Z","receivedAt":"2011-06-08T07:30:45Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is a testcase that was broken by b2c8c0a (merge-recursive: When we\ndetect we can skip an update, actually skip it 2011-02-28) and fixed by\n6db4105 (Revert \"Merge branch 'en/merge-recursive'\" 2011-05-19).  Include\nthis testcase to ensure we don't regress it again.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t6022-merge-rename.sh |   22 ++++++++++++++++++++++\n 1 files changed, 22 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 11c5c60..52a8f0b 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -673,4 +673,26 @@ test_expect_failure 'avoid unnecessary update, with D/F conflict' '\n \ttest_cmp expect actual # \"df\" should have stayed intact\n '\n \n+test_expect_success 'setup merge of rename + small change' '\n+\tgit reset --hard &&\n+\tgit checkout --orphan rename-plus-small-change &&\n+\tgit rm -rf . &&\n+\n+\techo ORIGINAL >file &&\n+\tgit add file &&\n+\n+\ttest_tick &&\n+\tgit commit -m Initial &&\n+\tgit checkout -b rename_branch &&\n+\tgit mv file renamed_file &&\n+\tgit commit -m Rename &&\n+\tgit checkout rename-plus-small-change &&\n+\techo NEW-VERSION >file &&\n+\tgit commit -a -m Reformat\n+'\n+\n+test_expect_success 'merge rename + small change' '\n+\tgit merge rename_branch\n+'\n+\n test_done\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169545","messageId":"1307518278-23814-17-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 16/48] merge-recursive: Make BUG message more legible by adding a newline","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:46Z","receivedAt":"2011-06-08T07:30:46Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hopefully no one ever hits this error except when making large changes to\nmerge-recursive.c and debugging...\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex db9ba19..3fcd0a5 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -230,7 +230,7 @@ struct tree *write_tree_from_memory(struct merge_options *o)\n \t\tfor (i = 0; i < active_nr; i++) {\n \t\t\tstruct cache_entry *ce = active_cache[i];\n \t\t\tif (ce_stage(ce))\n-\t\t\t\tfprintf(stderr, \"BUG: %d %.*s\", ce_stage(ce),\n+\t\t\t\tfprintf(stderr, \"BUG: %d %.*s\\n\", ce_stage(ce),\n \t\t\t\t\t(int)ce_namelen(ce), ce->name);\n \t\t}\n \t\tdie(\"Bug in merge-recursive.c\");\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169574","messageId":"1307518278-23814-18-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 17/48] merge-recursive: Correct a comment","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:47Z","receivedAt":"2011-06-08T07:30:47Z","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 merge-recursive.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 3fcd0a5..3d464d9 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1439,7 +1439,7 @@ static int process_df_entry(struct merge_options *o,\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) */\n+\t\t/* directory -> (directory, file) or <nothing> -> (directory, file) */\n \t\tconst char *add_branch;\n \t\tconst char *other_branch;\n \t\tunsigned mode;\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169546","messageId":"1307518278-23814-19-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 18/48] merge-recursive: Mark some diff_filespec struct arguments const","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:48Z","receivedAt":"2011-06-08T07:30:48Z","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 merge-recursive.c |   19 ++++++++++---------\n 1 files changed, 10 insertions(+), 9 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 3d464d9..317bf23 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -461,9 +461,10 @@ static struct string_list *get_renames(struct merge_options *o,\n \treturn renames;\n }\n \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, int options)\n+static int update_stages_options(const char *path, const struct diff_filespec *o,\n+\t\t\t\t const struct diff_filespec *a,\n+\t\t\t\t const struct diff_filespec *b,\n+\t\t\t\t int clear, int options)\n {\n \tif (clear)\n \t\tif (remove_file_from_cache(path))\n@@ -712,9 +713,9 @@ struct merge_file_info {\n \n static int merge_3way(struct merge_options *o,\n \t\t      mmbuffer_t *result_buf,\n-\t\t      struct diff_filespec *one,\n-\t\t      struct diff_filespec *a,\n-\t\t      struct diff_filespec *b,\n+\t\t      const struct diff_filespec *one,\n+\t\t      const struct diff_filespec *a,\n+\t\t      const struct diff_filespec *b,\n \t\t      const char *branch1,\n \t\t      const char *branch2)\n {\n@@ -772,9 +773,9 @@ static int merge_3way(struct merge_options *o,\n }\n \n static struct merge_file_info merge_file(struct merge_options *o,\n-\t\t\t\t         struct diff_filespec *one,\n-\t\t\t\t\t struct diff_filespec *a,\n-\t\t\t\t\t struct diff_filespec *b,\n+\t\t\t\t\t const struct diff_filespec *one,\n+\t\t\t\t\t const struct diff_filespec *a,\n+\t\t\t\t\t const struct diff_filespec *b,\n \t\t\t\t\t const char *branch1,\n \t\t\t\t\t const char *branch2)\n {\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169540","messageId":"1307518278-23814-20-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 19/48] merge-recursive: Remember to free generated unique path names","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:49Z","receivedAt":"2011-06-08T07:30:49Z","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 merge-recursive.c |   21 ++++++++++++++-------\n 1 files changed, 14 insertions(+), 7 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 317bf23..45675b6 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1072,7 +1072,6 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t   renamed: clean merge */\n \t\t\t\tupdate_file(o, 1, ren1->pair->two->sha1, ren1->pair->two->mode, ren1_dst);\n \t\t\t} else if (!sha_eq(dst_other.sha1, null_sha1)) {\n-\t\t\t\tconst char *new_path;\n \t\t\t\tclean_merge = 0;\n \t\t\t\ttry_merge = 1;\n \t\t\t\toutput(o, 1, \"CONFLICT (rename/add): Rename %s->%s in %s. \"\n@@ -1101,9 +1100,10 @@ static int process_renames(struct merge_options *o,\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\tchar *new_path = unique_path(o, ren1_dst, branch2);\n \t\t\t\t\toutput(o, 1, \"Adding as %s instead\", new_path);\n \t\t\t\t\tupdate_file(o, 0, dst_other.sha1, dst_other.mode, new_path);\n+\t\t\t\t\tfree(new_path);\n \t\t\t\t}\n \t\t\t} else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {\n \t\t\t\tren2 = item->util;\n@@ -1271,13 +1271,14 @@ static int merge_content(struct merge_options *o,\n \t}\n \n \tif (df_conflict_remains) {\n-\t\tconst char *new_path;\n+\t\tchar *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\tfree(new_path);\n \t} else {\n \t\tupdate_file(o, mfi.clean, mfi.sha, mfi.mode, path);\n \t}\n@@ -1433,12 +1434,17 @@ 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\tchar *new_path;\n+\t\tint free_me = 0;\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\t\tfree_me = 1;\n+\t\t}\n \t\tclean_merge = 0;\n-\t\thandle_delete_modify(o, path, new_path,\n+\t\thandle_delete_modify(o, path, free_me ? new_path : path,\n \t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n+\t\tif (free_me)\n+\t\t\tfree(new_path);\n \t} else if (!o_sha && !!a_sha != !!b_sha) {\n \t\t/* directory -> (directory, file) or <nothing> -> (directory, file) */\n \t\tconst char *add_branch;\n@@ -1461,12 +1467,13 @@ static int process_df_entry(struct merge_options *o,\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\tchar *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\tupdate_file(o, 0, sha, mode, new_path);\n+\t\t\tfree(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-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169549","messageId":"1307518278-23814-21-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 20/48] merge-recursive: Avoid working directory changes during recursive case","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:50Z","receivedAt":"2011-06-08T07:30:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"make_room_for_directories_of_df_conflicts() is about making sure necessary\nworking directory changes can succeed.  When o->call_depth > 0 (i.e. the\nrecursive case), we do not want to make any working directory changes so\nthis function should be skipped.\n\nNote that make_room_for_directories_of_df_conflicts() is broken as has\nbeen pointed out by Junio; it should NOT be unlinking files.  What it\nshould do is keep track of files that could be unlinked if a directory\nlater needs to be written in their place.  However, that work also is only\nrelevant in the non-recursive case, so this change is helpful either way.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |    7 +++++++\n 1 files changed, 7 insertions(+), 0 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 45675b6..7da6aa0 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -355,6 +355,13 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,\n \tint last_len = 0;\n \tint i;\n \n+\t/*\n+\t * If we're merging merge-bases, we don't want to bother with\n+\t * any working directory changes.\n+\t */\n+\tif (o->call_depth)\n+\t\treturn;\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-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169550","messageId":"1307518278-23814-22-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 21/48] merge-recursive: Fix recursive case with D/F conflict via add/add conflict","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:51Z","receivedAt":"2011-06-08T07:30:51Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When a D/F conflict is introduced via an add/add conflict, when\no->call_depth > 0 we need to ensure that the higher stage entry from the\nbase stage is removed.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |    2 ++\n t/t6036-recursive-corner-cases.sh |    4 ++--\n 2 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 7da6aa0..4771fb4 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1480,6 +1480,8 @@ static int process_df_entry(struct merge_options *o,\n \t\t\t       \"Adding %s as %s\",\n \t\t\t       conf, path, other_branch, path, new_path);\n \t\t\tupdate_file(o, 0, sha, mode, new_path);\n+\t\t\tif (o->call_depth)\n+\t\t\t\tremove_file_from_cache(path);\n \t\t\tfree(new_path);\n \t\t} else {\n \t\t\toutput(o, 2, \"Adding %s\", path);\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 2fa7c3e..423fb62 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -494,7 +494,7 @@ test_expect_failure 'git detects conflict and handles merge of D & E1 correctly'\n \ttest $(git rev-parse :2:a) = $(git rev-parse B:a)\n '\n \n-test_expect_failure 'git detects conflict and handles merge of E1 & D correctly' '\n+test_expect_success 'git detects conflict and handles merge of E1 & D correctly' '\n \tgit reset --hard &&\n \tgit reset --hard &&\n \tgit clean -fdqx &&\n@@ -528,7 +528,7 @@ test_expect_success 'git detects conflict and handles merge of D & E2 correctly'\n \ttest $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)\n '\n \n-test_expect_failure 'git detects conflict and handles merge of E2 & D correctly' '\n+test_expect_success 'git detects conflict and handles merge of E2 & D correctly' '\n \tgit reset --hard &&\n \tgit reset --hard &&\n \tgit clean -fdqx &&\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169544","messageId":"1307518278-23814-23-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 22/48] merge-recursive: Fix sorting order and directory change assumptions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:52Z","receivedAt":"2011-06-08T07:30:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We cannot assume that directory/file conflicts will appear in sorted\norder; for example, 'letters.txt' comes between 'letters' and\n'letters/file'.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c   |   31 ++++++++++++++++++++++++++-----\n t/t6020-merge-df.sh |   26 ++++++++++++++++++--------\n 2 files changed, 44 insertions(+), 13 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 4771fb4..ed1fdb2 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -333,6 +333,28 @@ static struct string_list *get_unmerged(void)\n \treturn unmerged;\n }\n \n+static int string_list_df_name_compare(const void *a, const void *b)\n+{\n+\tconst struct string_list_item *one = a;\n+\tconst struct string_list_item *two = b;\n+\t/*\n+\t * Here we only care that entries for D/F conflicts are\n+\t * adjacent, in particular with the file of the D/F conflict\n+\t * appearing before files below the corresponding directory.\n+\t * The order of the rest of the list is irrelevant for us.\n+\t *\n+\t * To achieve this, we sort with df_name_compare and provide\n+\t * the mode S_IFDIR so that D/F conflicts will sort correctly.\n+\t * We use the mode S_IFDIR for everything else for simplicity,\n+\t * since in other cases any changes in their order due to\n+\t * sorting cause no problems for us.\n+\t */\n+\treturn df_name_compare(one->string, strlen(one->string), S_IFDIR,\n+\t\t\t       two->string, strlen(two->string), S_IFDIR);\n+}\n+\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@@ -345,11 +367,6 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,\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 = 0;\n@@ -362,6 +379,10 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,\n \tif (o->call_depth)\n \t\treturn;\n \n+\t/* Ensure D/F conflicts are adjacent in the entries list. */\n+\tqsort(entries->items, entries->nr, sizeof(*entries->items),\n+\t      string_list_df_name_compare);\n+\n \tfor (i = 0; i < entries->nr; i++) {\n \t\tconst char *path = entries->items[i].string;\n \t\tint len = strlen(path);\ndiff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh\nindex eec8f4e..27c3d73 100755\n--- a/t/t6020-merge-df.sh\n+++ b/t/t6020-merge-df.sh\n@@ -59,15 +59,19 @@ test_expect_success 'setup modify/delete + directory/file conflict' '\n \tgit add letters &&\n \tgit commit -m initial &&\n \n+\t# Throw in letters.txt for sorting order fun\n+\t# (\"letters.txt\" sorts between \"letters\" and \"letters/file\")\n \techo i >>letters &&\n-\tgit add letters &&\n+\techo \"version 2\" >letters.txt &&\n+\tgit add letters letters.txt &&\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+\techo \"version 1\" >letters.txt &&\n+\tgit add letters letters.txt &&\n \tgit commit -m deleted\n '\n \n@@ -75,25 +79,31 @@ test_expect_success '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+\ttest 5 -eq $(git ls-files -s | wc -l) &&\n+\ttest 4 -eq $(git ls-files -u | wc -l) &&\n+\ttest 1 -eq $(git ls-files -o | wc -l) &&\n \n \ttest -f letters/file &&\n+\ttest -f letters.txt &&\n \ttest -f letters~modify\n '\n \n test_expect_success 'modify/delete + directory/file conflict; other way' '\n+\t# Yes, we really need the double reset since \"letters\" appears as\n+\t# both a file and a directory.\n+\tgit reset --hard &&\n \tgit reset --hard &&\n \tgit clean -f &&\n \tgit checkout modify^0 &&\n+\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+\ttest 5 -eq $(git ls-files -s | wc -l) &&\n+\ttest 4 -eq $(git ls-files -u | wc -l) &&\n+\ttest 1 -eq $(git ls-files -o | wc -l) &&\n \n \ttest -f letters/file &&\n+\ttest -f letters.txt &&\n \ttest -f letters~HEAD\n '\n \n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169541","messageId":"1307518278-23814-24-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 23/48] merge-recursive: Fix code checking for D/F conflicts still being present","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:53Z","receivedAt":"2011-06-08T07:30:53Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Previously, we were using lstat() to determine if a directory was still\npresent after a merge (and thus in the way of adding a file).  We should\nhave been using lstat() only to determine if untracked directories were in\nthe way (and then only when necessary to check for untracked directories);\nwe should instead using the index to determine if there is a tracked\ndirectory in the way.  Create a new function to do this and use it to\nreplace the existing checks for directories being in the way.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   49 ++++++++++++++++++++++++++++++++++---------------\n 1 files changed, 34 insertions(+), 15 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ed1fdb2..3a8e64e 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -592,6 +592,30 @@ static void flush_buffer(int fd, const char *buf, unsigned long size)\n \t}\n }\n \n+static int dir_in_way(const char *path, int check_working_copy)\n+{\n+\tint pathlen = strlen(path);\n+\tchar *dirpath = xmalloc(pathlen + 2);\n+\tstruct stat st;\n+\n+\tstrcpy(dirpath, path);\n+\tdirpath[pathlen] = '/';\n+\tdirpath[pathlen+1] = '\\0';\n+\n+\tint pos = cache_name_pos(dirpath, pathlen+1);\n+\n+\tif (pos < 0)\n+\t\tpos = -1 - pos;\n+\tif (pos < active_nr &&\n+\t    !strncmp(dirpath, active_cache[pos]->name, pathlen+1)) {\n+\t\tfree(dirpath);\n+\t\treturn 1;\n+\t}\n+\n+\tfree(dirpath);\n+\treturn check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode);\n+}\n+\n static int would_lose_untracked(const char *path)\n {\n \tint pos = cache_name_pos(path, strlen(path));\n@@ -881,7 +905,6 @@ static void conflict_rename_delete(struct merge_options *o,\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@@ -892,7 +915,7 @@ 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+\tif (dir_in_way(dest_name, !o->call_depth)) {\n \t\tdest_name = unique_path(o, dest_name, rename_branch);\n \t\tdf_conflict = 1;\n \t}\n@@ -914,13 +937,12 @@ 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-\tstruct stat st;\n-\tif (lstat(ren1_dst, &st) == 0 && S_ISDIR(st.st_mode)) {\n+\tif (dir_in_way(ren1_dst, !o->call_depth)) {\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}\n-\tif (lstat(ren2_dst, &st) == 0 && S_ISDIR(st.st_mode)) {\n+\tif (dir_in_way(ren2_dst, !o->call_depth)) {\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@@ -1080,7 +1102,7 @@ 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\tif (string_list_has_string(&o->current_directory_set, ren1_dst)) {\n+\t\t\t\tif (dir_in_way(ren1_dst, 0 /*check_wc*/)) {\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@@ -1159,7 +1181,7 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\ta = &src_other;\n \t\t\t\t}\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\tif (dir_in_way(ren1_dst, 0 /*check_wc*/)) {\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@@ -1264,7 +1286,6 @@ 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@@ -1281,7 +1302,7 @@ static int merge_content(struct merge_options *o,\n \n \tmfi = merge_file(o, &one, &a, &b, o->branch1, o->branch2);\n \tif (df_rename_conflict_branch &&\n-\t    lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {\n+\t    dir_in_way(path, !o->call_depth)) {\n \t\tdf_conflict_remains = 1;\n \t}\n \n@@ -1346,8 +1367,7 @@ 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} else if (dir_in_way(path, 0 /*check_wc*/)) {\n \t\t\tentry->processed = 0;\n \t\t\treturn 1; /* Assume clean until processed */\n \t\t} else {\n@@ -1370,7 +1390,7 @@ static int process_entry(struct merge_options *o,\n \t\t\tmode = b_mode;\n \t\t\tsha = b_sha;\n \t\t}\n-\t\tif (string_list_has_string(&o->current_directory_set, path)) {\n+\t\tif (dir_in_way(path, 0 /*check_wc*/)) {\n \t\t\t/* Handle D->F conflicts after all subfiles */\n \t\t\tentry->processed = 0;\n \t\t\treturn 1; /* Assume clean until processed */\n@@ -1418,7 +1438,6 @@ 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-\tstruct stat st;\n \n \tentry->processed = 1;\n \tif (entry->rename_df_conflict_info) {\n@@ -1464,7 +1483,7 @@ static int process_df_entry(struct merge_options *o,\n \t\t/* Modify/delete; deleted side may have put a directory in the way */\n \t\tchar *new_path;\n \t\tint free_me = 0;\n-\t\tif (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {\n+\t\tif (dir_in_way(path, !o->call_depth)) {\n \t\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n \t\t\tfree_me = 1;\n \t\t}\n@@ -1494,7 +1513,7 @@ static int process_df_entry(struct merge_options *o,\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\tif (dir_in_way(path, !o->call_depth)) {\n \t\t\tchar *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-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169542","messageId":"1307518278-23814-25-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 24/48] merge-recursive: Save D/F conflict filenames instead of unlinking them","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:54Z","receivedAt":"2011-06-08T07:30:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Rename make_room_for_directories_of_df_conflicts() to\nrecord_df_conflict_files() to reflect the change in functionality.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   40 ++++++++++++++++++++++++----------------\n merge-recursive.h |    1 +\n 2 files changed, 25 insertions(+), 16 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 3a8e64e..e59eec0 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -353,20 +353,24 @@ static int string_list_df_name_compare(const void *a, const void *b)\n \t\t\t       two->string, strlen(two->string), S_IFDIR);\n }\n \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+static void record_df_conflict_files(struct merge_options *o,\n+\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/* If there is a D/F conflict and the file for such a conflict\n+\t * currently exist in the working copy, we want to allow it to\n+\t * be removed to make room for the corresponding directory if\n+\t * needed.  The files underneath the directories of such D/F\n+\t * conflicts will be handled in process_entry(), while the\n+\t * files of such D/F conflicts will be processed later in\n+\t * process_df_entry().  If the corresponding directory ends up\n+\t * being removed by the merge, then no additional work needs\n+\t * to be done by process_df_entry() for the conflicting file.\n+\t * If the directory needs to be written to the working copy,\n+\t * then the conflicting file will simply be removed (e.g. in\n+\t * make_room_for_path).  If the directory is written to the\n+\t * working copy but the file also has a conflict that needs to\n+\t * be resolved, then process_df_entry() will reinstate the\n+\t * file with a new unique name.\n \t */\n \tconst char *last_file = NULL;\n \tint last_len = 0;\n@@ -383,6 +387,7 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,\n \tqsort(entries->items, entries->nr, sizeof(*entries->items),\n \t      string_list_df_name_compare);\n \n+\tstring_list_clear(&o->df_conflict_file_set, 1);\n \tfor (i = 0; i < entries->nr; i++) {\n \t\tconst char *path = entries->items[i].string;\n \t\tint len = strlen(path);\n@@ -391,14 +396,15 @@ static void make_room_for_directories_of_df_conflicts(struct merge_options *o,\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 * If so, record that it's okay to remove last_file to make\n+\t\t * room for path and friends if needed.\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\tstring_list_insert(&o->df_conflict_file_set, last_file);\n \t\t}\n \n \t\t/*\n@@ -1574,7 +1580,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\trecord_df_conflict_files(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);\n@@ -1800,6 +1806,8 @@ void init_merge_options(struct merge_options *o)\n \to->current_file_set.strdup_strings = 1;\n \tmemset(&o->current_directory_set, 0, sizeof(struct string_list));\n \to->current_directory_set.strdup_strings = 1;\n+\tmemset(&o->df_conflict_file_set, 0, sizeof(struct string_list));\n+\to->df_conflict_file_set.strdup_strings = 1;\n }\n \n int parse_merge_opt(struct merge_options *o, const char *s)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 7e1e972..58f3435 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -26,6 +26,7 @@ struct merge_options {\n \tstruct strbuf obuf;\n \tstruct string_list current_file_set;\n \tstruct string_list current_directory_set;\n+\tstruct string_list df_conflict_file_set;\n };\n \n /* merge_trees() but with recursive ancestor consolidation */\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169562","messageId":"1307518278-23814-26-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 25/48] merge-recursive: Split was_tracked() out of would_lose_untracked()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:55Z","receivedAt":"2011-06-08T07:30:55Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Checking whether a filename was part of stage 0 or stage 2 is code that we\nwould like to be able to call from a few other places without also\nlstat()-ing the file to see if it exists in the working copy.\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 e59eec0..d19c519 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -622,7 +622,7 @@ static int dir_in_way(const char *path, int check_working_copy)\n \treturn check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode);\n }\n \n-static int would_lose_untracked(const char *path)\n+static int was_tracked(const char *path)\n {\n \tint pos = cache_name_pos(path, strlen(path));\n \n@@ -639,11 +639,16 @@ static int would_lose_untracked(const char *path)\n \t\tswitch (ce_stage(active_cache[pos])) {\n \t\tcase 0:\n \t\tcase 2:\n-\t\t\treturn 0;\n+\t\t\treturn 1;\n \t\t}\n \t\tpos++;\n \t}\n-\treturn file_exists(path);\n+\treturn 0;\n+}\n+\n+static int would_lose_untracked(const char *path)\n+{\n+\treturn !was_tracked(path) && file_exists(path);\n }\n \n static int make_room_for_path(const char *path)\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169551","messageId":"1307518278-23814-27-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 26/48] merge-recursive: Allow make_room_for_path() to remove D/F entries","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:56Z","receivedAt":"2011-06-08T07:30:56Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If there were several files conflicting below a directory corresponding\nto a D/F conflict, and the file of that D/F conflict is in the way, we\nwant it to be removed.  Since files of D/F conflicts are handled last,\nthey can be reinstated later and possibly with a new unique name.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |   18 +++++++++++++++---\n t/t6036-recursive-corner-cases.sh |    2 +-\n 2 files changed, 16 insertions(+), 4 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex d19c519..e3033f2 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -651,11 +651,23 @@ static int would_lose_untracked(const char *path)\n \treturn !was_tracked(path) && file_exists(path);\n }\n \n-static int make_room_for_path(const char *path)\n+static int make_room_for_path(const struct merge_options *o, const char *path)\n {\n-\tint status;\n+\tint status, i;\n \tconst char *msg = \"failed to create path '%s'%s\";\n \n+\t/* Unlink any D/F conflict files that are in the way */\n+\tfor (i = 0; i < o->df_conflict_file_set.nr; i++) {\n+\t\tconst char *df_path = o->df_conflict_file_set.items[i].string;\n+\t\tsize_t pathlen = strlen(path);\n+\t\tsize_t df_pathlen = strlen(df_path);\n+\t\tif (df_pathlen < pathlen && strncmp(path, df_path, df_pathlen) == 0) {\n+\t\t\tunlink(df_path);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\t/* Make sure leading directories are created */\n \tstatus = safe_create_leading_directories_const(path);\n \tif (status) {\n \t\tif (status == -3) {\n@@ -723,7 +735,7 @@ static void update_file_flags(struct merge_options *o,\n \t\t\t}\n \t\t}\n \n-\t\tif (make_room_for_path(path) < 0) {\n+\t\tif (make_room_for_path(o, path) < 0) {\n \t\t\tupdate_wd = 0;\n \t\t\tfree(buf);\n \t\t\tgoto update_index;\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 423fb62..dea2a65 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -477,7 +477,7 @@ test_expect_success 'setup differently handled merges of directory/file conflict\n \tgit tag E2\n '\n \n-test_expect_failure 'git detects conflict and handles merge of D & E1 correctly' '\n+test_expect_success 'git detects conflict and handles merge of D & E1 correctly' '\n \tgit reset --hard &&\n \tgit reset --hard &&\n \tgit clean -fdqx &&\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169547","messageId":"1307518278-23814-28-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 27/48] merge-recursive: Consolidate different update_stages functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:57Z","receivedAt":"2011-06-08T07:30:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We are only calling update_stages_options() one way really, so we can\nconsolidate the slightly different variants into one and remove some\nparameters whose values are always the same.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   27 +++++++++------------------\n 1 files changed, 9 insertions(+), 18 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e3033f2..b4baa35 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -495,11 +495,12 @@ static struct string_list *get_renames(struct merge_options *o,\n \treturn renames;\n }\n \n-static int update_stages_options(const char *path, const struct diff_filespec *o,\n-\t\t\t\t const struct diff_filespec *a,\n-\t\t\t\t const struct diff_filespec *b,\n-\t\t\t\t int clear, int options)\n+static int update_stages(const char *path, const struct diff_filespec *o,\n+\t\t\t const struct diff_filespec *a,\n+\t\t\t const struct diff_filespec *b)\n {\n+\tint clear = 1;\n+\tint options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;\n \tif (clear)\n \t\tif (remove_file_from_cache(path))\n \t\t\treturn -1;\n@@ -515,14 +516,6 @@ static int update_stages_options(const char *path, const 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@@ -539,8 +532,7 @@ static int update_stages_and_entry(const char *path,\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-\toptions = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;\n-\treturn update_stages_options(path, o, a, b, clear, options);\n+\treturn update_stages(path, o, a, b);\n }\n \n static int remove_file(struct merge_options *o, int clean,\n@@ -936,8 +928,7 @@ static void conflict_rename_delete(struct merge_options *o,\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+\t\t\t      rename_branch == o->branch1 ? NULL : pair->two);\n \tif (dir_in_way(dest_name, !o->call_depth)) {\n \t\tdest_name = unique_path(o, dest_name, rename_branch);\n \t\tdf_conflict = 1;\n@@ -980,8 +971,8 @@ 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(ren1_dst, NULL, pair1->two, NULL, 1);\n-\t\tupdate_stages(ren2_dst, NULL, NULL, pair2->two, 1);\n+\t\tupdate_stages(ren1_dst, NULL, pair1->two, NULL);\n+\t\tupdate_stages(ren2_dst, NULL, NULL, pair2->two);\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-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169553","messageId":"1307518278-23814-29-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 28/48] merge-recursive: Split update_stages_and_entry; only update stages at end","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:58Z","receivedAt":"2011-06-08T07:30:58Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Instead of having the process_renames logic update the stages in the index\nfor the rename destination, have the index updated after process_entry or\nprocess_df_entry.  This will also allow us to have process_entry determine\nwhether a file was tracked and existed in the working copy before the\nmerge started.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   35 +++++++++++++++++------------------\n 1 files changed, 17 insertions(+), 18 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b4baa35..7878b30 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -90,6 +90,7 @@ struct stage_data {\n \t} stages[4];\n \tstruct rename_df_conflict_info *rename_df_conflict_info;\n \tunsigned processed:1;\n+\tunsigned involved_in_rename:1;\n };\n \n static inline void setup_rename_df_conflict_info(enum rename_type rename_type,\n@@ -516,15 +517,11 @@ static int update_stages(const char *path, const struct diff_filespec *o,\n \treturn 0;\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+static void update_entry(struct stage_data *entry,\n+\t\t\t struct diff_filespec *o,\n+\t\t\t struct diff_filespec *a,\n+\t\t\t struct diff_filespec *b)\n {\n-\tint options;\n-\n \tentry->processed = 0;\n \tentry->stages[1].mode = o->mode;\n \tentry->stages[2].mode = a->mode;\n@@ -532,7 +529,6 @@ static int update_stages_and_entry(const char *path,\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-\treturn update_stages(path, o, a, b);\n }\n \n static int remove_file(struct merge_options *o, int clean,\n@@ -1084,12 +1080,11 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t\t      ren2->dst_entry);\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-\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\tupdate_entry(ren1->dst_entry,\n+\t\t\t\t\t     ren1->pair->one,\n+\t\t\t\t\t     ren1->pair->two,\n+\t\t\t\t\t     ren2->pair->two);\n+\t\t\t\tren1->dst_entry->involved_in_rename = 1;\n \t\t\t}\n \t\t} else {\n \t\t\t/* Renamed in 1, maybe changed in 2 */\n@@ -1194,7 +1189,8 @@ 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\tupdate_stages_and_entry(ren1_dst, ren1->dst_entry, one, a, b, 1);\n+\t\t\t\tupdate_entry(ren1->dst_entry, one, a, b);\n+\t\t\t\tren1->dst_entry->involved_in_rename = 1;\n \t\t\t\tif (dir_in_way(ren1_dst, 0 /*check_wc*/)) {\n \t\t\t\t\tsetup_rename_df_conflict_info(RENAME_NORMAL,\n \t\t\t\t\t\t\t\t      ren1->pair,\n@@ -1291,6 +1287,7 @@ static void handle_delete_modify(struct merge_options *o,\n }\n \n static int merge_content(struct merge_options *o,\n+\t\t\t unsigned involved_in_rename,\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@@ -1331,6 +1328,8 @@ static int merge_content(struct merge_options *o,\n \t\t\treason = \"submodule\";\n \t\toutput(o, 1, \"CONFLICT (%s): Merge conflict in %s\",\n \t\t\t\treason, path);\n+\t\tif (involved_in_rename)\n+\t\t\tupdate_stages(path, &one, &a, &b);\n \t}\n \n \tif (df_conflict_remains) {\n@@ -1415,7 +1414,7 @@ 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\tclean_merge = merge_content(o, path,\n+\t\tclean_merge = merge_content(o, entry->involved_in_rename, 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    NULL);\n \t} else if (!o_sha && !a_sha && !b_sha) {\n@@ -1459,7 +1458,7 @@ static int process_df_entry(struct merge_options *o,\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\tclean_merge = merge_content(o, entry->involved_in_rename, 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-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169572","messageId":"1307518278-23814-30-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 29/48] merge-recursive: When we detect we can skip an update, actually skip it","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:30:59Z","receivedAt":"2011-06-08T07:30:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In 882fd11 (merge-recursive: Delay content merging for renames 2010-09-20),\nthere was code that checked for whether we could skip updating a file in\nthe working directory, based on whether the merged version matched the\ncurrent working copy.  Due to the desire to handle directory/file conflicts\nthat were resolvable, that commit deferred content merging by first\nupdating the index with the unmerged entries and then moving the actual\nmerging (along with the skip-the-content-update check) to another function\nthat ran later in the merge process.  As part moving the content merging\ncode, a bug was introduced such that although the message about skipping\nthe update would be printed (whenever GIT_MERGE_VERBOSITY was sufficiently\nhigh), the file would be unconditionally updated in the working copy\nanyway.\n\nWhen we detect that the file does not need to be updated in the working\ncopy, update the index appropriately and then return early before updating\nthe working copy.\n\nNote that there was a similar change in b2c8c0a (merge-recursive: When we\ndetect we can skip an update, actually skip it 2011-02-28), but it was\nreverted by 6db4105 (Revert \"Merge branch 'en/merge-recursive'\"\n2011-05-19) since it did not fix both of the relevant types of unnecessary\nupdate breakages and, worse, it made use of some band-aids that caused\nother problems.  The reason this change works is due to the changes of the\nlast few patches to (a) record_df_conflict_files instead of just unlinking\nthem early, (b) allowing make_room_for_path() to remove D/F entries, and\n(c) the splitting of update_stages_and_entry() to have its functionality\ncalled at different points.\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, 8 insertions(+), 4 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 7878b30..987a985 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1318,9 +1318,13 @@ static int merge_content(struct merge_options *o,\n \t}\n \n \tif (mfi.clean && !df_conflict_remains &&\n-\t    sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode)\n+\t    sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode &&\n+\t    was_tracked(path)) {\n \t\toutput(o, 3, \"Skipped %s (merged same as existing)\", path);\n-\telse\n+\t\tadd_cacheinfo(mfi.mode, mfi.sha, path,\n+\t\t\t      0 /*stage*/, 1 /*refresh*/, 0 /*options*/);\n+\t\treturn mfi.clean;\n+\t} else\n \t\toutput(o, 2, \"Auto-merging %s\", path);\n \n \tif (!mfi.clean) {\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 52a8f0b..5e2a686 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -631,7 +631,7 @@ test_expect_success 'setup avoid unnecessary update, normal rename' '\n \tgit commit -m \"Random, unrelated changes\"\n '\n \n-test_expect_failure 'avoid unnecessary update, normal rename' '\n+test_expect_success 'avoid unnecessary update, normal rename' '\n \tgit checkout -q avoid-unnecessary-update-1^0 &&\n \ttest-chmtime =1000000000 rename &&\n \ttest-chmtime -v +0 rename >expect &&\n@@ -664,7 +664,7 @@ test_expect_success 'setup to test avoiding unnecessary update, with D/F conflic\n \tgit commit -m \"Only unrelated changes\"\n '\n \n-test_expect_failure 'avoid unnecessary update, with D/F conflict' '\n+test_expect_success 'avoid unnecessary update, with D/F conflict' '\n \tgit checkout -q avoid-unnecessary-update-2^0 &&\n \ttest-chmtime =1000000000 df &&\n \ttest-chmtime -v +0 df >expect &&\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169555","messageId":"1307518278-23814-31-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 30/48] merge-recursive: Fix deletion of untracked file in rename/delete conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:00Z","receivedAt":"2011-06-08T07:31:00Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In the recursive case (o->call_depth > 0), we do not modify the working\ndirectory.  However, when o->call_depth==0, file renames can mean we need\nto delete the old filename from the working copy.  Since there have been\nlots of changes and mistakes here, let's go through the details.  Let's\nstart with a simple explanation of what we are trying to achieve:\n\n  Original goal: If a file is renamed on the side of history being merged\n  into head, the filename serving as the source of that rename needs to be\n  removed from the working directory.\n\nThe path to getting the above statement implemented in merge-recursive took\nseveral steps.  The relevant bits of code may be instructive to keep in\nmind for the explanation, especially since an English-only description\ninvolves double negatives that are hard to follow.  These bits of code are:\n  int remove_file(..., const char *path, int no_wd)\n  {\n    ...\n    int update_working_directory = !o->call_depth && !no_wd;\nand\n  remove_file(o, 1, ren1_src, <expression>);\nWhere the choice for <expression> has morphed over time:\n\n65ac6e9 (merge-recursive: adjust to loosened \"working file clobbered\"\ncheck 2006-10-27), introduced the \"no_wd\" parameter to remove_file() and\nused \"1\" for <expression>.  This meant ren1_src was never deleted, leaving\nit around in the working copy.\n\nIn 8371234 (Remove uncontested renamed files during merge. 2006-12-13),\n<expression> was changed to \"index_only\" (where index_only ==\n!!o->call_depth; see b7fa51da).   This was equivalent to using \"0\" for\n<expression> (due to the early logic in remove_file), and is orthogonal to\nthe condition we actually want to check at this point; it resulted in the\nsource file being removed except when index_only was false.  This was\nproblematic because the file could have been renamed on the side of history\nincluding head, in which case ren1_src could correspond to an untracked\nfile that should not be deleted.\n\nIn 183d797 (Keep untracked files not involved in a merge. 2007-02-04),\n<expression> was changed to \"index_only || stage == 3\".  While this gives\ncorrect behavior, the \"index_only ||\" portion of <expression> is\nunnecessary and makes the code slightly harder to follow.\n\nThere were also two further changes to this expression, though without\nany change in behavior.  First in b7fa51d (merge-recursive: get rid of the\nindex_only global variable 2008-09-02), it was changed to \"o->call_depth\n|| stage == 3\".  (index_only == !!o->call_depth).  Later, in 41d70bd6\n(merge-recursive: Small code clarification -- variable name and comments),\nthis was changed to \"o->call_depth || renamed_stage == 2\" (where stage was\nrenamed to other_stage and renamed_stage == other_stage ^ 1).\n\nSo we ended with <expression> being \"o->call_depth || renamed_stage == 2\".\nBut the \"o->call_depth ||\" piece was unnecessary.  We can remove it,\nleaving us with <expression> being \"renamed_stage == 2\".  This doesn't\nchange behavior at all, but it makes the code clearer.  Which is good,\nbecause it's about to get uglier.\n\n  Corrected goal: If a file is renamed on the side of history being merged\n  into head, the filename serving as the source of that rename needs to be\n  removed from the working directory *IF* that file is tracked in head AND\n  the file tracked in head is related to the original file.\n\nNote that the only difference between the original goal and the corrected\ngoal is the two extra conditions added at the end.  The first condition is\nrelevant in a rename/delete conflict.  If the file was deleted on the\nHEAD side of the merge and an untracked file of the same name was added to\nthe working copy, then without that extra condition the untracked file\nwill be erroneously deleted.  This changes <expression> to \"renamed_stage\n== 2 || !was_tracked(ren1_src)\".\n\nThe second additional condition is relevant in two cases.\n\nThe first case the second condition can occur is when a file is deleted\nand a completely different file is added with the same name.  To my\nknowledge, merge-recursive has no mechanism for detecting deleted-and-\nreplaced-by-different-file cases, so I am simply punting on this\npossibility.\n\nThe second case for the second condition to occur is when there is a\nrename/rename/add-source conflict.  That is, when the original file was\nrenamed on both sides of history AND the original filename is being\nre-used by some unrelated (but tracked) content.  This case also presents\nsome additional difficulties for us since we cannot currently detect these\nrename/rename/add-source conflicts; as long as the rename detection logic\n\"optimizes\" by ignoring filenames that are present at both ends of the\ndiff, these conflicts will go unnoticed.  However, rename/rename conflicts\nare handled by an entirely separate codepath not being discussed here, so\nthis case is not relevant for the line of code under consideration.\n\nIn summary:\n  Change <expression> from \"o->call_depth || renamed_stage == 2\" to\n  \"renamed_stage == 2 || !was_tracked(ren1_src)\", in order to\n  remove unnecessary code and avoid deleting untracked files.\n\n96 lines of explanation in the changelog to describe a one-line fix...\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    |    3 ++-\n t/t6039-merge-rename-corner-cases.sh |    2 +-\n 2 files changed, 3 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 987a985..749e501 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1102,7 +1102,8 @@ static int process_renames(struct merge_options *o,\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\tremove_file(o, 1, ren1_src, o->call_depth || renamed_stage == 2);\n+\t\t\tremove_file(o, 1, ren1_src,\n+\t\t\t\t    renamed_stage == 2 || !was_tracked(ren1_src));\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;\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex 06c7ea5..4f94528 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -26,7 +26,7 @@ test_expect_success 'setup rename/delete + untracked file' '\n \techo \"Myyy PRECIOUSSS\" >ring\n '\n \n-test_expect_failure \"Does git preserve Gollum's precious artifact?\" '\n+test_expect_success \"Does git preserve Gollum's precious artifact?\" '\n \ttest_must_fail git merge -s recursive rename-the-ring &&\n \n \t# Make sure git did not delete an untracked file\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169554","messageId":"1307518278-23814-32-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 31/48] merge-recursive: Make dead code for rename/rename(2to1) conflicts undead","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:01Z","receivedAt":"2011-06-08T07:31:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"The code for rename_rename_2to1 conflicts (two files both being renamed to\nthe same filename) was dead since the rename/add path was always being\nindependently triggered for each of the renames instead.  Further,\nreviving the dead code showed that it was inherently buggy and would\nalways segfault -- among a few other bugs.\n\nMove the else-if branch for the rename/rename block before the rename/add\nblock to make sure it is checked first, and fix up the rename/rename(2to1)\ncode segments to make it handle most cases.  Work is still needed to\nhandle higher dimensional corner cases such as rename/rename/modify/modify\nissues.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |   70 +++++++++++++++++++++++++-----------\n t/t6036-recursive-corner-cases.sh |   17 +++++----\n 2 files changed, 57 insertions(+), 30 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 749e501..4c42838 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -983,17 +983,36 @@ static void conflict_rename_rename_2to1(struct merge_options *o,\n \t\t\t\t\tstruct rename *ren2,\n \t\t\t\t\tconst char *branch2)\n {\n+\tchar *path = ren1->pair->two->path; /* same as ren2->pair->two->path */\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-\t       ren1->pair->one->path, new_path1,\n-\t       ren2->pair->one->path, new_path2);\n-\tremove_file(o, 0, ren1->pair->two->path, 0);\n-\tupdate_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode, new_path1);\n-\tupdate_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode, new_path2);\n-\tfree(new_path2);\n-\tfree(new_path1);\n+\tif (o->call_depth) {\n+\t\tstruct merge_file_info mfi;\n+\t\tstruct diff_filespec one, a, b;\n+\n+\t\tone.path = a.path = b.path = path;\n+\t\thashcpy(one.sha1, null_sha1);\n+\t\tone.mode = 0;\n+\t\thashcpy(a.sha1, ren1->pair->two->sha1);\n+\t\ta.mode = ren1->pair->two->mode;\n+\t\thashcpy(b.sha1, ren2->pair->two->sha1);\n+\t\tb.mode = ren2->pair->two->mode;\n+\t\tmfi = merge_file(o, &one, &a, &b, branch1, branch2);\n+\t\toutput(o, 1, \"Adding merged %s\", path);\n+\t\tupdate_file(o, 0, mfi.sha, mfi.mode, path);\n+\t} else {\n+\t\tchar *new_path1 = unique_path(o, path, branch1);\n+\t\tchar *new_path2 = unique_path(o, path, branch2);\n+\t\toutput(o, 1, \"Renaming %s to %s and %s to %s instead\",\n+\t\t       ren1->pair->one->path, new_path1,\n+\t\t       ren2->pair->one->path, new_path2);\n+\t\tremove_file(o, 0, path, 0);\n+\t\tupdate_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode,\n+\t\t\t    new_path1);\n+\t\tupdate_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode,\n+\t\t\t    new_path2);\n+\t\tfree(new_path2);\n+\t\tfree(new_path1);\n+\t}\n }\n \n static int process_renames(struct merge_options *o,\n@@ -1008,12 +1027,12 @@ static int process_renames(struct merge_options *o,\n \tfor (i = 0; i < a_renames->nr; i++) {\n \t\tsre = a_renames->items[i].util;\n \t\tstring_list_insert(&a_by_dst, sre->pair->two->path)->util\n-\t\t\t= sre->dst_entry;\n+\t\t\t= (void*)sre;\n \t}\n \tfor (i = 0; i < b_renames->nr; i++) {\n \t\tsre = b_renames->items[i].util;\n \t\tstring_list_insert(&b_by_dst, sre->pair->two->path)->util\n-\t\t\t= sre->dst_entry;\n+\t\t\t= (void*)sre;\n \t}\n \n \tfor (i = 0, j = 0; i < a_renames->nr || j < b_renames->nr;) {\n@@ -1125,6 +1144,23 @@ static int process_renames(struct merge_options *o,\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 ((item = string_list_lookup(renames2Dst, ren1_dst))) {\n+\t\t\t\tchar *ren2_src, *ren2_dst;\n+\t\t\t\tren2 = item->util;\n+\t\t\t\tren2_src = ren2->pair->one->path;\n+\t\t\t\tren2_dst = ren2->pair->two->path;\n+\n+\t\t\t\tclean_merge = 0;\n+\t\t\t\tren2->processed = 1;\n+\t\t\t\tremove_file(o, 1, ren2_src,\n+\t\t\t\t\t    renamed_stage == 3 || would_lose_untracked(ren1_src));\n+\n+\t\t\t\toutput(o, 1, \"CONFLICT (rename/rename): \"\n+\t\t\t\t       \"Rename %s->%s in %s. \"\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_src, ren2_dst, branch2);\n+\t\t\t\tconflict_rename_rename_2to1(o, ren1, branch1, ren2, 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@@ -1165,16 +1201,6 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\tupdate_file(o, 0, dst_other.sha1, dst_other.mode, new_path);\n \t\t\t\t\tfree(new_path);\n \t\t\t\t}\n-\t\t\t} else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {\n-\t\t\t\tren2 = item->util;\n-\t\t\t\tclean_merge = 0;\n-\t\t\t\tren2->processed = 1;\n-\t\t\t\toutput(o, 1, \"CONFLICT (rename/rename): \"\n-\t\t\t\t       \"Rename %s->%s in %s. \"\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_2to1(o, ren1, branch1, ren2, branch2);\n \t\t\t} else\n \t\t\t\ttry_merge = 1;\n \ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex dea2a65..8d1d303 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -60,13 +60,13 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '\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+\tcp one 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+\t\t-L \"\" \\\n+\t\t-L \"Temporary merge branch 2\" \\\n+\t\tmerged empty two &&\n \ttest $(git rev-parse :1:three) = $(git hash-object merged)\n '\n \n@@ -139,11 +139,12 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'\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+\t\t-L \"\" \\\n+\t\t-L \"Temporary merge branch 2\" \\\n+\t\tmerge-me empty merged &&\n+\n+\ttest $(git rev-parse :1:three) = $(git hash-object merge-me)\n '\n \n #\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169565","messageId":"1307518278-23814-33-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 32/48] merge-recursive: Add comments about handling rename/add-source cases","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:02Z","receivedAt":"2011-06-08T07:31:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"There are a couple of places where changes are needed to for situations\ninvolving rename/add-source issues.  Add comments about the needed changes\n(and existing bugs) until git has been enabled to detect such cases.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   11 +++++++++++\n 1 files changed, 11 insertions(+), 0 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 4c42838..5ccc59c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1072,6 +1072,9 @@ static int process_renames(struct merge_options *o,\n \t\t}\n \n \t\tren1->dst_entry->processed = 1;\n+\t\t/* BUG: We should only mark src_entry as processed if we\n+\t\t * are not dealing with a rename + add-source case.\n+\t\t */\n \t\tren1->src_entry->processed = 1;\n \n \t\tif (ren1->processed)\n@@ -1098,6 +1101,10 @@ static int process_renames(struct merge_options *o,\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} else {\n+\t\t\t\t/* BUG: We should only remove ren1_src in\n+\t\t\t\t * the base stage (think of rename +\n+\t\t\t\t * add-source cases).\n+\t\t\t\t */\n \t\t\t\tremove_file(o, 1, ren1_src, 1);\n \t\t\t\tupdate_entry(ren1->dst_entry,\n \t\t\t\t\t     ren1->pair->one,\n@@ -1121,6 +1128,10 @@ static int process_renames(struct merge_options *o,\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\t/* BUG: We should only remove ren1_src in the base\n+\t\t\t * stage and in other_stage (think of rename +\n+\t\t\t * add-source case).\n+\t\t\t */\n \t\t\tremove_file(o, 1, ren1_src,\n \t\t\t\t    renamed_stage == 2 || !was_tracked(ren1_src));\n \n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169552","messageId":"1307518278-23814-34-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 33/48] merge-recursive: Improve handling of rename target vs. directory addition","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:03Z","receivedAt":"2011-06-08T07:31:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When dealing with file merging and renames and D/F conflicts and possible\ncriss-cross merges (how's that for a corner case?), we did not do a\nthorough job ensuring the index and working directory had the correct\ncontents.   Fix the logic in merge_content() to handle this.  Also,\ncorrect some erroneous tests in t6022 that were expecting the wrong number\nof unmerged index entries.  These changes fix one of the tests in t6039\n(and almost fix another one from t6039 as well).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    |   27 ++++++++++++++++++++++-----\n t/t6022-merge-rename.sh              |    4 ++--\n t/t6039-merge-rename-corner-cases.sh |    2 +-\n 3 files changed, 25 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 5ccc59c..e6a97b2 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1370,19 +1370,34 @@ static int merge_content(struct merge_options *o,\n \t\t\treason = \"submodule\";\n \t\toutput(o, 1, \"CONFLICT (%s): Merge conflict in %s\",\n \t\t\t\treason, path);\n-\t\tif (involved_in_rename)\n+\t\tif (involved_in_rename && !df_conflict_remains)\n \t\t\tupdate_stages(path, &one, &a, &b);\n \t}\n \n \tif (df_conflict_remains) {\n \t\tchar *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\tif (o->call_depth) {\n+\t\t\tremove_file_from_cache(path);\n+\t\t} else {\n+\t\t\tif (!mfi.clean)\n+\t\t\t\tupdate_stages(path, &one, &a, &b);\n+\t\t\telse {\n+\t\t\t\tint file_from_stage2 = was_tracked(path);\n+\t\t\t\tstruct diff_filespec merged;\n+\t\t\t\thashcpy(merged.sha1, mfi.sha);\n+\t\t\t\tmerged.mode = mfi.mode;\n+\n+\t\t\t\tupdate_stages(path, NULL,\n+\t\t\t\t\t      file_from_stage2 ? &merged : NULL,\n+\t\t\t\t\t      file_from_stage2 ? NULL : &merged);\n+\t\t\t}\n+\n+\t\t}\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\tupdate_file(o, 0, mfi.sha, mfi.mode, new_path);\n \t\tfree(new_path);\n+\t\tmfi.clean = 0;\n \t} else {\n \t\tupdate_file(o, mfi.clean, mfi.sha, mfi.mode, path);\n \t}\n@@ -1574,6 +1589,8 @@ 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\tif (o->call_depth)\n+\t\t\t\tremove_file_from_cache(path);\n \t\t\tupdate_file(o, 0, sha, mode, new_path);\n \t\t\tif (o->call_depth)\n \t\t\t\tremove_file_from_cache(path);\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 5e2a686..cd1e8fb 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -307,7 +307,7 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '\n \tgrep \"Auto-merging dir\" output &&\n \tgrep \"Adding as dir~HEAD instead\" output &&\n \n-\ttest 2 -eq \"$(git ls-files -u | wc -l)\" &&\n+\ttest 3 -eq \"$(git ls-files -u | wc -l)\" &&\n \ttest 2 -eq \"$(git ls-files -u dir/file-in-the-way | wc -l)\" &&\n \n \ttest_must_fail git diff --quiet &&\n@@ -329,7 +329,7 @@ test_expect_success 'Same as previous, but merged other way' '\n \tgrep \"Auto-merging dir\" output &&\n \tgrep \"Adding as dir~renamed-file-has-no-conflicts instead\" output &&\n \n-\ttest 2 -eq \"$(git ls-files -u | wc -l)\" &&\n+\ttest 3 -eq \"$(git ls-files -u | wc -l)\" &&\n \ttest 2 -eq \"$(git ls-files -u dir/file-in-the-way | wc -l)\" &&\n \n \ttest_must_fail git diff --quiet &&\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex 4f94528..18e7821 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -234,7 +234,7 @@ test_expect_success 'setup content merge + rename/directory conflict' '\n \tgit commit -m left\n '\n \n-test_expect_failure 'rename/directory conflict + clean content merge' '\n+test_expect_success 'rename/directory conflict + clean content merge' '\n \tgit reset --hard &&\n \tgit reset --hard &&\n \tgit clean -fdqx &&\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169556","messageId":"1307518278-23814-35-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 34/48] merge-recursive: Consolidate process_entry() and process_df_entry()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:04Z","receivedAt":"2011-06-08T07:31:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"The whole point of adding process_df_entry() was to ensure that files of\nD/F conflicts were processed after paths under the corresponding\ndirectory.  However, given that the entries are in sorted order, all we\nneed to do is iterate through them in reverse order to achieve the same\neffect.  That lets us remove some duplicated code, and lets us keep\ntrack of one less thing as we read the code (\"do we need to make sure\nthis is processed before process_df_entry() or do we need to defer it\nuntil then?\").\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |  194 ++++++++++++++++------------------------------------\n 1 files changed, 60 insertions(+), 134 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e6a97b2..5673ccb 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -116,7 +116,6 @@ static inline void setup_rename_df_conflict_info(enum rename_type rename_type,\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@@ -358,20 +357,17 @@ static void record_df_conflict_files(struct merge_options *o,\n \t\t\t\t     struct string_list *entries)\n {\n \t/* If there is a D/F conflict and the file for such a conflict\n-\t * currently exist in the working copy, we want to allow it to\n-\t * be removed to make room for the corresponding directory if\n-\t * needed.  The files underneath the directories of such D/F\n-\t * conflicts will be handled in process_entry(), while the\n-\t * files of such D/F conflicts will be processed later in\n-\t * process_df_entry().  If the corresponding directory ends up\n-\t * being removed by the merge, then no additional work needs\n-\t * to be done by process_df_entry() for the conflicting file.\n-\t * If the directory needs to be written to the working copy,\n-\t * then the conflicting file will simply be removed (e.g. in\n-\t * make_room_for_path).  If the directory is written to the\n-\t * working copy but the file also has a conflict that needs to\n-\t * be resolved, then process_df_entry() will reinstate the\n-\t * file with a new unique name.\n+\t * currently exist in the working copy, we want to allow it to be\n+\t * removed to make room for the corresponding directory if needed.\n+\t * The files underneath the directories of such D/F conflicts will\n+\t * be processed before the corresponding file involved in the D/F\n+\t * conflict.  If the D/F directory ends up being removed by the\n+\t * merge, then we won't have to touch the D/F file.  If the D/F\n+\t * directory needs to be written to the working copy, then the D/F\n+\t * file will simply be removed (in make_room_for_path()) to make\n+\t * room for the necessary paths.  Note that if both the directory\n+\t * and the file need to be present, then the D/F file will be\n+\t * reinstated with a new unique name at the time it is processed.\n \t */\n \tconst char *last_file = NULL;\n \tint last_len = 0;\n@@ -1310,17 +1306,17 @@ static void handle_delete_modify(struct merge_options *o,\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\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\t       NULL == new_path ? \"\" : \" at \",\n+\t\t       NULL == new_path ? \"\" : new_path);\n+\t\tupdate_file(o, 0, b_sha, b_mode, new_path ? new_path : 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%s%s.\",\n \t\t       path, o->branch2,\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\t       NULL == new_path ? \"\" : \" at \",\n+\t\t       NULL == new_path ? \"\" : new_path);\n+\t\tupdate_file(o, 0, a_sha, a_mode, new_path ? new_path : path);\n \t}\n }\n \n@@ -1422,93 +1418,6 @@ 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-\t\tif ((!a_sha && !b_sha) ||\n-\t\t    (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||\n-\t\t    (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {\n-\t\t\t/* Deleted in both or deleted in one and\n-\t\t\t * unchanged in the other */\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, 1, path, !a_sha);\n-\t\t} else if (dir_in_way(path, 0 /*check_wc*/)) {\n-\t\t\tentry->processed = 0;\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-\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-\t} else if ((!o_sha && a_sha && !b_sha) ||\n-\t\t   (!o_sha && !a_sha && b_sha)) {\n-\t\t/* Case B: Added in one. */\n-\t\tunsigned mode;\n-\t\tconst unsigned char *sha;\n-\n-\t\tif (a_sha) {\n-\t\t\tmode = a_mode;\n-\t\t\tsha = a_sha;\n-\t\t} else {\n-\t\t\tmode = b_mode;\n-\t\t\tsha = b_sha;\n-\t\t}\n-\t\tif (dir_in_way(path, 0 /*check_wc*/)) {\n-\t\t\t/* Handle D->F conflicts after all subfiles */\n-\t\t\tentry->processed = 0;\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-\t\t}\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\tclean_merge = merge_content(o, entry->involved_in_rename, 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    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-\t\t * we had that path and want to actively remove it.\n-\t\t */\n-\t\tremove_file(o, 1, path, !a_mode);\n-\t} else\n-\t\tdie(\"Fatal merge failure, shouldn't happen.\");\n-\n-\treturn clean_merge;\n-}\n-\n-/*\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-{\n-\tint clean_merge = 1;\n-\tunsigned o_mode = entry->stages[1].mode;\n-\tunsigned a_mode = entry->stages[2].mode;\n-\tunsigned b_mode = entry->stages[3].mode;\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-\n \tentry->processed = 1;\n \tif (entry->rename_df_conflict_info) {\n \t\tstruct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;\n@@ -1543,27 +1452,41 @@ static int process_df_entry(struct merge_options *o,\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/* Modify/delete; deleted side may have put a directory in the way */\n-\t\tchar *new_path;\n-\t\tint free_me = 0;\n-\t\tif (dir_in_way(path, !o->call_depth)) {\n-\t\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n-\t\t\tfree_me = 1;\n+\t\t/* Case A: Deleted in one */\n+\t\tif ((!a_sha && !b_sha) ||\n+\t\t    (!b_sha && blob_unchanged(o_sha, a_sha, normalize, path)) ||\n+\t\t    (!a_sha && blob_unchanged(o_sha, b_sha, normalize, path))) {\n+\t\t\t/* Deleted in both or deleted in one and\n+\t\t\t * unchanged in the other */\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, 1, path, !a_sha);\n+\t\t} else {\n+\t\t\t/* Modify/delete; deleted side may have put a directory in the way */\n+\t\t\tchar *new_path = NULL;\n+\t\t\tint free_me = 0;\n+\t\t\tclean_merge = 0;\n+\t\t\tif (dir_in_way(path, !o->call_depth)) {\n+\t\t\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n+\t\t\t\tfree_me = 1;\n+\t\t\t}\n+\t\t\thandle_delete_modify(o, path, new_path,\n+\t\t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n+\t\t\tif (free_me)\n+\t\t\t\tfree(new_path);\n \t\t}\n-\t\tclean_merge = 0;\n-\t\thandle_delete_modify(o, path, free_me ? new_path : path,\n-\t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n-\t\tif (free_me)\n-\t\t\tfree(new_path);\n-\t} else if (!o_sha && !!a_sha != !!b_sha) {\n-\t\t/* directory -> (directory, file) or <nothing> -> (directory, file) */\n+\t} else if ((!o_sha && a_sha && !b_sha) ||\n+\t\t   (!o_sha && !a_sha && b_sha)) {\n+\t\t/* Case B: Added in one. */\n+\t\t/* [nothing|directory] -> ([nothing|directory], file) */\n+\n \t\tconst char *add_branch;\n \t\tconst char *other_branch;\n \t\tunsigned mode;\n@@ -1599,10 +1522,20 @@ static int process_df_entry(struct merge_options *o,\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\tentry->processed = 0;\n-\t\treturn 1; /* not handled; assume clean until processed */\n-\t}\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\tclean_merge = merge_content(o, entry->involved_in_rename, 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    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+\t\t * we had that path and want to actively remove it.\n+\t\t */\n+\t\tremove_file(o, 1, path, !a_mode);\n+\t} else\n+\t\tdie(\"Fatal merge failure, shouldn't happen.\");\n \n \treturn clean_merge;\n }\n@@ -1650,7 +1583,7 @@ int merge_trees(struct merge_options *o,\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);\n-\t\tfor (i = 0; i < entries->nr; i++) {\n+\t\tfor (i = entries->nr-1; i >=0; i--) {\n \t\t\tconst char *path = entries->items[i].string;\n \t\t\tstruct stage_data *e = entries->items[i].util;\n \t\t\tif (!e->processed\n@@ -1658,13 +1591,6 @@ int merge_trees(struct merge_options *o,\n \t\t\t\tclean = 0;\n \t\t}\n \t\tfor (i = 0; i < entries->nr; i++) {\n-\t\t\tconst char *path = entries->items[i].string;\n-\t\t\tstruct stage_data *e = entries->items[i].util;\n-\t\t\tif (!e->processed\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-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169573","messageId":"1307518278-23814-36-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 35/48] merge-recursive: Cleanup and consolidation of rename_conflict_info","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:05Z","receivedAt":"2011-06-08T07:31:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"The consolidation of process_entry() and process_df_entry() allows us to\nconsolidate more code paths concerning rename conflicts, and to do\na few additional related cleanups.  It also means we are using\nrename_df_conflict_info in some cases where there is no D/F conflict;\nrename it to rename_conflict_info.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |  134 ++++++++++++++++++++++++++---------------------------\n 1 files changed, 66 insertions(+), 68 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 5673ccb..5d0a62c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -66,10 +66,11 @@ static int sha_eq(const unsigned char *a, const unsigned char *b)\n enum rename_type {\n \tRENAME_NORMAL = 0,\n \tRENAME_DELETE,\n+\tRENAME_ONE_FILE_TO_ONE,\n \tRENAME_ONE_FILE_TO_TWO\n };\n \n-struct rename_df_conflict_info {\n+struct rename_conflict_info {\n \tenum rename_type rename_type;\n \tstruct diff_filepair *pair1;\n \tstruct diff_filepair *pair2;\n@@ -88,34 +89,33 @@ 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+\tstruct rename_conflict_info *rename_conflict_info;\n \tunsigned processed:1;\n-\tunsigned involved_in_rename: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+static inline void setup_rename_conflict_info(enum rename_type rename_type,\n+\t\t\t\t\t      struct diff_filepair *pair1,\n+\t\t\t\t\t      struct diff_filepair *pair2,\n+\t\t\t\t\t      const char *branch1,\n+\t\t\t\t\t      const char *branch2,\n+\t\t\t\t\t      struct stage_data *dst_entry1,\n+\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+\tstruct rename_conflict_info *ci = xcalloc(1, sizeof(struct rename_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->rename_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->rename_conflict_info = ci;\n \t}\n }\n \n@@ -939,10 +939,29 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t/* One file was renamed in both branches, but to different names. */\n \tchar *del[2];\n \tint delp = 0;\n+\tconst char *src      = pair1->one->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+\n+\toutput(o, 1, \"CONFLICT (rename/rename): \"\n+\t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n+\t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\",\n+\t       src, pair1->two->path, branch1,\n+\t       src, pair2->two->path, branch2,\n+\t       o->call_depth ? \" (left unresolved)\" : \"\");\n+\tif (o->call_depth) {\n+\t\t/*\n+\t\t * FIXME: Why remove file from cache, and then\n+\t\t * immediately readd it?  Why not just overwrite using\n+\t\t * update_file only?  Also...this is buggy for\n+\t\t * rename/add-source situations...\n+\t\t */\n+\t\tremove_file_from_cache(src);\n+\t\tupdate_file(o, 0, pair1->one->sha1, pair1->one->mode, src);\n+\t}\n+\n \tif (dir_in_way(ren1_dst, !o->call_depth)) {\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@@ -1083,20 +1102,16 @@ static int process_renames(struct merge_options *o,\n \t\tif (ren2) {\n \t\t\tconst char *ren2_src = ren2->pair->one->path;\n \t\t\tconst char *ren2_dst = ren2->pair->two->path;\n+\t\t\tenum rename_type rename_type;\n \t\t\t/* Renamed in 1 and renamed in 2 */\n \t\t\tif (strcmp(ren1_src, ren2_src) != 0)\n \t\t\t\tdie(\"ren1.src != ren2.src\");\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\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\trename_type = RENAME_ONE_FILE_TO_TWO;\n \t\t\t} else {\n+\t\t\t\trename_type = RENAME_ONE_FILE_TO_ONE;\n \t\t\t\t/* BUG: We should only remove ren1_src in\n \t\t\t\t * the base stage (think of rename +\n \t\t\t\t * add-source cases).\n@@ -1106,8 +1121,14 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t     ren1->pair->one,\n \t\t\t\t\t     ren1->pair->two,\n \t\t\t\t\t     ren2->pair->two);\n-\t\t\t\tren1->dst_entry->involved_in_rename = 1;\n \t\t\t}\n+\t\t\tsetup_rename_conflict_info(rename_type,\n+\t\t\t\t\t\t   ren1->pair,\n+\t\t\t\t\t\t   ren2->pair,\n+\t\t\t\t\t\t   branch1,\n+\t\t\t\t\t\t   branch2,\n+\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t   ren2->dst_entry);\n \t\t} else {\n \t\t\t/* Renamed in 1, maybe changed in 2 */\n \t\t\tstruct string_list_item *item;\n@@ -1138,19 +1159,13 @@ 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\tif (dir_in_way(ren1_dst, 0 /*check_wc*/)) {\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} 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\tsetup_rename_conflict_info(RENAME_DELETE,\n+\t\t\t\t\t\t\t   ren1->pair,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   branch1,\n+\t\t\t\t\t\t\t   branch2,\n+\t\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t\t   NULL);\n \t\t\t} else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {\n \t\t\t\tchar *ren2_src, *ren2_dst;\n \t\t\t\tren2 = item->util;\n@@ -1224,16 +1239,13 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\ta = &src_other;\n \t\t\t\t}\n \t\t\t\tupdate_entry(ren1->dst_entry, one, a, b);\n-\t\t\t\tren1->dst_entry->involved_in_rename = 1;\n-\t\t\t\tif (dir_in_way(ren1_dst, 0 /*check_wc*/)) {\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}\n+\t\t\t\tsetup_rename_conflict_info(RENAME_NORMAL,\n+\t\t\t\t\t\t\t   ren1->pair,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   branch1,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t\t   NULL);\n \t\t\t}\n \t\t}\n \t}\n@@ -1321,12 +1333,11 @@ static void handle_delete_modify(struct merge_options *o,\n }\n \n static int merge_content(struct merge_options *o,\n-\t\t\t unsigned involved_in_rename,\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 const char *df_rename_conflict_branch)\n+\t\t\t struct rename_conflict_info *rename_conflict_info)\n {\n \tconst char *reason = \"content\";\n \tstruct merge_file_info mfi;\n@@ -1346,8 +1357,7 @@ 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 (df_rename_conflict_branch &&\n-\t    dir_in_way(path, !o->call_depth)) {\n+\tif (rename_conflict_info && dir_in_way(path, !o->call_depth)) {\n \t\tdf_conflict_remains = 1;\n \t}\n \n@@ -1366,7 +1376,7 @@ static int merge_content(struct merge_options *o,\n \t\t\treason = \"submodule\";\n \t\toutput(o, 1, \"CONFLICT (%s): Merge conflict in %s\",\n \t\t\t\treason, path);\n-\t\tif (involved_in_rename && !df_conflict_remains)\n+\t\tif (rename_conflict_info && !df_conflict_remains)\n \t\t\tupdate_stages(path, &one, &a, &b);\n \t}\n \n@@ -1389,7 +1399,7 @@ static int merge_content(struct merge_options *o,\n \t\t\t}\n \n \t\t}\n-\t\tnew_path = unique_path(o, path, df_rename_conflict_branch);\n+\t\tnew_path = unique_path(o, path, rename_conflict_info->branch1);\n \t\toutput(o, 1, \"Adding as %s instead\", new_path);\n \t\tupdate_file(o, 0, mfi.sha, mfi.mode, new_path);\n \t\tfree(new_path);\n@@ -1419,14 +1429,14 @@ static int process_entry(struct merge_options *o,\n \tunsigned char *b_sha = stage_sha(entry->stages[3].sha, b_mode);\n \n \tentry->processed = 1;\n-\tif (entry->rename_df_conflict_info) {\n-\t\tstruct rename_df_conflict_info *conflict_info = entry->rename_df_conflict_info;\n-\t\tchar *src;\n+\tif (entry->rename_conflict_info) {\n+\t\tstruct rename_conflict_info *conflict_info = entry->rename_conflict_info;\n \t\tswitch (conflict_info->rename_type) {\n \t\tcase RENAME_NORMAL:\n-\t\t\tclean_merge = merge_content(o, entry->involved_in_rename, path,\n+\t\tcase RENAME_ONE_FILE_TO_ONE:\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\t\t\t\t    conflict_info);\n \t\t\tbreak;\n \t\tcase RENAME_DELETE:\n \t\t\tclean_merge = 0;\n@@ -1435,19 +1445,7 @@ static int process_entry(struct merge_options *o,\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-\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@@ -1525,7 +1523,7 @@ 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\tclean_merge = merge_content(o, entry->involved_in_rename, 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\t\t\t\t    NULL);\n \t} else if (!o_sha && !a_sha && !b_sha) {\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169559","messageId":"1307518278-23814-37-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 36/48] merge-recursive: Provide more info in conflict markers with file renames","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:06Z","receivedAt":"2011-06-08T07:31:06Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Whenever there are merge conflicts in file contents, we would mark the\ndifferent sides of the conflict with the two branches being merged.\nHowever, when there is a rename involved as well, the branchname is not\nsufficient to specify where the conflicting content came from.  In such\ncases, mark the two sides of the conflict with branchname:filename rather\nthan just branchname.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    |   20 +++++++++++++++++---\n t/t6022-merge-rename.sh              |    8 ++++----\n t/t6039-merge-rename-corner-cases.sh |    2 +-\n 3 files changed, 22 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 5d0a62c..da507a3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1340,6 +1340,7 @@ static int merge_content(struct merge_options *o,\n \t\t\t struct rename_conflict_info *rename_conflict_info)\n {\n \tconst char *reason = \"content\";\n+\tchar *side1 = NULL, *side2 = NULL;\n \tstruct merge_file_info mfi;\n \tstruct diff_filespec one, a, b;\n \tunsigned df_conflict_remains = 0;\n@@ -1356,10 +1357,23 @@ static int merge_content(struct merge_options *o,\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 (rename_conflict_info && dir_in_way(path, !o->call_depth)) {\n-\t\tdf_conflict_remains = 1;\n+\tif (rename_conflict_info) {\n+\t\tconst char *path1 = rename_conflict_info->pair2 ?\n+\t\t\t\t    rename_conflict_info->pair2->one->path : path;\n+\t\tconst char *path2 = rename_conflict_info->pair1 ?\n+\t\t\t\t    rename_conflict_info->pair1->one->path : path;\n+\t\tside1 = xmalloc(strlen(o->branch1) + strlen(path1) + 2);\n+\t\tside2 = xmalloc(strlen(o->branch2) + strlen(path2) + 2);\n+\t\tsprintf(side1, \"%s:%s\", o->branch1, path1);\n+\t\tsprintf(side2, \"%s:%s\", o->branch2, path2);\n+\n+\t\tif (dir_in_way(path, !o->call_depth))\n+\t\t\tdf_conflict_remains = 1;\n \t}\n+\tmfi = merge_file(o, &one, &a, &b,\n+\t\t\t side1 ? side1:o->branch1, side2 ? side2:o->branch2);\n+\tfree(side1);\n+\tfree(side2);\n \n \tif (mfi.clean && !df_conflict_remains &&\n \t    sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode &&\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex cd1e8fb..cfce3d3 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -351,11 +351,11 @@ cat >expected <<\\EOF &&\n 8\n 9\n 10\n-<<<<<<< HEAD\n+<<<<<<< HEAD:dir\n 12\n =======\n 11\n->>>>>>> dir-not-in-way\n+>>>>>>> dir-not-in-way:sub/file\n EOF\n \n test_expect_success 'Rename+D/F conflict; renamed file cannot merge, dir not in way' '\n@@ -405,11 +405,11 @@ cat >expected <<\\EOF &&\n 8\n 9\n 10\n-<<<<<<< HEAD\n+<<<<<<< HEAD:dir\n 11\n =======\n 12\n->>>>>>> renamed-file-has-conflicts\n+>>>>>>> renamed-file-has-conflicts:sub/file\n EOF\n \n test_expect_success 'Same as previous, but merged other way' '\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex 18e7821..4d2fd10 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -258,7 +258,7 @@ test_expect_success 'rename/directory conflict + clean content merge' '\n \ttest -f newfile~HEAD\n '\n \n-test_expect_failure 'rename/directory conflict + content merge conflict' '\n+test_expect_success 'rename/directory conflict + content merge conflict' '\n \tgit reset --hard &&\n \tgit reset --hard &&\n \tgit clean -fdqx &&\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169575","messageId":"1307518278-23814-38-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 37/48] merge-recursive: Fix modify/delete resolution in the recursive case","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:07Z","receivedAt":"2011-06-08T07:31:07Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When o->call_depth>0 and we have conflicts, we try to find \"middle ground\"\nwhen creating the virtual merge base.  In the case of content conflicts,\nthis can be done by doing a three-way content merge and using the result.\nIn all parts where the three-way content merge is clean, it is the correct\nmiddle ground, and in parts where it conflicts there is no middle ground\nbut the conflict markers provide a good compromise since they are unlikely\nto accidentally match any further changes.\n\nIn the case of a modify/delete conflict, we cannot do the same thing.\nAccepting either endpoint as the resolution for the virtual merge base\nruns the risk that when handling the non-recursive case we will silently\naccept one person's resolution over another without flagging a conflict.\nIn this case, the closest \"middle ground\" we have is actually the merge\nbase of the candidate merge bases.  (We could alternatively attempt a\nthree way content merge using an empty file in place of the deleted file,\nbut that seems to be more work than necessary.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |   32 +++++++++++++++++++++-----------\n t/t6036-recursive-corner-cases.sh |    4 ++--\n 2 files changed, 23 insertions(+), 13 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex da507a3..5a70a87 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1309,11 +1309,26 @@ 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 *o_sha, int o_mode,\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+\tchar *new_path = NULL;\n+\tint free_me = 0;\n+\tif (dir_in_way(path, !o->call_depth)) {\n+\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n+\t\tfree_me = 1;\n+\t}\n+\n+\tif (o->call_depth) {\n+\t\t/*\n+\t\t * We cannot arbitrarily accept either a_sha or b_sha as\n+\t\t * correct; since there is no true \"middle point\" between\n+\t\t * them, simply reuse the base version for virtual merge base.\n+\t\t */\n+\t\tremove_file_from_cache(path);\n+\t\tupdate_file(o, 0, o_sha, o_mode, new_path ? new_path : path);\n+\t} else if (!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%s%s.\",\n \t\t       path, o->branch1,\n@@ -1330,6 +1345,9 @@ static void handle_delete_modify(struct merge_options *o,\n \t\t       NULL == new_path ? \"\" : new_path);\n \t\tupdate_file(o, 0, a_sha, a_mode, new_path ? new_path : path);\n \t}\n+\tif (free_me)\n+\t\tfree(new_path);\n+\n }\n \n static int merge_content(struct merge_options *o,\n@@ -1482,17 +1500,9 @@ static int process_entry(struct merge_options *o,\n \t\t\tremove_file(o, 1, path, !a_sha);\n \t\t} else {\n \t\t\t/* Modify/delete; deleted side may have put a directory in the way */\n-\t\t\tchar *new_path = NULL;\n-\t\t\tint free_me = 0;\n \t\t\tclean_merge = 0;\n-\t\t\tif (dir_in_way(path, !o->call_depth)) {\n-\t\t\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n-\t\t\t\tfree_me = 1;\n-\t\t\t}\n-\t\t\thandle_delete_modify(o, path, new_path,\n+\t\t\thandle_delete_modify(o, path, o_sha, o_mode,\n \t\t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n-\t\t\tif (free_me)\n-\t\t\t\tfree(new_path);\n \t\t}\n \t} else if ((!o_sha && a_sha && !b_sha) ||\n \t\t   (!o_sha && !a_sha && b_sha)) {\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 8d1d303..d27477b 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -290,7 +290,7 @@ test_expect_success 'setup criss-cross + modify/delete resolved differently' '\n \tgit tag E\n '\n \n-test_expect_failure 'git detects conflict merging criss-cross+modify/delete' '\n+test_expect_success 'git detects conflict merging criss-cross+modify/delete' '\n \tgit checkout D^0 &&\n \n \ttest_must_fail git merge -s recursive E^0 &&\n@@ -302,7 +302,7 @@ test_expect_failure 'git detects conflict merging criss-cross+modify/delete' '\n \ttest $(git rev-parse :2:file) = $(git rev-parse B:file)\n '\n \n-test_expect_failure 'git detects conflict merging criss-cross+modify/delete, reverse direction' '\n+test_expect_success 'git detects conflict merging criss-cross+modify/delete, reverse direction' '\n \tgit reset --hard &&\n \tgit checkout E^0 &&\n \n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169560","messageId":"1307518278-23814-39-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 38/48] merge-recursive: Introduce a merge_file convenience function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:08Z","receivedAt":"2011-06-08T07:31:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"merge_file previously required diff_filespec arguments, but all callers\nonly had sha1s and modes.  Rename merge_file to merge_file_1 and introduce\na new merge_file convenience function which takes the sha1s and modes and\ncreates the temporary diff_filespec variables needed to call merge_file_1.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   74 +++++++++++++++++++++++++++-------------------------\n 1 files changed, 38 insertions(+), 36 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 5a70a87..3a364fa 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -831,12 +831,12 @@ static int merge_3way(struct merge_options *o,\n \treturn merge_status;\n }\n \n-static struct merge_file_info merge_file(struct merge_options *o,\n-\t\t\t\t\t const struct diff_filespec *one,\n-\t\t\t\t\t const struct diff_filespec *a,\n-\t\t\t\t\t const struct diff_filespec *b,\n-\t\t\t\t\t const char *branch1,\n-\t\t\t\t\t const char *branch2)\n+static struct merge_file_info merge_file_1(struct merge_options *o,\n+\t\t\t\t\t   const struct diff_filespec *one,\n+\t\t\t\t\t   const struct diff_filespec *a,\n+\t\t\t\t\t   const struct diff_filespec *b,\n+\t\t\t\t\t   const char *branch1,\n+\t\t\t\t\t   const char *branch2)\n {\n \tstruct merge_file_info result;\n \tresult.merge = 0;\n@@ -905,6 +905,26 @@ static struct merge_file_info merge_file(struct merge_options *o,\n \treturn result;\n }\n \n+static struct merge_file_info merge_file(struct merge_options *o,\n+\t\t\t\t\t const char *path,\n+\t\t\t\t\t const unsigned char *o_sha, int o_mode,\n+\t\t\t\t\t const unsigned char *a_sha, int a_mode,\n+\t\t\t\t\t const unsigned char *b_sha, int b_mode,\n+\t\t\t\t\t const char *branch1,\n+\t\t\t\t\t const char *branch2)\n+{\n+\tstruct diff_filespec one, a, b;\n+\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+\treturn merge_file_1(o, &one, &a, &b, branch1, branch2);\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@@ -1002,16 +1022,10 @@ static void conflict_rename_rename_2to1(struct merge_options *o,\n \t/* Two files were renamed to the same thing. */\n \tif (o->call_depth) {\n \t\tstruct merge_file_info mfi;\n-\t\tstruct diff_filespec one, a, b;\n-\n-\t\tone.path = a.path = b.path = path;\n-\t\thashcpy(one.sha1, null_sha1);\n-\t\tone.mode = 0;\n-\t\thashcpy(a.sha1, ren1->pair->two->sha1);\n-\t\ta.mode = ren1->pair->two->mode;\n-\t\thashcpy(b.sha1, ren2->pair->two->sha1);\n-\t\tb.mode = ren2->pair->two->mode;\n-\t\tmfi = merge_file(o, &one, &a, &b, branch1, branch2);\n+\t\tmfi = merge_file(o, path, null_sha1, 0,\n+\t\t\t\t ren1->pair->two->sha1, ren1->pair->two->mode,\n+\t\t\t\t ren2->pair->two->sha1, ren2->pair->two->mode,\n+\t\t\t\t branch1, branch2);\n \t\toutput(o, 1, \"Adding merged %s\", path);\n \t\tupdate_file(o, 0, mfi.sha, mfi.mode, path);\n \t} else {\n@@ -1198,24 +1212,12 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t       ren1_dst, branch2);\n \t\t\t\tif (o->call_depth) {\n \t\t\t\t\tstruct merge_file_info mfi;\n-\t\t\t\t\tstruct diff_filespec one, a, b;\n-\n-\t\t\t\t\tone.path = a.path = b.path =\n-\t\t\t\t\t\t(char *)ren1_dst;\n-\t\t\t\t\thashcpy(one.sha1, null_sha1);\n-\t\t\t\t\tone.mode = 0;\n-\t\t\t\t\thashcpy(a.sha1, ren1->pair->two->sha1);\n-\t\t\t\t\ta.mode = ren1->pair->two->mode;\n-\t\t\t\t\thashcpy(b.sha1, dst_other.sha1);\n-\t\t\t\t\tb.mode = dst_other.mode;\n-\t\t\t\t\tmfi = merge_file(o, &one, &a, &b,\n-\t\t\t\t\t\t\t branch1,\n-\t\t\t\t\t\t\t branch2);\n+\t\t\t\t\tmfi = merge_file(o, ren1_dst, null_sha1, 0,\n+\t\t\t\t\t\t\t ren1->pair->two->sha1, ren1->pair->two->mode,\n+\t\t\t\t\t\t\t dst_other.sha1, dst_other.mode,\n+\t\t\t\t\t\t\t branch1, branch2);\n \t\t\t\t\toutput(o, 1, \"Adding merged %s\", ren1_dst);\n-\t\t\t\t\tupdate_file(o, 0,\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\tupdate_file(o, 0, mfi.sha, mfi.mode, ren1_dst);\n \t\t\t\t\ttry_merge = 0;\n \t\t\t\t} else {\n \t\t\t\t\tchar *new_path = unique_path(o, ren1_dst, branch2);\n@@ -1388,13 +1390,13 @@ static int merge_content(struct merge_options *o,\n \t\tif (dir_in_way(path, !o->call_depth))\n \t\t\tdf_conflict_remains = 1;\n \t}\n-\tmfi = merge_file(o, &one, &a, &b,\n-\t\t\t side1 ? side1:o->branch1, side2 ? side2:o->branch2);\n+\tmfi = merge_file_1(o, &one, &a, &b,\n+\t\t\t   side1 ? side1:o->branch1, side2 ? side2:o->branch2);\n \tfree(side1);\n \tfree(side2);\n \n \tif (mfi.clean && !df_conflict_remains &&\n-\t    sha_eq(mfi.sha, a_sha) && mfi.mode == a.mode &&\n+\t    sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode &&\n \t    was_tracked(path)) {\n \t\toutput(o, 3, \"Skipped %s (merged same as existing)\", path);\n \t\tadd_cacheinfo(mfi.mode, mfi.sha, path,\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169557","messageId":"1307518278-23814-40-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 39/48] merge-recursive: Fix rename/rename(1to2) resolution for virtual merge base","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:09Z","receivedAt":"2011-06-08T07:31:09Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When renaming one file to two files, we really should be doing a content\nmerge.  Also, in the recursive case, undoing the renames and recording the\nmerged file in the index with the source of the rename (while deleting\nboth destinations) allows the renames to be re-detected in the\nnon-recursive merge and will result in fewer spurious conflicts.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                 |   30 +++++++++++++-----------------\n t/t6036-recursive-corner-cases.sh |    2 +-\n 2 files changed, 14 insertions(+), 18 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 3a364fa..576e02d 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -971,17 +971,6 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t       src, pair1->two->path, branch1,\n \t       src, pair2->two->path, branch2,\n \t       o->call_depth ? \" (left unresolved)\" : \"\");\n-\tif (o->call_depth) {\n-\t\t/*\n-\t\t * FIXME: Why remove file from cache, and then\n-\t\t * immediately readd it?  Why not just overwrite using\n-\t\t * update_file only?  Also...this is buggy for\n-\t\t * rename/add-source situations...\n-\t\t */\n-\t\tremove_file_from_cache(src);\n-\t\tupdate_file(o, 0, pair1->one->sha1, pair1->one->mode, src);\n-\t}\n-\n \tif (dir_in_way(ren1_dst, !o->call_depth)) {\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@@ -993,14 +982,21 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t\t       ren2_dst, branch1, dst_name2);\n \t}\n \tif (o->call_depth) {\n-\t\tremove_file_from_cache(dst_name1);\n-\t\tremove_file_from_cache(dst_name2);\n+\t\tstruct merge_file_info mfi;\n+\t\tmfi = merge_file(o, src,\n+\t\t\t\t pair1->one->sha1, pair1->one->mode,\n+\t\t\t\t pair1->two->sha1, pair1->two->mode,\n+\t\t\t\t pair2->two->sha1, pair2->two->mode,\n+\t\t\t\t branch1, branch2);\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, 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 * FIXME: For rename/add-source conflicts (if we could detect\n+\t\t * such), this is wrong.  We should instead find a unique\n+\t\t * pathname and then either rename the add-source file to that\n+\t\t * unique path, or use that unique path instead of src here.\n \t\t */\n+\t\tupdate_file(o, 0, mfi.sha, mfi.mode, src);\n+\t\tremove_file_from_cache(ren1_dst);\n+\t\tremove_file_from_cache(ren2_dst);\n \t} else {\n \t\tupdate_stages(ren1_dst, NULL, pair1->two, NULL);\n \t\tupdate_stages(ren2_dst, NULL, NULL, pair2->two);\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex d27477b..a1b609f 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -611,7 +611,7 @@ test_expect_success 'setup rename/rename(1to2)/modify followed by what looks lik\n \tgit tag E\n '\n \n-test_expect_failure 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '\n+test_expect_success 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '\n \tgit checkout D^0 &&\n \n \tgit merge -s recursive E^0 &&\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169570","messageId":"1307518278-23814-41-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 40/48] merge-recursive: Small cleanups for conflict_rename_rename_1to2","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:10Z","receivedAt":"2011-06-08T07:31:10Z","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 merge-recursive.c |   60 +++++++++++++++++++++++-----------------------------\n 1 files changed, 27 insertions(+), 33 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 576e02d..9c0daf5 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -951,58 +951,55 @@ 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 diff_filepair *pair1,\n-\t\t\t\t\tconst char *branch1,\n-\t\t\t\t\tstruct diff_filepair *pair2,\n-\t\t\t\t\tconst char *branch2)\n+\t\t\t\t\tstruct rename_conflict_info *ci)\n {\n \t/* One file was renamed in both branches, but to different names. */\n+\tstruct diff_filespec *one = ci->pair1->one;\n+\tstruct diff_filespec *a = ci->pair1->two;\n+\tstruct diff_filespec *b = ci->pair2->two;\n+\tconst char *dst_name_a = a->path;\n+\tconst char *dst_name_b = b->path;\n \tchar *del[2];\n \tint delp = 0;\n-\tconst char *src      = pair1->one->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 \n \toutput(o, 1, \"CONFLICT (rename/rename): \"\n \t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n \t       \"rename \\\"%s\\\"->\\\"%s\\\" in \\\"%s\\\"%s\",\n-\t       src, pair1->two->path, branch1,\n-\t       src, pair2->two->path, branch2,\n+\t       one->path, a->path, ci->branch1,\n+\t       one->path, b->path, ci->branch2,\n \t       o->call_depth ? \" (left unresolved)\" : \"\");\n-\tif (dir_in_way(ren1_dst, !o->call_depth)) {\n-\t\tdst_name1 = del[delp++] = unique_path(o, ren1_dst, branch1);\n+\tif (dir_in_way(a->path, !o->call_depth)) {\n+\t\tdst_name_a = del[delp++] = unique_path(o, a->path, ci->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\t       a->path, ci->branch2, dst_name_a);\n \t}\n-\tif (dir_in_way(ren2_dst, !o->call_depth)) {\n-\t\tdst_name2 = del[delp++] = unique_path(o, ren2_dst, branch2);\n+\tif (dir_in_way(b->path, !o->call_depth)) {\n+\t\tdst_name_b = del[delp++] = unique_path(o, b->path, ci->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\t       b->path, ci->branch1, dst_name_b);\n \t}\n \tif (o->call_depth) {\n \t\tstruct merge_file_info mfi;\n-\t\tmfi = merge_file(o, src,\n-\t\t\t\t pair1->one->sha1, pair1->one->mode,\n-\t\t\t\t pair1->two->sha1, pair1->two->mode,\n-\t\t\t\t pair2->two->sha1, pair2->two->mode,\n-\t\t\t\t branch1, branch2);\n+\t\tmfi = merge_file(o, one->path,\n+\t\t\t\t one->sha1, one->mode,\n+\t\t\t\t a->sha1, a->mode,\n+\t\t\t\t b->sha1, b->mode,\n+\t\t\t\t ci->branch1, ci->branch2);\n \t\t/*\n \t\t * FIXME: For rename/add-source conflicts (if we could detect\n \t\t * such), this is wrong.  We should instead find a unique\n \t\t * pathname and then either rename the add-source file to that\n \t\t * unique path, or use that unique path instead of src here.\n \t\t */\n-\t\tupdate_file(o, 0, mfi.sha, mfi.mode, src);\n-\t\tremove_file_from_cache(ren1_dst);\n-\t\tremove_file_from_cache(ren2_dst);\n+\t\tupdate_file(o, 0, mfi.sha, mfi.mode, one->path);\n+\t\tremove_file_from_cache(a->path);\n+\t\tremove_file_from_cache(b->path);\n \t} else {\n-\t\tupdate_stages(ren1_dst, NULL, pair1->two, NULL);\n-\t\tupdate_stages(ren2_dst, NULL, NULL, pair2->two);\n+\t\tupdate_stages(a->path, NULL, a, NULL);\n+\t\tupdate_stages(b->path, NULL, NULL, b);\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\tupdate_file(o, 0, a->sha1, a->mode, dst_name_a);\n+\t\tupdate_file(o, 0, b->sha1, b->mode, dst_name_b);\n \t}\n \twhile (delp--)\n \t\tfree(del[delp]);\n@@ -1476,10 +1473,7 @@ static int process_entry(struct merge_options *o,\n \t\t\tbreak;\n \t\tcase RENAME_ONE_FILE_TO_TWO:\n \t\t\tclean_merge = 0;\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_rename_rename_1to2(o, conflict_info);\n \t\t\tbreak;\n \t\tdefault:\n \t\t\tentry->processed = 0;\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169571","messageId":"1307518278-23814-42-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 41/48] merge-recursive: Defer rename/rename(2to1) handling until process_entry","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:11Z","receivedAt":"2011-06-08T07:31:11Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This puts the code for the different types of double rename conflicts\ncloser together (fewer lines of other code separating the two paths) and\nincreases similarity between how they are handled.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |  104 +++++++++++++++++++++++++++++++---------------------\n 1 files changed, 62 insertions(+), 42 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 9c0daf5..dbc1dd3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -67,7 +67,8 @@ enum rename_type {\n \tRENAME_NORMAL = 0,\n \tRENAME_DELETE,\n \tRENAME_ONE_FILE_TO_ONE,\n-\tRENAME_ONE_FILE_TO_TWO\n+\tRENAME_ONE_FILE_TO_TWO,\n+\tRENAME_TWO_FILES_TO_ONE\n };\n \n struct rename_conflict_info {\n@@ -1006,32 +1007,40 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\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-\t\t\t\t\tstruct rename *ren2,\n-\t\t\t\t\tconst char *branch2)\n+\t\t\t\t\tstruct rename_conflict_info *ci)\n {\n-\tchar *path = ren1->pair->two->path; /* same as ren2->pair->two->path */\n-\t/* Two files were renamed to the same thing. */\n+\t/* Two files, a & b, were renamed to the same thing, c. */\n+\tstruct diff_filespec *a = ci->pair1->one;\n+\tstruct diff_filespec *b = ci->pair2->one;\n+\tstruct diff_filespec *c1 = ci->pair1->two;\n+\tstruct diff_filespec *c2 = ci->pair2->two;\n+\tchar *path = c1->path; /* == c2->path */\n+\n+\toutput(o, 1, \"CONFLICT (rename/rename): \"\n+\t       \"Rename %s->%s in %s. \"\n+\t       \"Rename %s->%s in %s\",\n+\t       a->path, c1->path, ci->branch1,\n+\t       b->path, c2->path, ci->branch2);\n+\n+\tremove_file(o, 1, a->path, would_lose_untracked(a->path));\n+\tremove_file(o, 1, b->path, would_lose_untracked(b->path));\n+\n \tif (o->call_depth) {\n \t\tstruct merge_file_info mfi;\n \t\tmfi = merge_file(o, path, null_sha1, 0,\n-\t\t\t\t ren1->pair->two->sha1, ren1->pair->two->mode,\n-\t\t\t\t ren2->pair->two->sha1, ren2->pair->two->mode,\n-\t\t\t\t branch1, branch2);\n+\t\t\t\t c1->sha1, c1->mode,\n+\t\t\t\t c2->sha1, c2->mode,\n+\t\t\t\t ci->branch1, ci->branch2);\n \t\toutput(o, 1, \"Adding merged %s\", path);\n \t\tupdate_file(o, 0, mfi.sha, mfi.mode, path);\n \t} else {\n-\t\tchar *new_path1 = unique_path(o, path, branch1);\n-\t\tchar *new_path2 = unique_path(o, path, branch2);\n+\t\tchar *new_path1 = unique_path(o, path, ci->branch1);\n+\t\tchar *new_path2 = unique_path(o, path, ci->branch2);\n \t\toutput(o, 1, \"Renaming %s to %s and %s to %s instead\",\n-\t\t       ren1->pair->one->path, new_path1,\n-\t\t       ren2->pair->one->path, new_path2);\n+\t\t       a->path, new_path1, b->path, new_path2);\n \t\tremove_file(o, 0, path, 0);\n-\t\tupdate_file(o, 0, ren1->pair->two->sha1, ren1->pair->two->mode,\n-\t\t\t    new_path1);\n-\t\tupdate_file(o, 0, ren2->pair->two->sha1, ren2->pair->two->mode,\n-\t\t\t    new_path2);\n+\t\tupdate_file(o, 0, c1->sha1, c1->mode, new_path1);\n+\t\tupdate_file(o, 0, c2->sha1, c2->mode, new_path2);\n \t\tfree(new_path2);\n \t\tfree(new_path1);\n \t}\n@@ -1062,6 +1071,7 @@ static int process_renames(struct merge_options *o,\n \t\tstruct rename *ren1 = NULL, *ren2 = NULL;\n \t\tconst char *branch1, *branch2;\n \t\tconst char *ren1_src, *ren1_dst;\n+\t\tstruct string_list_item *lookup;\n \n \t\tif (i >= a_renames->nr) {\n \t\t\tren2 = b_renames->items[j++].util;\n@@ -1093,30 +1103,30 @@ static int process_renames(struct merge_options *o,\n \t\t\tren1 = tmp;\n \t\t}\n \n+\t\tif (ren1->processed)\n+\t\t\tcontinue;\n+\t\tren1->processed = 1;\n \t\tren1->dst_entry->processed = 1;\n \t\t/* BUG: We should only mark src_entry as processed if we\n \t\t * are not dealing with a rename + add-source case.\n \t\t */\n \t\tren1->src_entry->processed = 1;\n \n-\t\tif (ren1->processed)\n-\t\t\tcontinue;\n-\t\tren1->processed = 1;\n-\n \t\tren1_src = ren1->pair->one->path;\n \t\tren1_dst = ren1->pair->two->path;\n \n \t\tif (ren2) {\n+\t\t\t/* One file renamed on both sides */\n \t\t\tconst char *ren2_src = ren2->pair->one->path;\n \t\t\tconst char *ren2_dst = ren2->pair->two->path;\n \t\t\tenum rename_type rename_type;\n-\t\t\t/* Renamed in 1 and renamed in 2 */\n \t\t\tif (strcmp(ren1_src, ren2_src) != 0)\n-\t\t\t\tdie(\"ren1.src != ren2.src\");\n+\t\t\t\tdie(\"ren1_src != ren2_src\");\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\trename_type = RENAME_ONE_FILE_TO_TWO;\n+\t\t\t\tclean_merge = 0;\n \t\t\t} else {\n \t\t\t\trename_type = RENAME_ONE_FILE_TO_ONE;\n \t\t\t\t/* BUG: We should only remove ren1_src in\n@@ -1136,9 +1146,32 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t   branch2,\n \t\t\t\t\t\t   ren1->dst_entry,\n \t\t\t\t\t\t   ren2->dst_entry);\n+\t\t} else if ((lookup = string_list_lookup(renames2Dst, ren1_dst))) {\n+\t\t\t/* Two different files renamed to the same thing */\n+\t\t\tchar *ren2_dst;\n+\t\t\tren2 = lookup->util;\n+\t\t\tren2_dst = ren2->pair->two->path;\n+\t\t\tif (strcmp(ren1_dst, ren2_dst) != 0)\n+\t\t\t\tdie(\"ren1_dst != ren2_dst\");\n+\n+\t\t\tclean_merge = 0;\n+\t\t\tren2->processed = 1;\n+\t\t\t/*\n+\t\t\t * BUG: We should only mark src_entry as processed\n+\t\t\t * if we are not dealing with a rename + add-source\n+\t\t\t * case.\n+\t\t\t */\n+\t\t\tren2->src_entry->processed = 1;\n+\n+\t\t\tsetup_rename_conflict_info(RENAME_TWO_FILES_TO_ONE,\n+\t\t\t\t\t\t   ren1->pair,\n+\t\t\t\t\t\t   ren2->pair,\n+\t\t\t\t\t\t   branch1,\n+\t\t\t\t\t\t   branch2,\n+\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t   ren2->dst_entry);\n \t\t} else {\n \t\t\t/* Renamed in 1, maybe changed in 2 */\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;\n@@ -1173,23 +1206,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   NULL);\n-\t\t\t} else if ((item = string_list_lookup(renames2Dst, ren1_dst))) {\n-\t\t\t\tchar *ren2_src, *ren2_dst;\n-\t\t\t\tren2 = item->util;\n-\t\t\t\tren2_src = ren2->pair->one->path;\n-\t\t\t\tren2_dst = ren2->pair->two->path;\n-\n-\t\t\t\tclean_merge = 0;\n-\t\t\t\tren2->processed = 1;\n-\t\t\t\tremove_file(o, 1, ren2_src,\n-\t\t\t\t\t    renamed_stage == 3 || would_lose_untracked(ren1_src));\n-\n-\t\t\t\toutput(o, 1, \"CONFLICT (rename/rename): \"\n-\t\t\t\t       \"Rename %s->%s in %s. \"\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_src, ren2_dst, branch2);\n-\t\t\t\tconflict_rename_rename_2to1(o, ren1, branch1, ren2, 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@@ -1475,6 +1491,10 @@ static int process_entry(struct merge_options *o,\n \t\t\tclean_merge = 0;\n \t\t\tconflict_rename_rename_1to2(o, conflict_info);\n \t\t\tbreak;\n+\t\tcase RENAME_TWO_FILES_TO_ONE:\n+\t\t\tclean_merge = 0;\n+\t\t\tconflict_rename_rename_2to1(o, conflict_info);\n+\t\t\tbreak;\n \t\tdefault:\n \t\t\tentry->processed = 0;\n \t\t\tbreak;\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169561","messageId":"1307518278-23814-43-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 42/48] merge-recursive: Record more data needed for merging with dual renames","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:12Z","receivedAt":"2011-06-08T07:31:12Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"When two different files are renamed to one, we need to be able to do\nthree-way merges for both of those files.  To do that, we need to record\nthe sha1sum of the (possibly modified) file on the unrenamed side.  Modify\nsetup_rename_conflict_info() to take this extra information and record it\nwhen the rename_type is RENAME_TWO_FILES_TO_ONE.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   42 +++++++++++++++++++++++++++++++++++++++---\n 1 files changed, 39 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex dbc1dd3..fdf67a3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -79,6 +79,8 @@ struct rename_conflict_info {\n \tconst char *branch2;\n \tstruct stage_data *dst_entry1;\n \tstruct stage_data *dst_entry2;\n+\tstruct diff_filespec ren1_other;\n+\tstruct diff_filespec ren2_other;\n };\n \n /*\n@@ -100,7 +102,10 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\t\t\t\t      const char *branch1,\n \t\t\t\t\t      const char *branch2,\n \t\t\t\t\t      struct stage_data *dst_entry1,\n-\t\t\t\t\t      struct stage_data *dst_entry2)\n+\t\t\t\t\t      struct stage_data *dst_entry2,\n+\t\t\t\t\t      struct merge_options *o,\n+\t\t\t\t\t      struct stage_data *src_entry1,\n+\t\t\t\t\t      struct stage_data *src_entry2)\n {\n \tstruct rename_conflict_info *ci = xcalloc(1, sizeof(struct rename_conflict_info));\n \tci->rename_type = rename_type;\n@@ -118,6 +123,24 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \t\tci->pair2 = pair2;\n \t\tdst_entry2->rename_conflict_info = ci;\n \t}\n+\n+\tif (rename_type == RENAME_TWO_FILES_TO_ONE) {\n+\t\t/*\n+\t\t * For each rename, there could have been\n+\t\t * modifications on the side of history where that\n+\t\t * file was not renamed.\n+\t\t */\n+\t\tint ostage1 = o->branch1 == branch1 ? 3 : 2;\n+\t\tint ostage2 = ostage1 ^ 1;\n+\n+\t\tci->ren1_other.path = pair1->one->path;\n+\t\thashcpy(ci->ren1_other.sha1, src_entry1->stages[ostage1].sha);\n+\t\tci->ren1_other.mode = src_entry1->stages[ostage1].mode;\n+\n+\t\tci->ren2_other.path = pair2->one->path;\n+\t\thashcpy(ci->ren2_other.sha1, src_entry2->stages[ostage2].sha);\n+\t\tci->ren2_other.mode = src_entry2->stages[ostage2].mode;\n+\t}\n }\n \n static int show(struct merge_options *o, int v)\n@@ -1145,7 +1168,10 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t   branch1,\n \t\t\t\t\t\t   branch2,\n \t\t\t\t\t\t   ren1->dst_entry,\n-\t\t\t\t\t\t   ren2->dst_entry);\n+\t\t\t\t\t\t   ren2->dst_entry,\n+\t\t\t\t\t\t   o,\n+\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t   NULL);\n \t\t} else if ((lookup = string_list_lookup(renames2Dst, ren1_dst))) {\n \t\t\t/* Two different files renamed to the same thing */\n \t\t\tchar *ren2_dst;\n@@ -1169,7 +1195,11 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t   branch1,\n \t\t\t\t\t\t   branch2,\n \t\t\t\t\t\t   ren1->dst_entry,\n-\t\t\t\t\t\t   ren2->dst_entry);\n+\t\t\t\t\t\t   ren2->dst_entry,\n+\t\t\t\t\t\t   o,\n+\t\t\t\t\t\t   ren1->src_entry,\n+\t\t\t\t\t\t   ren2->src_entry);\n+\n \t\t} else {\n \t\t\t/* Renamed in 1, maybe changed in 2 */\n \t\t\t/* we only use sha1 and mode of these */\n@@ -1205,6 +1235,9 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t\t   branch1,\n \t\t\t\t\t\t\t   branch2,\n \t\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   o,\n+\t\t\t\t\t\t\t   NULL,\n \t\t\t\t\t\t\t   NULL);\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@@ -1256,6 +1289,9 @@ static int process_renames(struct merge_options *o,\n \t\t\t\t\t\t\t   branch1,\n \t\t\t\t\t\t\t   NULL,\n \t\t\t\t\t\t\t   ren1->dst_entry,\n+\t\t\t\t\t\t\t   NULL,\n+\t\t\t\t\t\t\t   o,\n+\t\t\t\t\t\t\t   NULL,\n \t\t\t\t\t\t\t   NULL);\n \t\t\t}\n \t\t}\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169568","messageId":"1307518278-23814-44-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 43/48] merge-recursive: Create function for merging with branchname:file markers","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:13Z","receivedAt":"2011-06-08T07:31:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"We want to be able to reuse the code to do a three-way file content merge\nand have the conflict markers use both branchname and filename.  Split it\nout into a separate function.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c |   51 ++++++++++++++++++++++++++++++++++++++-------------\n 1 files changed, 38 insertions(+), 13 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex fdf67a3..b8fd686 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -929,6 +929,36 @@ static struct merge_file_info merge_file_1(struct merge_options *o,\n \treturn result;\n }\n \n+static struct merge_file_info\n+merge_file_special_markers(struct merge_options *o,\n+\t\t\t   const struct diff_filespec *one,\n+\t\t\t   const struct diff_filespec *a,\n+\t\t\t   const struct diff_filespec *b,\n+\t\t\t   const char *branch1,\n+\t\t\t   const char *filename1,\n+\t\t\t   const char *branch2,\n+\t\t\t   const char *filename2)\n+{\n+\tchar *side1 = NULL;\n+\tchar *side2 = NULL;\n+\tstruct merge_file_info mfi;\n+\n+\tif (filename1) {\n+\t\tside1 = xmalloc(strlen(branch1) + strlen(filename1) + 2);\n+\t\tsprintf(side1, \"%s:%s\", branch1, filename1);\n+\t}\n+\tif (filename2) {\n+\t\tside2 = xmalloc(strlen(branch2) + strlen(filename2) + 2);\n+\t\tsprintf(side2, \"%s:%s\", branch2, filename2);\n+\t}\n+\n+\tmfi = merge_file_1(o, one, a, b,\n+\t\t\t   side1 ? side1 : branch1, side2 ? side2 : branch2);\n+\tfree(side1);\n+\tfree(side2);\n+\treturn mfi;\n+}\n+\n static struct merge_file_info merge_file(struct merge_options *o,\n \t\t\t\t\t const char *path,\n \t\t\t\t\t const unsigned char *o_sha, int o_mode,\n@@ -1405,7 +1435,7 @@ static int merge_content(struct merge_options *o,\n \t\t\t struct rename_conflict_info *rename_conflict_info)\n {\n \tconst char *reason = \"content\";\n-\tchar *side1 = NULL, *side2 = NULL;\n+\tconst char *path1 = NULL, *path2 = NULL;\n \tstruct merge_file_info mfi;\n \tstruct diff_filespec one, a, b;\n \tunsigned df_conflict_remains = 0;\n@@ -1423,22 +1453,17 @@ static int merge_content(struct merge_options *o,\n \tb.mode = b_mode;\n \n \tif (rename_conflict_info) {\n-\t\tconst char *path1 = rename_conflict_info->pair2 ?\n-\t\t\t\t    rename_conflict_info->pair2->one->path : path;\n-\t\tconst char *path2 = rename_conflict_info->pair1 ?\n-\t\t\t\t    rename_conflict_info->pair1->one->path : path;\n-\t\tside1 = xmalloc(strlen(o->branch1) + strlen(path1) + 2);\n-\t\tside2 = xmalloc(strlen(o->branch2) + strlen(path2) + 2);\n-\t\tsprintf(side1, \"%s:%s\", o->branch1, path1);\n-\t\tsprintf(side2, \"%s:%s\", o->branch2, path2);\n+\t\tpath1 = rename_conflict_info->pair2 ?\n+\t\t\trename_conflict_info->pair2->one->path : path;\n+\t\tpath2 = rename_conflict_info->pair1 ?\n+\t\t\trename_conflict_info->pair1->one->path : path;\n \n \t\tif (dir_in_way(path, !o->call_depth))\n \t\t\tdf_conflict_remains = 1;\n \t}\n-\tmfi = merge_file_1(o, &one, &a, &b,\n-\t\t\t   side1 ? side1:o->branch1, side2 ? side2:o->branch2);\n-\tfree(side1);\n-\tfree(side2);\n+\tmfi = merge_file_special_markers(o, &one, &a, &b,\n+\t\t\t\t\t o->branch1, path1,\n+\t\t\t\t\t o->branch2, path2);\n \n \tif (mfi.clean && !df_conflict_remains &&\n \t    sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode &&\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169564","messageId":"1307518278-23814-45-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 44/48] merge-recursive: Consider modifications in rename/rename(2to1) conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:14Z","receivedAt":"2011-06-08T07:31:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Our previous conflict resolution for renaming two different files to the\nsame name ignored the fact that each of those files may have modifications\nfrom both sides of history to consider.  We need to do a three-way merge\nfor each of those files, and then handle the conflict of both sets of\nmerged contents trying to be recorded with the same name.\n\nIt is important to note that this changes our strategy in the recursive\ncase.  After doing a three-way content merge of each of the files\ninvolved, we still are faced with the fact that we are trying to put both\nof the results (including conflict markers) into the same path.  We could\ndo another two-way merge, but I think that becomes confusing.  Also,\ntaking a hint from the modify/delete and rename/delete cases we handled\nearlier, a more useful \"common ground\" would be to keep the three-way\ncontent merge but record it with the original filename.  The renames can\nstill be detected, we just allow it to be done in the o->call_depth=0\ncase.  This seems to result in simpler & easier to understand merge\nconflicts as well, as evidenced by some of the changes needed in our\ntestsuite in t6036.  (However, it should be noted that this change will\ncause problems those renames also occur along with a file being added\nwhose name matches the source of the rename.  Since git currently cannot\ndetect rename/add-source situations, though, this codepath is not\ncurrently used for those cases anyway.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    |   30 ++++++++++++++++++--------\n t/t6036-recursive-corner-cases.sh    |   38 +++++++++-------------------------\n t/t6039-merge-rename-corner-cases.sh |    2 +-\n 3 files changed, 32 insertions(+), 38 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b8fd686..ea538e9 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1068,6 +1068,8 @@ static void conflict_rename_rename_2to1(struct merge_options *o,\n \tstruct diff_filespec *c1 = ci->pair1->two;\n \tstruct diff_filespec *c2 = ci->pair2->two;\n \tchar *path = c1->path; /* == c2->path */\n+\tstruct merge_file_info mfi_c1;\n+\tstruct merge_file_info mfi_c2;\n \n \toutput(o, 1, \"CONFLICT (rename/rename): \"\n \t       \"Rename %s->%s in %s. \"\n@@ -1078,22 +1080,32 @@ static void conflict_rename_rename_2to1(struct merge_options *o,\n \tremove_file(o, 1, a->path, would_lose_untracked(a->path));\n \tremove_file(o, 1, b->path, would_lose_untracked(b->path));\n \n+\tmfi_c1 = merge_file_special_markers(o, a, c1, &ci->ren1_other,\n+\t\t\t\t\t    o->branch1, c1->path,\n+\t\t\t\t\t    o->branch2, ci->ren1_other.path);\n+\tmfi_c2 = merge_file_special_markers(o, b, &ci->ren2_other, c2,\n+\t\t\t\t\t    o->branch1, ci->ren2_other.path,\n+\t\t\t\t\t    o->branch2, c2->path);\n+\n \tif (o->call_depth) {\n-\t\tstruct merge_file_info mfi;\n-\t\tmfi = merge_file(o, path, null_sha1, 0,\n-\t\t\t\t c1->sha1, c1->mode,\n-\t\t\t\t c2->sha1, c2->mode,\n-\t\t\t\t ci->branch1, ci->branch2);\n-\t\toutput(o, 1, \"Adding merged %s\", path);\n-\t\tupdate_file(o, 0, mfi.sha, mfi.mode, path);\n+\t\t/*\n+\t\t * If mfi_c1.clean && mfi_c2.clean, then it might make\n+\t\t * sense to do a two-way merge of those results.  But, I\n+\t\t * think in all cases, it makes sense to have the virtual\n+\t\t * merge base just undo the renames; they can be detected\n+\t\t * again later for the non-recursive merge.\n+\t\t */\n+\t\tremove_file(o, 0, path, 0);\n+\t\tupdate_file(o, 0, mfi_c1.sha, mfi_c1.mode, a->path);\n+\t\tupdate_file(o, 0, mfi_c2.sha, mfi_c2.mode, b->path);\n \t} else {\n \t\tchar *new_path1 = unique_path(o, path, ci->branch1);\n \t\tchar *new_path2 = unique_path(o, path, ci->branch2);\n \t\toutput(o, 1, \"Renaming %s to %s and %s to %s instead\",\n \t\t       a->path, new_path1, b->path, new_path2);\n \t\tremove_file(o, 0, path, 0);\n-\t\tupdate_file(o, 0, c1->sha1, c1->mode, new_path1);\n-\t\tupdate_file(o, 0, c2->sha1, c2->mode, new_path2);\n+\t\tupdate_file(o, 0, mfi_c1.sha, mfi_c1.mode, new_path1);\n+\t\tupdate_file(o, 0, mfi_c2.sha, mfi_c2.mode, new_path2);\n \t\tfree(new_path2);\n \t\tfree(new_path1);\n \t}\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex a1b609f..5c3868a 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -51,23 +51,15 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '\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+\ttest 2 = $(git ls-files -s | wc -l) &&\n+\ttest 2 = $(git ls-files -u | wc -l) &&\n+\ttest 2 = $(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 one merged &&\n-\t>empty &&\n-\ttest_must_fail git merge-file \\\n-\t\t-L \"Temporary merge branch 1\" \\\n-\t\t-L \"\" \\\n-\t\t-L \"Temporary merge branch 2\" \\\n-\t\tmerged empty two &&\n-\ttest $(git rev-parse :1:three) = $(git hash-object merged)\n+\ttest $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&\n+\ttest $(git rev-parse R2:three) = $(git hash-object three~R2^0)\n '\n \n #\n@@ -126,25 +118,15 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'\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+\ttest 2 = $(git ls-files -s | wc -l) &&\n+\ttest 2 = $(git ls-files -u | wc -l) &&\n+\ttest 2 = $(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 1\" \\\n-\t\t-L \"\" \\\n-\t\t-L \"Temporary merge branch 2\" \\\n-\t\tmerge-me empty merged &&\n-\n-\ttest $(git rev-parse :1:three) = $(git hash-object merge-me)\n+\ttest $(git rev-parse L2:three) = $(git hash-object three~HEAD) &&\n+\ttest $(git rev-parse R2:three) = $(git hash-object three~R2^0)\n '\n \n #\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex 4d2fd10..86e96d0 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -524,7 +524,7 @@ test_expect_success 'setup rename/rename (2to1) + modify/modify' '\n \tgit commit -m C\n '\n \n-test_expect_failure 'handle rename/rename (2to1) conflict correctly' '\n+test_expect_success 'handle rename/rename (2to1) conflict correctly' '\n \tgit checkout B^0 &&\n \n \ttest_must_fail git merge -s recursive C^0 >out &&\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169567","messageId":"1307518278-23814-46-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 45/48] merge-recursive: Make modify/delete handling code reusable","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:15Z","receivedAt":"2011-06-08T07:31:15Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"modify/delete and rename/delete share a lot of similarities; we'd like all\nthe criss-cross and D/F conflict handling specializations to be shared\nbetween the two.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c       |   89 +++++++++++++++++++++++++++-------------------\n t/t6022-merge-rename.sh |    4 +-\n 2 files changed, 54 insertions(+), 39 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ea538e9..e38c5b0 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -979,6 +979,50 @@ static struct merge_file_info merge_file(struct merge_options *o,\n \treturn merge_file_1(o, &one, &a, &b, branch1, branch2);\n }\n \n+static void handle_change_delete(struct merge_options *o,\n+\t\t\t\t const char *path,\n+\t\t\t\t const unsigned char *o_sha, int o_mode,\n+\t\t\t\t const unsigned char *a_sha, int a_mode,\n+\t\t\t\t const unsigned char *b_sha, int b_mode,\n+\t\t\t\t const char *change, const char *change_past)\n+{\n+\tchar *new_path = NULL;\n+\tint free_me = 0;\n+\tif (dir_in_way(path, !o->call_depth)) {\n+\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n+\t\tfree_me = 1;\n+\t}\n+\n+\tif (o->call_depth) {\n+\t\t/*\n+\t\t * We cannot arbitrarily accept either a_sha or b_sha as\n+\t\t * correct; since there is no true \"middle point\" between\n+\t\t * them, simply reuse the base version for virtual merge base.\n+\t\t */\n+\t\tremove_file_from_cache(path);\n+\t\tupdate_file(o, 0, o_sha, o_mode, new_path ? new_path : path);\n+\t} else if (!a_sha) {\n+\t\toutput(o, 1, \"CONFLICT (%s/delete): %s deleted in %s \"\n+\t\t       \"and %s in %s. Version %s of %s left in tree%s%s.\",\n+\t\t       change, path, o->branch1,\n+\t\t       change_past, o->branch2, o->branch2, path,\n+\t\t       NULL == new_path ? \"\" : \" at \",\n+\t\t       NULL == new_path ? \"\" : new_path);\n+\t\tupdate_file(o, 0, b_sha, b_mode, new_path ? new_path : path);\n+\t} else {\n+\t\toutput(o, 1, \"CONFLICT (%s/delete): %s deleted in %s \"\n+\t\t       \"and %s in %s. Version %s of %s left in tree%s%s.\",\n+\t\t       change, path, o->branch2,\n+\t\t       change_past, o->branch1, o->branch1, path,\n+\t\t       NULL == new_path ? \"\" : \" at \",\n+\t\t       NULL == new_path ? \"\" : new_path);\n+\t\tupdate_file(o, 0, a_sha, a_mode, new_path ? new_path : path);\n+\t}\n+\tif (free_me)\n+\t\tfree(new_path);\n+\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@@ -1396,47 +1440,18 @@ error_return:\n \treturn ret;\n }\n \n-static void handle_delete_modify(struct merge_options *o,\n+static void handle_modify_delete(struct merge_options *o,\n \t\t\t\t const char *path,\n \t\t\t\t unsigned char *o_sha, int o_mode,\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-\tchar *new_path = NULL;\n-\tint free_me = 0;\n-\tif (dir_in_way(path, !o->call_depth)) {\n-\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n-\t\tfree_me = 1;\n-\t}\n-\n-\tif (o->call_depth) {\n-\t\t/*\n-\t\t * We cannot arbitrarily accept either a_sha or b_sha as\n-\t\t * correct; since there is no true \"middle point\" between\n-\t\t * them, simply reuse the base version for virtual merge base.\n-\t\t */\n-\t\tremove_file_from_cache(path);\n-\t\tupdate_file(o, 0, o_sha, o_mode, new_path ? new_path : path);\n-\t} else if (!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%s%s.\",\n-\t\t       path, o->branch1,\n-\t\t       o->branch2, o->branch2, path,\n-\t\t       NULL == new_path ? \"\" : \" at \",\n-\t\t       NULL == new_path ? \"\" : new_path);\n-\t\tupdate_file(o, 0, b_sha, b_mode, new_path ? new_path : 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%s%s.\",\n-\t\t       path, o->branch2,\n-\t\t       o->branch1, o->branch1, path,\n-\t\t       NULL == new_path ? \"\" : \" at \",\n-\t\t       NULL == new_path ? \"\" : new_path);\n-\t\tupdate_file(o, 0, a_sha, a_mode, new_path ? new_path : path);\n-\t}\n-\tif (free_me)\n-\t\tfree(new_path);\n-\n+\thandle_change_delete(o,\n+\t\t\t     path,\n+\t\t\t     o_sha, o_mode,\n+\t\t\t     a_sha, a_mode,\n+\t\t\t     b_sha, b_mode,\n+\t\t\t     \"modify\", \"modified\");\n }\n \n static int merge_content(struct merge_options *o,\n@@ -1586,7 +1601,7 @@ static int process_entry(struct merge_options *o,\n \t\t} else {\n \t\t\t/* Modify/delete; deleted side may have put a directory in the way */\n \t\t\tclean_merge = 0;\n-\t\t\thandle_delete_modify(o, path, o_sha, o_mode,\n+\t\t\thandle_modify_delete(o, path, o_sha, o_mode,\n \t\t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n \t\t}\n \t} else if ((!o_sha && a_sha && !b_sha) ||\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex cfce3d3..a1ed00b 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -303,7 +303,7 @@ test_expect_success 'Rename+D/F conflict; renamed file merges but dir in way' '\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 \"CONFLICT (modify/delete): dir/file-in-the-way\" output &&\n \tgrep \"Auto-merging dir\" output &&\n \tgrep \"Adding as dir~HEAD instead\" output &&\n \n@@ -325,7 +325,7 @@ test_expect_success 'Same as previous, but merged other way' '\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 \"CONFLICT (modify/delete): 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-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169558","messageId":"1307518278-23814-47-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 46/48] merge-recursive: Have conflict_rename_delete reuse modify/delete code","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:16Z","receivedAt":"2011-06-08T07:31:16Z","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 merge-recursive.c |   48 +++++++++++++++++++++++++++++++-----------------\n 1 files changed, 31 insertions(+), 17 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex e38c5b0..93b7da5 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1028,24 +1028,38 @@ static void conflict_rename_delete(struct merge_options *o,\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-\tint df_conflict = 0;\n+\tconst struct diff_filespec *orig = pair->one;\n+\tconst struct diff_filespec *dest = pair->two;\n+\tconst char *path;\n+\tconst unsigned char *a_sha = NULL;\n+\tconst unsigned char *b_sha = NULL;\n+\tint a_mode = 0;\n+\tint b_mode = 0;\n+\n+\tif (rename_branch == o->branch1) {\n+\t\ta_sha = dest->sha1;\n+\t\ta_mode = dest->mode;\n+\t} else {\n+\t\tb_sha = dest->sha1;\n+\t\tb_mode = dest->mode;\n+\t}\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-\tif (dir_in_way(dest_name, !o->call_depth)) {\n-\t\tdest_name = unique_path(o, dest_name, rename_branch);\n-\t\tdf_conflict = 1;\n+\tif (o->call_depth) {\n+\t\tremove_file_from_cache(dest->path);\n+\t\tpath = orig->path;\n+\t} else {\n+\t\tpath = dest->path;\n+\t\tupdate_stages(dest->path, NULL,\n+\t\t\t      rename_branch == o->branch1 ? dest : NULL,\n+\t\t\t      rename_branch == o->branch1 ? NULL : dest);\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+\thandle_change_delete(o,\n+\t\t\t     path,\n+\t\t\t     orig->sha1, orig->mode,\n+\t\t\t     a_sha, a_mode,\n+\t\t\t     b_sha, b_mode,\n+\t\t\t     \"rename\", \"renamed\");\n }\n \n static void conflict_rename_rename_1to2(struct merge_options *o,\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169563","messageId":"1307518278-23814-48-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 47/48] merge-recursive: add handling for rename/rename/add-dest/add-dest","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:17Z","receivedAt":"2011-06-08T07:31:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Each side of the rename in rename/rename(1to2) could potentially also be\ninvolved in a rename/add conflict.  Ensure stages for such conflicts are\nalso recorded.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    |   21 +++++++++++++++++++--\n t/t6039-merge-rename-corner-cases.sh |    2 +-\n 2 files changed, 20 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 93b7da5..0cfc215 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1062,6 +1062,19 @@ static void conflict_rename_delete(struct merge_options *o,\n \t\t\t     \"rename\", \"renamed\");\n }\n \n+static struct diff_filespec* filespec_from_entry(struct diff_filespec *target,\n+\t\t\t\t\t\t struct stage_data *entry,\n+\t\t\t\t\t\t int stage)\n+{\n+\tunsigned char *sha = entry->stages[stage].sha;\n+\tunsigned mode = entry->stages[stage].mode;\n+\tif (mode == 0 || is_null_sha1(sha))\n+\t\treturn NULL;\n+\thashcpy(target->sha1, sha);\n+\ttarget->mode = mode;\n+\treturn target;\n+}\n+\n static void conflict_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t\tstruct rename_conflict_info *ci)\n {\n@@ -1107,8 +1120,12 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t\tremove_file_from_cache(a->path);\n \t\tremove_file_from_cache(b->path);\n \t} else {\n-\t\tupdate_stages(a->path, NULL, a, NULL);\n-\t\tupdate_stages(b->path, NULL, NULL, b);\n+\t\tstruct diff_filespec other;\n+\t\tupdate_stages(a->path, NULL,\n+\t\t\t      a, filespec_from_entry(&other, ci->dst_entry1, 3));\n+\n+\t\tupdate_stages(b->path, NULL,\n+\t\t\t      filespec_from_entry(&other, ci->dst_entry2, 2), b);\n \n \t\tupdate_file(o, 0, a->sha1, a->mode, dst_name_a);\n \t\tupdate_file(o, 0, b->sha1, b->mode, dst_name_b);\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex 86e96d0..d2d93e7 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -437,7 +437,7 @@ test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '\n \tgit commit -m two\n '\n \n-test_expect_failure 'rename/rename/add-dest merge still knows about conflicting file versions' '\n+test_expect_success 'rename/rename/add-dest merge still knows about conflicting file versions' '\n \tgit checkout C^0 &&\n \ttest_must_fail git merge -s recursive B^0 &&\n \n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169569","messageId":"1307518278-23814-49-git-send-email-newren@gmail.com","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"[PATCH 48/48] merge-recursive: Fix working copy handling for rename/rename/add/add","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-06-08T07:31:18Z","receivedAt":"2011-06-08T07:31:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"If either side of a rename/rename(1to2) conflict is itself also involved\nin a rename/add-dest conflict, then we need to make sure both the rename\nand the added file appear in the working copy.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n merge-recursive.c                    |   73 ++++++++++++++++++++++------------\n t/t6039-merge-rename-corner-cases.sh |   11 +++++-\n 2 files changed, 58 insertions(+), 26 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0cfc215..612a8cb 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1075,6 +1075,52 @@ static struct diff_filespec* filespec_from_entry(struct diff_filespec *target,\n \treturn target;\n }\n \n+static void handle_file(struct merge_options *o,\n+\t\t\tstruct diff_filespec *rename,\n+\t\t\tint stage,\n+\t\t\tstruct rename_conflict_info *ci)\n+{\n+\tchar *dst_name = rename->path;\n+\tstruct stage_data *dst_entry;\n+\tconst char *cur_branch, *other_branch;\n+\tstruct diff_filespec other;\n+\tstruct diff_filespec *add;\n+\n+\tif (stage == 2) {\n+\t\tdst_entry = ci->dst_entry1;\n+\t\tcur_branch = ci->branch1;\n+\t\tother_branch = ci->branch2;\n+\t} else {\n+\t\tdst_entry = ci->dst_entry2;\n+\t\tcur_branch = ci->branch2;\n+\t\tother_branch = ci->branch1;\n+\t}\n+\n+\tadd = filespec_from_entry(&other, dst_entry, stage ^ 1);\n+\tif (stage == 2)\n+\t\tupdate_stages(rename->path, NULL, rename, add);\n+\telse\n+\t\tupdate_stages(rename->path, NULL, add, rename);\n+\n+\tif (add) {\n+\t\tchar *add_name = unique_path(o, rename->path, other_branch);\n+\t\tupdate_file(o, 0, add->sha1, add->mode, add_name);\n+\n+\t\tremove_file(o, 0, rename->path, 0);\n+\t\tdst_name = unique_path(o, rename->path, cur_branch);\n+\t} else {\n+\t\tif (dir_in_way(rename->path, !o->call_depth)) {\n+\t\t\tdst_name = unique_path(o, rename->path, cur_branch);\n+\t\t\toutput(o, 1, \"%s is a directory in %s adding as %s instead\",\n+\t\t\t       rename->path, other_branch, dst_name);\n+\t\t}\n+\t}\n+\tupdate_file(o, 0, rename->sha1, rename->mode, dst_name);\n+\n+\tif (dst_name != rename->path)\n+\t\tfree(dst_name);\n+}\n+\n static void conflict_rename_rename_1to2(struct merge_options *o,\n \t\t\t\t\tstruct rename_conflict_info *ci)\n {\n@@ -1082,10 +1128,6 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \tstruct diff_filespec *one = ci->pair1->one;\n \tstruct diff_filespec *a = ci->pair1->two;\n \tstruct diff_filespec *b = ci->pair2->two;\n-\tconst char *dst_name_a = a->path;\n-\tconst char *dst_name_b = b->path;\n-\tchar *del[2];\n-\tint delp = 0;\n \n \toutput(o, 1, \"CONFLICT (rename/rename): \"\n \t       \"Rename \\\"%s\\\"->\\\"%s\\\" in branch \\\"%s\\\" \"\n@@ -1093,16 +1135,6 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t       one->path, a->path, ci->branch1,\n \t       one->path, b->path, ci->branch2,\n \t       o->call_depth ? \" (left unresolved)\" : \"\");\n-\tif (dir_in_way(a->path, !o->call_depth)) {\n-\t\tdst_name_a = del[delp++] = unique_path(o, a->path, ci->branch1);\n-\t\toutput(o, 1, \"%s is a directory in %s adding as %s instead\",\n-\t\t       a->path, ci->branch2, dst_name_a);\n-\t}\n-\tif (dir_in_way(b->path, !o->call_depth)) {\n-\t\tdst_name_b = del[delp++] = unique_path(o, b->path, ci->branch2);\n-\t\toutput(o, 1, \"%s is a directory in %s adding as %s instead\",\n-\t\t       b->path, ci->branch1, dst_name_b);\n-\t}\n \tif (o->call_depth) {\n \t\tstruct merge_file_info mfi;\n \t\tmfi = merge_file(o, one->path,\n@@ -1120,18 +1152,9 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t\tremove_file_from_cache(a->path);\n \t\tremove_file_from_cache(b->path);\n \t} else {\n-\t\tstruct diff_filespec other;\n-\t\tupdate_stages(a->path, NULL,\n-\t\t\t      a, filespec_from_entry(&other, ci->dst_entry1, 3));\n-\n-\t\tupdate_stages(b->path, NULL,\n-\t\t\t      filespec_from_entry(&other, ci->dst_entry2, 2), b);\n-\n-\t\tupdate_file(o, 0, a->sha1, a->mode, dst_name_a);\n-\t\tupdate_file(o, 0, b->sha1, b->mode, dst_name_b);\n+\t\thandle_file(o, a, 2, ci);\n+\t\thandle_file(o, b, 3, ci);\n \t}\n-\twhile (delp--)\n-\t\tfree(del[delp]);\n }\n \n static void conflict_rename_rename_2to1(struct merge_options *o,\ndiff --git a/t/t6039-merge-rename-corner-cases.sh b/t/t6039-merge-rename-corner-cases.sh\nindex d2d93e7..9db1663 100755\n--- a/t/t6039-merge-rename-corner-cases.sh\n+++ b/t/t6039-merge-rename-corner-cases.sh\n@@ -444,11 +444,20 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting\n \ttest 5 -eq $(git ls-files -s | wc -l) &&\n \ttest 2 -eq $(git ls-files -u b | wc -l) &&\n \ttest 2 -eq $(git ls-files -u c | wc -l) &&\n+\ttest 4 -eq $(git ls-files -o | wc -l) &&\n \n \ttest $(git rev-parse :2:b) = $(git rev-parse C:b) &&\n \ttest $(git rev-parse :3:b) = $(git rev-parse B:b) &&\n \ttest $(git rev-parse :2:c) = $(git rev-parse C:c) &&\n-\ttest $(git rev-parse :3:c) = $(git rev-parse B:c)\n+\ttest $(git rev-parse :3:c) = $(git rev-parse B:c) &&\n+\n+\ttest $(git hash-object c~HEAD) = $(git rev-parse C:c) &&\n+\ttest $(git hash-object c~B\\^0) = $(git rev-parse B:c) &&\n+\ttest $(git hash-object b~HEAD) = $(git rev-parse C:b) &&\n+\ttest $(git hash-object b~B\\^0) = $(git rev-parse B:b) &&\n+\n+\ttest ! -f b &&\n+\ttest ! -f c\n '\n \n test_expect_success 'setup simple rename/rename (1to2) conflict' '\n-- \n1.7.6.rc0.62.g2d69f\n"},{"id":"169877","messageId":"7vipsctez0.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-06-11T18:12:03Z","receivedAt":"2011-06-11T18:12:03Z","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> You can note that I did not fix all the testcases I added.  I consider\n> some cases either unfixable or not worth fixing.\n\nThanks for working on these.  A quick comment (after reading a handful of\ntests in the early part of the series) I have on rename cases is that the\ntest might be aiming too high to be too strict in \"undetected rename\"\ncases. Consider:\n\n - We start from commit A that has path file1 where commit B and C fork\n   from;\n - One side, commit B, renames file1 to file2 and edits file2;\n - The other side, commit C, modifies file1.\n\nWhen merging B and C, we will ideally want to see file1 removed and\ncontent level merge done between C's edit to file1 and B's edit to file2\nleft in file2. The content level merge may or may not conflict, and if\nit cleanly merges, then that is Ok.\n\nBut if B's edit is too extensive, we may see what B did as \"delete file1,\nadd file2\", and we may report conflict at file1 (delete/modify) while\nresolving file2 cleanly (one side added). When notifying the user with\ndelete/modify conflict at file1, we should make him aware that among the\npaths added by the merge (i.e. file2) there might be a corresponding\nrewrite of it on the other side, so that such a case can be manually\nmerged. As long as that is done, a test of such a case should consider\nboth clean merge (rename noticed) and a conflict at the tree structure\nlevel (delete/modify with new) valid expected result, I think.\n\nTo put it another way, merge-recursive should expect that one side of\nhistory may rewrite a path it renames beyond recognition, and when\npunting, loudly ask the user for help, with enough information to help the\nuser to help it (e.g. perhaps say \"file1 has delete/modify conflict; the\ndeleted side has added file2---you the user might want to inspect it and\nsee if it is a rename with extensive rewrite and resolve it accordingly if\nthat is the case\").  We might want to dig deeper at that point by checking\nthe similarity again between the two blobs ourselves at that stage before\npunting.\n\nIf we change what C did to \"removes file1\", instead of \"modifies file1\",\nthe story changes.\n\nWhen merging B and C, that would be rename/delete conflict (B renamed, C\ndeleted). However, if we see that B's history as \"delete file1 and add\nfile2\", it would merge cleanly with C that deletes file1. The end result\nwould be an addition of file2. This tastes bad, as it will end up with a\nclean merge that the user may not even notice.\n\nIdeally we would want the merge to somehow warn the user to see if the\nadded \"file2\" still is wanted, and the worst part of this problem is that\nthis cannot be mechanically inferred. We could warn against every merge\nthat has a history that adds new paths and removes some other paths, but\nthen the warning becomes meaningless.\n\nThe changes done by B to the other parts of the tree (that wasn't involved\nin the conflict) still want the updated content in file2 to work\ncorrectly, and the changes done by C will not want the original content in\nfile1 (now in file2). The latter may or may not care about the presense of\nwhat was added to \"file2\" by B. If C's work was to remove file1 that was\nnothing but dead code (and other parts of C's work removed all the\ncallsite of code in original \"file1\" to make them dead), while B's work\nwas to add a code with purpose similar to the original code \"file1\" had\n(and other parts of B's work calls that new code), then the right merge\nresult of \"file2\" might be to keep the line removal of dead code made by C\nwhile keeping the code added by B there. So (I am thinking aloud here\nwithout thinking things through) a possible approach to this issue might\ninvolve considering a removal of a path and modifying the contents of a\npath to make it empty similarly.\n\n> However, there is one large class of problems that I think is fixable,\n> I'm just not sure whether it is worth fixing.  git's rename detection\n> optimization of only considering files that exist on one side of the\n> diff but not the other causes issues with merges (undetected conflicts,\n> spurious conflicts, or merged cleanly but wrongly due to deleting files\n> that should be present).  To fix these cases, we would need some way of\n> including rewritten files as potential rename candidates,...\n\nThis may be part of the larger \"when punting, loudly ask the user for\nhelp, or we may want to go deeper\". If we can come up with a way to avoid\n\"undetected conflicts\" (\"merged cleanly but wrongly\" is saying the same\nthing as \"undetected conflicts\"), maybe we can attempt a cheap way first,\nand then only when we know there is something complicated going on, we can\nredo the diff with -B/-M options on. Spurious conflicts can be handled the\nsame way, as we should be able to come up with a clean merge once we dig\ndeeper (that's the definition of \"spurious\", right?).\n"},{"id":"169887","messageId":"7vzklnshcm.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"BANLkTimd0O70e7KhT-G5quxQhF_Nwc30Hg@mail.gmail.com","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-06-12T06:18:17Z","receivedAt":"2011-06-12T06:18:17Z","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> It does sound potentially expensive, though, and might mean a lot\n> more work in merge-recursive to handle that extra information.  Is that a\n> path we want to take at some point?\n\nProbably you can start with backend specific option (e.g. -Xbreak=yes) to\nexperiment. We made recursive the default not because it deals with\nrenames (in a broken way) but primarily because it handles criss-cross\nbetter; at some point we might also want to add another backend specific\noption (e.g. -Xrename=off) to allow the users to keep the \"recursive\"\naspect of the strategy while declining a more expensive (and brittle)\nrename handling to take effect.\n\nMy gut feeling is that -Xbreak=yes, once the code does work well enough,\nwould have to become the default. It would make the default mode of merge\npossibly quite expensive but it is Ok as long as we give projects with\nsimple/clean history an easy way to use either \"recursive -Xrename=off\" or\neven \"resolve\" to avoid cost that is unnecessary to handle their needs.\n"},{"id":"169888","messageId":"7vsjrfsgux.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"7vzklnshcm.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-06-12T06:28:54Z","receivedAt":"2011-06-12T06:28:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Elijah Newren <newren@gmail.com> writes:\n>\n>> It does sound potentially expensive, though, and might mean a lot\n>> more work in merge-recursive to handle that extra information.  Is that a\n>> path we want to take at some point?\n>\n> Probably you can start with backend specific option (e.g. -Xbreak=yes) to\n> experiment. We made recursive the default not because it deals with\n> renames (in a broken way) but primarily because it handles criss-cross\n> better; at some point we might also want to add another backend specific\n> option (e.g. -Xrename=off) to allow the users to keep the \"recursive\"\n> aspect of the strategy while declining a more expensive (and brittle)\n> rename handling to take effect.\n>\n> My gut feeling is that -Xbreak=yes, once the code does work well enough,\n> would have to become the default. It would make the default mode of merge\n> possibly quite expensive but it is Ok as long as we give projects with\n> simple/clean history an easy way to use either \"recursive -Xrename=off\" or\n> even \"resolve\" to avoid cost that is unnecessary to handle their needs.\n\nI forgot to mention that there is jk/maint-merge-rename-create effort by\nPeff already queued in 'pu' to use \"break\", which of course has\nunfortunate conflicts with your series.\n"},{"id":"171045","messageId":"4E1AA085.9010908@viscovery.net","threadId":"27574","inReplyTo":"1307518278-23814-23-git-send-email-newren@gmail.com","subject":"Re: [PATCH 22/48] merge-recursive: Fix sorting order and directory change assumptions","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2011-07-11T07:04:37Z","receivedAt":"2011-07-11T07:04:37Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 6/8/2011 9:30, schrieb Elijah Newren:\n>  test_expect_success 'modify/delete + directory/file conflict; other way' '\n> +\t# Yes, we really need the double reset since \"letters\" appears as\n> +\t# both a file and a directory.\n> +\tgit reset --hard &&\n>  \tgit reset --hard &&\n>  \tgit clean -f &&\n>  \tgit checkout modify^0 &&\n> +\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> +\ttest 5 -eq $(git ls-files -s | wc -l) &&\n> +\ttest 4 -eq $(git ls-files -u | wc -l) &&\n> +\ttest 1 -eq $(git ls-files -o | wc -l) &&\n>  \n>  \ttest -f letters/file &&\n> +\ttest -f letters.txt &&\n>  \ttest -f letters~HEAD\n>  '\n\nA heads-up: This test case fails here on Windows. The messages produced are:\n\nMerging:\n63e2d2b modified\nvirtual delete\nfound 1 common ancestor(s):\ne6b31cd initial\nCONFLICT (modify/delete): letters deleted in delete and modified in HEAD.\nVersion HEAD of letters left in tree at letters~HEAD.\nAdding letters/file\nerror: failed to create path 'letters/file': perhaps a D/F conflict?\nAuto-merging letters.txt\nCONFLICT (add/add): Merge conflict in letters.txt\nAutomatic merge failed; fix conflicts and then commit the result.\n\nwhereas they are on Linux:\n\nMerging:\n274ce87 modified\nvirtual delete\nfound 1 common ancestor(s):\n343abf7 initial\nRemoving letters to make room for subdirectory; may re-add later.\nAdding letters/file\nCONFLICT (modify/delete): letters deleted in delete and modified in HEAD.\nVersion HEAD of letters left in tree at letters~HEAD.\nAuto-merging letters.txt\nCONFLICT (add/add): Merge conflict in letters.txt\nAutomatic merge failed; fix conflicts and then commit the result.\n\nAs you can see, \"Removing letters...\" is missing on Windows, and the file\n'letters' is indeed left in the working tree. Any quick ideas where to\nbegin debugging this?\n\n-- Hannes\n"},{"id":"171046","messageId":"4E1AA2E7.5020301@viscovery.net","threadId":"27574","inReplyTo":"1307518278-23814-27-git-send-email-newren@gmail.com","subject":"Re: [PATCH 26/48] merge-recursive: Allow make_room_for_path() to remove D/F entries","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2011-07-11T07:14:47Z","receivedAt":"2011-07-11T07:14:47Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 6/8/2011 9:30, schrieb Elijah Newren:\n> diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\n> index 423fb62..dea2a65 100755\n> --- a/t/t6036-recursive-corner-cases.sh\n> +++ b/t/t6036-recursive-corner-cases.sh\n> @@ -477,7 +477,7 @@ test_expect_success 'setup differently handled merges of directory/file conflict\n>  \tgit tag E2\n>  '\n>  \n> -test_expect_failure 'git detects conflict and handles merge of D & E1 correctly' '\n> +test_expect_success 'git detects conflict and handles merge of D & E1 correctly' '\n>  \tgit reset --hard &&\n>  \tgit reset --hard &&\n>  \tgit clean -fdqx &&\n\nThis fails on Windows, very likely for the same reason as t6020 that I\nposted earlier: the message \"Removing a... to make room...\" is missing as\nwell.\n\n-- Hannes\n"},{"id":"171145","messageId":"4E1BF77C.2020207@viscovery.net","threadId":"27574","inReplyTo":"4E1AA085.9010908@viscovery.net","subject":"Re: [PATCH 22/48] merge-recursive: Fix sorting order and directory change assumptions","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2011-07-12T07:27:56Z","receivedAt":"2011-07-12T07:27:56Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 7/11/2011 9:04, schrieb Johannes Sixt:\n> A heads-up: This test case fails here on Windows. The messages produced are:\n\n> As you can see, \"Removing letters...\" is missing on Windows, and the file\n> 'letters' is indeed left in the working tree. Any quick ideas where to\n> begin debugging this?\n\nAnd the reason for this is that the qsort call in record_df_conflict_files\nassumes that qsort is a stable sort; but this is not guaranteed. In\nparticular, the entry \"letters\" can be moved after \"letters/file\", which\nis not expected by the loop that follows the qsort.\n\n-- Hannes\n"},{"id":"171214","messageId":"4E1D46A5.6050904@viscovery.net","threadId":"27574","inReplyTo":"1307518278-23814-27-git-send-email-newren@gmail.com","subject":"Re: [PATCH 26/48] merge-recursive: Allow make_room_for_path() to remove D/F entries","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2011-07-13T07:17:57Z","receivedAt":"2011-07-13T07:17:57Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 6/8/2011 9:30, schrieb Elijah Newren:\n> +static int make_room_for_path(const struct merge_options *o, const char *path)\n>  {\n> -\tint status;\n> +\tint status, i;\n>  \tconst char *msg = \"failed to create path '%s'%s\";\n>  \n> +\t/* Unlink any D/F conflict files that are in the way */\n> +\tfor (i = 0; i < o->df_conflict_file_set.nr; i++) {\n> +\t\tconst char *df_path = o->df_conflict_file_set.items[i].string;\n> +\t\tsize_t pathlen = strlen(path);\n> +\t\tsize_t df_pathlen = strlen(df_path);\n> +\t\tif (df_pathlen < pathlen && strncmp(path, df_path, df_pathlen) == 0) {\n> +\t\t\tunlink(df_path);\n> +\t\t\tbreak;\n> +\t\t}\n> +\t}\n\nEach time this loop is entered it tries to remove the same path again,\neven if it does not exist anymore or was morphed into a directory in the\nmeantime. I suggest to remove a path from o->df_conflict_file_set after it\nwas unlinked. Or even better: have a separate \"make room\" phase somewhere\nin the merge process.\n\n-- Hannes\n"},{"id":"171216","messageId":"4E1D482E.4090403@viscovery.net","threadId":"27574","inReplyTo":"4E1BF77C.2020207@viscovery.net","subject":"Re: [PATCH 22/48] merge-recursive: Fix sorting order and directory change assumptions","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2011-07-13T07:24:30Z","receivedAt":"2011-07-13T07:24:30Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 7/12/2011 9:27, schrieb Johannes Sixt:\n> Am 7/11/2011 9:04, schrieb Johannes Sixt:\n>> A heads-up: This test case fails here on Windows. The messages produced are:\n> \n>> As you can see, \"Removing letters...\" is missing on Windows, and the file\n>> 'letters' is indeed left in the working tree. Any quick ideas where to\n>> begin debugging this?\n> \n> And the reason for this is that the qsort call in record_df_conflict_files\n> assumes that qsort is a stable sort; but this is not guaranteed. In\n> particular, the entry \"letters\" can be moved after \"letters/file\", which\n> is not expected by the loop that follows the qsort.\n\nPerhaps something like below. It passes the tests that previously failed.\n\n--- >8 ---\nFrom: Johannes Sixt <j6t@kdbg.org>\nSubject: [PATCH] fixup! Do not assume that qsort is stable\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\n merge-recursive.c |   13 +++++++++++--\n 1 files changed, 11 insertions(+), 2 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 88167c3..81fac5f 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -361,6 +361,8 @@ static int string_list_df_name_compare(const void *a, const void *b)\n {\n \tconst struct string_list_item *one = a;\n \tconst struct string_list_item *two = b;\n+\tint onelen = strlen(one->string);\n+\tint twolen = strlen(two->string);\n \t/*\n \t * Here we only care that entries for D/F conflicts are\n \t * adjacent, in particular with the file of the D/F conflict\n@@ -373,8 +375,15 @@ static int string_list_df_name_compare(const void *a, const void *b)\n \t * since in other cases any changes in their order due to\n \t * sorting cause no problems for us.\n \t */\n-\treturn df_name_compare(one->string, strlen(one->string), S_IFDIR,\n-\t\t\t       two->string, strlen(two->string), S_IFDIR);\n+\tint cmp = df_name_compare(one->string, onelen, S_IFDIR,\n+\t\t\t\t  two->string, twolen, S_IFDIR);\n+\t/*\n+\t * Now that 'foo' and 'foo/bar' compare equal, we have to make sure\n+\t * that 'foo' comes before 'foo/bar'.\n+\t */\n+\tif (cmp)\n+\t\treturn cmp;\n+\treturn onelen - twolen;\n }\n \n static void record_df_conflict_files(struct merge_options *o,\n-- \n1.7.6.1317.gd8c5a.dirty\n"},{"id":"171285","messageId":"7v62n57wde.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"4E1D482E.4090403@viscovery.net","subject":"Re: [PATCH 22/48] merge-recursive: Fix sorting order and directory change assumptions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-13T20:34:37Z","receivedAt":"2011-07-13T20:34:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Both good comments; thanks.\n"},{"id":"171616","messageId":"7vvcuzdusv.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-6-git-send-email-newren@gmail.com","subject":"Re: [PATCH 05/48] t6039: Add tests for content issues with modify/rename/directory conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:37:52Z","receivedAt":"2011-07-18T23:37:52Z","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> Signed-off-by: Elijah Newren <newren@gmail.com>\n\nCould you add a description for this? Your description on 06/48 is\nbeautifly done. Here is my attempt.\n\n    One branch renames a file and creates a directory where the file used to\n    be, and the other branch updates the file in place. Merging them should\n    resolve it cleanly as long as the content level change on the branches do\n    not overlap and rename is detected, or should leave conflict without\n    losing information.\n\n    One branch renames a file and makes a file where the directory the renamed\n    file used to be in, and the other branch updates the file in\n    place. Merging them should resolve it cleanly as long as the content level\n    change on the branches do not overlap and rename is detected, or should\n    leave conflict without losing information.\n\nI think I said this in my earlier review, and this is not limited to 05/48\nbut also applies to 03/48 and 04/48 as well, but there won't be perfect\nrename detection, and the rename detection logic could change (improve)\nover time. Ideally, I think the test should declare either case as a\nsuccess: (1) detection succeeds and avoids unnecessary conflict, or (2)\nrename is missed and conflict is reported, but otherwise there is no data\nloss. If it expects only one but not the other, any time the rename logic\nis improved, the behaviour could change between (1) and (2) and it will\ncause a false positive \"breakage\" of these tests.\n\nThanks.\n"},{"id":"171617","messageId":"7voc0rdusj.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-7-git-send-email-newren@gmail.com","subject":"Re: [PATCH 06/48] t6039: Add failing testcases for rename/rename/add-{source,dest} conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:38:04Z","receivedAt":"2011-07-18T23:38:04Z","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> Add testcases that cover three failures with current git merge, all\n> involving renaming one file on both sides of history:\n>\n> Case 1:\n> ...\n> Case 3:\n> ...\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n\nVery nicely described.\n\n> +test_expect_failure 'detect conflict with rename/rename(1to2)/add-source merge' '\n> +\tgit checkout B^0 &&\n> +\n> +\ttest_must_fail git merge -s recursive C^0 &&\n> +\n> +\ttest -f a &&\n> +\ttest -f b &&\n> +\ttest -f c\n\nPlease check the contents of the index here as well.  I think:\n\n - \"a\" should be at stage#0, with \"something completely different\" from C;\n - \"b\" should have stage #1 and #2, both with the original \"a\" from A; and\n - \"c\" should have stage #1 and #3, both with the original \"a\" from A\n\nright?  The last test in this patch is nicely done in this regard.\n\n> +test_expect_success 'setup rename/rename(1to2)/add-source resolvable conflict' '\n> +\tgit rm -rf . &&\n> +\tgit clean -fdqx &&\n> +\trm -rf .git &&\n> +\tgit init &&\n> +\n> +\t>a &&\n> +\tgit add a &&\n> +\ttest_tick &&\n> +\tgit commit -m base &&\n> +\tgit tag A &&\n> +\n> +\tgit checkout -b B A &&\n> +\tgit mv a b &&\n> +\ttest_tick &&\n> +\tgit commit -m one &&\n> +\n> +\tgit checkout -b C A &&\n> +\tgit mv a b &&\n> +\techo important-info >a &&\n> +\ttest_tick &&\n> +\tgit commit -m two\n> +'\n> +\n> +test_expect_failure 'rename/rename/add-source still tracks new a file' '\n> +\tgit checkout C^0 &&\n> +\tgit merge -s recursive B^0 &&\n> +\n> +\ttest 2 -eq $(git ls-files -s | wc -l) &&\n\nInstead of just checking the numbers, could you make the expected result a\nbit more explicit? What do we expect here? Perhaps\n\n - \"a\" in the index is at stage #0, with \"important-info\";\n - \"a\" in the working tree is clean;\n - \"b\" in the index is at stage #0, with empty content; and\n - \"b\" in the working tree is clean.\n\nAgain, the last test in this patch is nicely done in this regard.\n\n> +test_expect_success 'setup rename/rename(1to2)/add-dest conflict' '\n> +\tgit rm -rf . &&\n> +\tgit clean -fdqx &&\n> +\trm -rf .git &&\n> +\tgit init &&\n> +\n> +\techo stuff >a &&\n> +\tgit add a &&\n> +\ttest_tick &&\n> +\tgit commit -m base &&\n> +\tgit tag A &&\n> +\n> +\tgit checkout -b B A &&\n> +\tgit mv a b &&\n> +\techo precious-data >c &&\n> +\tgit add c &&\n> +\ttest_tick &&\n> +\tgit commit -m one &&\n> +\n> +\tgit checkout -b C A &&\n> +\tgit mv a c &&\n> +\techo important-info >b &&\n> +\tgit add b &&\n> +\ttest_tick &&\n> +\tgit commit -m two\n> +'\n> +\n> +test_expect_failure 'rename/rename/add-dest merge still knows about conflicting file versions' '\n> +\tgit checkout C^0 &&\n> +\ttest_must_fail git merge -s recursive B^0 &&\n> +\n> +\ttest 5 -eq $(git ls-files -s | wc -l) &&\n> +\ttest 2 -eq $(git ls-files -u b | wc -l) &&\n> +\ttest 2 -eq $(git ls-files -u c | wc -l) &&\n> +\ttest $(git rev-parse :2:b) = $(git rev-parse C:b) &&\n> +\ttest $(git rev-parse :3:b) = $(git rev-parse B:b) &&\n> +\ttest $(git rev-parse :2:c) = $(git rev-parse C:c) &&\n> +\ttest $(git rev-parse :3:c) = $(git rev-parse B:c)\n\n5 - 2 - 2 = 1 so we know that it is merged cleanly in the index, but we\nwould want to also test \"a\" in the working tree is merged and clean.\n\nThanks.\n"},{"id":"171618","messageId":"7vhb6jdusb.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-9-git-send-email-newren@gmail.com","subject":"Re: [PATCH 08/48] t6036: Add differently resolved modify/delete conflict in criss-cross test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:38:12Z","receivedAt":"2011-07-18T23:38:12Z","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> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  t/t6036-recursive-corner-cases.sh |   83 +++++++++++++++++++++++++++++++++++++\n>  1 files changed, 83 insertions(+), 0 deletions(-)\n>\n> diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\n> index 319b6fa..52d2ecf 100755\n> --- a/t/t6036-recursive-corner-cases.sh\n> +++ b/t/t6036-recursive-corner-cases.sh\n> @@ -231,4 +231,87 @@ test_expect_success 'git detects differently handled merges conflict' '\n>  \ttest $(git rev-parse :1:new_a) = $(git hash-object merged)\n>  '\n>  \n> +#\n> +# criss-cross + modify/delete:\n> +#\n> +#      B   D\n> +#      o---o\n> +#     / \\ / \\\n> +#  A o   X   ? F\n> +#     \\ / \\ /\n> +#      o---o\n> +#      C   E\n> +#\n> +#   Commit A: file with contents 'A\\n'\n> +#   Commit B: file with contents 'B\\n'\n> +#   Commit C: file not present\n> +#   Commit D: file with contents 'B\\n'\n> +#   Commit E: file not present\n> +#\n> +# Now, when we merge commits D & E, does git detect the conflict?\n\nStarting from A, upper branch wants to have B instead of A while the lower\nbranch wants the path gone, so it should result in a modify/delete conflict.\n\n  s/# Now,.*$/Merging commits D & E should result in modify/delete conflict./\n"},{"id":"171619","messageId":"7vaacbdus4.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-10-git-send-email-newren@gmail.com","subject":"Re: [PATCH 09/48] t6036: criss-cross with weird content can fool git into clean merge","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:38:19Z","receivedAt":"2011-07-18T23:38:19Z","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> +#   Commit E: file with contents:\n> +#      <<<<<<< Temporary merge branch 1\n> +#      C\n> +#      =======\n> +#      B\n> +#      >>>>>>> Temporary merge branch 2\n> +#\n> +# Now, when we merge commits D & E, does git detect the conflict?\n\nThis is way too artificial that I personally feel not worth worrying\nabout.\n"},{"id":"171620","messageId":"7v39i3durr.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-12-git-send-email-newren@gmail.com","subject":"Re: [PATCH 11/48] t6036: criss-cross w/ rename/rename(1to2)/modify+rename/rename(2to1)/modify","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:38:32Z","receivedAt":"2011-07-18T23:38:32Z","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> +# criss-cross with rename/rename(1to2)/modify followed by\n> +# rename/rename(2to1)/modify:\n> +#\n> +#      B   D\n> +#      o---o\n> +#     / \\ / \\\n> +#  A o   X   ? F\n> +#     \\ / \\ /\n> +#      o---o\n> +#      C   E\n> +#\n> +#   Commit A: new file: a\n> +#   Commit B: rename a->b, modifying by adding a line\n> +#   Commit C: rename a->c\n> +#   Commit D: merge B&C, resolving conflict by keeping contents in newname\n> +#   Commit E: merge B&C, resolving conflict similar to D but adding another line\n> +#\n> +# There is a conflict merging B & C, but one of filename not of file\n> +# content.  Whoever created D and E chose specific resolutions for that\n> +# conflict resolution.  Now, since: (1) there is no content conflict\n> +# merging B & C, (2) D does not modify that merged content further, and (3)\n> +# both D & E resolve the name conflict in the same way, the modification to\n> +# newname in E should not cause any conflicts when it is merged with D.\n> +# (Note that this can be accomplished by having the virtual merge base have\n> +# the merged contents of b and c stored in a file named a, which seems like\n> +# the most logical choice anyway.)\n\nInteresting. I do not necessarily agree with the choice \"a\", but it feels\nsound to say \"B and C do not agree what the final pathname should be, but\nwe know this content was derived from the common A:a so we use one path\nwhose name is arbitrary in the virtual merge base X between D and E\" and\nthen further let the rename detection to notice that that arbitrary path\ngets renamed between X-D to \"newname\" and X-E also to \"newname\" to resolve\nit as both sides renaming it to the same new name. It is akin to what we\ndo at the content level, i.e. \"B and C do not agree what the final\ncontents should be, so we leave the conflict marker but that may cancel\nout at the final merge stage\".\n\n> +test_expect_failure 'handle rename/rename(1to2)/modify followed by what looks like rename/rename(2to1)/modify' '\n> +\tgit checkout D^0 &&\n> +\n> +\tgit merge -s recursive E^0 &&\n> +\n> +\ttest 1 -eq $(git ls-files -s | wc -l) &&\n> +\ttest 0 -eq $(git ls-files -u | wc -l) &&\n> +\ttest 0 -eq $(git ls-files -o | wc -l) &&\n> +\n> +\ttest 8 -eq $(wc -l < newname)\n\nBe a bit more explicit with what you are actually testing. Are you\nexpecting that the index has a single stage #0 entry for \"newname\" whose\ncontents are the same as E?\n\n> +'\n>  \n>  test_done\n"},{"id":"171621","messageId":"7vvcuzcg73.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-13-git-send-email-newren@gmail.com","subject":"Re: [PATCH 12/48] t6036: criss-cross + rename/rename(1to2)/add-source + modify/modify","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:38:40Z","receivedAt":"2011-07-18T23:38:40Z","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> Now THAT's a corner case.\n> ...\n> +# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify:\n> +#\n> +#      B   D\n> +#      o---o\n> +#     / \\ / \\\n> +#  A o   X   ? F\n> +#     \\ / \\ /\n> +#      o---o\n> +#      C   E\n> +#\n> +#   Commit A: new file: a\n> +#   Commit B: rename a->b\n> +#   Commit C: rename a->c, add different a\n> +#   Commit D: merge B&C, keeping b&c and (new) a modified at beginning\n> +#   Commit E: merge B&C, keeping b&c and (new) a modified at end\n\nTHAT may be a corner case, but is it a useful corner case?  What on earth\nthe person who did D (or E) was thinking to keep both b and c that are\nderived from A:a to begin with?\n\n> +test_expect_failure 'correctly resolves criss-cross with rename/rename/add and modify/modify conflict' '\n\nI won't repeat the same two comments here from reviews for the previous\npatches.\n"},{"id":"171622","messageId":"7voc0rcg6j.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-20-git-send-email-newren@gmail.com","subject":"Re: [PATCH 19/48] merge-recursive: Remember to free generated unique path names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:39:00Z","receivedAt":"2011-07-18T23:39:00Z","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> @@ -1433,12 +1434,17 @@ 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\tchar *new_path;\n> +\t\tint free_me = 0;\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\t\tfree_me = 1;\n> +\t\t}\n>  \t\tclean_merge = 0;\n> -\t\thandle_delete_modify(o, path, new_path,\n> +\t\thandle_delete_modify(o, path, free_me ? new_path : path,\n>  \t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n\nHere free_me is not about free_me but is used as a substitute for \"did I\nstuff the path to be used in new_path, or should I use the original path?\",\nand I find the variable misnamed.  How about doing it this way instead?\n\n\tchar *renamed = NULL;\n        if (lstat(path, &st) ...)\n        \trenamed = unique_path(...);\n\tclean_merge = 0;\n\thandle_delete_modify(o, path, renamed ? renamed : path, ...);\n\n> +\t\tif (free_me)\n> +\t\t\tfree(new_path);\n\nand then you can free renamed unconditoinally here.\n\nOther than that looks good to me.\n\nThanks.\n"},{"id":"171623","messageId":"7vhb6jcg68.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-23-git-send-email-newren@gmail.com","subject":"Re: [PATCH 22/48] merge-recursive: Fix sorting order and directory change assumptions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:39:11Z","receivedAt":"2011-07-18T23:39:11Z","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> We cannot assume that directory/file conflicts will appear in sorted\n> order; for example, 'letters.txt' comes between 'letters' and\n> 'letters/file'.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c   |   31 ++++++++++++++++++++++++++-----\n>  t/t6020-merge-df.sh |   26 ++++++++++++++++++--------\n>  2 files changed, 44 insertions(+), 13 deletions(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 4771fb4..ed1fdb2 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -333,6 +333,28 @@ static struct string_list *get_unmerged(void)\n>  \treturn unmerged;\n>  }\n>  \n> +static int string_list_df_name_compare(const void *a, const void *b)\n> +{\n> +\tconst struct string_list_item *one = a;\n> +\tconst struct string_list_item *two = b;\n> +\t/*\n> +\t * Here we only care that entries for D/F conflicts are\n> +\t * adjacent, in particular with the file of the D/F conflict\n> +\t * appearing before files below the corresponding directory.\n> +\t * The order of the rest of the list is irrelevant for us.\n> +\t *\n> +\t * To achieve this, we sort with df_name_compare and provide\n> +\t * the mode S_IFDIR so that D/F conflicts will sort correctly.\n> +\t * We use the mode S_IFDIR for everything else for simplicity,\n> +\t * since in other cases any changes in their order due to\n> +\t * sorting cause no problems for us.\n> +\t */\n\nI recall there was an issue of this sorting reported earlier...\n\n> diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh\n> index eec8f4e..27c3d73 100755\n> --- a/t/t6020-merge-df.sh\n> +++ b/t/t6020-merge-df.sh\n> @@ -59,15 +59,19 @@ test_expect_success 'setup modify/delete + directory/file conflict' '\n>  \tgit add letters &&\n>  \tgit commit -m initial &&\n>  \n> +\t# Throw in letters.txt for sorting order fun\n> +\t# (\"letters.txt\" sorts between \"letters\" and \"letters/file\")\n>  \techo i >>letters &&\n> -\tgit add letters &&\n> +\techo \"version 2\" >letters.txt &&\n> +\tgit add letters letters.txt &&\n\nNice.\n"},{"id":"171624","messageId":"7vaacbcg5s.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-27-git-send-email-newren@gmail.com","subject":"Re: [PATCH 26/48] merge-recursive: Allow make_room_for_path() to remove D/F entries","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:39:27Z","receivedAt":"2011-07-18T23:39:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"What 23/-26/48 tries to do feels quite sane.\nThanks.\n"},{"id":"171625","messageId":"7v39i3cg5j.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-28-git-send-email-newren@gmail.com","subject":"Re: [PATCH 27/48] merge-recursive: Consolidate different update_stages functions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:39:36Z","receivedAt":"2011-07-18T23:39:36Z","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> We are only calling update_stages_options() one way really, so we can\n> consolidate the slightly different variants into one and remove some\n> parameters whose values are always the same.\n\nNice; I wonder if this can come much earlier in the series?\n"},{"id":"171626","messageId":"7vvcuzb1kp.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-29-git-send-email-newren@gmail.com","subject":"Re: [PATCH 28/48] merge-recursive: Split update_stages_and_entry; only update stages at end","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:39:50Z","receivedAt":"2011-07-18T23:39:50Z","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> Instead of having the process_renames logic update the stages in the index\n> for the rename destination, have the index updated after process_entry or\n> process_df_entry.  This will also allow us to have process_entry determine\n> whether a file was tracked and existed in the working copy before the\n> merge started.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c |   35 +++++++++++++++++------------------\n>  1 files changed, 17 insertions(+), 18 deletions(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index b4baa35..7878b30 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -90,6 +90,7 @@ struct stage_data {\n>  \t} stages[4];\n>  \tstruct rename_df_conflict_info *rename_df_conflict_info;\n>  \tunsigned processed:1;\n> +\tunsigned involved_in_rename:1;\n>  };\n>  \n>  static inline void setup_rename_df_conflict_info(enum rename_type rename_type,\n> @@ -516,15 +517,11 @@ static int update_stages(const char *path, const struct diff_filespec *o,\n>  \treturn 0;\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> +static void update_entry(struct stage_data *entry,\n> +\t\t\t struct diff_filespec *o,\n> +\t\t\t struct diff_filespec *a,\n> +\t\t\t struct diff_filespec *b)\n>  {\n> -\tint options;\n> -\n>  \tentry->processed = 0;\n>  \tentry->stages[1].mode = o->mode;\n>  \tentry->stages[2].mode = a->mode;\n> @@ -532,7 +529,6 @@ static int update_stages_and_entry(const char *path,\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> -\treturn update_stages(path, o, a, b);\n>  }\n>  \n>  static int remove_file(struct merge_options *o, int clean,\n> @@ -1084,12 +1080,11 @@ static int process_renames(struct merge_options *o,\n>  \t\t\t\t\t\t\t      ren2->dst_entry);\n>  \t\t\t} else {\n>  \t\t\t\tremove_file(o, 1, ren1_src, 1);\n\nHopefully this unconditional removal from the working tree will be fixed\nby the end of the series, at least for the virtual ancestor merges.\n\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\tupdate_entry(ren1->dst_entry,\n> +\t\t\t\t\t     ren1->pair->one,\n> +\t\t\t\t\t     ren1->pair->two,\n> +\t\t\t\t\t     ren2->pair->two);\n> +\t\t\t\tren1->dst_entry->involved_in_rename = 1;\n\nSo the idea is to just record what the stage data should be, and delay\ncalling update_stages() until we run the content level merge?\n\n> @@ -1291,6 +1287,7 @@ static void handle_delete_modify(struct merge_options *o,\n>  }\n>  \n>  static int merge_content(struct merge_options *o,\n> +\t\t\t unsigned involved_in_rename,\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> @@ -1331,6 +1328,8 @@ static int merge_content(struct merge_options *o,\n>  \t\t\treason = \"submodule\";\n>  \t\toutput(o, 1, \"CONFLICT (%s): Merge conflict in %s\",\n>  \t\t\t\treason, path);\n> +\t\tif (involved_in_rename)\n> +\t\t\tupdate_stages(path, &one, &a, &b);\n>  \t}\n>  \n>  \tif (df_conflict_remains) {\n> @@ -1415,7 +1414,7 @@ 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\tclean_merge = merge_content(o, path,\n> +\t\tclean_merge = merge_content(o, entry->involved_in_rename, 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    NULL);\n>  \t} else if (!o_sha && !a_sha && !b_sha) {\n> @@ -1459,7 +1458,7 @@ static int process_df_entry(struct merge_options *o,\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\tclean_merge = merge_content(o, entry->involved_in_rename, 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\nFeels sane from a cursory look, but are there cases where we do update_entry()\nabove and end up not calling merge_content() at all?\n"},{"id":"171627","messageId":"7voc0rb1ki.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-30-git-send-email-newren@gmail.com","subject":"Re: [PATCH 29/48] merge-recursive: When we detect we can skip an update, actually skip it","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:39:57Z","receivedAt":"2011-07-18T23:39:57Z","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 reason this change works is due to the changes of the\n> last few patches to (a) record_df_conflict_files instead of just unlinking\n> them early, (b) allowing make_room_for_path() to remove D/F entries, and\n> (c) the splitting of update_stages_and_entry() to have its functionality\n> called at different points.\n\nNice.\n"},{"id":"171631","messageId":"7vhb6jb1k2.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-22-git-send-email-newren@gmail.com","subject":"Re: [PATCH 21/48] merge-recursive: Fix recursive case with D/F conflict via add/add conflict","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:40:13Z","receivedAt":"2011-07-18T23:40:13Z","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> When a D/F conflict is introduced via an add/add conflict, when\n> o->call_depth > 0 we need to ensure that the higher stage entry from the\n> base stage is removed.\n\n\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c                 |    2 ++\n>  t/t6036-recursive-corner-cases.sh |    4 ++--\n>  2 files changed, 4 insertions(+), 2 deletions(-)\n>\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index 7da6aa0..4771fb4 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -1480,6 +1480,8 @@ static int process_df_entry(struct merge_options *o,\n>  \t\t\t       \"Adding %s as %s\",\n>  \t\t\t       conf, path, other_branch, path, new_path);\n>  \t\t\tupdate_file(o, 0, sha, mode, new_path);\n> +\t\t\tif (o->call_depth)\n> +\t\t\t\tremove_file_from_cache(path);\n\nPuzzling... This codepath is inside\n\n\tif (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {\n        \t...\n\nwhich _is_ all about the working tree. Why should the code even looking at\nthe working tree if it can be called for recursive virtual ancestor merge?\nI'll keep reading the series, hoping that this is something that will be\nfixed in a later patch (ah, 23/48 does that).\n\nAt the outermost merge, you would keep the made-up name \"new_path\" as well\nas the original name \"path\"?\n\nWhen you added \"d/f\" (file in a directory) and I added \"d\" (file), merging\nyour work to my tree with this code would result in\n\n\td at stage #2 (no 0/1/3);\n\td~madeupsuffix in the working tree;\n        d/f at stage #3 (no 0/1/2); and\n        d/f in the working tree.\n\nright? I am not sure if you also have d-madeupsuffix in the index.\n\nAs I said in my review of an earlier patch for test suite, having \"d\" in\nthe index would be helpful to the user (\"git diff\" cannot be used to see\nwhat's different) so we _might_ be better off leaving this instead:\n\n\td-madeupsuffix at stage #2 (no 0/1/3);\n\td~madeupsuffix in the working tree;\n        d/f at stage #3 (no 0/1/2); and\n        d/f in the working tree.\n\nand removing \"d\" from the index even for the outermost merge.\n\nThanks.\n"},{"id":"171628","messageId":"7vaacbb1jn.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-8-git-send-email-newren@gmail.com","subject":"Re: [PATCH 07/48] t6039: Ensure rename/rename conflicts leave index and workdir in sane state","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:40:28Z","receivedAt":"2011-07-18T23:40:28Z","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> +test_expect_success 'setup simple rename/rename (1to2) conflict' '\n> +\tgit rm -rf . &&\n> +\tgit clean -fdqx &&\n> +\trm -rf .git &&\n> +\tgit init &&\n> +\n> +\techo stuff >a &&\n> +\tgit add a &&\n> +\ttest_tick &&\n> +\tgit commit -m A &&\n> +\tgit tag A &&\n> +\n> +\tgit checkout -b B A &&\n> +\tgit mv a b &&\n> +\ttest_tick &&\n> +\tgit commit -m B &&\n> +\n> +\tgit checkout -b C A &&\n> +\tgit mv a c &&\n> +\ttest_tick &&\n> +\tgit commit -m C\n> +'\n\nThis looks like a simpler variant of the last test in 06/48; shouldn't it\ncome before the more complex one?\n\n> +\n> +test_expect_success 'merge has correct working tree contents' '\n> +\tgit checkout C^0 &&\n> +\n> +\ttest_must_fail git merge -s recursive B^0 &&\n> +\n> +\ttest 3 -eq $(git ls-files -s | wc -l) &&\n> +\ttest 3 -eq $(git ls-files -u | wc -l) &&\n> +\ttest 0 -eq $(git ls-files -o | wc -l) &&\n> +\n> +\ttest -f b &&\n> +\ttest -f c\n> +'\n\nThis test is being a bit too lazy, compared to the better ones in 06/48,\nno? What do we want to see here? Perhaps\n\n - \"a\" at stage #1 with \"stuff\", not in the working tree;\n - \"b\" at stage #3 with \"stuff\", same in the working tree; and\n - \"c\" at stage #2 with \"stuff\", same in the working tree\n\n> +# Test for all kinds of things that can go wrong with rename/rename (2to1):\n> +#   Commit A: new files: a & b\n> +#   Commit B: rename a->c, modify b\n> +#   Commit C: rename b->c, modify a\n> +#\n> +# Merging of B & C should NOT be clean.  Questions:\n> +#   * Both a & b should be removed by the merge; are they?\n> +#   * The two c's should contain modifications to a & b; do they?\n> +#   * The index should contain two files, both for c; does it?\n> +#   * The working copy should have two files, both of form c~<unique>; does it?\n> +#   * Nothing else should be present.  Is anything?\n\nWhat is the most useful thing to leave in the index and in the working\ntree for the person who needs to resolve such a merge using the working\ntree, starting from B and merging C? The above \"Questions\" lists what the\ncurrent code might try to do but I am not sure if it is really useful. For\nexample, in the index, you would have to stuff two stage #1 entries (\"a\"\nfrom A and \"b\" from A) for path \"c\", with stage #2 (\"c\" from B) and stage\n#3 (\"c\" from C) entries, and represent what B tried to do to \"a\" (in the\nabove you said \"rename a->c\" but it does not have to be a rename without\ncontent change) and what C tried to do to \"b\" in the half-conflicted\nresult that is in a single file \"c\". Because the result are totally\nunrelated files (one side wants a variant of original \"a\" there, the other\nside wants a variant of \"b\"), such a half-merge result is totally useless\nto help the person to come up with anything.\n\nAlso renaming \"c\" to \"c~<unique>\", if they do not have corresponding\nentries in the index to let you check with \"git diff\", would make the\nresult _harder_ to use, not easier. So if you are going to rename \"c\" to\n\"c-B\" and \"c-C\", at least it would make much more sense to have in the\nindex:\n\n - \"c-B\", with stage #1 (\"a\" from A), stage #2 (\"c\" from B) and stage #3\n   (\"a\" from C);\n - \"c-C\", with stage #1 (\"b\" from A), stage #2 (\"b\" from B) and stage #3\n   (\"c\" from C); and\n - No \"a\" nor \"b\" in the index nor in the working tree.\n\nno?\n\nThat way, you could run \"git diff\" to get what happened to the two\nvariants of \"a\" and \"b\" at the content level, and decide to clean things\nup with:\n\n    $ git diff ;# view content level merge\n    $ edit c-B c-C; git add c-B c-C\n    $ git mv c-B c-some-saner-name\n    $ git mv c-C c-another-saner-name\n    $ edit other files that refer to c like Makefile\n    $ git commit\n\nTo take it one step further to the extreme, it might give us a more\nreasonable and useful conflicted state if we deliberately dropped some\ninformation instead in a case like this, e.g.:\n\n - We may want to have \"a\" at stage #1 (from A) in the index;\n - No \"a\" remains in the working tree;\n - \"b\" at stage #1 (from A), stage #2 (from B) and stage #3 (\"c\" from C);\n - \"b\" in the working tree a conflicted-merge of the above three;\n - \"c\" at stage #1 (\"a\" from A), stage #2 (from B), and stage #3 (\"a\" from\n   C); and\n - \"c\" in the working tree a conflicted-merge of the above three.\n\nNote that unlike the current merge-recursive that tries to come up with a\ntemporary pathname to store both versions of C, this would ignore \"mv b c\"\non the A->C branch, and make the conflicted tentative merge asymmetric\n(merging B into C and merging C into B would give different conflicts),\nbut I suspect that the asymmetry may not hurt us.\n\nWhether the merger wants to keep \"c\" that was derived from \"a\" (in line\nwith the HEAD) or \"c\" that was derived from \"b\" (in line with MERGE_HEAD),\nif the result were to keep both files in some shape, the content level\nedit, renaming of at least one side, and adjusting other files that refer\nto it, are all required anyway, e.g.\n\n    $ git diff ;# view content level merge\n    $ edit b c; git add b c\n    $ edit other files that refer to c line Makefile (the content C's\n      change wants is now in \"b\").\n    $ git commit\n\nwould be a way to pick \"c\" as \"c-some-saner-name\" and \"b\" as\n\"c-another-saner-name\" in the previous workflow, but needs much less\ntyping. The complexity of the workflow would be the same if the final\nresolution is to take what one side did and dropping the other's work,\nI think.\n"},{"id":"171629","messageId":"7v39i3b1jg.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-11-git-send-email-newren@gmail.com","subject":"Re: [PATCH 10/48] t6036: tests for criss-cross merges with various directory/file conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:40:35Z","receivedAt":"2011-07-18T23:40:35Z","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> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  t/t6036-recursive-corner-cases.sh |  149 +++++++++++++++++++++++++++++++++++++\n>  1 files changed, 149 insertions(+), 0 deletions(-)\n>\n> diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\n> index dab52a4..4993f67 100755\n> --- a/t/t6036-recursive-corner-cases.sh\n> +++ b/t/t6036-recursive-corner-cases.sh\n> @@ -397,4 +397,153 @@ test_expect_failure 'git detects conflict w/ criss-cross+contrived resolution' '\n>  \ttest $(git rev-parse :3:file) = $(git rev-parse E:file)\n>  '\n>  \n> +#\n> +# criss-cross + d/f conflict via add/add:\n> +#   Commit A: Neither file 'a' nor directory 'a/' exist.\n> +#   Commit B: Introduce 'a'\n> +#   Commit C: Introduce 'a/file'\n> +# Two different later cases:\n> +#   Commit D1: Merge B & C, keeping 'a' and deleting 'a/'\n> +#   Commit E1: Merge B & C, deleting 'a' but keeping 'a/file'\n\nWhere does the content of a/file (or a) go?  Discarded?\n\n> +#   Commit D2: Merge B & C, keeping a modified 'a' and deleting 'a/'\n> +#   Commit E2: Merge B & C, deleting 'a' but keeping a modified 'a/file'\n\nWhat \"modification\" is there to be made?  Are these evil merges?\n\n> +#   Note: D == D1.\n\nIf you are not building nor testing D2, do not bring it up in the above\ndescription. Otherwise, use D1 and D2 throughout the test and description.\n\n> +# Finally, someone goes to merge D1&E1 or D1&E2 or D2&E1.  What happens?\n\nState what _should_ happen here.  We are not wasting brainwave for mental\nmasturbation but are trying to solve real problems for real projects here.\n\nAlso aren't we interested in what should happen when D2 and E2 are merged?\nIf not why not?\n\n> +#      B   D1 or D2\n> +#      o---o\t\t\t(file)\n> +#     / \\ / \\\n> +#  A o   X   ? F\n> +#     \\ / \\ /\n> +#      o---o\n> +#      C   E1 or E2\t\t(dir)\n> +#\n\n> +test_expect_failure 'git detects conflict and handles merge of D & E1 correctly' '\n> +\tgit reset --hard &&\n> +\tgit reset --hard &&\n> +\tgit clean -fdqx &&\n> +\tgit checkout D^0 &&\n\nWhy do you need to reset twice?  Superstition (you have a commented one\nintroduced later in the series --- perhaps this shows a bug in reset)?\n\n\"Start with a clean state at this commit (in this case D^0)\" needs a\nhelper shell function to make everything below easier to read.\n\n> +\t# FIXME: If merge-base could keep both a and a/file in its tree, then\n> +\t# we could this merge would actually be able to succeed.\n\nWhat exactly do you refer to in the above sentence with \"merge-base\"?\n\n> +\ttest_must_fail git merge -s recursive E1^0 &&\n> +\n> +\ttest 2 -eq $(git ls-files -s | wc -l) &&\n> +\ttest 1 -eq $(git ls-files -u | wc -l) &&\n> +\ttest 0 -eq $(git ls-files -o | wc -l) &&\n> +\n> +\ttest $(git rev-parse :2:a) = $(git rev-parse B:a)\n> +'\n\nWhat is expected here? \"a\" at stage #2 (\"a\" from B) is being tested, and\nwhat is the other entry?  \"a/file\" at stage #3 (\"a/file\" from E1) or\nsomething else? What should the working tree have?\n\n> +test_expect_failure 'git detects conflict and handles merge of E1 & D correctly' '\n\nInstead of saying \"correctly\", state what you think should happen, in\nother words, what your definition of \"correct\" is, because in these\nmade-up cases what is expected could be vastly different from people to\npeople.\n\nThis is the same merge in the opposite direction, right?\n"},{"id":"171630","messageId":"7vvcuz9myv.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-19-git-send-email-newren@gmail.com","subject":"Re: [PATCH 18/48] merge-recursive: Mark some diff_filespec struct arguments const","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-18T23:40:40Z","receivedAt":"2011-07-18T23:40:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"13/-18/48 all look very sensible.\n\nThanks.\n"},{"id":"171760","messageId":"4E276180.1050406@cisco.com","threadId":"27574","inReplyTo":"7vvcuzcg73.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 12/48] t6036: criss-cross + rename/rename(1to2)/add-source + modify/modify","fromName":"Phil Hord","fromEmail":"hordp@cisco.com","sentAt":"2011-07-20T23:15:12Z","receivedAt":"2011-07-20T23:15:12Z","isPatch":true,"sender":{"key":"phil.hord@gmail.com","avatar":"https://avatars.githubusercontent.com/u/123908?v=4"},"body":"On 07/18/2011 07:38 PM, Junio C Hamano wrote:\n> Elijah Newren<newren@gmail.com>  writes:\n>\n>> Now THAT's a corner case.\n>> ...\n>> +# criss-cross with rename/rename(1to2)/add-source + resolvable modify/modify:\n>> +#\n>> +#      B   D\n>> +#      o---o\n>> +#     / \\ / \\\n>> +#  A o   X   ? F\n>> +#     \\ / \\ /\n>> +#      o---o\n>> +#      C   E\n>> +#\n>> +#   Commit A: new file: a\n>> +#   Commit B: rename a->b\n>> +#   Commit C: rename a->c, add different a\n>> +#   Commit D: merge B&C, keeping b&c and (new) a modified at beginning\n>> +#   Commit E: merge B&C, keeping b&c and (new) a modified at end\n> THAT may be a corner case, but is it a useful corner case?  What on earth\n> the person who did D (or E) was thinking to keep both b and c that are\n> derived from A:a to begin with?\n\nI think this may happen rather commonly in one of our repos.  I must say \nI was quite surprised when I recognized it in this discussion.\n\nThe details are boring, so feel free to ignore the rest of this message \nunless you just want to understand or contradict me.  There is one \ndifference in my real workflow, but for now I'll imagine this difference \nis not there.\n\nCiao.\n\n\nBegin boring details:\n\nWe have an expensive, enterprise, config control system that tracks \n\"low-volume\" file revisions by unique filenames in a specific format.  \nHere's the life story of a file named foo:\n\n    foo_00    # Revision 00 (initial rev)\n    foo_01    # Revision 01\n    foo_02    # Revision 02\n    foo_A     # Revision A (first 'published' version)\n    foo_B     # Revision B (all subsequent versions get published)\n\nWhen we transition to an externally published version (a one-time \nevent), the revisions change from numbers to letters.\n\nWe often need to refer to a specific file version by 'versioned name', \nso we keep all the revisions around in a pool of files.\n\nIf I have pending changes not yet in the \"config control\" system, I keep \na source file in git to hold them until I'm ready to label them with the \nnext revision number.\n\nI have some tools that promote my current edition into the next \nappropriately named-revision.\n\n   'next' is used to promote the current source to the next appropriate \nrevision-named file.\n\n   'publish' does the same thing, but it explicitly promotes the file to \nrevision 'A' from the current numeric revision.\n\nA few weeks ago, this is how foo showed up in my repo:\n\n     pending/foo\n     deploy/foo_00\n     deploy/foo_01\n\nHere's a greatly simplified overview of the tools with this state:\n\n     next foo        # \"mv -n pending/foo deploy/foo_02\"\n     publish foo     # \"mv -n pending/foo deploy/foo_A\"\n\n[Elijah's \"Commit A: new file: a\": a=pending/foo]\n\nThe story begins.\n\n[Elijah's \"Commit B: rename a->b\": b=deploy/foo_01]\n\nChanges in pending/foo need to be released, so I prep with 'next foo'.\n\n\n     $ next foo        # \"mv pending/foo deploy/foo_02\"\n     deploy/foo_00\n     deploy/foo_01\n     deploy/foo_02\n\nThen I commit and push.\n     $ git add -u . && git add . && git commit -m \"next foo\" && git push\n\n[Elijah's \"Commit C: rename a->c, add different a\": c=deploy/foo_A]\n\nMy boss has been told (but he forgot to tell me) that we need to go to rev A in this release so the package can be published externally.  So, in his repo he preps with 'publish foo'.\n\n     $publish foo     #\"mv -n pending/foo deploy/foo_A\"\n     deploy/foo_00\n     deploy/foo_01\n     deploy/foo_A\n\n\nMy boss also has some changes for a future version (probably rev B) so \nhe adds those, too.\n     $ echo Frabnotz > pending/foo\n\nThen he commits and pushes.\n     $ git add -u . && git add . && git commit -m \"publish foo\" && git push\n\nIt's easy to embellish the rest.  It's so easy, I won't.  But you can imagine it.\n\nThe only deviation in this story from my real workflow is that I never delete pending/foo.  But if it changed radically each time, I probably would.\n\nPhil\n"},{"id":"171806","messageId":"7vaac73265.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-31-git-send-email-newren@gmail.com","subject":"Re: [PATCH 30/48] merge-recursive: Fix deletion of untracked file in rename/delete conflicts","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-21T18:43:14Z","receivedAt":"2011-07-21T18:43:14Z","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> In summary:\n>   Change <expression> from \"o->call_depth || renamed_stage == 2\" to\n>   \"renamed_stage == 2 || !was_tracked(ren1_src)\", in order to\n>   remove unnecessary code and avoid deleting untracked files.\n>\n> 96 lines of explanation in the changelog to describe a one-line fix...\n\nBeautifully written analysis, though ;-)\n"},{"id":"171807","messageId":"7v39hz325z.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-35-git-send-email-newren@gmail.com","subject":"Re: [PATCH 34/48] merge-recursive: Consolidate process_entry() and process_df_entry()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-21T18:43:20Z","receivedAt":"2011-07-21T18:43: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 whole point of adding process_df_entry() was to ensure that files of\n> D/F conflicts were processed after paths under the corresponding\n> directory.  However, given that the entries are in sorted order, all we\n> need to do is iterate through them in reverse order to achieve the same\n> effect.  That lets us remove some duplicated code, and lets us keep\n> track of one less thing as we read the code (\"do we need to make sure\n> this is processed before process_df_entry() or do we need to defer it\n> until then?\").\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  merge-recursive.c |  194 ++++++++++++++++------------------------------------\n>  1 files changed, 60 insertions(+), 134 deletions(-)\n\n;-).\n"},{"id":"171808","messageId":"7vvcuv1nle.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-37-git-send-email-newren@gmail.com","subject":"Re: [PATCH 36/48] merge-recursive: Provide more info in conflict markers with file renames","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-21T18:43:25Z","receivedAt":"2011-07-21T18:43:25Z","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> Whenever there are merge conflicts in file contents, we would mark the\n> different sides of the conflict with the two branches being merged.\n> However, when there is a rename involved as well, the branchname is not\n> sufficient to specify where the conflicting content came from.  In such\n> cases, mark the two sides of the conflict with branchname:filename rather\n> than just branchname.\n>\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n\nNice.\n"},{"id":"171809","messageId":"7voc0n1nl8.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-38-git-send-email-newren@gmail.com","subject":"Re: [PATCH 37/48] merge-recursive: Fix modify/delete resolution in the recursive case","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-21T18:43:31Z","receivedAt":"2011-07-21T18:43:31Z","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> When o->call_depth>0 and we have conflicts, we try to find \"middle ground\"\n> when creating the virtual merge base.  In the case of content conflicts,\n> this can be done by doing a three-way content merge and using the result.\n> In all parts where the three-way content merge is clean, it is the correct\n> middle ground, and in parts where it conflicts there is no middle ground\n> but the conflict markers provide a good compromise since they are unlikely\n> to accidentally match any further changes.\n>\n> In the case of a modify/delete conflict, we cannot do the same thing.\n> Accepting either endpoint as the resolution for the virtual merge base\n> runs the risk that when handling the non-recursive case we will silently\n> accept one person's resolution over another without flagging a conflict.\n> In this case, the closest \"middle ground\" we have is actually the merge\n> base of the candidate merge bases.  (We could alternatively attempt a\n> three way content merge using an empty file in place of the deleted file,\n> but that seems to be more work than necessary.)\n\nWhat did we use before this patch as the \"middle ground\"?\n\nDoesn't the \"middle ground\" also need to contain information from both\nsides?  At the content level, The half-merge result with conflict marker\ncontains all the necessary information on what both sides agreed to do\noutside the marker area, while showing what each side wished to do inside,\nso as you describe, it is a good \"middle ground\".\n\nUsing the merge-base would mean that at the tree-structural level of this\nmerge you ignore the wish of one side that wanted to delete the path, and\nthen at the content level you also ignore the wish of both sides (the side\nthat wanted to delete the path wanted to leave zero content, while the\nother side wanted to modify from the version in merge-base, all of which\nis ignored by the above argument).\n\nWhen you want to make a criss-corss merge between E and F that looks like\nthis:\n\n      A---C---E---?\n     / \\ /       /\n    O   .       /\n     \\ / \\     /\n      B---D---F\n\nif there is no deletion, we run a content level merge between C and D by\nusing X that is a potentially conflicting merge between A and B as a\n\"virtual\" ancestor.\n\n      A---C---E---?\n     / \\ /       /\n    O   X       /\n     \\ / \\     /\n      B---D---F\n\nX would contain both what both A and B agreed to do on top of O, and what\nA and B wanted to do that they do not agree with. The differing opinion of\nA and B are recorded inside the conflict markers.  The change that turns X\ninto E contains C's opinion (i.e. it would likely agree to take what both\nA and B agreed to do, and it may agree with A and the result in E would\nresemble what A wanted to bring to the result despite B's objection). If C\nand D both agree how to resolve the conflict, then B's \"objection\" will\ncancel out from the three-way merge between E and F that uses X as the\nancestor.\n\nIn a delete/modify situation (e.g. A modified while B deleted), there are\nthree possibilities:\n\n * C and D both agree to delete the path;\n\n * C and D both agree to keep the path, with modifications that may or may\n   not conflict (which can be handled by the usual content-level merge);\n\n * C decides to delete, while D decides to modify (or vice versa).\n\nAnd in the last case, the outer merge ? needs to decide if it wants to\nkeep or delete the path anyway, so a simplest solution is to punt the\nwhole recursive business and make it the responsibility of the user to\nresolve it as a merge between E and F using O as the common ancestor.\nThis patch does so in all three cases.\n\nI however wonder if we can do better in the second case (I do not think\nthe first case would come into the picture, as we would not see such a\npath when merging E and F as it would have been long gone from both\nbranches). We wouldn't know which commits C and D are exactly, but we do\nhave E and F. If A modified the path and B deleted it, and C and D both\ndecided to keep the path and E and F both inherited that path, wouldn't it\nbe fair to say that what both branches wanted to do is closer to what A\ndid than what B did? In other words, instead of using O, wouldn't it give\nus a better result if we used A (the side that did not delete) as the\ncommon ancestor for the content level merge when both E and F has the\npath?\n\n> diff --git a/merge-recursive.c b/merge-recursive.c\n> index da507a3..5a70a87 100644\n> --- a/merge-recursive.c\n> +++ b/merge-recursive.c\n> @@ -1309,11 +1309,26 @@ 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 *o_sha, int o_mode,\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> +\tchar *new_path = NULL;\n> +\tint free_me = 0;\n\nNo need for free_me anymore, no?\n\n> +\tif (dir_in_way(path, !o->call_depth)) {\n> +\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n> +\t\tfree_me = 1;\n> +\t}\n> +\n> +\tif (o->call_depth) {\n> +\t\t/*\n> +\t\t * We cannot arbitrarily accept either a_sha or b_sha as\n> +\t\t * correct; since there is no true \"middle point\" between\n> +\t\t * them, simply reuse the base version for virtual merge base.\n> +\t\t */\n> +\t\tremove_file_from_cache(path);\n> +\t\tupdate_file(o, 0, o_sha, o_mode, new_path ? new_path : path);\n> +\t} else if (!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%s%s.\",\n>  \t\t       path, o->branch1,\n> @@ -1330,6 +1345,9 @@ static void handle_delete_modify(struct merge_options *o,\n>  \t\t       NULL == new_path ? \"\" : new_path);\n>  \t\tupdate_file(o, 0, a_sha, a_mode, new_path ? new_path : path);\n>  \t}\n> +\tif (free_me)\n> +\t\tfree(new_path);\n> +\n\n... as new_path is non-NULL only when we allocated it.\n"},{"id":"172010","messageId":"7vhb6aqdw7.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-40-git-send-email-newren@gmail.com","subject":"Re: [PATCH 39/48] merge-recursive: Fix rename/rename(1to2) resolution for virtual merge base","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-07-25T20:55:04Z","receivedAt":"2011-07-25T20:55:04Z","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> When renaming one file to two files, we really should be doing a content\n> merge.  Also, in the recursive case, undoing the renames and recording the\n> merged file in the index with the source of the rename (while deleting\n> both destinations) allows the renames to be re-detected in the\n> non-recursive merge and will result in fewer spurious conflicts.\n\nIn other words,...\n\n> +\t\t * FIXME: For rename/add-source conflicts (if we could detect\n> +\t\t * such), this is wrong.  We should instead find a unique\n> +\t\t * pathname and then either rename the add-source file to that\n> +\t\t * unique path, or use that unique path instead of src here.\n>  \t\t */\n> +\t\tupdate_file(o, 0, mfi.sha, mfi.mode, src);\n> +\t\tremove_file_from_cache(ren1_dst);\n> +\t\tremove_file_from_cache(ren2_dst);\n\n... the use of \"src\" here is taking a \"middle ground\" and punting on\nresolving the conflicting opinions of both branches for the outer merge to\nresolve. I think that is a sensible thing to do.\n\nThe rename destinations of both branches are removed. What happens if\nren1_dst (the path one branch wanted to rename src to) were added by the\nother branch (which wanted to rename src to ren2_dst), causing rename/add\nconflict between branches (i.e. not the one you worry about in the above\nFIXME which is about one branch renaming src to ren1_dst while adding an\nunrelated content to src)?\n"},{"id":"172830","messageId":"7v4o1y81sv.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"1307518278-23814-1-git-send-email-newren@gmail.com","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-08-04T00:20:48Z","receivedAt":"2011-08-04T00:20:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Unfortunately I seem to have found a regression that manifests in the real\nlife.\n\nWhen this series is merged to 'next', it mismerges a trivial renamed path.\nA sample commit to reproduce is 2d11f21c3, which is a merge between\n28b9264dd6 and 5b42477b59.  Check out the former and try to merge in the\nlatter.\n\nThe merge base of these two commits is 2cfe8a68ccb (no criss-cross).\n\nOne branch (current one, 28b9264dd6) does not have that path, but it does\nhave t/t4037-whitespace-status.sh:\n\n    $ git ls-tree 28b9264dd6 t/t4037-whitespace-status.sh\n    100755 blob 3c728a3ebf9ce52e5c24c81525d5cb749cfb2957 t/t4037-whitespace-status.sh\n\nWhat happened to that path on the current branch is this:\n\n    $ git log -m -M --raw --pretty=short 2cfe8a68ccb..28b9264dd6 -- \\\n      t/t40{37,40}-whitespace-status.sh\n    commit af7b41c923677ff9291bab56ec7069922e37453b\n    Author: Jeff King <peff@peff.net>\n\n        diff_tree: disable QUICK optimization with diff filter\n\n    :100755 100755 abc4934... 3c728a3... M  t/t4037-whitespace-status.sh\n\nIn other words, we had t4037, and we have t4037 but we updated its\ncontents from abc4934 to 3c728a3.\n\nRunning \"log -m -M --raw --pretty=short 2cfe8a68ccb..5b42477b59\" shows\nthat the merged branch (5b42477b59) has renamed t4037 to t4040 at\n0e098b6d:\n\n    commit 0e098b6d79fbcab763874f2b6fde5aa82144d150\n    Author: Johannes Sixt <j6t@kdbg.org>\n\n        Make test case number unique\n\n    :100755 100755 a30b03b... a30b03b... R100 t/t4037-whitespace-status.sh    t/t4040-wh..\n\nThis commit is on a side-branch that has not been merged at the merge base\n2cfe8a68ccb; its contents a30b03b is older than what 2cfe8a68ccb has, and\nwas updated by 2cfe8a68ccb to abc4934.\n\nAnother merge after this commit updated it at 7d0cf357:\n\n    commit 7d0cf357a31cc8a442342696788d776265482ce9 (from 98b256bd...)\n    Merge: 98b256b 2cfe8a6\n    Author: Junio C Hamano <gitster@pobox.com>\n\n        Merge branch 'jc/maint-diff-q-filter'\n\n    :100755 100755 a30b03b... abc4934... M  t/t4040-whitespace-status.sh\n\nThis abc4934 is the same contents as the merge base had at t4037.\n\nSo this is an \"our side kept t4037 and updated its contents from abc4934\nto 3c728a3, while the other side renamed t4037 to t4040 and kept its\ncontents as abc4934\". We should merge this as structural change \"our side\nleft it untouched, their side renamed, so take the rename without\nconflict\" and content level change \"our side updated but their side left\nit untouched, so take our modification without conflict\".\n\nIn other words, in the result, we should have 3c728a3 at t4040, and that\nindeed is what we got in the recorded history:\n\n    $ git ls-tree 2d11f21c3 -- t/t4040-whitespace-status.sh\n    100755 blob 3c728a3ebf9ce52e5c24c81525d5cb749cfb2957 t/t4040-whitespace-status.sh\n\nHowever, the merge retried with this series cleanly resolves the path, but\nwith wrong contents:\n\n    $ git checkout 28b9264dd6\n    $ git merge 5b42477b59\n    $ git ls-files -s t/t4040-whitespace-status.sh\n    100755 abc49348b196cf0fec232b6f2399356e4fe324d5 0 t/t4040-whitespace-status.sh\n\nCorrect answer should be:\n\n    $ git ls-files -s t/t4040-whitespace-status.sh\n    100755 3c728a3ebf9ce52e5c24c81525d5cb749cfb2957 0 t/t4040-whitespace-status.sh\n\nI am rewinding today's integration of 'next' to unmerge this topic\nnow. :-(...\n"},{"id":"172835","messageId":"7vaabq6j6g.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"7v4o1y81sv.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-08-04T01:48:23Z","receivedAt":"2011-08-04T01:48:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"A very simple reproduction recipe.\n\n-- >8 --\n#!/bin/sh\n\nmkdir en && cd en || exit\n\ngit init\n\necho 1 >one\ngit add one\ngit commit -m 'origin'\n\ngit checkout -b side\ngit mv one two\ngit commit -m 'side renames one to two'\n\ngit checkout master\necho 2 >one\ngit add one\ngit commit -m 'master updates one'\n\ngit checkout HEAD^0\ngit merge side\n-- 8< --\n\nTonight's \"pu\" fails like this:\n\n$ git merge side\nerror: addinfo_cache failed for path 'two'\nMerge made by the 'recursive' strategy.\n one |    1 -\n two |    1 +\n 2 files changed, 1 insertions(+), 1 deletions(-)\n delete mode 100644 one\n create mode 100644 two\n"},{"id":"172840","messageId":"CABPp-BHUuCMNMdVM20M2tPVQvDRmsTGRwfLoYW3-cgKHWXJ8Zg@mail.gmail.com","threadId":"27574","inReplyTo":"7vaabq6j6g.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-04T02:12:25Z","receivedAt":"2011-08-04T02:12:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Aug 3, 2011 at 7:48 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> A very simple reproduction recipe.\n\nThanks, I'll take a look this weekend.  I'm a little surprised that\nyou mentioned merging this to next already; I was assuming you wanted\nme to address the various things you pointed out in your reviews first\n(as well as the comments from Johannes).  I've been swamped at work,\nparticularly after trying to catch up on the pile of work accumulated\nduring my vacation, but should have time again this weekend...finally.\n\n\nElijah\n"},{"id":"172935","messageId":"CABPp-BE=9r+upGUD45J7fPshqQE97UMZzaA+cu_WJ1A2p_Bigg@mail.gmail.com","threadId":"27574","inReplyTo":"7v4o1y81sv.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-04T17:26:20Z","receivedAt":"2011-08-04T17:26:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Aug 3, 2011 at 6:20 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Unfortunately I seem to have found a regression that manifests in the real\n> life.\n>\n> When this series is merged to 'next', it mismerges a trivial renamed path.\n\nI know the problem, and can think of a couple potential solutions but\nI'm not sure which one of those (if any) should be used.  I may need\nadvice from an unpack_trees expert.\n\nThis breakage was introduced in d487486 (merge-recursive: When we\ndetect we can skip an update, actually skip it 2011-06-08), and the\nreason for the breakage is that was_tracked(path) is lying to us.  Let\nme explain...\n\nIf you run with GIT_MERGE_VERBOSITY=3, you'll note that you get the message\n  \"Skipped two (merged same as existing)\"\nwhich is NOT the path it should be taking.  The reason for this is\nthat the call to was_tracked('two') just before that is returning true\nfor us when the correct answer is false.  Explaining why we get the\nwrong answer requires understanding a couple things.\n\nUsing your simple reproduction recipe as an example to explain this,\nthe sha1sums and paths for your merge case are:\n  Base commit: d00491f... one\n  HEAD commit: 0cfbf08... one\n  side commit: d00491f... two\nIn other words, 'one' is modified in HEAD, and unchanged other than\nbeing renamed (to 'two') on side.\n\nNow, was_tracked() simply checks whether there is a cache entry in the\ncurrent index with either stage 0 or stage 2.  Clearly, I was\nexpecting 'two' to only appear with stage 3, but that's not what\nhappened.\n\nWhen unpacking these trees, we got three cache entries: one for path\n'one' at stage 1, one for path 'one' at stage 2, and one for path\n'two' at stage 0.  Yes, stage 0 and not stage 3.  The reason for this\nis that this is case 2ALT from\nDocumentation/technical/trivial-merge.txt:\n\ncase  ancest    head    remote    result\n----------------------------------------\n...\n2ALT  (empty)+  *empty* remote    remote\n\nThis means the threeway_merge code considers it a clean merge and\ncalls merged_entry on it, which clears the CE_STAGEMASK bits from the\ncache entry flags.\n\nSince the cache entry for path 'two' has the CE_STAGEMASK bits\ncleared, when we call was_tracked on that path, we get back the answer\ntrue.  'two' was not tracked in HEAD, though, so this is the wrong\nanswer and the source of the bug.\n\n\nNow, the question is...how do we fix the was_tracked function?  It\nwould be nice to make use of the original index we had before\nunpacking, but that is overwritten at the end of unpack_trees.  Should\nwe save it somewhere?  Or should we save off all the tracked filenames\nbefore unpacking into a linked list and then have was_tracked() use\nthat instead of looking it up in the current cache? (I'm slightly\nworried that may be slow given the number of lookups that exist).\n\nThoughts?\n\nElijah\n"},{"id":"172944","messageId":"7vpqkl3sok.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"CABPp-BE=9r+upGUD45J7fPshqQE97UMZzaA+cu_WJ1A2p_Bigg@mail.gmail.com","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-08-04T19:03:39Z","receivedAt":"2011-08-04T19:03:39Z","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> ...  It\n> would be nice to make use of the original index we had before\n> unpacking, but that is overwritten at the end of unpack_trees.\n\nI somehow thought that we can give separate src and dst index\nto the unpack_tree() machinery these days. Aren't you using it?\n"},{"id":"172945","messageId":"CABPp-BFx38gLQDn0sccp74Z=RtEVzaxWiVqopxkWwXTSGuYxxw@mail.gmail.com","threadId":"27574","inReplyTo":"7vpqkl3sok.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-04T19:16:35Z","receivedAt":"2011-08-04T19:16:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Aug 4, 2011 at 1:03 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Elijah Newren <newren@gmail.com> writes:\n>\n>> ...  It\n>> would be nice to make use of the original index we had before\n>> unpacking, but that is overwritten at the end of unpack_trees.\n>\n> I somehow thought that we can give separate src and dst index\n> to the unpack_tree() machinery these days. Aren't you using it?\n\nAh, yes, it appears to be possible.  I have not touched that part of\nmerge-recursive, so it still reads:\n\topts.fn = threeway_merge;\n\topts.src_index = &the_index;\n\topts.dst_index = &the_index;\n\tsetup_unpack_trees_porcelain(&opts, \"merge\");\nI'll take that route to fix this.\n\nThanks.\n"},{"id":"173008","messageId":"7vr54z15dn.fsf@alter.siamese.dyndns.org","threadId":"27574","inReplyTo":"CABPp-BFx38gLQDn0sccp74Z=RtEVzaxWiVqopxkWwXTSGuYxxw@mail.gmail.com","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-08-06T05:22:12Z","receivedAt":"2011-08-06T05:22:12Z","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> On Thu, Aug 4, 2011 at 1:03 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Elijah Newren <newren@gmail.com> writes:\n>>\n>>> ... It\n>>> would be nice to make use of the original index we had before\n>>> unpacking, but that is overwritten at the end of unpack_trees.\n>>\n>> I somehow thought that we can give separate src and dst index\n>> to the unpack_tree() machinery these days. Aren't you using it?\n>\n> Ah, yes, it appears to be possible.\n\nBut why do you care about the original index (i.e. the starting state of\nour side) in the first place? If your algorithm depends on the original\nindex, wouldn't that mean you would screw up the same merge if the user\nmerged branches in the opposite direction?\n\nI think the fact you have a path \"two\" at stage 0, combined with the two\ndiffs you ran for rename detection between the common ancestor and two\nbranches, should be enough to decide if one branch added the path or moved\nit from elsewhere (in which case the common ancestor would not have that\npath), or it kept the path at the original place (with or without content\nchange), no?\n"},{"id":"173069","messageId":"CABPp-BERsAoL2eiTMiqDPx4EtZX599m+XOTH4VOu14-fZbR-Ew@mail.gmail.com","threadId":"27574","inReplyTo":"7vr54z15dn.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 00/48] Handling more corner cases in merge-recursive.c","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-06T20:31:52Z","receivedAt":"2011-08-06T20:31:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 5, 2011 at 11:22 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> But why do you care about the original index (i.e. the starting state of\n> our side) in the first place? If your algorithm depends on the original\n> index, wouldn't that mean you would screw up the same merge if the user\n> merged branches in the opposite direction?\n\nNo, it does not mess up the same merge in the opposite direction.  The\ncode in question (with some local modifications) is\n\n       if (mfi.clean && !df_conflict_remains &&\n           sha_eq(mfi.sha, a_sha) && mfi.mode == a_mode) {\n               output(o, 3, \"Skipped %s (merged same as existing)\", path);\n               /*\n                * The content merge resulted in the same file contents we\n                * already had.  We can return early if those file contents\n                * are recorded at the correct path (which may not be true\n                * if the merge involves a rename).\n                */\n               if (was_tracked(&o->original_index, path)) {\n                       add_cacheinfo(mfi.mode, mfi.sha, path,\n                                     0 /*stage*/, 1 /*refresh*/, 0 /*options*/)\n                       return mfi.clean;\n               }\n       } else\n                output(o, 2, \"Auto-merging %s\", path);\n\nAnd the merge is:\n  BASE: original-path: v1\n  HEAD: original-path: v2\n  SIDE: renamed-path: v1\n\nSo, HEAD did have the correct contents that we wanted, as checked in\nthe first if.  However, we cannot bail early because those contents\nwere recorded at the wrong path.  Repeating the merge in the other\ndirection would simply show that we don't have the right contents\n(a_sha != mfi.sha).  Only if we have both the right contents and have\nit at the right path should we exit early.\n\nIf we use \"&the_index\" instead of \"&o->original_index\", which is what\nmy previous series essentially did, then we get the wrong answer about\nwhether the contents are recorded at the necessary path, i.e.\nwas_tracked() lies to us.\n\n\nNow, your gut feel and question here does turn out to be\nimportant...in a different case.  You'll note that I'm passing an\nindex to was_tracked(); that's because there is one case (the call to\nwas_tracked() from would_lose_untracked()) where it is important that\nwe use the index as modified by unpack_trees rather than the original\nindex and not doing so causes a bug depending on the direction things\nare merged.  I added a big comment in the code about that one.\n\n> I think the fact you have a path \"two\" at stage 0, combined with the two\n> diffs you ran for rename detection between the common ancestor and two\n> branches, should be enough to decide if one branch added the path or moved\n> it from elsewhere (in which case the common ancestor would not have that\n> path), or it kept the path at the original place (with or without content\n> change), no?\n\nYou are correct.  I just need to reorder the patch series somewhat\n(other patches added the passing of the relevant diff_filepair\ninformation to merge_content, which is needed to do these checks you\nsuggest), and then I can change the was_tracked() call to instead\ncompare pathnames.\n"},{"id":"173135","messageId":"CABPp-BHn=zbQZyOKjmM96SRLifyOdJj5YfRmonCG7etxX43Aiw@mail.gmail.com","threadId":"27574","inReplyTo":"7vvcuzdusv.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 05/48] t6039: Add tests for content issues with modify/rename/directory conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-08T15:49:40Z","receivedAt":"2011-08-08T15:49:40Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nThanks for the many detailed reviews.  I've spent much of the weekend\n(and before) trying to go through and clean things up, and am still\nworking on it.  There are some things to comment on outside of the\nseries re-roll I am putting together...\n\nOn Mon, Jul 18, 2011 at 5:37 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Could you add a description for this? Your description on 06/48 is\n> beautifly done. Here is my attempt.\n...\nDone. :-)  It'll be in the reroll I submit.\n\n> I think I said this in my earlier review, and this is not limited to 05/48\n> but also applies to 03/48 and 04/48 as well, but there won't be perfect\n> rename detection, and the rename detection logic could change (improve)\n> over time. Ideally, I think the test should declare either case as a\n> success: (1) detection succeeds and avoids unnecessary conflict, or (2)\n> rename is missed and conflict is reported, but otherwise there is no data\n> loss. If it expects only one but not the other, any time the rename logic\n> is improved, the behaviour could change between (1) and (2) and it will\n> cause a false positive \"breakage\" of these tests.\n\nThe same could be said for all the rename tests, but I'm a bit worried\nabout doing this, particularly for the set of tests I added.  I came\nup with my testcases by carefully reviewing the code looking for ways\nI could trigger problems (which were almost never covered by an\nexisting regression test), plus trying a few small modifications of\nany tests I did come up with.  The tests I added that involve renames\nreally are specifically designed to test renaming situations.  If\nrename detection logic is modified in a way that affects these tests,\nand these tests aren't updated, then we are turning some useful unique\ntests into mere duplication of simpler tests we have elsewhere.\n\nIf you are worried that these tests lack robustness in the face of\nrename-detection changes, perhaps we should modify the tests to use\nfile contents that are more likely to continue being detected as\nrenames (e.g. longer lines or maybe more of them)?  Or, maybe add an\nearlier check in the test to independently verify that the rename is\ndetected?\n"},{"id":"173142","messageId":"CABPp-BEKTnOdib=KdKWMkEhEBrUUth2prn_r2N=bEbu0ZoD+6A@mail.gmail.com","threadId":"27574","inReplyTo":"7vaacbb1jn.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 07/48] t6039: Ensure rename/rename conflicts leave index and workdir in sane state","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-08T17:59:19Z","receivedAt":"2011-08-08T17:59:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jul 18, 2011 at 5:40 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> +# Test for all kinds of things that can go wrong with rename/rename (2to1):\n>> +#   Commit A: new files: a & b\n>> +#   Commit B: rename a->c, modify b\n>> +#   Commit C: rename b->c, modify a\n>> +#\n>> +# Merging of B & C should NOT be clean.  Questions:\n>> +#   * Both a & b should be removed by the merge; are they?\n>> +#   * The two c's should contain modifications to a & b; do they?\n>> +#   * The index should contain two files, both for c; does it?\n>> +#   * The working copy should have two files, both of form c~<unique>; does it?\n>> +#   * Nothing else should be present.  Is anything?\n>\n> What is the most useful thing to leave in the index and in the working\n> tree for the person who needs to resolve such a merge using the working\n> tree, starting from B and merging C? The above \"Questions\" lists what the\n> current code might try to do but I am not sure if it is really useful. For\n> example, in the index, you would have to stuff two stage #1 entries (\"a\"\n> from A and \"b\" from A) for path \"c\", with stage #2 (\"c\" from B) and stage\n> #3 (\"c\" from C) entries, and represent what B tried to do to \"a\" (in the\n> above you said \"rename a->c\" but it does not have to be a rename without\n> content change) and what C tried to do to \"b\" in the half-conflicted\n> result that is in a single file \"c\". Because the result are totally\n> unrelated files (one side wants a variant of original \"a\" there, the other\n> side wants a variant of \"b\"), such a half-merge result is totally useless\n> to help the person to come up with anything.\n>\n> Also renaming \"c\" to \"c~<unique>\", if they do not have corresponding\n> entries in the index to let you check with \"git diff\", would make the\n> result _harder_ to use, not easier. So if you are going to rename \"c\" to\n> \"c-B\" and \"c-C\", at least it would make much more sense to have in the\n> index:\n>\n>  - \"c-B\", with stage #1 (\"a\" from A), stage #2 (\"c\" from B) and stage #3\n>   (\"a\" from C);\n>  - \"c-C\", with stage #1 (\"b\" from A), stage #2 (\"b\" from B) and stage #3\n>   (\"c\" from C); and\n>  - No \"a\" nor \"b\" in the index nor in the working tree.\n>\n> no?\n>\n> That way, you could run \"git diff\" to get what happened to the two\n> variants of \"a\" and \"b\" at the content level, and decide to clean things\n> up with:\n>\n>    $ git diff ;# view content level merge\n>    $ edit c-B c-C; git add c-B c-C\n>    $ git mv c-B c-some-saner-name\n>    $ git mv c-C c-another-saner-name\n>    $ edit other files that refer to c like Makefile\n>    $ git commit\n\nThat sounds very interesting.  My first thought is that you'd have to\ndo the same thing in the case of a D/F conflict, but I notice that\nlater in the patch series you asked for exactly that.  The idea\ncertainly has potential, though I might need to think it through a\nlittle more.\n\n> To take it one step further to the extreme, it might give us a more\n> reasonable and useful conflicted state if we deliberately dropped some\n> information instead in a case like this, e.g.:\n>\n>  - We may want to have \"a\" at stage #1 (from A) in the index;\n>  - No \"a\" remains in the working tree;\n>  - \"b\" at stage #1 (from A), stage #2 (from B) and stage #3 (\"c\" from C);\n>  - \"b\" in the working tree a conflicted-merge of the above three;\n>  - \"c\" at stage #1 (\"a\" from A), stage #2 (from B), and stage #3 (\"a\" from\n>   C); and\n>  - \"c\" in the working tree a conflicted-merge of the above three.\n>\n> Note that unlike the current merge-recursive that tries to come up with a\n> temporary pathname to store both versions of C, this would ignore \"mv b c\"\n> on the A->C branch, and make the conflicted tentative merge asymmetric\n> (merging B into C and merging C into B would give different conflicts),\n> but I suspect that the asymmetry may not hurt us.\n>\n> Whether the merger wants to keep \"c\" that was derived from \"a\" (in line\n> with the HEAD) or \"c\" that was derived from \"b\" (in line with MERGE_HEAD),\n> if the result were to keep both files in some shape, the content level\n> edit, renaming of at least one side, and adjusting other files that refer\n> to it, are all required anyway, e.g.\n>\n>    $ git diff ;# view content level merge\n>    $ edit b c; git add b c\n>    $ edit other files that refer to c line Makefile (the content C's\n>      change wants is now in \"b\").\n>    $ git commit\n>\n> would be a way to pick \"c\" as \"c-some-saner-name\" and \"b\" as\n> \"c-another-saner-name\" in the previous workflow, but needs much less\n> typing. The complexity of the workflow would be the same if the final\n> resolution is to take what one side did and dropping the other's work,\n> I think.\n\nI think the asymmetry is slightly confusing and could become\nproblematic.  If we decide to turn on break detection, then we would\nhit problems in a scenario such as:\n\nCommit A: files a, b are present\nCommit B: rename a->c, add an unrelated a\nCommit C: rename b->c, add an unrelated b\n\nIn that case, \"undoing\" the rename as you suggest gives us a conflict\nwith other content that was added at the path.\n\n\nAlso, as mentioned above, D/F conflicts hit similar cases where we\nneed to rename the path in the working copy.  If we try to handle them\nsimilarly to how you are suggesting for the rename/rename(2to1) case,\nwe can do so in some cases but hit problems in others.  For example,\ntake a rename/delete conflict with D/F conflicts:\n\nCommit A: file a is present\nCommit B: rename a -> df, possibly also modifying it\nCommit C: delete a, add files a/foo and df/bar\n\nWe can't use either the path 'df' or 'a' for recording the content.  I\nthink the rules become too confusing for \"selectively undoing renames\"\nand it'd be easier to just use <bad-dest-path>~<unique> in all cases.\nHowever, I think your suggestion to move index stage information to\nthese uniquely renamed paths could probably work and may be useful.\n"},{"id":"173143","messageId":"CABPp-BHbXt=FJ4-wx6ci+xOAo0Ud_dwBpSkoaKvUVuSxhkjotg@mail.gmail.com","threadId":"27574","inReplyTo":"7vaacbdus4.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 09/48] t6036: criss-cross with weird content can fool git into clean merge","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-08T18:02:05Z","receivedAt":"2011-08-08T18:02:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jul 18, 2011 at 5:38 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Elijah Newren <newren@gmail.com> writes:\n>\n>> +#   Commit E: file with contents:\n>> +#      <<<<<<< Temporary merge branch 1\n>> +#      C\n>> +#      =======\n>> +#      B\n>> +#      >>>>>>> Temporary merge branch 2\n>> +#\n>> +# Now, when we merge commits D & E, does git detect the conflict?\n>\n> This is way too artificial that I personally feel not worth worrying\n> about.\n\nI wasn't going to try to do anything to fix it.  Should I drop this\npatch from the series or is it worth keeping around for completeness?\n"},{"id":"173145","messageId":"CABPp-BGbwhDJF2=9gPWP08auk16qckho3db8eOes9JjTN0QdCw@mail.gmail.com","threadId":"27574","inReplyTo":"7v39i3b1jg.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 10/48] t6036: tests for criss-cross merges with various directory/file conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-08T19:07:42Z","receivedAt":"2011-08-08T19:07:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jul 18, 2011 at 5:40 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> +test_expect_failure 'git detects conflict and handles merge of D & E1 correctly' '\n>> +     git reset --hard &&\n>> +     git reset --hard &&\n>> +     git clean -fdqx &&\n>> +     git checkout D^0 &&\n>\n> Why do you need to reset twice?  Superstition (you have a commented one\n> introduced later in the series --- perhaps this shows a bug in reset)?\n\nI could have sworn that when I first created these tests last\nSeptember that the double reset was needed to get back to a clean\nstate.  I can't seem to duplicate the issue now, though I do see some\n\"error\" messages printed on the first reset despite the reset working\nsuccessfully.  I'll drop the extra one.  *shrug*\n\n\n[I'm not ignoring the many other good comments you made on this patch;\nthey just got incorporated into code and comment changes for the next\nversion of the series I send out.]\n"},{"id":"173149","messageId":"CABPp-BEBFa9vKcKBehq9gV+S1WW+0L+8XZ2+jHdiXsqBp4k+uA@mail.gmail.com","threadId":"27574","inReplyTo":"7vhb6jcg68.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 22/48] merge-recursive: Fix sorting order and directory change assumptions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-08T19:21:08Z","receivedAt":"2011-08-08T19:21:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jul 18, 2011 at 5:39 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> +static int string_list_df_name_compare(const void *a, const void *b)\n>> +{\n>> +     const struct string_list_item *one = a;\n>> +     const struct string_list_item *two = b;\n>> +     /*\n>> +      * Here we only care that entries for D/F conflicts are\n>> +      * adjacent, in particular with the file of the D/F conflict\n>> +      * appearing before files below the corresponding directory.\n>> +      * The order of the rest of the list is irrelevant for us.\n>> +      *\n>> +      * To achieve this, we sort with df_name_compare and provide\n>> +      * the mode S_IFDIR so that D/F conflicts will sort correctly.\n>> +      * We use the mode S_IFDIR for everything else for simplicity,\n>> +      * since in other cases any changes in their order due to\n>> +      * sorting cause no problems for us.\n>> +      */\n>\n> I recall there was an issue of this sorting reported earlier...\n\nYes, in git-fast-export, though.  It was 060df62 (fast-export: Fix\noutput order of D/F changes 2010-07-09), if you're curious.\n"},{"id":"173158","messageId":"CABPp-BEBJnMCwKKL67=LkB8pea+7QkpoXih3P7rfF5AiDRQYZQ@mail.gmail.com","threadId":"27574","inReplyTo":"4E1D46A5.6050904@viscovery.net","subject":"Re: [PATCH 26/48] merge-recursive: Allow make_room_for_path() to remove D/F entries","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-08T20:56:19Z","receivedAt":"2011-08-08T20:56:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Wed, Jul 13, 2011 at 1:17 AM, Johannes Sixt <j.sixt@viscovery.net> wrote:\n> Am 6/8/2011 9:30, schrieb Elijah Newren:\n>> +static int make_room_for_path(const struct merge_options *o, const char *path)\n>>  {\n>> -     int status;\n>> +     int status, i;\n>>       const char *msg = \"failed to create path '%s'%s\";\n>>\n>> +     /* Unlink any D/F conflict files that are in the way */\n>> +     for (i = 0; i < o->df_conflict_file_set.nr; i++) {\n>> +             const char *df_path = o->df_conflict_file_set.items[i].string;\n>> +             size_t pathlen = strlen(path);\n>> +             size_t df_pathlen = strlen(df_path);\n>> +             if (df_pathlen < pathlen && strncmp(path, df_path, df_pathlen) == 0) {\n>> +                     unlink(df_path);\n>> +                     break;\n>> +             }\n>> +     }\n>\n> Each time this loop is entered it tries to remove the same path again,\n> even if it does not exist anymore or was morphed into a directory in the\n> meantime. I suggest to remove a path from o->df_conflict_file_set after it\n> was unlinked. Or even better: have a separate \"make room\" phase somewhere\n> in the merge process.\n\nRemoving it from o->df_conflict_file_set makes sense.  However, there\nappears to be no API in string_list.h for deleting entries.  Are such\noperations discouraged?  I'm not sure whether to add such API, just\nhack it directly, or wait for someone else to come along and change\nthis to a better data structure (such as a hash)...\n\nI don't think it's possible to move this \"make room\" phase anywhere\nearlier in the merge process.  When we have D/F conflicts, the files\nof those D/F conflicts should only be removed if at least one of the\npaths under the corresponding directory are not removed by the merge\nprocess.  We don't know whether those paths will need to be removed\nuntil we call process_entry() on each of them, and from there we go\nright to this function when we find one that needs to stick around.\nSo I simply don't see how to move it any earlier.\n\n\nHowever, there is clearly a bug in this code.  If there is a D/F\nconflict at 'd' (e.g. paths 'd' and 'd/foo' are present), and there is\na file named 'd_bla', then the need to merge 'd_bla' can cause 'd' to\nbe deleted.  Oops.  (Granted, it's a bug that would be masked by the\nlater call to process_entry() on 'd', which would reinstate that file\nif necessary, but that doesn't mean this code is right.)  I'll fix\nthat up.\n\nHowever, I don't see how any of this would address any failure you're\nseeing on windows.  Maybe one of my other changes, including one or\ntwo other bugfixes I've found will help?  I'll have to ping you when I\nsubmit the re-roll.\n"},{"id":"173169","messageId":"CABPp-BFHo0NK9Y+QGfDw-=zBBbNUQtfO9Fr4qUmdxyZEy0_7Mw@mail.gmail.com","threadId":"27574","inReplyTo":"7voc0n1nl8.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 37/48] merge-recursive: Fix modify/delete resolution in the recursive case","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-08T22:09:08Z","receivedAt":"2011-08-08T22:09:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Thu, Jul 21, 2011 at 12:43 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Elijah Newren <newren@gmail.com> writes:\n>\n>> When o->call_depth>0 and we have conflicts, we try to find \"middle ground\"\n>> when creating the virtual merge base.  In the case of content conflicts,\n>> this can be done by doing a three-way content merge and using the result.\n>> In all parts where the three-way content merge is clean, it is the correct\n>> middle ground, and in parts where it conflicts there is no middle ground\n>> but the conflict markers provide a good compromise since they are unlikely\n>> to accidentally match any further changes.\n>>\n>> In the case of a modify/delete conflict, we cannot do the same thing.\n>> Accepting either endpoint as the resolution for the virtual merge base\n>> runs the risk that when handling the non-recursive case we will silently\n>> accept one person's resolution over another without flagging a conflict.\n>> In this case, the closest \"middle ground\" we have is actually the merge\n>> base of the candidate merge bases.  (We could alternatively attempt a\n>> three way content merge using an empty file in place of the deleted file,\n>> but that seems to be more work than necessary.)\n>\n> What did we use before this patch as the \"middle ground\"?\n\nWe used precisely what you seem to suggest at the end of your email.\nIt's a trade-off, and I agree with you about the cases where my new\nchoice is suboptimal, but I personally think the original choice has\nworse trade-offs.\n\n> Doesn't the \"middle ground\" also need to contain information from both\n> sides?  At the content level, The half-merge result with conflict marker\n> contains all the necessary information on what both sides agreed to do\n> outside the marker area, while showing what each side wished to do inside,\n> so as you describe, it is a good \"middle ground\".\n>\n> Using the merge-base would mean that at the tree-structural level of this\n> merge you ignore the wish of one side that wanted to delete the path, and\n> then at the content level you also ignore the wish of both sides (the side\n> that wanted to delete the path wanted to leave zero content, while the\n> other side wanted to modify from the version in merge-base, all of which\n> is ignored by the above argument).\n\nI think it is optimal if the \"middle ground\" can contain information\nfrom both sides, but I simply do not see how it is possible in either\na modify/delete or rename/delete case.  That is, at least not without\nsome kind of significant change to the index data structures or\nallowing a \"virtual merge base\" to be an index with higher stage merge\nentries instead of just a tree (though that would introduce several\nnew complications).\n\nAnd yes, I am essentially ignoring the intents of both sides, and\nintentionally so.  I thought I addressed that in the arguments above,\nbut perhaps I could have been more clear and explicit about that.\nYour explanation below provides a great framework for clarifying why I\nmade this change, so let me respond below using the framework you have\nbeautifully illustrated and explained.\n\n> When you want to make a criss-corss merge between E and F that looks like\n> this:\n>\n>      A---C---E---?\n>     / \\ /       /\n>    O   .       /\n>     \\ / \\     /\n>      B---D---F\n>\n> if there is no deletion, we run a content level merge between C and D by\n> using X that is a potentially conflicting merge between A and B as a\n> \"virtual\" ancestor.\n>\n>      A---C---E---?\n>     / \\ /       /\n>    O   X       /\n>     \\ / \\     /\n>      B---D---F\n>\n> X would contain both what both A and B agreed to do on top of O, and what\n> A and B wanted to do that they do not agree with. The differing opinion of\n> A and B are recorded inside the conflict markers.  The change that turns X\n> into E contains C's opinion (i.e. it would likely agree to take what both\n> A and B agreed to do, and it may agree with A and the result in E would\n> resemble what A wanted to bring to the result despite B's objection). If C\n> and D both agree how to resolve the conflict, then B's \"objection\" will\n> cancel out from the three-way merge between E and F that uses X as the\n> ancestor.\n>\n> In a delete/modify situation (e.g. A modified while B deleted), there are\n> three possibilities:\n>\n>  * C and D both agree to delete the path;\n>\n>  * C and D both agree to keep the path, with modifications that may or may\n>   not conflict (which can be handled by the usual content-level merge);\n>\n>  * C decides to delete, while D decides to modify (or vice versa).\n\nThis is a really nicely written explanation.  I particularly like this\nlist of three cases.  Let me augment this list of situations (which\nI'll number (1)-(3)) with a list of four choices for virtual merge\nbase X:\n\n(J) Make X not have any content at the path (i.e. accept the deletion in B)\n\n(K) Make X have the content from O at path (i.e. what my patch does)\n\n(L) Make X have the content from A at path (which we did before)\n\n(M) Make X have some other content at path\n\nI'll get back to these 12 situations (3 cases each with how any of 4\nchoices could affect them) in a minute...\n\n> And in the last case, the outer merge ? needs to decide if it wants to\n> keep or delete the path anyway, so a simplest solution is to punt the\n> whole recursive business and make it the responsibility of the user to\n> resolve it as a merge between E and F using O as the common ancestor.\n> This patch does so in all three cases.\n>\n> I however wonder if we can do better in the second case (I do not think\n> the first case would come into the picture, as we would not see such a\n> path when merging E and F as it would have been long gone from both\n> branches). We wouldn't know which commits C and D are exactly, but we do\n> have E and F. If A modified the path and B deleted it, and C and D both\n> decided to keep the path and E and F both inherited that path, wouldn't it\n> be fair to say that what both branches wanted to do is closer to what A\n> did than what B did? In other words, instead of using O, wouldn't it give\n> us a better result if we used A (the side that did not delete) as the\n> common ancestor for the content level merge when both E and F has the\n> path?\n\nYes, absolutely.  In the second case, using the content of the path\nfrom A for the virtual merge ancestor would be a slight improvement\nover using the content for path from O.  But let's look at all three\ncases you specified and how different choices for the virtual merge\nbase affect it.  Here are my goals for any merge, in priority order:\n\n1st goal: detect real conflicts and notify the user\n2nd goal: avoid conflicts in cases where we should be able to\nreasonably confidently merge cleanly\n3rd goal: make it easy for the user to figure out how to resolve problems\n\nObviously, we can't always meet these goals, particularly due to\n\"semantic\" conflicts that may be present.  But they're useful to keep\nin mind.  Here is how I broke down the three cases:\n\n\n\nCase 1: C and D both agree to delete the path\n\nIn this case, regardless of the choice (J)-(M), the merge will be\nresolved correctly.\n\n\nCase 2: C and D both agree to keep the path, with modifications that\nmay or may not conflict (which can be handled by the usual\ncontent-level merge);\n\nIn this case, choice (J) would cause content conflicts whenever C & D\nhad different content at path and would treat it as a two-way content\nmerge.  That makes choice (J) particularly problematic as it breaks\nthe first goal.  In order to get the merge right, we really need to do\na three-way content merge, using content that can serve as a\nreasonable merge-base for the contents chosen at C & D.  That makes\nchoice (M) for X a poor one.  Both choices (K) and (L) are reasonable,\nwith (L) being a better choice (as you also point out above) to\noptimize the 2nd goal.\n\n\nCase 3: C decides to delete, while D decides to modify (or vice versa)\n\nIt is worth noting here that the most likely choice for the content at\npath for D is exactly the content that is at A.  git is designed for\ntrees of slowly changing data after all.\n\nThis is a case where git should detect the modify/delete conflict and\nalert the user.  In this case, choice (J) is particularly bad.\nRegardless of the content that D has for path, the merge conflict\nwon't even be detected and git would silently resolve this by\nrecording the content from D at path.  That's a fail for the first\ngoal.\n\nAnother choice that is likely to fail the first goal is (L).  If the\ncontent at path D is the same as at A (the most likely case), then git\nwould not detect the conflict and would simply silently delete the\npath.\n\nIn fact, any choice here for the merge base could cause the conflict\nto go silently undetected (whenever the content at D matched our\nchoice for X), meaning that no choice for the merge base will be\nperfect.  That would mean of the four choices, (M) would be best as it\nwould be least likely to match the choice made at D, but (K) would be\npretty reasonable.\n\n\nSo, as I view it: choice (J) is horrible in two of the three cases;\nchoice (M) is really bad in the second case for goal 2; and choice (L)\nis likely to be bad in the third case for goal 1.  Choice (K) is\npretty reasonable in all three cases, and that choice is what this\npatch implements.\n"},{"id":"173178","messageId":"CABPp-BEM6G4E7mf6280S6vdT-2UDDiKdfXtYS451s--YYO9wGA@mail.gmail.com","threadId":"27574","inReplyTo":"7vhb6aqdw7.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 39/48] merge-recursive: Fix rename/rename(1to2) resolution for virtual merge base","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-08T22:58:20Z","receivedAt":"2011-08-08T22:58:20Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jul 25, 2011 at 2:55 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Elijah Newren <newren@gmail.com> writes:\n>> +             update_file(o, 0, mfi.sha, mfi.mode, src);\n>> +             remove_file_from_cache(ren1_dst);\n>> +             remove_file_from_cache(ren2_dst);\n>\n> ... the use of \"src\" here is taking a \"middle ground\" and punting on\n> resolving the conflicting opinions of both branches for the outer merge to\n> resolve. I think that is a sensible thing to do.\n>\n> The rename destinations of both branches are removed. What happens if\n> ren1_dst (the path one branch wanted to rename src to) were added by the\n> other branch (which wanted to rename src to ren2_dst), causing rename/add\n> conflict between branches (i.e. not the one you worry about in the above\n> FIXME which is about one branch renaming src to ren1_dst while adding an\n> unrelated content to src)?\n\nYou found another bug; this time one that was already present in git\nrather than having been introduced by my series (the\nremove_file_from_cache() calls were already present, I just moved them\naround a little bit).  I have a testcase that exposes this new bug\nnow.\n\nGood catch.  :-)\n"},{"id":"173199","messageId":"4E40DB55.5020101@viscovery.net","threadId":"27574","inReplyTo":"CABPp-BEBJnMCwKKL67=LkB8pea+7QkpoXih3P7rfF5AiDRQYZQ@mail.gmail.com","subject":"Re: [PATCH 26/48] merge-recursive: Allow make_room_for_path() to remove D/F entries","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2011-08-09T07:01:41Z","receivedAt":"2011-08-09T07:01:41Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 8/8/2011 22:56, schrieb Elijah Newren:\n> Hi,\n> \n> On Wed, Jul 13, 2011 at 1:17 AM, Johannes Sixt <j.sixt@viscovery.net> wrote:\n>> Am 6/8/2011 9:30, schrieb Elijah Newren:\n>>> +static int make_room_for_path(const struct merge_options *o, const char *path)\n>>>  {\n>>> -     int status;\n>>> +     int status, i;\n>>>       const char *msg = \"failed to create path '%s'%s\";\n>>>\n>>> +     /* Unlink any D/F conflict files that are in the way */\n>>> +     for (i = 0; i < o->df_conflict_file_set.nr; i++) {\n>>> +             const char *df_path = o->df_conflict_file_set.items[i].string;\n>>> +             size_t pathlen = strlen(path);\n>>> +             size_t df_pathlen = strlen(df_path);\n>>> +             if (df_pathlen < pathlen && strncmp(path, df_path, df_pathlen) == 0) {\n>>> +                     unlink(df_path);\n>>> +                     break;\n>>> +             }\n>>> +     }\n>>\n>> Each time this loop is entered it tries to remove the same path again,\n>> even if it does not exist anymore or was morphed into a directory in the\n>> meantime. I suggest to remove a path from o->df_conflict_file_set after it\n>> was unlinked. Or even better: have a separate \"make room\" phase somewhere\n>> in the merge process.\n> \n> Removing it from o->df_conflict_file_set makes sense.  However, there\n> appears to be no API in string_list.h for deleting entries.  Are such\n> operations discouraged?  I'm not sure whether to add such API, just\n> hack it directly, or wait for someone else to come along and change\n> this to a better data structure (such as a hash)...\n> \n> I don't think it's possible to move this \"make room\" phase anywhere\n> earlier in the merge process.  When we have D/F conflicts, the files\n> of those D/F conflicts should only be removed if at least one of the\n> paths under the corresponding directory are not removed by the merge\n> process.  We don't know whether those paths will need to be removed\n> until we call process_entry() on each of them, and from there we go\n> right to this function when we find one that needs to stick around.\n> So I simply don't see how to move it any earlier.\n\nI missed that the loop is selective: It removes only a subset of the\npaths. Without a deeper understanding of the merge machinery I'll just\naccept what you explain. Removing the paths from the list that were\nunlinked seems to be the right approach.\n\nHere's an attempt for a delete_item API (note: only compile-tested).\nBike-shed painters welcome: delete_item, remove_item, free_item?\n\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n\ndiff --git a/Documentation/technical/api-string-list.txt b/Documentation/technical/api-string-list.txt\nindex 3f575bd..ce24eb9 100644\n--- a/Documentation/technical/api-string-list.txt\n+++ b/Documentation/technical/api-string-list.txt\n@@ -29,6 +29,9 @@ member (you need this if you add things later) and you should set the\n \n . Can sort an unsorted list using `sort_string_list`.\n \n+. Can remove individual items of an unsorted list using\n+  `unsorted_string_list_delete_item`.\n+\n . Finally it should free the list using `string_list_clear`.\n \n Example:\n@@ -112,6 +115,13 @@ write `string_list_insert(...)->util = ...;`.\n The above two functions need to look through all items, as opposed to their\n counterpart for sorted lists, which performs a binary search.\n \n+`unsorted_string_list_delete_item`::\n+\n+\tRemove an item from a string_list. The `string` pointer of the items\n+\twill be freed in case the `strdup_strings` member of the string_list\n+\tis set. The third parameter controls if the `util` pointer of the\n+\titems should be freed or not.\n+\n Data structures\n ---------------\n \ndiff --git a/string-list.c b/string-list.c\nindex 5168118..d9810ab 100644\n--- a/string-list.c\n+++ b/string-list.c\n@@ -185,3 +185,12 @@ int unsorted_string_list_has_string(struct string_list *list,\n \treturn unsorted_string_list_lookup(list, string) != NULL;\n }\n \n+void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util)\n+{\n+\tif (list->strdup_strings)\n+\t\tfree(list->items[i].string);\n+\tif (free_util)\n+\t\tfree(list->items[i].util);\n+\tlist->items[i] = list->items[list->nr-1];\n+\tlist->nr--;\n+}\ndiff --git a/string-list.h b/string-list.h\nindex bda6983..0684cb7 100644\n--- a/string-list.h\n+++ b/string-list.h\n@@ -44,4 +44,5 @@ void sort_string_list(struct string_list *list);\n int unsorted_string_list_has_string(struct string_list *list, const char *string);\n struct string_list_item *unsorted_string_list_lookup(struct string_list *list,\n \t\t\t\t\t\t     const char *string);\n+void unsorted_string_list_delete_item(struct string_list *list, int i, int free_util);\n #endif /* STRING_LIST_H */\n\n\n> [...]\n> \n> However, I don't see how any of this would address any failure you're\n> seeing on windows.  Maybe one of my other changes, including one or\n> two other bugfixes I've found will help?  I'll have to ping you when I\n> submit the re-roll.\n\nI stumbled over this only because on Windows we have an implementation of\nunlink() that asks whether to retry a failed unlink attempt. I was using\nan old version that asks when a directory was tried to unlink where a\nretry is pointless.\n\n-- Hannes\n"}]}