git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 00/48] Handling more corner cases in merge-recursive.c

From
Elijah Newren <newren@gmail.com>
Date
Jun 8, 2011, 07:30 UTC
Message-ID
<1307518278-23814-1-git-send-email-newren@gmail.com>

Well, this took me a LOT longer than I expected, but I now have a (monster) patch series designed to fix various corner cases in merge-recursive.c. For those waiting for the 1.7.6 freeze to end, reading this patch series makes for some great mental gymnastics. :-)

You might be surprised by just how corner cases we have and the types of bugs that can be triggered (including deleting untracked files). This series includes testcases for the unnecessary file update issues[1] triggered by my series from last fall[2] and also a testcase that had been regressed by my earlier (and now reverted) bandaids designed to fix the unnecessary file update issue[3] (to make sure we fix both those issues). The current series aims to fix these problems using the strategy Junio outlined[4]. However, this series is about a lot more than just those issues.

[1] http://thread.gmane.org/gmane.comp.version-control.git/167800 [2] http://thread.gmane.org/gmane.comp.version-control.git/156559 [3] http://thread.gmane.org/gmane.comp.version-control.git/172617 [4] http://thread.gmane.org/gmane.comp.version-control.git/169209

You can note that I did not fix all the testcases I added. I consider some cases either unfixable or not worth fixing (see [5] for mailing list discussion about the main such case).

[5] http://thread.gmane.org/gmane.comp.version-control.git/157591

However, there is one large class of problems that I think is fixable, I'm just not sure whether it is worth fixing. git's rename detection optimization of only considering files that exist on one side of the diff but not the other causes issues with merges (undetected conflicts, spurious conflicts, or merged cleanly but wrongly due to deleting files that should be present). To fix these cases, we would need some way of including rewritten files as potential rename candidates, meaning we would also need to somehow check for rewritten files. (I refer to these as rename/add-source situations). The potential problem with applying such a fix is that it could make merges significantly more costly in the common case, in order to fix some relatively rare problems that might occur. I don't know whether it is justified, especially since git's merging can't be expected to know how to merge everything; at some point the user is responsible for checking the result. Patch 32 does add some comments to the code if anyone does decide they want to look into this, though.

Please test! I apologize for the earlier regressions I caused in merge-recursive. I've tried to be MUCH more careful this time, but the changes are numerous and much more invasive too. I'm trying to put this series through the testing wringer and will continue to do so, but extra testing from others would be great.

Anyway, if you've read this far...thanks!  
Elijah Newren (48):

In patches 1-12, I add a bunch of corner case tests. Since there are so many I want to add and I think putting them all in t6036 would be a bit unwieldy, I split the tests. Since t6036 currently only contains testcases that involve criss-cross merges, I mark it for such usage, and create t6039 for dealing with corner cases involving renames without criss-cross merges.

  [01] t6039: Add a testcase where git deletes an untracked file
  [02] t6039: Add failing testcase for rename/modify/add-source conflict
  [03] t6039: Add a pair of cases where undetected renames cause issues
  [04] t6039: Add a testcase where undetected rename causes silent file deletion
  [05] t6039: Add tests for content issues with modify/rename/directory conflicts
  [06] t6039: Add failing testcases for rename/rename/add-{source,dest} conflicts
  [07] t6039: Ensure rename/rename conflicts leave index and workdir in sane state
  [08] t6036: Add differently resolved modify/delete conflict in criss-cross test
  [09] t6036: criss-cross with weird content can fool git into clean merge
  [10] t6036: tests for criss-cross merges with various directory/file conflicts
  [11] t6036: criss-cross w/ rename/rename(1to2)/modify+rename/rename(2to1)/modify
  [12] t6036: criss-cross + rename/rename(1to2)/add-source + modify/modify

In patches 13-14, I add two tests that Junio reverted from master (6db41050) which we would still like fixed, and a test that my previous band-aids had broken to make sure we don't break it again.

  [13] t6022: Remove unnecessary untracked files to make test cleaner
  [14] t6022: New tests checking for unnecessary updates of files
  [15] t6022: Add testcase for merging a renamed file with a simple change

In patches 16-19, I'm just doing minor cleanups. None of these are critical; I'll be happy to drop any of them.

  [16] merge-recursive: Make BUG message more legible by adding a newline
  [17] merge-recursive: Correct a comment
  [18] merge-recursive: Mark some diff_filespec struct arguments const
  [19] merge-recursive: Remember to free generated unique path names

