File/directory conflicts are somewhat difficult to resolve; by way of contrast, renames are much easier to handle. For file/directory conflicts, we already have to record the file under a different name in the working copy due to the directory being in the way; if we also record the file under a different name in the index then it simplifies matters for the user, and ensures that 'git reset --hard' and 'git merge --abort' will clean up files created by the merge operation. Note that there are multiple ways to get a file/directory conflict: * add/add (one side adds a file, the other a directory) * modify/delete (the side that deletes puts a directory in the way) * rename vs add-directory (directory added on one side in way of rename) As such, there are multiple code paths that need updating. FIXME: Several testcases were updated to show how this affected the testsuite in general, but there are still 11 more tests across five testfiles that need fixing. As I said, this is just WIP/RFC. Signed-off-by: Elijah Newren <newren@xxxxxxxxx> --- merge-recursive.c | 38 ++++++++++++++++++++++------ t/t3030-merge-recursive.sh | 16 ++++++------ t/t6020-merge-df.sh | 4 +-- t/t6022-merge-rename.sh | 4 +-- t/t6036-recursive-corner-cases.sh | 5 ++-- t/t6042-merge-rename-corner-cases.sh | 4 +-- t/t6043-merge-rename-directories.sh | 4 +-- 7 files changed, 49 insertions(+), 26 deletions(-) diff --git a/merge-recursive.c b/merge-recursive.c index 1446e92bea..34906b0f90 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -1527,6 +1527,20 @@ static int handle_change_delete(struct merge_options *o, * path. We could call update_file_flags() with update_cache=0 * and update_wd=0, but that's a no-op. */ + if (!o->call_depth && alt_path) { + struct diff_filespec orig, new; + int stage = (change_branch == o->branch1) ? 2 : 3; + + remove_file_from_cache(path); + orig.mode = o_mode; + oidcpy(&orig.oid, o_oid); + new.mode = changed_mode; + oidcpy(&new.oid, changed_oid); + if (update_stages(o, alt_path, &orig, + stage == 2 ? &new : NULL, + stage == 3 ? &new : NULL)) + ret = -1; + } if (change_branch != o->branch1 || alt_path) ret = update_file(o, 0, changed_oid, changed_mode, update_path); } @@ -3089,11 +3103,11 @@ static int merge_content(struct merge_options *o, if (df_conflict_remains || is_dirty) { char *new_path; - if (o->call_depth) { - remove_file_from_cache(path); - } else { + new_path = unique_path(o, path, rename_conflict_info->branch1); + remove_file_from_cache(path); + if (!o->call_depth) { if (!mfi.clean) { - if (update_stages(o, path, &one, &a, &b)) + if (update_stages(o, new_path, &one, &a, &b)) return -1; } else { int file_from_stage2 = was_tracked(o, path); @@ -3101,14 +3115,13 @@ static int merge_content(struct merge_options *o, oidcpy(&merged.oid, &mfi.oid); merged.mode = mfi.mode; - if (update_stages(o, path, NULL, + if (update_stages(o, new_path, NULL, file_from_stage2 ? &merged : NULL, file_from_stage2 ? NULL : &merged)) return -1; } } - new_path = unique_path(o, path, rename_conflict_info->branch1); if (is_dirty) { output(o, 1, _("Refusing to lose dirty file at %s"), path); @@ -3244,10 +3257,19 @@ static int process_entry(struct merge_options *o, output(o, 1, _("CONFLICT (%s): There is a directory with name %s in %s. " "Adding %s as %s"), conf, path, other_branch, path, new_path); + remove_file_from_cache(path); + if (!o->call_depth) { + struct diff_filespec dfs; + + dfs.mode = mode; + oidcpy(&dfs.oid, oid); + if (update_stages(o, new_path, NULL, + a_oid ? &dfs : NULL, + a_oid ? NULL : &dfs)) + clean_merge = -1; + } if (update_file(o, 0, oid, mode, new_path)) clean_merge = -1; - else if (o->call_depth) - remove_file_from_cache(path); free(new_path); } else { output(o, 2, _("Adding %s"), path); diff --git a/t/t3030-merge-recursive.sh b/t/t3030-merge-recursive.sh index ff641b348a..6d456da001 100755 --- a/t/t3030-merge-recursive.sh +++ b/t/t3030-merge-recursive.sh @@ -369,9 +369,9 @@ test_expect_success 'merge-recursive d/f conflict result' ' git ls-files -s >actual && ( - echo "100644 $o0 1 a" && - echo "100644 $o1 2 a" && echo "100644 $o4 0 a/c" && + echo "100644 $o0 1 a~$c1" && + echo "100644 $o1 2 a~$c1" && echo "100644 $o0 0 b" && echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" @@ -393,9 +393,9 @@ test_expect_success 'merge-recursive d/f conflict result the other way' ' git ls-files -s >actual && ( - echo "100644 $o0 1 a" && - echo "100644 $o1 3 a" && echo "100644 $o4 0 a/c" && + echo "100644 $o0 1 a~$c1" && + echo "100644 $o1 3 a~$c1" && echo "100644 $o0 0 b" && echo "100644 $o0 0 c" && echo "100644 $o1 0 d/e" @@ -420,9 +420,9 @@ test_expect_success 'merge-recursive d/f conflict result' ' echo "100644 $o1 0 a" && echo "100644 $o0 0 b" && echo "100644 $o0 0 c" && - echo "100644 $o6 3 d" && echo "100644 $o0 1 d/e" && - echo "100644 $o1 2 d/e" + echo "100644 $o1 2 d/e" && + echo "100644 $o6 3 d~$c6" ) >expected && test_cmp expected actual @@ -444,9 +444,9 @@ test_expect_success 'merge-recursive d/f conflict result' ' echo "100644 $o1 0 a" && echo "100644 $o0 0 b" && echo "100644 $o0 0 c" && - echo "100644 $o6 2 d" && echo "100644 $o0 1 d/e" && - echo "100644 $o1 3 d/e" + echo "100644 $o1 3 d/e" && + echo "100644 $o6 2 d~$c6" ) >expected && test_cmp expected actual diff --git a/t/t6020-merge-df.sh b/t/t6020-merge-df.sh index 2af1beec5f..f4ea318ec8 100755 --- a/t/t6020-merge-df.sh +++ b/t/t6020-merge-df.sh @@ -81,7 +81,7 @@ test_expect_success 'modify/delete + directory/file conflict' ' test 5 -eq $(git ls-files -s | wc -l) && test 4 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && test -f letters/file && test -f letters.txt && @@ -100,7 +100,7 @@ test_expect_success 'modify/delete + directory/file conflict; other way' ' test 5 -eq $(git ls-files -s | wc -l) && test 4 -eq $(git ls-files -u | wc -l) && - test 1 -eq $(git ls-files -o | wc -l) && + test 0 -eq $(git ls-files -o | wc -l) && test -f letters/file && test -f letters.txt && diff --git a/t/t6022-merge-rename.sh b/t/t6022-merge-rename.sh index b760c223c6..0ea3760265 100755 --- a/t/t6022-merge-rename.sh +++ b/t/t6022-merge-rename.sh @@ -385,7 +385,7 @@ test_expect_success 'Rename+D/F conflict; renamed file cannot merge and dir in t test_must_fail git merge --strategy=recursive dir-in-way && test 5 -eq "$(git ls-files -u | wc -l)" && - test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" && + test 3 -eq "$(git ls-files -u dir~HEAD | wc -l)" && test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && test_must_fail git diff --quiet && @@ -421,7 +421,7 @@ test_expect_success 'Same as previous, but merged other way' ' test_must_fail git merge --strategy=recursive renamed-file-has-conflicts && test 5 -eq "$(git ls-files -u | wc -l)" && - test 3 -eq "$(git ls-files -u dir | grep -v file-in-the-way | wc -l)" && + test 3 -eq "$(git ls-files -u dir~renamed-file-has-conflicts | wc -l)" && test 2 -eq "$(git ls-files -u dir/file-in-the-way | wc -l)" && test_must_fail git diff --quiet && diff --git a/t/t6036-recursive-corner-cases.sh b/t/t6036-recursive-corner-cases.sh index 59e52c5a09..f8790e6975 100755 --- a/t/t6036-recursive-corner-cases.sh +++ b/t/t6036-recursive-corner-cases.sh @@ -471,7 +471,7 @@ test_expect_success 'setup differently handled merges of directory/file conflict test_must_fail git merge C^0 && git clean -fd && git rm -rf a/ && - git rm a && + git rm a~HEAD && git cat-file -p B:a >a2 && git add a2 && git commit -m D2 && @@ -492,6 +492,7 @@ test_expect_success 'setup differently handled merges of directory/file conflict test_must_fail git merge B^0 && git clean -fd && git rm -rf a/ && + git rm a~B^0 && test_write_lines 1 2 3 4 5 6 7 8 >a && git add a && git commit -m E3 && @@ -501,7 +502,7 @@ test_expect_success 'setup differently handled merges of directory/file conflict test_must_fail git merge B^0 && git clean -fd && git rm -rf a/ && - git rm a && + git rm a~B^0 && test_write_lines 1 2 3 4 5 6 7 8 >a2 && git add a2 && git commit -m E4 && diff --git a/t/t6042-merge-rename-corner-cases.sh b/t/t6042-merge-rename-corner-cases.sh index 07dd09d985..25cb50478c 100755 --- a/t/t6042-merge-rename-corner-cases.sh +++ b/t/t6042-merge-rename-corner-cases.sh @@ -305,14 +305,14 @@ test_expect_success 'rename/directory conflict + clean content merge' ' git ls-files -u >out && test_line_count = 1 out && git ls-files -o >out && - test_line_count = 2 out && + test_line_count = 1 out && echo 0 >expect && git cat-file -p base:file >>expect && echo 7 >>expect && test_cmp expect newfile~HEAD && - test $(git rev-parse :2:newfile) = $(git hash-object expect) && + test $(git rev-parse :2:newfile~HEAD) = $(git hash-object expect) && test_path_is_file newfile/realfile && test_path_is_file newfile~HEAD diff --git a/t/t6043-merge-rename-directories.sh b/t/t6043-merge-rename-directories.sh index 4a71f17edd..12047a3309 100755 --- a/t/t6043-merge-rename-directories.sh +++ b/t/t6043-merge-rename-directories.sh @@ -1160,10 +1160,10 @@ test_expect_success '5d-check: Directory/file/file conflict due to directory ren git ls-files -u >out && test_line_count = 1 out && git ls-files -o >out && - test_line_count = 2 out && + test_line_count = 1 out && git rev-parse >actual \ - :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d :0:y/d/e && + :0:y/b :0:y/c :0:z/d :0:y/f :2:y/d~HEAD :0:y/d/e && git rev-parse >expect \ O:z/b O:z/c B:z/d B:z/f A:y/d B:y/d/e && test_cmp expect actual && -- 2.18.0.550.g44d6daf40a.dirty