merge-recursive: symlink's descendants not in way

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@gmail.com> Signed-off-by: Jonathan Tan <jonathantanmy@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jonathan Tan committed Sep 18, 2019 at 13:27 UTC 83e3ad3b126125d23eea10283d85682a1a680daa
2 files changed +30 -1
merge-recursive.c
+2 -1
@@ -764,7 +764,8 @@ static int dir_in_way(struct index_state *istate, const char *path,
764
765 strbuf_release(&dirpath);
766 return check_working_copy && !lstat(path, &st) && S_ISDIR(st.st_mode) &&
767 - !(empty_ok && is_empty_dir(path));
767 + !(empty_ok && is_empty_dir(path)) &&
768 + !has_symlink_leading_path(path, strlen(path));
769 }
770
771 /*
t/t3030-merge-recursive.sh
+28
@@ -452,6 +452,34 @@ test_expect_success 'merge-recursive d/f conflict result' '
452
453 '
454
455 +test_expect_success SYMLINKS 'dir in working tree with symlink ancestor does not produce d/f conflict' '
456 + git init sym &&
457 + (
458 + cd sym &&
459 + ln -s . foo &&
460 + mkdir bar &&
461 + >bar/file &&
462 + git add foo bar/file &&
463 + git commit -m "foo symlink" &&
464 +
465 + git checkout -b branch1 &&
466 + git commit --allow-empty -m "empty commit" &&
467 +
468 + git checkout master &&
469 + git rm foo &&
470 + mkdir foo &&
471 + >foo/bar &&
472 + git add foo/bar &&
473 + git commit -m "replace foo symlink with real foo dir and foo/bar file" &&
474 +
475 + git checkout branch1 &&
476 +
477 + git cherry-pick master &&
478 + test_path_is_dir foo &&
479 + test_path_is_file foo/bar
480 + )
481 +'
482 +
483 test_expect_success 'reset and 3-way merge' '
484
485 git reset --hard "$c2" &&