Check if the issue persists with Git 2.33+ (Q3 2021), which uses git merge ORT (now the default: "Ostensibly Recursive's Twin").
Also merging a branch with directory renames into a branch that changes the directory to a symlink was mishandled by the ort merge strategy, which has been corrected with Git 2.39 (Q4 2022).
See commit 2b86c10 (22 Oct 2022) by Elijah Newren (newren
).
(Merged by Taylor Blau -- ttaylorr
-- in commit 969230b, 30 Oct 2022)
merge-ort
: fix bug with dir rename vs change dir to symlink
Reported-by: Stefano Rivera
Signed-off-by: Elijah Newren
When changing a directory to a symlink on one side of history, and renaming the parent of that directory to a different directory name on the other side, e.g. with this kind of setup:
Base commit: Has a file named dir/subdir/file
Side1: Rename dir/ -> renamed-dir/
Side2: delete dir/subdir/file, add dir/subdir as symlink
Then merge-ort was running into an assertion failure:
git: merge-ort.c:2622: apply_directory_rename_modifications: Assertion `ci->dirmask == 0' failed
merge-recursive did not have as obvious an issue handling this case, likely because we never fixed it to handle the case from commit 902c521 ("t6423
: more involved directory rename test", 2020-10-15, Git v2.30.0-rc0 -- merge listed in batch #4) where we need to be careful about nested renames when a directory rename occurs (dir/ -> renamed-dir/ implies dir/subdir/ -> renamed-dir/subdir/).
However, merge-recursive does have multiple problems with this testcase:
- Incorrect stages for the file: merge-recursive omits the stage in the index corresponding to the base stage, making
git status
(man) report "added by us" for renamed-dir/subdir/file instead of the expected "deleted by them".
- Poor directory/file conflict handling: For the renamed-dir/subdir symlink, instead of reporting a file/directory conflict as expected, it reports "
Error: Refusing to lose untracked file at renamed-dir/subdir
".
This is a lie because there is no untracked file at that location.
It then does the normal suboptimal merge-recursive thing of having the symlink be tracked in the index at a location where it can't be written due to D/F
conflicts (namely, renamed-dir/subdir
), but writes it to the working tree at a different location as a new untracked file (namely, renamed-dir/subdir~B^0
)
Technically, these problems don't prevent the user from resolving the merge if they can figure out to ignore the confusion, but because both pieces of output are quite confusing I don't want to modify the test to claim the recursive also passes it even if it doesn't have the bug that ort did.
So, fix the bug in ort by splitting the conflict_info
for "dir/subdir" into two, one for the directory part, one for the file (i.e.
symlink) part, since the symlink is being renamed by directory rename detection.
The directory part is needed for proper nesting, since there are still conflict_info
fields for files underneath it (though those are marked as is_null,
they are still present until the entries are processed, and the entry processing wants every non-toplevel entry to have a parent directory).