When the working tree has: - bar (directory) - bar/file (file) - foo (symlink to .) (note that lstat() for "foo/bar" would tell us that it is a directory) and the user merges a commit that deletes the foo symlink and instead contains: - bar (directory, as above) - bar/file (file, as above) - foo (directory) - foo/bar (file) the merge should happen without requiring user intervention. However, this does not happen. This is because dir_in_way(), when checking the working tree, thinks that "foo/bar" is a directory. But a symlink should be treated much the same as a file: since dir_in_way() is only checking to see if there is a directory in the way, we don't want symlinks in leading paths to sometimes cause dir_in_way() to return true. Teach dir_in_way() to also check for symlinks in leading paths before reporting whether a directory is in the way. Helped-by: Elijah Newren <newren@xxxxxxxxx> Signed-off-by: Jonathan Tan <jonathantanmy@xxxxxxxxxx> --- Changes from v1: - Used has_symlink_leading_path(). This drastically shortens the diff. - Updated commit message following suggestions from Junio, Szeder Gábor, and Elijah Newren. - Updated test to add prereq and verification that the working tree contains what we want. --- merge-recursive.c | 3 ++- t/t3030-merge-recursive.sh | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/merge-recursive.c b/merge-recursive.c index 6b812d67e3..22a12cfeba 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -764,7 +764,8 @@ static int dir_in_way(struct index_state *istate, const char *path, strbuf_release(&dirpath); return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode) && - !(empty_ok && is_empty_dir(path)); + !(empty_ok && is_empty_dir(path)) && + !has_symlink_leading_path(path, strlen(path)); } /* diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index ff641b348a..faa8892741 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -452,6 +452,34 @@ test_expect_success 'merge-recursive d/f conflict result' ' ' +test_expect_success SYMLINKS 'dir in working tree with symlink ancestor does not produce d/f conflict' ' + git init sym && + ( + cd sym && + ln -s . foo && + mkdir bar && + >bar/file && + git add foo bar/file && + git commit -m "foo symlink" && + + git checkout -b branch1 && + git commit --allow-empty -m "empty commit" && + + git checkout master && + git rm foo && + mkdir foo && + >foo/bar && + git add foo/bar && + git commit -m "replace foo symlink with real foo dir and foo/bar file" && + + git checkout branch1 && + + git cherry-pick master && + test_path_is_dir foo && + test_path_is_file foo/bar + ) +' + test_expect_success 'reset and 3-way merge' ' git reset --hard "$c2" && -- 2.23.0.237.gc6a4ce50a0-goog