{"thread":{"id":"28074","subject":"[PATCHv2 02/56] t6042: Add failing testcase for rename/modify/add-source conflict","startedAt":"2011-08-12T05:19:33Z","lastAt":"2011-08-13T09:08:17Z","messageCount":65,"participants":["Elijah Newren","Junio C Hamano","Johannes Sixt","Alex Riesen"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"173387","messageId":"1313126429-17368-1-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":null,"subject":"[PATCHv2 00/57] Re-roll of en/merge-recursive from pu","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:33Z","receivedAt":"2011-08-12T05:19:33Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is a re-roll of en/merge-recursive from pu, fixing half a dozen\nbugs (some pre-existing ones, some caused by my first series).\n\nI think the series is now ready for wide consumption and should be\nsafe for the upcoming release, particularly due to some heavy\nadditional testing I performed.  If others disagree, I'm fine and can\nwait out another release cycle.  Either way, I am open for suggestions\nor ideas on additional testing people would like to see if they have\nworries about the robustness of these changes.\n\n\n*** Heavy additional testing I performed ***\n\nBecause it's so hard to rule out regressions with so many changes to a\ncomplicated portion of the code (though hopefully it is less complicated\nnow), and because we've had multiple problems in the past with the\nchanges I've been making to merge-recursive, I came up with an idea to\ntest this series more thoroughly.  So, I wrote a script to take every\nsingle merge commit in git.git that had exactly two parents (no octopus\nmerges) and redid them both with /usr/bin/git and the version of git\nfrom this series.  I checked to ensure that the two different versions\nof git:\n  (a) EITHER both failed to merge cleanly OR both merged cleanly\nAND\n  (b) the output of 'git ls-tree -r HEAD' matched between the two\n\nI ran this process with the original version of the series and indeed\nfound that my original series mis-merged half a dozen or so merges (out\nof about 5000).\n\nWith this new version of the series, I get no such mis-merges, which\ngives me relatively strong confidence that there are no merge\nregressions relative to git-1.7.4.4 (the version I happened to have in\n/usr/bin).\n\n*** What's fixed ***\n\nEverything that I claimed the original series fixed, is still fixed in\nthis series.\n\nThis series fixes the following bugs from the previous series:\n  * The mis-merge that Junio reported (which would have been found had I\n    been more thorough in the new testcases I myself had added -- oops)\n  * The windows bug identified by Johannes (at least I hope I fixed that\n    one -- I'll need him to verify)\n  * The potential incorrect deletion of a path that I reported (while\n    discussing the patch that Johannes flagged for other reasons)\n  * A fix to my more detailed conflict markers when renames are involved\n    (it sometimes swapped filenames between branches)\n\nThis series also fixes the following bugs that exist in current git and\nhad not been fixed by my previous series:\n  * The incorrectly-deleted file in the created virtual merge base problem\n    that Junio identified when rename/rename(1to2) conflicts also involved\n    rename/add-dest conflicts for one or more of the two renames.  [That's\n    a mouthful, isn't it?]\n  * Sometimes git would erroneously print 'refusing to lose untracked file...'\n    warning messages.\n  * There are three more cases where files were needlessly getting rewritten\n    despite already having the correct contents (though none of these\n    cases involved content merges).\n\nThe fixes to problems in the original series were made as modifications to\nthe relevant patches.  The new fixes were simply added to the end of the\nseries, in order to facilitate review.\n\n*** What's changed ***\n\nI took the series with Junio's modifications in pu, and made changes as\nnecessary to address his comments and other problems that were found.  I\nhave left his signed-off-by on patches where no changes were made.  Most\npatches were unchanged; the full list of changed patches are:\n\n  [05] t6042: Add tests for content issues with modify/rename/directory conflicts\n    Changes: Fleshed out the commit message\n\n  [07] t6042: Ensure rename/rename conflicts leave index and workdir in sane state\n    Changes: Move this patch prior to the previous one (as suggested by\n             Junio), since it has a simpler rename/rename(1to2) case in\n             it.  (Did not remove Junio's s-o-b, since this was a\n             trivial test-reorder change.)\n\n  [08] t6036: Add differently resolved modify/delete conflict in criss-cross test\n    Changes: Made wording change suggested by Junio.  (Did not remove\n             Junio's s-o-b, since I took his exact suggestion.)\n\n  [10] t6036: tests for criss-cross merges with various directory/file conflicts\n    Changes: Lots of wording cleanups and making the tests more thorough\n\n  [11] t6036: criss-cross w/ rename/rename(1to2)/modify+rename/rename(2to1)/modify\n    Changes: More thorough testing of result, usage of some of Junio's\n             wording in a comment\n\n  [12] t6036: criss-cross + rename/rename(1to2)/add-source + modify/modify\n    Changes: More thorough testing of result, improvement on wording describing test\n\n  [15] t6022: Add testcase for merging a renamed file with a simple change\n    Changes: Fix to check actual result is correct; that would have caught the\n             bug Junio instead found the hard way.\n\n  [19] merge-recursive: Consolidate different update_stages functions\n    Changes: Moved earlier in the series, as per Junio's request; used\n             to be patch number 27.  (Did not remove Junio's s-o-b,\n             since no significant changes were necessary.)\n\n  [23] merge-recursive: Fix sorting order and directory change assumptions\n    Changes: Mentioned Johannes' squashed-in changes and added his\n             signed-off-by from his squashed-in patch.  (Did not remove\n             Junio's s-o-b, since he had added his to Johannes' patch.)\n\n  [27] string-list: Add API to remove an item from an unsorted list\n    Changes: This is an entirely new patch, recently submitted to the list by\n             Johannes.\n\n  [28] merge-recursive: Allow make_room_for_path() to remove D/F entries\n    Changes: (1) Made use of Johannes' new string-list API to remove\n             items we have already checked in order to avoid trying to\n             unlink a path more than once, (2) fixed bug with not\n             checking that basepath was directory\n\n  [36] merge-recursive: Provide more info in conflict markers with file renames\n    Changes: Fix bug where I could get filenames from two branches reversed\n\n  \n  [37] merge-recursive: When we detect we can skip an update, actually skip it\n    Changes: Moved to later in the series (used to be patch #29).  Also,\n             changed the \"was_tracked\" logic to use information about\n             what paths were involved in renames to determine whether\n             the file really 'was tracked' before the merge started.\n             Also, added another minor tweak to get the expected\n             \"Skipped\" messages that t6022.12 expects.\n\n  [51-56]\n    These are entirely new patches providing testcases and fixes for\n    bugs that exist with current git and were not fixed in my previous\n    series (but probably should have been).\n\n*** What is still missing ***\n\nTwo things:\n  * Junio had a great suggestion about alternate handling of the index in the\n    case of rename/rename(2to1) and directory/file conflicts (just rename the\n    entry in the index to match how we are renaming in the working copy to\n    some new unique name, in order to allow 'git diff' to provide more useful\n    information to the user).  Just didn't get to it.\n  * Support for running break detection in diffs, in order to fix the testcase\n    corrected by Peff in this series.  Simply didn't get around to it either.\nNeither of these two things represent regressions relative to what is in\ncurrent releases of git, however.\n\n*** Full patch list and diffstat ***\n\nElijah Newren (54):\n  [01] t6042: Add a testcase where git deletes an untracked file\n  [02] t6042: Add failing testcase for rename/modify/add-source conflict\n  [03] t6042: Add a pair of cases where undetected renames cause issues\n  [04] t6042: Add a testcase where undetected rename causes silent file deletion\n  [05] t6042: Add tests for content issues with modify/rename/directory conflicts\n  [06] t6042: Ensure rename/rename conflicts leave index and workdir in sane state\n  [07] t6042: Add failing testcases for rename/rename/add-{source,dest} conflicts\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  [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  [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: Consolidate different update_stages functions\n  [20] merge-recursive: Remember to free generated unique path names\n  [21] merge-recursive: Avoid working directory changes during recursive case\n  [22] merge-recursive: Fix recursive case with D/F conflict via add/add conflict\n  [23] merge-recursive: Fix sorting order and directory change assumptions\n  [24] merge-recursive: Fix code checking for D/F conflicts still being present\n  [25] merge-recursive: Save D/F conflict filenames instead of unlinking them\n  [26] merge-recursive: Split was_tracked() out of would_lose_untracked()\n  [28] merge-recursive: Allow make_room_for_path() to remove D/F entries\n  [29] merge-recursive: Split update_stages_and_entry; only update stages at end\n  [30] merge-recursive: Fix deletion of untracked file in rename/delete conflicts\n  [31] merge-recursive: Make dead code for rename/rename(2to1) conflicts undead\n  [32] merge-recursive: Add comments about handling rename/add-source cases\n  [33] merge-recursive: Improve handling of rename target vs. directory addition\n  [34] merge-recursive: Consolidate process_entry() and process_df_entry()\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: When we detect we can skip an update, actually skip it\n  [38] merge-recursive: Fix modify/delete resolution in the recursive case\n  [39] merge-recursive: Introduce a merge_file convenience function\n  [40] merge-recursive: Fix rename/rename(1to2) resolution for virtual merge base\n  [41] merge-recursive: Small cleanups for conflict_rename_rename_1to2\n  [42] merge-recursive: Defer rename/rename(2to1) handling until process_entry\n  [43] merge-recursive: Record more data needed for merging with dual renames\n  [44] merge-recursive: Create function for merging with branchname:file markers\n  [45] merge-recursive: Consider modifications in rename/rename(2to1) conflicts\n  [46] merge-recursive: Make modify/delete handling code reusable\n  [47] merge-recursive: Have conflict_rename_delete reuse modify/delete code\n  [48] merge-recursive: add handling for rename/rename/add-dest/add-dest\n  [49] merge-recursive: Fix working copy handling for rename/rename/add/add\n  [51] t6022: Add testcase for spurious \"refusing to lose untracked\" messages\n  [52] merge-recursive: Fix spurious 'refusing to lose untracked file...' messages\n  [53] t6022: Additional tests checking for unnecessary updates of files\n  [54] merge-recursive: Avoid unnecessary file rewrites\n  [55] t6042: Add testcase demonstrating missing file in virtual merge base\n  [56] merge-recursive: Fix virtual merge base for rename/rename(1to2)/add-dest\n\nJeff King (1):\n  [50] t3030: fix accidental success in symlink rename\n\nJohannes Sixt (1):\n  [27] string-list: Add API to remove an item from an unsorted list\n\n Documentation/technical/api-string-list.txt |   10 +\n merge-recursive.c                           | 1067 +++++++++++++++++----------\n merge-recursive.h                           |    1 +\n string-list.c                               |    9 +\n string-list.h                               |    1 +\n t/t3030-merge-recursive.sh                  |    7 +-\n t/t6020-merge-df.sh                         |   26 +-\n t/t6022-merge-rename.sh                     |  291 +++++++-\n t/t6036-recursive-corner-cases.sh           |  598 ++++++++++++++-\n t/t6042-merge-rename-corner-cases.sh        |  625 ++++++++++++++++\n 10 files changed, 2207 insertions(+), 428 deletions(-)\n create mode 100755 t/t6042-merge-rename-corner-cases.sh\n\n-- \n1.7.6.100.gac5c1\n"},{"id":"173335","messageId":"1313126429-17368-2-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 01/56] t6042: Add a testcase where git deletes an untracked file","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:34Z","receivedAt":"2011-08-12T05:19:34Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n t/t6036-recursive-corner-cases.sh    |    2 +-\n t/t6042-merge-rename-corner-cases.sh |   36 ++++++++++++++++++++++++++++++++++\n 2 files changed, 37 insertions(+), 1 deletions(-)\n create mode 100755 t/t6042-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/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nnew file mode 100755\nindex 0000000..5054459\n--- /dev/null\n+++ b/t/t6042-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.100.gac5c1\n"},{"id":"173334","messageId":"1313126429-17368-3-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 02/56] t6042: Add failing testcase for rename/modify/add-source conflict","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:35Z","receivedAt":"2011-08-12T05:19:35Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n t/t6042-merge-rename-corner-cases.sh |   39 ++++++++++++++++++++++++++++++++++\n 1 files changed, 39 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 5054459..276d7dd 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-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.100.gac5c1\n"},{"id":"173336","messageId":"1313126429-17368-4-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 03/56] t6042: Add a pair of cases where undetected renames cause issues","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:36Z","receivedAt":"2011-08-12T05:19:36Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n t/t6042-merge-rename-corner-cases.sh |   61 ++++++++++++++++++++++++++++++++++\n 1 files changed, 61 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 276d7dd..f338fb4 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-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.100.gac5c1\n"},{"id":"173338","messageId":"1313126429-17368-5-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 04/56] t6042: Add a testcase where undetected rename causes silent file deletion","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:37Z","receivedAt":"2011-08-12T05:19:37Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n t/t6042-merge-rename-corner-cases.sh |   65 ++++++++++++++++++++++++++++++++++\n 1 files changed, 65 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex f338fb4..db5560c 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-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.100.gac5c1\n"},{"id":"173337","messageId":"1313126429-17368-6-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 05/56] t6042: Add tests for content issues with modify/rename/directory conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:38Z","receivedAt":"2011-08-12T05:19:38Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Add testcases that cover a variety of merge issues with files being\nrenamed and modified on different sides of history, when there are\ndirectories possibly conflicting with the rename location.\n\nCase 1:\nOn one side of history, a file is modified and a new directory is added.\nOn the other side of history, the file is modified in a non-conflicting\nway but is renamed to the location of the new directory.\n\nCase 2:\n[Same as case 1, but there is also a content conflict.  In detail:]\nOn one side of history, a file is modified and a new directory is added.\nOn the other side of history, the file is modified in a conflicting way\nand it is renamed to the location of the new directory.\n\nCase 3:\n[Similar to case 1, but the \"conflicting\" directory is the directory\nwhere the file original resided.  In detail:]\nOn one side of history, a file is modified.  On the other side of history,\nthe file is modified in a non-conflicting way, but the directory it was\nunder is removed and the file is renamed to the location of the directory\nit used to reside in (i.e. 'sub/file' gets renamed to 'sub').  This is\nflagged as a directory/rename conflict, but should be able to be resolved\nsince the directory can be cleanly removed by the merge.\n\nOne branch renames a file and makes a file where the directory the renamed\nfile used to be in, and the other branch updates the file in\nplace. Merging them should resolve it cleanly as long as the content level\nchange on the branches do not overlap and rename is detected, or should\nleave conflict without losing information.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nChanges since v1: New and improved commit message.\n\n t/t6042-merge-rename-corner-cases.sh |  141 ++++++++++++++++++++++++++++++++++\n 1 files changed, 141 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex db5560c..b465667 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-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.100.gac5c1\n"},{"id":"173341","messageId":"1313126429-17368-7-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 06/56] t6042: Ensure rename/rename conflicts leave index and workdir in sane state","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:39Z","receivedAt":"2011-08-12T05:19:39Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n t/t6042-merge-rename-corner-cases.sh |  102 ++++++++++++++++++++++++++++++++++\n 1 files changed, 102 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex b465667..371fb39 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -339,4 +339,106 @@ test_expect_success 'disappearing dir in rename/directory conflict handled' '\n \ttest -f sub\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+# Testcase setup for simple rename/rename (1to2) conflict:\n+#   Commit A: new file: a\n+#   Commit B: rename a->b\n+#   Commit C: rename a->c\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 $(git rev-parse :1:a) = $(git rev-parse A:a) &&\n+\ttest $(git rev-parse :3:b) = $(git rev-parse A:a) &&\n+\ttest $(git rev-parse :2:c) = $(git rev-parse A:a) &&\n+\n+\ttest ! -f a &&\n+\ttest $(git hash-object b) = $(git rev-parse A:a) &&\n+\ttest $(git hash-object c) = $(git rev-parse A:a)\n+'\n+\n test_done\n-- \n1.7.6.100.gac5c1\n"},{"id":"173385","messageId":"1313126429-17368-8-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 07/56] t6042: Add failing testcases for rename/rename/add-{source,dest} conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:40Z","receivedAt":"2011-08-12T05:19:40Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nChanges since v1: moved this patch (and its content) prior to the\n  patch it used to follow (as suggested by Junio), since it has a\n  simpler rename/rename(1to2) case in it.\n\n t/t6042-merge-rename-corner-cases.sh |  125 ++++++++++++++++++++++++++++++++++\n 1 files changed, 125 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 371fb39..427fe1c 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -441,4 +441,129 @@ test_expect_success 'merge has correct working tree contents' '\n \ttest $(git hash-object c) = $(git rev-parse A:a)\n '\n \n+# Testcase setup for rename/rename(1to2)/add-source conflict:\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 4 -eq $(git ls-files -s | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse 3:a) = $(git rev-parse C:a) &&\n+\ttest $(git rev-parse 1:a) = $(git rev-parse A:a) &&\n+\ttest $(git rev-parse 2:b) = $(git rev-parse B:b) &&\n+\ttest $(git rev-parse 3:c) = $(git rev-parse C:c) &&\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+\tgit add 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+\ttest $(git rev-parse HEAD:a) = $(git rev-parse C:a) &&\n+\ttest $(git rev-parse HEAD:b) = $(git rev-parse A:a)\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 :1:a) = $(git rev-parse A:a) &&\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.100.gac5c1\n"},{"id":"173340","messageId":"1313126429-17368-9-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 08/56] t6036: Add differently resolved modify/delete conflict in criss-cross test","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:41Z","receivedAt":"2011-08-12T05:19:41Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Signed-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nChanges since v1: Made wording change suggested by Junio.\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..90b50bb 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+# Merging commits D & E should result in modify/delete 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.100.gac5c1\n"},{"id":"173388","messageId":"1313126429-17368-10-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 09/56] t6036: criss-cross with weird content can fool git into clean merge","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:42Z","receivedAt":"2011-08-12T05:19:42Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Signed-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 90b50bb..991c56d 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.100.gac5c1\n"},{"id":"173339","messageId":"1313126429-17368-11-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 10/56] t6036: tests for criss-cross merges with various directory/file conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:43Z","receivedAt":"2011-08-12T05:19:43Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Signed-off-by: Elijah Newren <newren@gmail.com>\n---\nChanges since v1: Lots of wording cleanups, made the tests more thorough.\n\n t/t6036-recursive-corner-cases.sh |  159 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 159 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 991c56d..acff84d 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -4,6 +4,12 @@ test_description='recursive merge corner cases involving criss-cross merges'\n \n . ./test-lib.sh\n \n+get_clean_checkout () {\n+\tgit reset --hard &&\n+\tgit clean -fdqx &&\n+\tgit checkout \"$1\"\n+}\n+\n #\n #  L1  L2\n #   o---o\n@@ -397,4 +403,157 @@ 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+#   Commit D: Merge B & C, keeping 'a' and deleting 'a/'\n+#\n+# Two different later cases:\n+#   Commit E1: Merge B & C, deleting 'a' but keeping 'a/file'\n+#   Commit E2: Merge B & C, deleting 'a' but keeping a slightly modified 'a/file'\n+#\n+#      B   D\n+#      o---o\n+#     / \\ / \\\n+#  A o   X   ? F\n+#     \\ / \\ /\n+#      o---o\n+#      C   E1 or E2\n+#\n+# Merging D & E1 requires we first create a virtual merge base X from\n+# merging A & B in memory.  Now, if X could keep both 'a' and 'a/file' in\n+# the index, then the merge of D & E1 could be resolved cleanly with both\n+# 'a' and 'a/file' removed.  Since git does not currently allow creating\n+# such a tree, the best we can do is have X contain both 'a~<unique>' and\n+# 'a/file' resulting in the merge of D and E1 having a rename/delete\n+# conflict for 'a'.  (Although this merge appears to be unsolvable with git\n+# currently, git could do a lot better than it currently does with these\n+# d/f conflicts, which is the purpose of this test.)\n+#\n+# Merge of D & E2 has similar issues for path 'a', but should always result\n+# in a modify/delete conflict for path 'a/file'.\n+#\n+# We run each merge in both directions, to check for directional issues\n+# with D/F conflict handling.\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>ignore-me &&\n+\tgit add ignore-me &&\n+\ttest_tick &&\n+\tgit commit -m A &&\n+\tgit tag 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 'merge of D & E1 fails but has appropriate contents' '\n+\tget_clean_checkout D^0 &&\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 :0:ignore-me) = $(git rev-parse A:ignore-me) &&\n+\ttest $(git rev-parse :2:a) = $(git rev-parse B:a)\n+'\n+\n+test_expect_failure 'merge of E1 & D fails but has appropriate contents' '\n+\tget_clean_checkout E1^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 1 -eq $(git ls-files -u | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\n+\ttest $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&\n+\ttest $(git rev-parse :3:a) = $(git rev-parse B:a)\n+'\n+\n+test_expect_success 'merge of D & E2 fails but has appropriate contents' '\n+\tget_clean_checkout D^0 &&\n+\n+\ttest_must_fail git merge -s recursive E2^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+\ttest $(git rev-parse :2:a) = $(git rev-parse B:a) &&\n+\ttest $(git rev-parse :3:a/file) = $(git rev-parse E2:a/file) &&\n+\ttest $(git rev-parse :1:a/file) = $(git rev-parse C:a/file) &&\n+\ttest $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&\n+\n+\ttest -f a~HEAD\n+'\n+\n+test_expect_failure 'merge of E2 & D fails but has appropriate contents' '\n+\tget_clean_checkout E2^0 &&\n+\n+\ttest_must_fail git merge -s recursive D^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+\ttest $(git rev-parse :3:a) = $(git rev-parse B:a) &&\n+\ttest $(git rev-parse :2:a/file) = $(git rev-parse E2:a/file) &&\n+\ttest $(git rev-parse :1:a/file) = $(git rev-parse C:a/file)\n+\ttest $(git rev-parse :0:ignore-me) = $(git rev-parse A:ignore-me) &&\n+\n+\ttest -f a~D^0\n+'\n+\n test_done\n-- \n1.7.6.100.gac5c1\n"},{"id":"173390","messageId":"1313126429-17368-12-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 11/56] t6036: criss-cross w/ rename/rename(1to2)/modify+rename/rename(2to1)/modify","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:44Z","receivedAt":"2011-08-12T05:19:44Z","isPatch":false,"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---\nChanges since v1: More thorough testing of results, usage of some of\n  Junio's wording in a comment.\n\n t/t6036-recursive-corner-cases.sh |   88 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 88 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex acff84d..38cace6 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -556,4 +556,92 @@ test_expect_failure 'merge of E2 & D fails but has appropriate contents' '\n \ttest -f a~D^0\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+# Comment from Junio: I do not necessarily agree with the choice \"a\", but\n+# it feels sound to say \"B and C do not agree what the final pathname\n+# should be, but we know this content was derived from the common A:a so we\n+# use one path whose name is arbitrary in the virtual merge base X between\n+# D and E\" and then further let the rename detection to notice that that\n+# arbitrary path gets renamed between X-D to \"newname\" and X-E also to\n+# \"newname\" to resolve it as both sides renaming it to the same new\n+# name. It is akin to what we do at the content level, i.e. \"B and C do not\n+# agree what the final contents should be, so we leave the conflict marker\n+# but that may cancel out at the final merge stage\".\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 $(git rev-parse HEAD:newname) = $(git rev-parse E:newname)\n+'\n+\n test_done\n-- \n1.7.6.100.gac5c1\n"},{"id":"173343","messageId":"1313126429-17368-13-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 12/56] t6036: criss-cross + rename/rename(1to2)/add-source + modify/modify","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:45Z","receivedAt":"2011-08-12T05:19:45Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is another challenging testcase trying to exercise the virtual merge\nbase creation in the rename/rename(1to2) code.  A testcase is added that\nwe should be able to merge cleanly, but which requires a virtual merge\nbase to be created that is aware of rename/rename(1to2)/add-source\nconflicts and can handle those.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nChanges since v1: More thorough testing of results, improvement on\n  wording in description of testcase.\n\n t/t6036-recursive-corner-cases.sh |   77 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 77 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex 38cace6..526a2ea 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -644,4 +644,81 @@ test_expect_failure 'handle rename/rename(1to2)/modify followed by what looks li\n \ttest $(git rev-parse HEAD:newname) = $(git rev-parse E: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+# Merging commits D & E should result in no conflict; doing so correctly\n+# requires getting the virtual merge base (from merging B&C) right, handling\n+# renaming carefully (both in the virtual merge base and later), and getting\n+# content merge handled.\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 'detect rename/rename/add-source for virtual merge-base' '\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 $(git rev-parse HEAD:b) = $(git rev-parse A:a) &&\n+\ttest $(git rev-parse HEAD:c) = $(git rev-parse A:a) &&\n+\ttest \"$(cat a)\" = \"$(printf \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n\")\"\n+'\n+\n test_done\n-- \n1.7.6.100.gac5c1\n"},{"id":"173342","messageId":"1313126429-17368-14-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 13/56] t6022: Remove unnecessary untracked files to make test cleaner","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:46Z","receivedAt":"2011-08-12T05:19:46Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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.100.gac5c1\n"},{"id":"173345","messageId":"1313126429-17368-15-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 14/56] t6022: New tests checking for unnecessary updates of files","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:47Z","receivedAt":"2011-08-12T05:19:47Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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.100.gac5c1\n"},{"id":"173386","messageId":"1313126429-17368-16-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 15/56] t6022: Add testcase for merging a renamed file with a simple change","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:48Z","receivedAt":"2011-08-12T05:19:48Z","isPatch":false,"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---\nChanges since v1: Check that the result of the merge is actually\n  correct; had I done that in v1, I would have caught the bug that\n  Junio found the hard way.\n\n t/t6022-merge-rename.sh |   27 +++++++++++++++++++++++++++\n 1 files changed, 27 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 11c5c60..6ff4bd2 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -673,4 +673,31 @@ 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+\tgit clean -fdqx &&\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+\ttest 1 -eq $(git ls-files -s | wc -l) &&\n+\ttest 0 -eq $(git ls-files -o | wc -l) &&\n+\ttest $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file)\n+'\n+\n test_done\n-- \n1.7.6.100.gac5c1\n"},{"id":"173348","messageId":"1313126429-17368-17-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 16/56] merge-recursive: Make BUG message more legible by adding a newline","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:49Z","receivedAt":"2011-08-12T05:19:49Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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.100.gac5c1\n"},{"id":"173389","messageId":"1313126429-17368-18-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 17/56] merge-recursive: Correct a comment","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:50Z","receivedAt":"2011-08-12T05:19:50Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Signed-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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.100.gac5c1\n"},{"id":"173349","messageId":"1313126429-17368-19-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 18/56] merge-recursive: Mark some diff_filespec struct arguments const","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:51Z","receivedAt":"2011-08-12T05:19:51Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Signed-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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.100.gac5c1\n"},{"id":"173344","messageId":"1313126429-17368-20-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 19/56] merge-recursive: Consolidate different update_stages functions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:52Z","receivedAt":"2011-08-12T05:19:52Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nChanges since v1: This patch now appears much earlier in the series than before.\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 317bf23..8a5c1a6 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -461,11 +461,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@@ -481,14 +482,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@@ -505,8 +498,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@@ -862,8 +854,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 (lstat(dest_name, &st) == 0 && S_ISDIR(st.st_mode)) {\n \t\tdest_name = unique_path(o, dest_name, rename_branch);\n \t\tdf_conflict = 1;\n@@ -907,8 +898,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.100.gac5c1\n"},{"id":"173384","messageId":"1313126429-17368-21-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 20/56] merge-recursive: Remember to free generated unique path names","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:53Z","receivedAt":"2011-08-12T05:19:53Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Signed-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c |   20 ++++++++++++--------\n 1 files changed, 12 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 8a5c1a6..b5a8f17 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1063,7 +1063,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@@ -1092,9 +1091,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@@ -1262,13 +1262,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@@ -1424,12 +1425,14 @@ static int process_df_entry(struct merge_options *o,\n \t\t}\n \t} else if (o_sha && (!a_sha || !b_sha)) {\n \t\t/* Modify/delete; deleted side may have put a directory in the way */\n-\t\tconst char *new_path = path;\n-\t\tif (lstat(path, &st) == 0 && S_ISDIR(st.st_mode))\n-\t\t\tnew_path = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n+\t\tchar *renamed = NULL;\n+\t\tif (lstat(path, &st) == 0 && S_ISDIR(st.st_mode)) {\n+\t\t\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n+\t\t}\n \t\tclean_merge = 0;\n-\t\thandle_delete_modify(o, path, new_path,\n+\t\thandle_delete_modify(o, path, renamed ? renamed : path,\n \t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n+\t\tfree(renamed);\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@@ -1452,12 +1455,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.100.gac5c1\n"},{"id":"173346","messageId":"1313126429-17368-22-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 21/56] merge-recursive: Avoid working directory changes during recursive case","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:54Z","receivedAt":"2011-08-12T05:19:54Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 b5a8f17..8863b02 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.100.gac5c1\n"},{"id":"173347","messageId":"1313126429-17368-23-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 22/56] merge-recursive: Fix recursive case with D/F conflict via add/add conflict","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:55Z","receivedAt":"2011-08-12T05:19:55Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 8863b02..85620f7 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1468,6 +1468,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 526a2ea..ed6c6f4 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -509,7 +509,7 @@ test_expect_failure 'merge of D & E1 fails but has appropriate contents' '\n \ttest $(git rev-parse :2:a) = $(git rev-parse B:a)\n '\n \n-test_expect_failure 'merge of E1 & D fails but has appropriate contents' '\n+test_expect_success 'merge of E1 & D fails but has appropriate contents' '\n \tget_clean_checkout E1^0 &&\n \n \ttest_must_fail git merge -s recursive D^0 &&\n@@ -539,7 +539,7 @@ test_expect_success 'merge of D & E2 fails but has appropriate contents' '\n \ttest -f a~HEAD\n '\n \n-test_expect_failure 'merge of E2 & D fails but has appropriate contents' '\n+test_expect_success 'merge of E2 & D fails but has appropriate contents' '\n \tget_clean_checkout E2^0 &&\n \n \ttest_must_fail git merge -s recursive D^0 &&\n-- \n1.7.6.100.gac5c1\n"},{"id":"173358","messageId":"1313126429-17368-24-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 23/56] merge-recursive: Fix sorting order and directory change assumptions","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:56Z","receivedAt":"2011-08-12T05:19:56Z","isPatch":false,"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\nThanks to Johannes for a pointer about qsort stability issues with\nWindows and suggested code change.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Johannes Sixt <j6t@kdbg.org>\n---\nChanges since v1: Mentioned Johannes' pointer and squashed in his\n  patch that Junio had appended to the end of the en/merge-recursive\n  series.  Added his s-o-b because of that.\n\n merge-recursive.c   |   40 +++++++++++++++++++++++++++++++++++-----\n t/t6020-merge-df.sh |   26 ++++++++++++++++++--------\n 2 files changed, 53 insertions(+), 13 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 85620f7..76b895f 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -333,6 +333,37 @@ 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+\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+\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+\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+\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 +376,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 +388,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.100.gac5c1\n"},{"id":"173383","messageId":"1313126429-17368-25-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 24/56] merge-recursive: Fix code checking for D/F conflicts still being present","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:57Z","receivedAt":"2011-08-12T05:19:57Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 76b895f..9aacf5e 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -593,6 +593,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 pos, 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+\tpos = 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@@ -882,7 +906,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\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 (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@@ -1463,7 +1482,7 @@ static int process_df_entry(struct merge_options *o,\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 *renamed = NULL;\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\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n \t\t}\n \t\tclean_merge = 0;\n@@ -1491,7 +1510,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.100.gac5c1\n"},{"id":"173354","messageId":"1313126429-17368-26-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 25/56] merge-recursive: Save D/F conflict filenames instead of unlinking them","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:58Z","receivedAt":"2011-08-12T05:19:58Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 9aacf5e..e1723d4 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -362,20 +362,24 @@ static int string_list_df_name_compare(const void *a, const void *b)\n \treturn onelen - twolen;\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@@ -392,6 +396,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@@ -400,14 +405,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@@ -1571,7 +1577,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@@ -1797,6 +1803,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.100.gac5c1\n"},{"id":"173353","messageId":"1313126429-17368-27-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 26/56] merge-recursive: Split was_tracked() out of would_lose_untracked()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:19:59Z","receivedAt":"2011-08-12T05:19:59Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 e1723d4..c12e4d5 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -623,7 +623,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@@ -640,11 +640,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.100.gac5c1\n"},{"id":"173351","messageId":"1313126429-17368-28-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 27/56] string-list: Add API to remove an item from an unsorted list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:00Z","receivedAt":"2011-08-12T05:20:00Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Johannes Sixt <j.sixt@viscovery.net>\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>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nNew patch that wasn't part of v1, submitted by Johannes.\n\n Documentation/technical/api-string-list.txt |   10 ++++++++++\n string-list.c                               |    9 +++++++++\n string-list.h                               |    1 +\n 3 files changed, 20 insertions(+), 0 deletions(-)\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-- \n1.7.6.100.gac5c1\n"},{"id":"173350","messageId":"1313126429-17368-29-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 28/56] merge-recursive: Allow make_room_for_path() to remove D/F entries","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:01Z","receivedAt":"2011-08-12T05:20:01Z","isPatch":false,"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---\nChanges since v1:\n  (1) Made use of Johannes' new string-list API to remove items we\n      have already checked in order to avoid trying to unlink a path\n      more than once\n  (2) Moved the comment about removing paths to make room to the\n      location where we actually remove the path.\n  (3) Fixed a bug with accidentally removing wrong paths (by adding the\n        path[df_pathlen] == '/'\n      check)\n\n merge-recursive.c                 |   26 ++++++++++++++++++++++----\n t/t6036-recursive-corner-cases.sh |    2 +-\n 2 files changed, 23 insertions(+), 5 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c12e4d5..1547691 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -412,7 +412,6 @@ static void record_df_conflict_files(struct merge_options *o,\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\tstring_list_insert(&o->df_conflict_file_set, last_file);\n \t\t}\n \n@@ -652,11 +651,30 @@ 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(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 &&\n+\t\t    path[df_pathlen] == '/' &&\n+\t\t    strncmp(path, df_path, df_pathlen) == 0) {\n+\t\t\toutput(o, 3,\n+\t\t\t       \"Removing %s to make room for subdirectory\\n\",\n+\t\t\t       df_path);\n+\t\t\tunlink(df_path);\n+\t\t\tunsorted_string_list_delete_item(&o->df_conflict_file_set,\n+\t\t\t\t\t\t\t i, 0);\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@@ -724,7 +742,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 ed6c6f4..279f33c 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -496,7 +496,7 @@ test_expect_success 'setup differently handled merges of directory/file conflict\n \tgit tag E2\n '\n \n-test_expect_failure 'merge of D & E1 fails but has appropriate contents' '\n+test_expect_success 'merge of D & E1 fails but has appropriate contents' '\n \tget_clean_checkout D^0 &&\n \n \ttest_must_fail git merge -s recursive E1^0 &&\n-- \n1.7.6.100.gac5c1\n"},{"id":"173352","messageId":"1313126429-17368-30-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 29/56] merge-recursive: Split update_stages_and_entry; only update stages at end","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:02Z","receivedAt":"2011-08-12T05:20:02Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 1547691..650d5ec 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@@ -524,15 +525,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@@ -540,7 +537,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@@ -1099,12 +1095,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@@ -1209,7 +1204,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@@ -1306,6 +1302,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@@ -1346,6 +1343,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@@ -1430,7 +1429,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@@ -1474,7 +1473,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.100.gac5c1\n"},{"id":"173360","messageId":"1313126429-17368-31-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 30/56] merge-recursive: Fix deletion of untracked file in rename/delete conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:03Z","receivedAt":"2011-08-12T05:20:03Z","isPatch":false,"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 remove\n  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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c                    |    3 ++-\n t/t6042-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 650d5ec..f395d20 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1117,7 +1117,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/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 427fe1c..668ec6d 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-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.100.gac5c1\n"},{"id":"173356","messageId":"1313126429-17368-32-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 31/56] merge-recursive: Make dead code for rename/rename(2to1) conflicts undead","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:04Z","receivedAt":"2011-08-12T05:20:04Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 f395d20..f74feb5 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -998,17 +998,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@@ -1023,12 +1042,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@@ -1140,6 +1159,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@@ -1180,16 +1216,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 279f33c..b046e1b 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -66,13 +66,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@@ -145,11 +145,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.100.gac5c1\n"},{"id":"173371","messageId":"1313126429-17368-33-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 32/56] merge-recursive: Add comments about handling rename/add-source cases","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:05Z","receivedAt":"2011-08-12T05:20:05Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 f74feb5..5f28905 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1087,6 +1087,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@@ -1113,6 +1116,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@@ -1136,6 +1143,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.100.gac5c1\n"},{"id":"173372","messageId":"1313126429-17368-34-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 33/56] merge-recursive: Improve handling of rename target vs. directory addition","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:06Z","receivedAt":"2011-08-12T05:20:06Z","isPatch":false,"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 t6042\n(and almost fix another one from t6042 as well).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c                    |   27 ++++++++++++++++++++++-----\n t/t6022-merge-rename.sh              |    4 ++--\n t/t6042-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 5f28905..f1160d5 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1381,19 +1381,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@@ -1582,6 +1597,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 6ff4bd2..fcc1d4c 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/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 668ec6d..968055d 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-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.100.gac5c1\n"},{"id":"173355","messageId":"1313126429-17368-35-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 34/56] merge-recursive: Consolidate process_entry() and process_df_entry()","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:07Z","receivedAt":"2011-08-12T05:20:07Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c |  188 ++++++++++++++++-------------------------------------\n 1 files changed, 57 insertions(+), 131 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f1160d5..74b1139 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@@ -367,20 +366,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@@ -1325,17 +1321,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@@ -1433,93 +1429,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@@ -1554,24 +1463,38 @@ 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 *renamed = NULL;\n-\t\tif (dir_in_way(path, !o->call_depth)) {\n-\t\t\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\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 *renamed = NULL;\n+\t\t\tclean_merge = 0;\n+\t\t\tif (dir_in_way(path, !o->call_depth)) {\n+\t\t\t\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n+\t\t\t}\n+\t\t\thandle_delete_modify(o, path, renamed,\n+\t\t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n+\t\t\tfree(renamed);\n \t\t}\n-\t\tclean_merge = 0;\n-\t\thandle_delete_modify(o, path, renamed ? renamed : path,\n-\t\t\t\t     a_sha, a_mode, b_sha, b_mode);\n-\t\tfree(renamed);\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@@ -1607,10 +1530,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@@ -1658,7 +1591,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; 0 <= i; 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@@ -1666,13 +1599,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.100.gac5c1\n"},{"id":"173357","messageId":"1313126429-17368-36-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 35/56] merge-recursive: Cleanup and consolidation of rename_conflict_info","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:08Z","receivedAt":"2011-08-12T05:20:08Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 74b1139..0635e1b 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@@ -954,10 +954,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@@ -1098,20 +1117,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@@ -1121,8 +1136,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@@ -1153,19 +1174,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@@ -1239,16 +1254,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@@ -1336,12 +1348,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@@ -1361,8 +1372,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@@ -1377,7 +1387,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@@ -1400,7 +1410,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@@ -1430,14 +1440,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@@ -1446,19 +1456,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@@ -1533,7 +1531,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.100.gac5c1\n"},{"id":"173362","messageId":"1313126429-17368-37-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 36/56] merge-recursive: Provide more info in conflict markers with file renames","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:09Z","receivedAt":"2011-08-12T05:20:09Z","isPatch":false,"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---\nChanges since v1: Fix bug where I could get filenames from two\n  branches reversed depending on the direction of the merge.\n\n merge-recursive.c                    |   28 +++++++++++-\n t/t6022-merge-rename.sh              |   75 ++++++++++++++++++++++++++++++++--\n t/t6042-merge-rename-corner-cases.sh |    2 +-\n 3 files changed, 97 insertions(+), 8 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 0635e1b..bc99f1a 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1355,6 +1355,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@@ -1371,10 +1372,31 @@ 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, *path2;\n+\t\tstruct diff_filepair *pair1 = rename_conflict_info->pair1;\n+\n+\t\tpath1 = (o->branch1 == rename_conflict_info->branch1) ?\n+\t\t\tpair1->two->path : pair1->one->path;\n+\t\t/* If rename_conflict_info->pair2 != NULL, we are in\n+\t\t * RENAME_ONE_FILE_TO_ONE case.  Otherwise, we have a\n+\t\t * normal rename.\n+\t\t */\n+\t\tpath2 = (rename_conflict_info->pair2 ||\n+\t\t\t o->branch2 == rename_conflict_info->branch1) ?\n+\t\t\tpair1->two->path : pair1->one->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 fcc1d4c..4695cbc 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:sub/file\n 11\n =======\n 12\n->>>>>>> renamed-file-has-conflicts\n+>>>>>>> renamed-file-has-conflicts:dir\n EOF\n \n test_expect_success 'Same as previous, but merged other way' '\n@@ -700,4 +700,71 @@ test_expect_success 'merge rename + small change' '\n \ttest $(git rev-parse HEAD:renamed_file) = $(git rev-parse HEAD~1:file)\n '\n \n+test_expect_success 'setup for use of extended merge markers' '\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\\n8\\n\" >original_file &&\n+\tgit add original_file &&\n+\tgit commit -mA &&\n+\n+\tgit checkout -b rename &&\n+\techo 9 >>original_file &&\n+\tgit add original_file &&\n+\tgit mv original_file renamed_file &&\n+\tgit commit -mB &&\n+\n+\tgit checkout master &&\n+\techo 8.5 >>original_file &&\n+\tgit add original_file &&\n+\tgit commit -mC\n+'\n+\n+cat >expected <<\\EOF &&\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+<<<<<<< HEAD:renamed_file\n+9\n+=======\n+8.5\n+>>>>>>> master^0:original_file\n+EOF\n+\n+test_expect_success 'merge master into rename has correct extended markers' '\n+\tgit checkout rename^0 &&\n+\ttest_must_fail git merge -s recursive master^0 &&\n+\ttest_cmp expected renamed_file\n+'\n+\n+cat >expected <<\\EOF &&\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+<<<<<<< HEAD:original_file\n+8.5\n+=======\n+9\n+>>>>>>> rename^0:renamed_file\n+EOF\n+\n+test_expect_success 'merge rename into master has correct extended markers' '\n+\tgit reset --hard &&\n+\tgit checkout master^0 &&\n+\ttest_must_fail git merge -s recursive rename^0 &&\n+\ttest_cmp expected renamed_file\n+'\n+\n test_done\ndiff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 968055d..bfc3179 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-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.100.gac5c1\n"},{"id":"173365","messageId":"1313126429-17368-38-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 37/56] merge-recursive: When we detect we can skip an update, actually skip it","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:10Z","receivedAt":"2011-08-12T05:20:10Z","isPatch":false,"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 earlier\nin this series to (a) record_df_conflict_files instead of just unlinking\nthem early, (b) allowing make_room_for_path() to remove D/F entries,\n(c) the splitting of update_stages_and_entry() to have its functionality\ncalled at different points, and (d) making the pathnames of the files\ninvolved in the merge available to merge_content().\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nChanges since v1:\n  (1) Moved to later in the series (used to be patch #29).\n  (2) Changed the \"was_tracked\" logic to use information about what\n      paths were involved in renames to determine whether the file\n      really 'was tracked' before the merge started.  Thus, the code\n      is now about checking \"!path_renamed_outside_HEAD\" rather than\n      \"was_tracked(path)\" (The latter was problematic because it tells\n      us whether path was tracked once unpack_trees finishes() rather\n      than whether the path was tracked before the merge started.)\n  (3) Added another minor tweak to get the expected \"Skipped\" messages\n      that t6022.12 expects.  We need to report that we are skipping\n      the update even if the content is only on the other side of the\n      merge and we need to update the working directory contents.  At\n      least, that's what the author of t6022.12 who put the message in\n      explicitly expected.\n\n merge-recursive.c       |   19 ++++++++++++++++---\n t/t6022-merge-rename.sh |    4 ++--\n 2 files changed, 18 insertions(+), 5 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex bc99f1a..ccf71d3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1356,6 +1356,7 @@ static int merge_content(struct merge_options *o,\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@@ -1373,7 +1374,6 @@ static int merge_content(struct merge_options *o,\n \tb.mode = b_mode;\n \n \tif (rename_conflict_info) {\n-\t\tconst char *path1, *path2;\n \t\tstruct diff_filepair *pair1 = rename_conflict_info->pair1;\n \n \t\tpath1 = (o->branch1 == rename_conflict_info->branch1) ?\n@@ -1399,9 +1399,22 @@ static int merge_content(struct merge_options *o,\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\tint path_renamed_outside_HEAD;\n \t\toutput(o, 3, \"Skipped %s (merged same as existing)\", path);\n-\telse\n+\t\t/*\n+\t\t * The content merge resulted in the same file contents we\n+\t\t * already had.  We can return early if those file contents\n+\t\t * are recorded at the correct path (which may not be true\n+\t\t * if the merge involves a rename).\n+\t\t */\n+\t\tpath_renamed_outside_HEAD = !path2 || !strcmp(path, path2);\n+\t\tif (!path_renamed_outside_HEAD) {\n+\t\t\tadd_cacheinfo(mfi.mode, mfi.sha, path,\n+\t\t\t\t      0 /*stage*/, 1 /*refresh*/, 0 /*options*/);\n+\t\t\treturn mfi.clean;\n+\t\t}\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 4695cbc..d96d3c5 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.100.gac5c1\n"},{"id":"173359","messageId":"1313126429-17368-39-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 38/56] merge-recursive: Fix modify/delete resolution in the recursive case","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:11Z","receivedAt":"2011-08-12T05:20:11Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c                 |   38 +++++++++++++++++++++++-------------\n t/t6036-recursive-corner-cases.sh |    4 +-\n 2 files changed, 26 insertions(+), 16 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex ccf71d3..a0dc0bf 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1324,27 +1324,42 @@ 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 *renamed = NULL;\n+\tif (dir_in_way(path, !o->call_depth)) {\n+\t\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\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, renamed ? renamed : 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\t       NULL == renamed ? \"\" : \" at \",\n+\t\t       NULL == renamed ? \"\" : renamed);\n+\t\tupdate_file(o, 0, b_sha, b_mode, renamed ? renamed : 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\t       NULL == renamed ? \"\" : \" at \",\n+\t\t       NULL == renamed ? \"\" : renamed);\n+\t\tupdate_file(o, 0, a_sha, a_mode, renamed ? renamed : path);\n \t}\n+\tfree(renamed);\n+\n }\n \n static int merge_content(struct merge_options *o,\n@@ -1514,14 +1529,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 *renamed = NULL;\n \t\t\tclean_merge = 0;\n-\t\t\tif (dir_in_way(path, !o->call_depth)) {\n-\t\t\t\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\n-\t\t\t}\n-\t\t\thandle_delete_modify(o, path, renamed,\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\tfree(renamed);\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 b046e1b..314fdae 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -296,7 +296,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@@ -308,7 +308,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.100.gac5c1\n"},{"id":"173363","messageId":"1313126429-17368-40-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 39/56] merge-recursive: Introduce a merge_file convenience function","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:12Z","receivedAt":"2011-08-12T05:20:12Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c |   72 +++++++++++++++++++++++++++-------------------------\n 1 files changed, 37 insertions(+), 35 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex a0dc0bf..76a451c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -846,12 +846,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@@ -920,6 +920,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@@ -1017,16 +1037,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@@ -1213,24 +1227,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@@ -1408,8 +1410,8 @@ 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-- \n1.7.6.100.gac5c1\n"},{"id":"173361","messageId":"1313126429-17368-41-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 40/56] merge-recursive: Fix rename/rename(1to2) resolution for virtual merge base","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:13Z","receivedAt":"2011-08-12T05:20:13Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 76a451c..58a6ce3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -986,17 +986,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@@ -1008,14 +997,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 314fdae..5a7af0c 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -633,7 +633,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.100.gac5c1\n"},{"id":"173367","messageId":"1313126429-17368-42-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 41/56] merge-recursive: Small cleanups for conflict_rename_rename_1to2","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:14Z","receivedAt":"2011-08-12T05:20:14Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Signed-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 58a6ce3..8b65051 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -966,58 +966,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@@ -1505,10 +1502,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.100.gac5c1\n"},{"id":"173382","messageId":"1313126429-17368-43-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 42/56] merge-recursive: Defer rename/rename(2to1) handling until process_entry","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:15Z","receivedAt":"2011-08-12T05:20:15Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 8b65051..77c2c41 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@@ -1021,32 +1022,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@@ -1077,6 +1086,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@@ -1108,30 +1118,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@@ -1151,9 +1161,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@@ -1188,23 +1221,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@@ -1504,6 +1520,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.100.gac5c1\n"},{"id":"173370","messageId":"1313126429-17368-44-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 43/56] merge-recursive: Record more data needed for merging with dual renames","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:16Z","receivedAt":"2011-08-12T05:20:16Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 77c2c41..63f3aa0 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@@ -1160,7 +1183,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@@ -1184,7 +1210,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@@ -1220,6 +1250,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@@ -1271,6 +1304,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.100.gac5c1\n"},{"id":"173368","messageId":"1313126429-17368-45-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 44/56] merge-recursive: Create function for merging with branchname:file markers","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:17Z","receivedAt":"2011-08-12T05:20:17Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c |   42 +++++++++++++++++++++++++++++++++---------\n 1 files changed, 33 insertions(+), 9 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 63f3aa0..390abea 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -944,6 +944,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@@ -1417,7 +1447,6 @@ 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@@ -1447,18 +1476,13 @@ static int merge_content(struct merge_options *o,\n \t\tpath2 = (rename_conflict_info->pair2 ||\n \t\t\t o->branch2 == rename_conflict_info->branch1) ?\n \t\t\tpair1->two->path : pair1->one->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_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.100.gac5c1\n"},{"id":"173364","messageId":"1313126429-17368-46-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 45/56] merge-recursive: Consider modifications in rename/rename(2to1) conflicts","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:18Z","receivedAt":"2011-08-12T05:20:18Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c                    |   30 ++++++++++++++++++--------\n t/t6036-recursive-corner-cases.sh    |   38 +++++++++-------------------------\n t/t6042-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 390abea..c305857 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1083,6 +1083,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@@ -1093,22 +1095,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 5a7af0c..d8c6bda 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -57,23 +57,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@@ -132,25 +124,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/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex bfc3179..3be5059 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -376,7 +376,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.100.gac5c1\n"},{"id":"173366","messageId":"1313126429-17368-47-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 46/56] merge-recursive: Make modify/delete handling code reusable","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:19Z","receivedAt":"2011-08-12T05:20:19Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c       |   82 +++++++++++++++++++++++++++-------------------\n t/t6022-merge-rename.sh |    4 +-\n 2 files changed, 50 insertions(+), 36 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex c305857..17fe7c3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -994,6 +994,46 @@ 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 *renamed = NULL;\n+\tif (dir_in_way(path, !o->call_depth)) {\n+\t\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\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, renamed ? renamed : 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 == renamed ? \"\" : \" at \",\n+\t\t       NULL == renamed ? \"\" : renamed);\n+\t\tupdate_file(o, 0, b_sha, b_mode, renamed ? renamed : 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 == renamed ? \"\" : \" at \",\n+\t\t       NULL == renamed ? \"\" : renamed);\n+\t\tupdate_file(o, 0, a_sha, a_mode, renamed ? renamed : path);\n+\t}\n+\tfree(renamed);\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@@ -1411,44 +1451,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 *renamed = NULL;\n-\tif (dir_in_way(path, !o->call_depth)) {\n-\t\trenamed = unique_path(o, path, a_sha ? o->branch1 : o->branch2);\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, renamed ? renamed : 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 == renamed ? \"\" : \" at \",\n-\t\t       NULL == renamed ? \"\" : renamed);\n-\t\tupdate_file(o, 0, b_sha, b_mode, renamed ? renamed : 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 == renamed ? \"\" : \" at \",\n-\t\t       NULL == renamed ? \"\" : renamed);\n-\t\tupdate_file(o, 0, a_sha, a_mode, renamed ? renamed : path);\n-\t}\n-\tfree(renamed);\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@@ -1614,7 +1628,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 d96d3c5..74dcf20 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.100.gac5c1\n"},{"id":"173375","messageId":"1313126429-17368-48-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 47/56] merge-recursive: Have conflict_rename_delete reuse modify/delete code","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:20Z","receivedAt":"2011-08-12T05:20:20Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Signed-off-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\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 17fe7c3..c89a5ce 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1039,24 +1039,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.100.gac5c1\n"},{"id":"173369","messageId":"1313126429-17368-49-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 48/56] merge-recursive: add handling for rename/rename/add-dest/add-dest","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:21Z","receivedAt":"2011-08-12T05:20:21Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c                    |   21 +++++++++++++++++++--\n t/t6042-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 c89a5ce..8ddd520 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1073,6 +1073,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@@ -1118,8 +1131,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/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 3be5059..6875919 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -551,7 +551,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.100.gac5c1\n"},{"id":"173374","messageId":"1313126429-17368-50-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 49/56] merge-recursive: Fix working copy handling for rename/rename/add/add","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:22Z","receivedAt":"2011-08-12T05:20:22Z","isPatch":false,"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>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\nNo changes since v1.\n\n merge-recursive.c                    |   73 ++++++++++++++++++++++------------\n t/t6042-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 8ddd520..05c8aa0 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1086,6 +1086,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@@ -1093,10 +1139,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@@ -1104,16 +1146,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@@ -1131,18 +1163,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/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh\nindex 6875919..32591f9 100755\n--- a/t/t6042-merge-rename-corner-cases.sh\n+++ b/t/t6042-merge-rename-corner-cases.sh\n@@ -558,12 +558,21 @@ 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 :1:a) = $(git rev-parse A:a) &&\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_done\n-- \n1.7.6.100.gac5c1\n"},{"id":"173373","messageId":"1313126429-17368-51-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 50/56] t3030: fix accidental success in symlink rename","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:23Z","receivedAt":"2011-08-12T05:20:23Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Jeff King <peff@peff.net>\n\nIn this test, we have merge two branches. On one branch, we\nrenamed \"a\" to \"e\". On the other, we renamed \"a\" to \"e\" and\nthen added a symlink pointing at \"a\" pointing to \"e\".\n\nThe results for the test indicate that the merge should\nsucceed, but also that \"a\" should no longer exist. Since\nboth sides renamed \"a\" to the same destination, we will end\nup comparing those destinations for content.\n\nBut what about what's left? One side (the rename only),\nreplaced \"a\" with nothing. The other side replaced it with a\nsymlink. The common base must also be nothing, because any\n\"a\" before this was meaningless (it was totally unrelated\ncontent that ended up getting renamed).\n\nThe only sensible resolution is to keep the symlink. The\nrename-only side didn't touch the content versus the common\nbase, and the other side added content. The 3-way merge\ndictates that we take the side with a change.\n\nAnd this gives the overall merge an intuitive result.  One\nside made one change (a rename), and the other side made two\nchanges: an identical rename, and an addition (that just\nhappened to be at the same spot). The end result should\ncontain both changes.\n\nSigned-off-by: Jeff King <peff@peff.net>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nThis patch was not part of v1, but Junio pulled it out of a separate\nseries and combined it.\n\n t/t3030-merge-recursive.sh |    7 +++++--\n 1 files changed, 5 insertions(+), 2 deletions(-)\n\ndiff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh\nindex 0c02d56..55ef189 100755\n--- a/t/t3030-merge-recursive.sh\n+++ b/t/t3030-merge-recursive.sh\n@@ -267,7 +267,8 @@ test_expect_success 'setup 8' '\n \t\tln -s e a &&\n \t\tgit add a e &&\n \t\ttest_tick &&\n-\t\tgit commit -m \"rename a->e, symlink a->e\"\n+\t\tgit commit -m \"rename a->e, symlink a->e\" &&\n+\t\toln=`printf e | git hash-object --stdin`\n \tfi\n '\n \n@@ -630,16 +631,18 @@ test_expect_success 'merge-recursive copy vs. rename' '\n \n if test_have_prereq SYMLINKS\n then\n-\ttest_expect_success 'merge-recursive rename vs. rename/symlink' '\n+\ttest_expect_failure 'merge-recursive rename vs. rename/symlink' '\n \n \t\tgit checkout -f rename &&\n \t\tgit merge rename-ln &&\n \t\t( git ls-tree -r HEAD ; git ls-files -s ) >actual &&\n \t\t(\n+\t\t\techo \"120000 blob $oln\ta\"\n \t\t\techo \"100644 blob $o0\tb\"\n \t\t\techo \"100644 blob $o0\tc\"\n \t\t\techo \"100644 blob $o0\td/e\"\n \t\t\techo \"100644 blob $o0\te\"\n+\t\t\techo \"120000 $oln 0\ta\"\n \t\t\techo \"100644 $o0 0\tb\"\n \t\t\techo \"100644 $o0 0\tc\"\n \t\t\techo \"100644 $o0 0\td/e\"\n-- \n1.7.6.100.gac5c1\n"},{"id":"173380","messageId":"1313126429-17368-52-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 51/56] t6022: Add testcase for spurious \"refusing to lose untracked\" messages","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:24Z","receivedAt":"2011-08-12T05:20:24Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Signed-off-by: Elijah Newren <newren@gmail.com>\n---\nCompletely new patch (about a problem that existed previous to v1, but which\nI hadn't noticed).\n\n t/t6022-merge-rename.sh |   26 ++++++++++++++++++++++++++\n 1 files changed, 26 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 74dcf20..0fd2b0a 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -767,4 +767,30 @@ test_expect_success 'merge rename into master has correct extended markers' '\n \ttest_cmp expected renamed_file\n '\n \n+test_expect_success 'setup spurious \"refusing to lose untracked\" message' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\t> irrelevant_file &&\n+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n\" >original_file &&\n+\tgit add irrelevant_file original_file &&\n+\tgit commit -mA &&\n+\n+\tgit checkout -b rename &&\n+\tgit mv original_file renamed_file &&\n+\tgit commit -mB &&\n+\n+\tgit checkout master &&\n+\tgit rm original_file &&\n+\tgit commit -mC\n+'\n+\n+test_expect_failure 'no spurious \"refusing to lose untracked\" message' '\n+\tgit checkout master^0 &&\n+\ttest_must_fail git merge rename^0 2>errors.txt &&\n+\t! grep \"refusing to lose untracked file\" errors.txt\n+'\n+\n test_done\n-- \n1.7.6.100.gac5c1\n"},{"id":"173381","messageId":"1313126429-17368-53-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 52/56] merge-recursive: Fix spurious 'refusing to lose untracked file...' messages","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:25Z","receivedAt":"2011-08-12T05:20:25Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Calling update_stages() before update_file() can sometimes result in git\nthinking the file being updated is untracked (whenever update_stages\nmoves it to stage 3).  Reverse the call order, and add a big comment to\nupdate_stages to hopefully prevent others from making the same mistake.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nCompletely new patch (about a problem that existed previous to v1, but which\nI hadn't noticed).\n\n merge-recursive.c       |   34 ++++++++++++++++++++--------------\n t/t6022-merge-rename.sh |    2 +-\n 2 files changed, 21 insertions(+), 15 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 05c8aa0..b2deb53 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -528,6 +528,15 @@ 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+\n+\t/*\n+\t * NOTE: It is usually a bad idea to call update_stages on a path\n+\t * before calling update_file on that same path, since it can\n+\t * sometimes lead to spurious \"refusing to lose untracked file...\"\n+\t * messages from update_file (via make_room_for path via\n+\t * would_lose_untracked).  Instead, reverse the order of the calls\n+\t * (executing update_file first and then update_stages).\n+\t */\n \tint clear = 1;\n \tint options = ADD_CACHE_OK_TO_ADD | ADD_CACHE_SKIP_DFCHECK;\n \tif (clear)\n@@ -1041,7 +1050,6 @@ static void conflict_rename_delete(struct merge_options *o,\n {\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@@ -1055,22 +1063,21 @@ static void conflict_rename_delete(struct merge_options *o,\n \t\tb_mode = dest->mode;\n \t}\n \n+\thandle_change_delete(o,\n+\t\t\t     o->call_depth ? orig->path : dest->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 \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 \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 struct diff_filespec *filespec_from_entry(struct diff_filespec *target,\n@@ -1108,11 +1115,6 @@ static void handle_file(struct merge_options *o,\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@@ -1127,6 +1129,10 @@ static void handle_file(struct merge_options *o,\n \t\t}\n \t}\n \tupdate_file(o, 0, rename->sha1, rename->mode, dst_name);\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 (dst_name != rename->path)\n \t\tfree(dst_name);\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 0fd2b0a..8f75762 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -787,7 +787,7 @@ test_expect_success 'setup spurious \"refusing to lose untracked\" message' '\n \tgit commit -mC\n '\n \n-test_expect_failure 'no spurious \"refusing to lose untracked\" message' '\n+test_expect_success 'no spurious \"refusing to lose untracked\" message' '\n \tgit checkout master^0 &&\n \ttest_must_fail git merge rename^0 2>errors.txt &&\n \t! grep \"refusing to lose untracked file\" errors.txt\n-- \n1.7.6.100.gac5c1\n"},{"id":"173377","messageId":"1313126429-17368-54-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 53/56] t6022: Additional tests checking for unnecessary updates of files","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:26Z","receivedAt":"2011-08-12T05:20:26Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"I stumbled across a case, this one not involving a content merge, where\ngit currently rewrites a file unnecessarily.  A quick audit uncovered two\nadditional situations (also not involving content merges) with the same\nproblem.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nCompletely new patch (about a problem that existed previous to v1, but which\nI hadn't noticed).\n\n t/t6022-merge-rename.sh |   91 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 91 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex 8f75762..c2993fc 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -673,6 +673,97 @@ test_expect_success 'avoid unnecessary update, with D/F conflict' '\n \ttest_cmp expect actual # \"df\" should have stayed intact\n '\n \n+test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\t>irrelevant &&\n+\tmkdir df &&\n+\t>df/file &&\n+\tgit add -A &&\n+\tgit commit -mA &&\n+\n+\tgit checkout -b side\n+\tgit rm -rf df &&\n+\tgit commit -mB &&\n+\n+\tgit checkout master &&\n+\tgit rm -rf df &&\n+\techo bla >df &&\n+\tgit add -A &&\n+\tgit commit -m \"Add a newfile\"\n+'\n+\n+test_expect_failure 'avoid unnecessary update, dir->(file,nothing)' '\n+\tgit checkout -q master^0 &&\n+\ttest-chmtime =1000000000 df &&\n+\ttest-chmtime -v +0 df >expect &&\n+\tgit merge side &&\n+\ttest-chmtime -v +0 df >actual &&\n+\ttest_cmp expect actual # \"df\" should have stayed intact\n+'\n+\n+test_expect_success 'setup avoid unnecessary update, modify/delete' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\t>irrelevant &&\n+\t>file &&\n+\tgit add -A &&\n+\tgit commit -mA &&\n+\n+\tgit checkout -b side\n+\tgit rm -f file &&\n+\tgit commit -m \"Delete file\" &&\n+\n+\tgit checkout master &&\n+\techo bla >file &&\n+\tgit add -A &&\n+\tgit commit -m \"Modify file\"\n+'\n+\n+test_expect_failure 'avoid unnecessary update, modify/delete' '\n+\tgit checkout -q master^0 &&\n+\ttest-chmtime =1000000000 file &&\n+\ttest-chmtime -v +0 file >expect &&\n+\ttest_must_fail git merge side &&\n+\ttest-chmtime -v +0 file >actual &&\n+\ttest_cmp expect actual # \"file\" should have stayed intact\n+'\n+\n+test_expect_success 'setup avoid unnecessary update, rename/add-dest' '\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\\n8\\n\" >file &&\n+\tgit add -A &&\n+\tgit commit -mA &&\n+\n+\tgit checkout -b side\n+\tcp file newfile &&\n+\tgit add -A &&\n+\tgit commit -m \"Add file copy\" &&\n+\n+\tgit checkout master &&\n+\tgit mv file newfile &&\n+\tgit commit -m \"Rename file\"\n+'\n+\n+test_expect_failure 'avoid unnecessary update, rename/add-dest' '\n+\tgit checkout -q master^0 &&\n+\ttest-chmtime =1000000000 newfile &&\n+\ttest-chmtime -v +0 newfile >expect &&\n+\tgit merge side &&\n+\ttest-chmtime -v +0 newfile >actual &&\n+\ttest_cmp expect actual # \"file\" 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-- \n1.7.6.100.gac5c1\n"},{"id":"173379","messageId":"1313126429-17368-55-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 54/56] merge-recursive: Avoid unnecessary file rewrites","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:27Z","receivedAt":"2011-08-12T05:20:27Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Often times, a potential conflict at a path is resolved by merge-recursive\nby using the content that was already present at that location.  In such\ncases, we do not want to overwrite the content that is already present, as\nthat could trigger unnecessary recompilations.  One of the patches earlier\nin this series (\"merge-recursive: When we detect we can skip an update,\nactually skip it\") fixed the cases that involved content merges, but there\nwere a few other cases as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nCompletely new patch (about a problem that existed previous to v1, but which\nI hadn't noticed).\n\n merge-recursive.c       |   30 ++++++++++++++++++++++++------\n t/t6022-merge-rename.sh |    6 +++---\n 2 files changed, 27 insertions(+), 9 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex b2deb53..f088132 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1038,7 +1038,14 @@ static void handle_change_delete(struct merge_options *o,\n \t\t       change_past, o->branch1, o->branch1, path,\n \t\t       NULL == renamed ? \"\" : \" at \",\n \t\t       NULL == renamed ? \"\" : renamed);\n-\t\tupdate_file(o, 0, a_sha, a_mode, renamed ? renamed : path);\n+\t\tif (renamed)\n+\t\t\tupdate_file(o, 0, a_sha, a_mode, renamed);\n+\t\t/*\n+\t\t * No need to call update_file() on path when !renamed, since\n+\t\t * that would needlessly touch path.  We could call\n+\t\t * update_file_flags() with update_cache=0 and update_wd=0,\n+\t\t * but that's a no-op.\n+\t\t */\n \t}\n \tfree(renamed);\n }\n@@ -1398,10 +1405,20 @@ static int process_renames(struct merge_options *o,\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-\t\t\t\t/* Added file on the other side\n-\t\t\t\t   identical to the file being\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\t/*\n+\t\t\t\t * Added file on the other side identical to\n+\t\t\t\t * the file being renamed: clean merge.\n+\t\t\t\t * Also, there is no need to overwrite the\n+\t\t\t\t * file already in the working copy, so call\n+\t\t\t\t * update_file_flags() instead of\n+\t\t\t\t * update_file().\n+\t\t\t\t */\n+\t\t\t\tupdate_file_flags(o,\n+\t\t\t\t\t\t  ren1->pair->two->sha1,\n+\t\t\t\t\t\t  ren1->pair->two->mode,\n+\t\t\t\t\t\t  ren1_dst,\n+\t\t\t\t\t\t  1, /* update_cache */\n+\t\t\t\t\t\t  0  /* update_wd    */);\n \t\t\t} else if (!sha_eq(dst_other.sha1, null_sha1)) {\n \t\t\t\tclean_merge = 0;\n \t\t\t\ttry_merge = 1;\n@@ -1729,7 +1746,8 @@ static int process_entry(struct merge_options *o,\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+\t\t\t/* do not overwrite file if already present */\n+\t\t\tupdate_file_flags(o, sha, mode, path, 1, !a_sha);\n \t\t}\n \t} else if (a_sha && b_sha) {\n \t\t/* Case C: Added in both (check for same permissions) and */\ndiff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh\nindex c2993fc..9d8584e 100755\n--- a/t/t6022-merge-rename.sh\n+++ b/t/t6022-merge-rename.sh\n@@ -696,7 +696,7 @@ test_expect_success 'setup avoid unnecessary update, dir->(file,nothing)' '\n \tgit commit -m \"Add a newfile\"\n '\n \n-test_expect_failure 'avoid unnecessary update, dir->(file,nothing)' '\n+test_expect_success 'avoid unnecessary update, dir->(file,nothing)' '\n \tgit checkout -q master^0 &&\n \ttest-chmtime =1000000000 df &&\n \ttest-chmtime -v +0 df >expect &&\n@@ -726,7 +726,7 @@ test_expect_success 'setup avoid unnecessary update, modify/delete' '\n \tgit commit -m \"Modify file\"\n '\n \n-test_expect_failure 'avoid unnecessary update, modify/delete' '\n+test_expect_success 'avoid unnecessary update, modify/delete' '\n \tgit checkout -q master^0 &&\n \ttest-chmtime =1000000000 file &&\n \ttest-chmtime -v +0 file >expect &&\n@@ -755,7 +755,7 @@ test_expect_success 'setup avoid unnecessary update, rename/add-dest' '\n \tgit commit -m \"Rename file\"\n '\n \n-test_expect_failure 'avoid unnecessary update, rename/add-dest' '\n+test_expect_success 'avoid unnecessary update, rename/add-dest' '\n \tgit checkout -q master^0 &&\n \ttest-chmtime =1000000000 newfile &&\n \ttest-chmtime -v +0 newfile >expect &&\n-- \n1.7.6.100.gac5c1\n"},{"id":"173378","messageId":"1313126429-17368-56-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 55/56] t6036: criss-cross + rename/rename(1to2)/add-dest + simple modify","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:28Z","receivedAt":"2011-08-12T05:20:28Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"This is another testcase trying to exercise the virtual merge base\ncreation in the rename/rename(1to2) code.  A testcase is added that we\nshould be able to merge cleanly, but which requires a virtual merge base\nto be created that correctly handles rename/add-dest conflicts within the\nrename/rename(1to2) testcase handling.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nCompletely new patch (about a problem that existed previous to v1, but which\nI hadn't noticed).\n\n t/t6036-recursive-corner-cases.sh |   69 +++++++++++++++++++++++++++++++++++++\n 1 files changed, 69 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex d8c6bda..e9c7a25 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -704,4 +704,73 @@ test_expect_failure 'detect rename/rename/add-source for virtual merge-base' '\n \ttest \"$(cat a)\" = \"$(printf \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n8\\n\")\"\n '\n \n+#\n+# criss-cross with rename/rename(1to2)/add-dest + simple 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, add c\n+#   Commit C: rename a->c\n+#   Commit D: merge B&C, keeping A:a and B:c\n+#   Commit E: merge B&C, keeping A:a and slightly modified c from B\n+#\n+# Merging commits D & E should result in no conflict.  The virtual merge\n+# base of B & C needs to not delete B:c for that to work, though...\n+\n+test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' '\n+\tgit rm -rf . &&\n+\tgit clean -fdqx &&\n+\trm -rf .git &&\n+\tgit init &&\n+\n+\t>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+\tprintf \"1\\n2\\n3\\n4\\n5\\n6\\n7\\n\" >c &&\n+\tgit add c &&\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 B^0 &&\n+\tgit merge --no-commit -s ours C^0 &&\n+\tgit mv b a &&\n+\tgit commit -m \"D is like B but renames b back to a\" &&\n+\tgit tag D &&\n+\n+\tgit checkout B^0 &&\n+\tgit merge --no-commit -s ours C^0 &&\n+\tgit mv b a &&\n+\techo 8 >>c &&\n+\tgit add c &&\n+\tgit commit -m \"E like D but has mod in c\" &&\n+\tgit tag E\n+'\n+\n+test_expect_failure 'virtual merge base handles rename/rename(1to2)/add-dest' '\n+\tgit checkout D^0 &&\n+\n+\tgit merge -s recursive E^0 &&\n+\n+\ttest 2 -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 $(git rev-parse HEAD:a) = $(git rev-parse A:a) &&\n+\ttest $(git rev-parse HEAD:c) = $(git rev-parse E:c)\n+'\n+\n test_done\n-- \n1.7.6.100.gac5c1\n"},{"id":"173376","messageId":"1313126429-17368-57-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 56/56] merge-recursive: Fix virtual merge base for rename/rename(1to2)/add-dest","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T05:20:29Z","receivedAt":"2011-08-12T05:20:29Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Earlier in this series, the patch \"merge-recursive: add handling for\nrename/rename/add-dest/add-dest\" added code to handle the rename on each\nside of history also being involved in a rename/add conflict, but only\ndid so in the non-recursive case.  Add code for the recursive case,\nensuring that the \"added\" files are not simply deleted.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nCompletely new patch (about a problem that existed previous to v1, but which\nI hadn't noticed).\n\n merge-recursive.c                 |   23 +++++++++++++++++++++--\n t/t6036-recursive-corner-cases.sh |    2 +-\n 2 files changed, 22 insertions(+), 3 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex f088132..05ba41c 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -1161,6 +1161,8 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\n \t       o->call_depth ? \" (left unresolved)\" : \"\");\n \tif (o->call_depth) {\n \t\tstruct merge_file_info mfi;\n+\t\tstruct diff_filespec other;\n+\t\tstruct diff_filespec *add;\n \t\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@@ -1173,8 +1175,25 @@ static void conflict_rename_rename_1to2(struct merge_options *o,\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, one->path);\n-\t\tremove_file_from_cache(a->path);\n-\t\tremove_file_from_cache(b->path);\n+\n+\t\t/*\n+\t\t * Above, we put the merged content at the merge-base's\n+\t\t * path.  Now we usually need to delete both a->path and\n+\t\t * b->path.  However, the rename on each side of the merge\n+\t\t * could also be involved in a rename/add conflict.  In\n+\t\t * such cases, we should keep the added file around,\n+\t\t * resolving the conflict at that path in its favor.\n+\t\t */\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);\n+\t\tif (add)\n+\t\t\tupdate_file(o, 0, add->sha1, add->mode, a->path);\n+\t\telse\n+\t\t\tremove_file_from_cache(a->path);\n+\t\tadd = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);\n+\t\tif (add)\n+\t\t\tupdate_file(o, 0, add->sha1, add->mode, b->path);\n+\t\telse\n+\t\t\tremove_file_from_cache(b->path);\n \t} else {\n \t\thandle_file(o, a, 2, ci);\n \t\thandle_file(o, b, 3, ci);\ndiff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh\nindex e9c7a25..dfee7d1 100755\n--- a/t/t6036-recursive-corner-cases.sh\n+++ b/t/t6036-recursive-corner-cases.sh\n@@ -760,7 +760,7 @@ test_expect_success 'setup criss-cross+rename/rename/add-dest + simple modify' '\n \tgit tag E\n '\n \n-test_expect_failure 'virtual merge base handles rename/rename(1to2)/add-dest' '\n+test_expect_success 'virtual merge base handles rename/rename(1to2)/add-dest' '\n \tgit checkout D^0 &&\n \n \tgit merge -s recursive E^0 &&\n-- \n1.7.6.100.gac5c1\n"},{"id":"173391","messageId":"7vei0rnprt.fsf@alter.siamese.dyndns.org","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"Re: [PATCHv2 00/57] Re-roll of en/merge-recursive from pu","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2011-08-12T05:48:54Z","receivedAt":"2011-08-12T05:48:54Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n> Because it's so hard to rule out regressions with so many changes to a\n> complicated portion of the code (though hopefully it is less complicated\n> now), and because we've had multiple problems in the past with the\n> changes I've been making to merge-recursive, I came up with an idea to\n> test this series more thoroughly.  So, I wrote a script to take every\n> single merge commit in git.git that had exactly two parents (no octopus\n> merges) and redid them both with /usr/bin/git and the version of git\n> from this series.  I checked to ensure that the two different versions\n> of git:\n>   (a) EITHER both failed to merge cleanly OR both merged cleanly\n> AND\n>   (b) the output of 'git ls-tree -r HEAD' matched between the two\n>\n> I ran this process with the original version of the series and indeed\n> found that my original series mis-merged half a dozen or so merges (out\n> of about 5000).\n\nThanks for doing this; note that the previous \"simple one-side renamed the\nother just updated in-place are merged incorrectly\" was caught by such a\ntest.\n\nOne thing we may need to be careful about is to compare the conflicted\nstate a failed merge leaves behind.  The latter half of (a) together with\n(b) will ensure that you did not introduce silent mismerges.\n\nAvoiding silent mismerges is of course one of the most important criteria,\nbut we also need to make sure that a conflicted state left in the index\nand the working tree files must not be harder to reconcile than what we\nhave been giving our users---otherwise the change will be seen as a\nregression by them.\n\n> *** What is still missing ***\n>\n> Two things:\n>   * Junio had a great suggestion about alternate handling of the index in the\n>     case of rename/rename(2to1) and directory/file conflicts (just rename the\n>     entry in the index to match how we are renaming in the working copy to\n>     some new unique name, in order to allow 'git diff' to provide more useful\n>     information to the user).  Just didn't get to it.\n>   * Support for running break detection in diffs, in order to fix the testcase\n>     corrected by Peff in this series.  Simply didn't get around to it either.\n\nI would say it is sensible to leave these two out. They can be done as a\nfollow-up series after dust settles.\n\nThanks.\n"},{"id":"173392","messageId":"4E44CF9D.8030501@viscovery.net","threadId":"28074","inReplyTo":"1313126429-17368-28-git-send-email-newren@gmail.com","subject":"Re: [PATCHv2 27/56] string-list: Add API to remove an item from an unsorted list","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2011-08-12T07:00:45Z","receivedAt":"2011-08-12T07:00:45Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 8/12/2011 7:20, schrieb Elijah Newren:\n> Here's an attempt for a delete_item API (note: only compile-tested).\n\nSeriously? You haven't even tested this patch, and still don't mark it\nwith RFC?\n\n> Bike-shed painters welcome: delete_item, remove_item, free_item?\n\nYou should know that a sentence like this shouldn't appear in the commit\nmessage.\n\nYeah, I know, you just copy-pasted my email text. But that was not a\ncommit message. Perhaps like this:\n\n    This implements removal of an entry in O(1) runtime by moving\n    the last entry to the vacated spot. As such, the routine works\n    only for unsorted lists.\n\nBTW, the code does this:\n\n> +\tlist->items[i] = list->items[list->nr-1];\n\ni.e., it assigns an entire struct. Is this perhaps problematic with older\nC compilers? (I don't know, I'm more used to C++, where this is well-defined.)\n\n-- Hannes\n"},{"id":"173393","messageId":"CALxABCZj385GOZK=8+tsc=hDbRi-R0ULO4E8q7-ntXg__wJMtQ@mail.gmail.com","threadId":"28074","inReplyTo":"4E44CF9D.8030501@viscovery.net","subject":"Re: [PATCHv2 27/56] string-list: Add API to remove an item from an unsorted list","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2011-08-12T09:27:40Z","receivedAt":"2011-08-12T09:27:40Z","isPatch":false,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"On Fri, Aug 12, 2011 at 09:00, Johannes Sixt <j.sixt@viscovery.net> wrote:\n> BTW, the code does this:\n>\n>> +     list->items[i] = list->items[list->nr-1];\n>\n> i.e., it assigns an entire struct. Is this perhaps problematic with older\n> C compilers? (I don't know, I'm more used to C++, where this is well-defined.)\n\nUnlikely. As long as I can remember it always was a memcpy-like operation.\n"},{"id":"173427","messageId":"CABPp-BE_cqtOBC_0tsy1nSOfH1XPGM0vdv9fHWp38Q0GBr7OhQ@mail.gmail.com","threadId":"28074","inReplyTo":"7vei0rnprt.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCHv2 00/57] Re-roll of en/merge-recursive from pu","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T21:59:30Z","receivedAt":"2011-08-12T21:59:30Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Hi,\n\nOn Thu, Aug 11, 2011 at 11:48 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Avoiding silent mismerges is of course one of the most important criteria,\n> but we also need to make sure that a conflicted state left in the index\n> and the working tree files must not be harder to reconcile than what we\n> have been giving our users---otherwise the change will be seen as a\n> regression by them.\n\nYeah, good point.  I tried re-running my previous re-merge all merges\nin git.git testcase, modified so that when both versions of git\nreported a conflict I would compare the output of 'git ls-files  -s'.\nThat uncovered a regression.  I think I know the fix but I need to\nretest and do some more checks.\n"},{"id":"173428","messageId":"CABPp-BFsqDhVnxDa9u3tV0yZky_az0MTCT_N5y+Oqm84+fOyvQ@mail.gmail.com","threadId":"28074","inReplyTo":"4E44CF9D.8030501@viscovery.net","subject":"Re: [PATCHv2 27/56] string-list: Add API to remove an item from an unsorted list","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-12T22:14:34Z","receivedAt":"2011-08-12T22:14:34Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 12, 2011 at 1:00 AM, Johannes Sixt <j.sixt@viscovery.net> wrote:\n> Am 8/12/2011 7:20, schrieb Elijah Newren:\n>> Here's an attempt for a delete_item API (note: only compile-tested).\n>\n> Seriously? You haven't even tested this patch, and still don't mark it\n> with RFC?\n>\n>> Bike-shed painters welcome: delete_item, remove_item, free_item?\n>\n> You should know that a sentence like this shouldn't appear in the commit\n> message.\n>\n> Yeah, I know, you just copy-pasted my email text. But that was not a\n> commit message. Perhaps like this:\n\nYou are right.  I apologize; I messed up here.\n\nHowever, I am unclear what you mean by not even testing the patch,\nthough.  I couldn't find any unit-test harness or any other kind of\ntestsuite for the string_list API.  I did review the code to make sure\nit looked right to me, added a use of your new function, and ran the\nstandard testsuite in addition to my \"re-merge all merges from\ngit.git\" testcase.  I even single stepped through the code in a\ndebugger for good measure.  What testing did you want to see in\nparticular?\n"},{"id":"173438","messageId":"1313202231-10348-1-git-send-email-newren@gmail.com","threadId":"28074","inReplyTo":"1313126429-17368-1-git-send-email-newren@gmail.com","subject":"[PATCHv2 57/57] merge-recursive: Don't re-sort a list whose order we depend upon","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-13T02:23:51Z","receivedAt":"2011-08-13T02:23:51Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"In record_df_conflict_files() we would resort the entries list using\ndf_name_compare to get a convenient ordering.  Unfortunately, this broke\nassumptions of the get_renames() code (via string_list_lookup() calls)\nwhich needed the list to be in the standard ordering.  When those lookups\nwould fail, duplicate stage_data entries could be inserted, causing the\nprocess_renames and process_entry code to fail (in particular, a path that\nthat process_renames had marked as processed would still be processed\nanyway in process_entry due to the duplicate entry).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\nReally this should just be a fixup commit to patch 23 (\"merge-recursive:\nFix sorting order and directory change assumptions\"), but that has some\n(minor) contextual conflicts, which would require me to resubmit the\nwhole 50+ patch-series again.  I've already flooded everyone's inboxes\nenough, so I just created an extra patch.\n\n merge-recursive.c |   16 ++++++++++++----\n 1 files changed, 12 insertions(+), 4 deletions(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 05ba41c..04f3c93 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -402,6 +402,7 @@ static void record_df_conflict_files(struct merge_options *o,\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+\tstruct string_list df_sorted_entries;\n \tconst char *last_file = NULL;\n \tint last_len = 0;\n \tint i;\n@@ -414,14 +415,20 @@ static void record_df_conflict_files(struct merge_options *o,\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+\tmemset(&df_sorted_entries, 0, sizeof(struct string_list));\n+\tfor (i = 0; i < entries->nr; i++) {\n+\t\tstruct string_list_item *next = &entries->items[i];\n+\t\tstring_list_append(&df_sorted_entries, next->string)->util =\n+\t\t\t\t   next->util;\n+\t}\n+\tqsort(df_sorted_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+\tfor (i = 0; i < df_sorted_entries.nr; i++) {\n+\t\tconst char *path = df_sorted_entries.items[i].string;\n \t\tint len = strlen(path);\n-\t\tstruct stage_data *e = entries->items[i].util;\n+\t\tstruct stage_data *e = df_sorted_entries.items[i].util;\n \n \t\t/*\n \t\t * Check if last_file & path correspond to a D/F conflict;\n@@ -449,6 +456,7 @@ static void record_df_conflict_files(struct merge_options *o,\n \t\t\tlast_file = NULL;\n \t\t}\n \t}\n+\tstring_list_clear(&df_sorted_entries, 0);\n }\n \n struct rename {\n-- \n1.7.6.100.g7c63c.dirty\n"},{"id":"173439","messageId":"CABPp-BH3eoYAM9-beQv57Byaf73hQECORxJnqt3JFXTvBpgPRw@mail.gmail.com","threadId":"28074","inReplyTo":"CABPp-BE_cqtOBC_0tsy1nSOfH1XPGM0vdv9fHWp38Q0GBr7OhQ@mail.gmail.com","subject":"Re: [PATCHv2 00/57] Re-roll of en/merge-recursive from pu","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2011-08-13T02:37:39Z","receivedAt":"2011-08-13T02:37:39Z","isPatch":false,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Fri, Aug 12, 2011 at 3:59 PM, Elijah Newren <newren@gmail.com> wrote:\n> On Thu, Aug 11, 2011 at 11:48 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> Avoiding silent mismerges is of course one of the most important criteria,\n>> but we also need to make sure that a conflicted state left in the index\n>> and the working tree files must not be harder to reconcile than what we\n>> have been giving our users---otherwise the change will be seen as a\n>> regression by them.\n>\n> Yeah, good point.  I tried re-running my previous re-merge all merges\n> in git.git testcase, modified so that when both versions of git\n> reported a conflict I would compare the output of 'git ls-files  -s'.\n> That uncovered a regression.  I think I know the fix but I need to\n> retest and do some more checks.\n\nWith the new patch that I just submitted (\"[PATCHv2 57/57]\nmerge-recursive: Don't re-sort a list whose order we depend upon\"), I\nnow have fixed up the only issue I found with the more thorough tests\nyou suggest.  In other words:\n\nI used the new patch on top of my series and redid my testing.  For\neach two-parent merge commit in git.git (about 5000 of them), I redid\nmy tests.  I verified that for each redone commit,  both versions of\ngit would:\n  (A) EITHER both fail to merge cleanly OR both merged cleanly\n  (B) had identical 'git ls-files -s' output\n  (C) had identical output from\n       find . -type f -print0 | grep -z -v ^./.git | sort -z -u |\nxargs -0 sha1sum\n(Note that (B) is different than before; it should be useful in\ncomparing failed merges as well as successful ones, making sure the\ncontents of the index match.  Also, the purpose of (C) is to ensure\nthat the contents of the working tree match.)\n\nIn all cases, (A) and (B) were true.  There were 16 commits for which\n(C) was not true; the working tree contents were different.  However,\neach and every such difference were due to the change in behavior to\nprovide more info in conflict markers (branchname:filename instead of\njust branchname) -- see patch 36 in the series.\n\n\nSo with the new patch, and the commit message changes Johannes\nsuggested for patch 27, I'm hoping this series is finally in good\nshape.  But, as before, comments on the changes and other ideas for\nmore thorough testing are welcome.\n"},{"id":"173443","messageId":"4E463F01.5060301@kdbg.org","threadId":"28074","inReplyTo":"CABPp-BFsqDhVnxDa9u3tV0yZky_az0MTCT_N5y+Oqm84+fOyvQ@mail.gmail.com","subject":"Re: [PATCHv2 27/56] string-list: Add API to remove an item from an unsorted list","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2011-08-13T09:08:17Z","receivedAt":"2011-08-13T09:08:17Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 13.08.2011 00:14, schrieb Elijah Newren:\n> On Fri, Aug 12, 2011 at 1:00 AM, Johannes Sixt <j.sixt@viscovery.net> wrote:\n>> Am 8/12/2011 7:20, schrieb Elijah Newren:\n>>> Here's an attempt for a delete_item API (note: only compile-tested).\n>>\n>> Seriously? You haven't even tested this patch, and still don't mark it\n>> with RFC?\n> ...\n> However, I am unclear what you mean by not even testing the patch,\n> though.  I couldn't find any unit-test harness or any other kind of\n> testsuite for the string_list API.  I did review the code to make sure\n> it looked right to me, added a use of your new function, and ran the\n> standard testsuite in addition to my \"re-merge all merges from\n> git.git\" testcase.  I even single stepped through the code in a\n> debugger for good measure.  What testing did you want to see in\n> particular?\n\nThis kind of testing is fine. And I appologize for asking a provocative\nquestion. Of course, I know that you did test the new function suitably,\notherwise you wouldn't have submitted the series.\n\nSo, the question is rather, why did that sentence remain in the commit\nmessage? The commit message should not be deceptive (and in particular\nnot blindly copy-pasted from an email that throws out a patch in the\nhopes that somebody picks it up and massages into a good shape - I\nthought it was clear that my patch was not a proper patch submission).\n\nThink about someone browses history constrained by pathspec\n'string-list.c'. This person will see this commit without any hint about\nthe merge-recursive series or that the new API is used in the next\ncommit, and will have to ask: \"Why the heck is did someone introduce\nthis code and didn't even test it?\"\n\n-- Hannes\n"}]}