Patches 20-22 are small but important bug fixes. Some of these might make sense to be pulled to maint independent of the other changes.

  [20] merge-recursive: Avoid working directory changes during recursive case
  [21] merge-recursive: Fix recursive case with D/F conflict via add/add conflict
  [22] merge-recursive: Fix sorting order and directory change assumptions

Patches 23-29 are where I try to implement what I understood Junio wanted merge-recursive to do to avoid so many lstat() and other unnecessary working directory changes (see link [4] above).

  [23] merge-recursive: Fix code checking for D/F conflicts still being present
  [24] merge-recursive: Save D/F conflict filenames instead of unlinking them
  [25] merge-recursive: Split was_tracked() out of would_lose_untracked()
  [26] merge-recursive: Allow make_room_for_path() to remove D/F entries
  [27] merge-recursive: Consolidate different update_stages functions
  [28] merge-recursive: Split update_stages_and_entry; only update stages at end
  [29] merge-recursive: When we detect we can skip an update, actually skip it

Patch 30 fixes the deletion of an untracked file; I believe it has the highest ratio of commit message length to patch size, since it has to explain why we have modified a certain line of code several times and still didn't have it quite right.

  [30] merge-recursive: Fix deletion of untracked file in rename/delete conflicts

The commit message for patch 31 seems like there's some good zombie joke in there that someone cleverer than myself ought to be able to come up with. Interestingly, our code for rename/rename (2to1) conflicts was never executed, and would trigger segfaults had there been a way to execute it.

  [31] merge-recursive: Make dead code for rename/rename(2to1) conflicts undead

There are a few cases where git's rename detection optimization of only considering files that exist on one side of the diff but not the other causes issues with merges (undetected conflicts, spurious conflicts, or merged cleanly but wrongly due to deleting files that should be present). To fix these cases, we would need some way of including rewritten files as potential rename candidates, meaning we would also need to somehow check for rewritten files. My series does not address these cases (I'm unsure whether the performance hit would be worth it). Instead, I simply have a half-dozen relevant test cases and add some comments to the code about some things that would need to be considered if we ever want to tackle these cases.

  [32] merge-recursive: Add comments about handling rename/add-source cases

Patch 33 was specifically written because of a rename/directory conflict testcase, but tries to worry about three-way content merges in combination with renames and D/F conflicts and possible criss-cross merges and getting the appropriate index and working directory contents.

  [33] merge-recursive: Improve handling of rename target vs. directory addition

Patch 34 is an attempted simplification of merge-recursive.c. We really don't need process_df_entry(), since we can simply iterate through the unmerged entries in the order we need. That can make it easier to keep track of things, which is particularly important for the rename/rename fixes below...

  [34] merge-recursive: Consolidate process_entry() and process_df_entry()

Patches 35-48 try to fix our handling of rename/rename conflicts (both one file being renamed to two or two files being renamed to one). In such cases, we need to consider that the renames may not be the only change; there could also be modifications requiring a three-way content merge. Further, as with all other rename cases, we need to consider the recursive case (o->call_depth > 0) and potential D/F conflicts. There is also the possibility of rename/add-source issues for each side of the rename, though we currently can't detect those anyway. The rename_rename_1to2 case is especially interesting, as we also have to worry about each rename potentially also being involved in rename/delete and rename/add-dest conflicts. This all makes handling rename/rename conflicts a bit hairy.

  [35] merge-recursive: Cleanup and consolidation of rename_conflict_info
  [36] merge-recursive: Provide more info in conflict markers with file renames
  [37] merge-recursive: Fix modify/delete resolution in the recursive case
  [38] merge-recursive: Introduce a merge_file convenience function
  [39] merge-recursive: Fix rename/rename(1to2) resolution for virtual merge base
  [40] merge-recursive: Small cleanups for conflict_rename_rename_1to2
  [41] merge-recursive: Defer rename/rename(2to1) handling until process_entry
  [42] merge-recursive: Record more data needed for merging with dual renames
  [43] merge-recursive: Create function for merging with branchname:file markers
  [44] merge-recursive: Consider modifications in rename/rename(2to1) conflicts
  [45] merge-recursive: Make modify/delete handling code reusable
  [46] merge-recursive: Have conflict_rename_delete reuse modify/delete code
  [47] merge-recursive: add handling for rename/rename/add-dest/add-dest
  [48] merge-recursive: Fix working copy handling for rename/rename/add/add
 merge-recursive.c                    |  985 +++++++++++++++++++++-------------
 merge-recursive.h                    |    1 +
 t/t6020-merge-df.sh                  |   26 +-
 t/t6022-merge-rename.sh              |  102 ++++-
 t/t6036-recursive-corner-cases.sh    |  502 ++++++++++++++++-
 t/t6039-merge-rename-corner-cases.sh |  556 +++++++++++++++++++
 6 files changed, 1752 insertions(+), 420 deletions(-)
 create mode 100755 t/t6039-merge-rename-corner-cases.sh
-- 
1.7.6.rc0.62.g2d69f
Next: Elijah Newren
Message 1 of 97 in “Handling more corner cases in merge-recursive.c”
  1. 00/48 Handling more corner cases in merge-recursive.cElijah Newren, Jun 8, 2011
  2. 01/48 t6039: Add a testcase where git deletes an untracked fileElijah Newren, Jun 8, 2011
  3. 02/48 t6039: Add failing testcase for rename/modify/add-source conflictElijah Newren, Jun 8, 2011
  4. 03/48 t6039: Add a pair of cases where undetected renames cause issuesElijah Newren, Jun 8, 2011
  5. 04/48 t6039: Add a testcase where undetected rename causes silent file deletionElijah Newren, Jun 8, 2011
  6. 05/48 t6039: Add tests for content issues with modify/rename/directory conflictsElijah Newren, Jun 8, 2011
  7. Junio C HamanoJul 18, 2011
  8. Elijah NewrenAug 8, 2011
  9. 06/48 t6039: Add failing testcases for rename/rename/add-{source,dest} conflictsElijah Newren, Jun 8, 2011
  10. Junio C HamanoJul 18, 2011
  11. 07/48 t6039: Ensure rename/rename conflicts leave index and workdir in sane stateElijah Newren, Jun 8, 2011
  12. Junio C HamanoJul 18, 2011
  13. Elijah NewrenAug 8, 2011
  14. 08/48 t6036: Add differently resolved modify/delete conflict in criss-cross testElijah Newren, Jun 8, 2011
  15. Junio C HamanoJul 18, 2011
  16. 09/48 t6036: criss-cross with weird content can fool git into clean mergeElijah Newren, Jun 8, 2011
  17. Junio C HamanoJul 18, 2011
  18. Elijah NewrenAug 8, 2011
  19. 10/48 t6036: tests for criss-cross merges with various directory/file conflictsElijah Newren, Jun 8, 2011
  20. Junio C HamanoJul 18, 2011
  21. Elijah NewrenAug 8, 2011
  22. 11/48 t6036: criss-cross w/ rename/rename(1to2)/modify+rename/rename(2to1)/modifyElijah Newren, Jun 8, 2011
  23. Junio C HamanoJul 18, 2011
  24. 12/48 t6036: criss-cross + rename/rename(1to2)/add-source + modify/modifyElijah Newren, Jun 8, 2011
  25. Junio C HamanoJul 18, 2011
  26. Phil HordJul 20, 2011
  27. 13/48 t6022: Remove unnecessary untracked files to make test cleanerElijah Newren, Jun 8, 2011
  28. 14/48 t6022: New tests checking for unnecessary updates of filesElijah Newren, Jun 8, 2011
  29. 15/48 t6022: Add testcase for merging a renamed file with a simple changeElijah Newren, Jun 8, 2011
  30. 16/48 merge-recursive: Make BUG message more legible by adding a newlineElijah Newren, Jun 8, 2011
  31. 17/48 merge-recursive: Correct a commentElijah Newren, Jun 8, 2011
  32. 18/48 merge-recursive: Mark some diff_filespec struct arguments constElijah Newren, Jun 8, 2011
  33. Junio C HamanoJul 18, 2011
  34. 19/48 merge-recursive: Remember to free generated unique path namesElijah Newren, Jun 8, 2011
  35. Junio C HamanoJul 18, 2011
  36. 20/48 merge-recursive: Avoid working directory changes during recursive caseElijah Newren, Jun 8, 2011
  37. 21/48 merge-recursive: Fix recursive case with D/F conflict via add/add conflictElijah Newren, Jun 8, 2011
  38. Junio C HamanoJul 18, 2011
  39. 22/48 merge-recursive: Fix sorting order and directory change assumptionsElijah Newren, Jun 8, 2011
  40. Johannes SixtJul 11, 2011
  41. Johannes SixtJul 12, 2011
  42. Johannes SixtJul 13, 2011
  43. Junio C HamanoJul 13, 2011
  44. Junio C HamanoJul 18, 2011
  45. Elijah NewrenAug 8, 2011
  46. 23/48 merge-recursive: Fix code checking for D/F conflicts still being presentElijah Newren, Jun 8, 2011
  47. 24/48 merge-recursive: Save D/F conflict filenames instead of unlinking themElijah Newren, Jun 8, 2011
  48. 25/48 merge-recursive: Split was_tracked() out of would_lose_untracked()Elijah Newren, Jun 8, 2011
  49. 26/48 merge-recursive: Allow make_room_for_path() to remove D/F entriesElijah Newren, Jun 8, 2011
  50. Johannes SixtJul 11, 2011
  51. Johannes SixtJul 13, 2011
  52. Elijah NewrenAug 8, 2011
  53. Johannes SixtAug 9, 2011
  54. Junio C HamanoJul 18, 2011
  55. 27/48 merge-recursive: Consolidate different update_stages functionsElijah Newren, Jun 8, 2011
  56. Junio C HamanoJul 18, 2011
  57. 28/48 merge-recursive: Split update_stages_and_entry; only update stages at endElijah Newren, Jun 8, 2011
  58. Junio C HamanoJul 18, 2011
  59. 29/48 merge-recursive: When we detect we can skip an update, actually skip itElijah Newren, Jun 8, 2011
  60. Junio C HamanoJul 18, 2011
  61. 30/48 merge-recursive: Fix deletion of untracked file in rename/delete conflictsElijah Newren, Jun 8, 2011
  62. Junio C HamanoJul 21, 2011
  63. 31/48 merge-recursive: Make dead code for rename/rename(2to1) conflicts undeadElijah Newren, Jun 8, 2011
  64. 32/48 merge-recursive: Add comments about handling rename/add-source casesElijah Newren, Jun 8, 2011
  65. 33/48 merge-recursive: Improve handling of rename target vs. directory additionElijah Newren, Jun 8, 2011
  66. 34/48 merge-recursive: Consolidate process_entry() and process_df_entry()Elijah Newren, Jun 8, 2011
  67. Junio C HamanoJul 21, 2011
  68. 35/48 merge-recursive: Cleanup and consolidation of rename_conflict_infoElijah Newren, Jun 8, 2011
  69. 36/48 merge-recursive: Provide more info in conflict markers with file renamesElijah Newren, Jun 8, 2011
  70. Junio C HamanoJul 21, 2011
  71. 37/48 merge-recursive: Fix modify/delete resolution in the recursive caseElijah Newren, Jun 8, 2011
  72. Junio C HamanoJul 21, 2011
  73. Elijah NewrenAug 8, 2011
  74. 38/48 merge-recursive: Introduce a merge_file convenience functionElijah Newren, Jun 8, 2011
  75. 39/48 merge-recursive: Fix rename/rename(1to2) resolution for virtual merge baseElijah Newren, Jun 8, 2011
  76. Junio C HamanoJul 25, 2011
  77. Elijah NewrenAug 8, 2011
  78. 40/48 merge-recursive: Small cleanups for conflict_rename_rename_1to2Elijah Newren, Jun 8, 2011
  79. 41/48 merge-recursive: Defer rename/rename(2to1) handling until process_entryElijah Newren, Jun 8, 2011
  80. 42/48 merge-recursive: Record more data needed for merging with dual renamesElijah Newren, Jun 8, 2011
  81. 43/48 merge-recursive: Create function for merging with branchname:file markersElijah Newren, Jun 8, 2011
  82. 44/48 merge-recursive: Consider modifications in rename/rename(2to1) conflictsElijah Newren, Jun 8, 2011
  83. 45/48 merge-recursive: Make modify/delete handling code reusableElijah Newren, Jun 8, 2011
  84. 46/48 merge-recursive: Have conflict_rename_delete reuse modify/delete codeElijah Newren, Jun 8, 2011
  85. 47/48 merge-recursive: add handling for rename/rename/add-dest/add-destElijah Newren, Jun 8, 2011
  86. 48/48 merge-recursive: Fix working copy handling for rename/rename/add/addElijah Newren, Jun 8, 2011
  87. Junio C HamanoJun 11, 2011
  88. Junio C HamanoAug 4, 2011
  89. Junio C HamanoAug 4, 2011
  90. Elijah NewrenAug 4, 2011
  91. Elijah NewrenAug 4, 2011
  92. Junio C HamanoAug 4, 2011
  93. Elijah NewrenAug 4, 2011
  94. Junio C HamanoAug 6, 2011
  95. Elijah NewrenAug 6, 2011
  96. Junio C HamanoJun 12, 2011
  97. Junio C HamanoJun 12, 2011

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.