diff --git a/merge-ort.c b/merge-ort.c index 325b19b182..e7bdcf7b96 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -2878,6 +2878,20 @@ static int process_renames(struct merge_options *opt, newinfo = new_ent->value; } + /* + * Directory renames can result in rename-to-self; the code + * below assumes we have A->B with different A & B, and tries + * to move all entries to path B. If A & B are the same path, + * the logic can get confused, so skip further processing when + * A & B are already the same path. + * + * As a reminder, we can avoid strcmp here because all paths + * are interned in opt->priv->paths; see the comment above + * "paths" in struct merge_options_internal. + */ + if (oldpath == newpath) + continue; + /* * If pair->one->path isn't in opt->priv->paths, that means * that either directory rename detection removed that diff --git a/t/t6423-merge-rename-directories.sh b/t/t6423-merge-rename-directories.sh index e1251b4e12..49eb10392b 100755 --- a/t/t6423-merge-rename-directories.sh +++ b/t/t6423-merge-rename-directories.sh @@ -4731,7 +4731,7 @@ test_setup_12i () { mkdir -p source/subdir && echo foo >source/subdir/foo && - echo bar >source/bar && + printf "%d\n" 1 2 3 4 5 6 7 >source/bar && echo baz >source/baz && git add source && git commit -m orig && @@ -4778,6 +4778,69 @@ test_expect_success '12i: Directory rename causes rename-to-self' ' ) ' +# Testcase 12i2, Identical to 12i except that source/subdir/bar modified on unrenamed side +# Commit O: source/{subdir/foo, bar, baz_1} +# Commit A: source/{foo, bar_2, baz_1} +# Commit B: source/{subdir/{foo, bar}, baz_2} +# Expected: source/{foo, bar, baz_2}, with conflicts on +# source/bar vs. source/subdir/bar + +test_setup_12i2 () { + git init 12i2 && + ( + cd 12i2 && + + mkdir -p source/subdir && + echo foo >source/subdir/foo && + printf "%d\n" 1 2 3 4 5 6 7 >source/bar && + echo baz >source/baz && + git add source && + git commit -m orig && + + git branch O && + git branch A && + git branch B && + + git switch A && + git mv source/subdir/foo source/foo && + echo 8 >> source/bar && + git add source/bar && + git commit -m A && + + git switch B && + git mv source/bar source/subdir/bar && + echo more baz >>source/baz && + git add source/baz && + git commit -m B + ) +} + +test_expect_success '12i2: Directory rename causes rename-to-self' ' + test_setup_12i2 && + ( + cd 12i2 && + + git checkout A^0 && + + test_must_fail git -c merge.directoryRenames=conflict merge -s recursive B^0 && + + test_path_is_missing source/subdir && + test_path_is_file source/bar && + test_path_is_file source/baz && + + git ls-files >actual && + uniq tracked && + test_line_count = 3 tracked && + + git status --porcelain -uno >actual && + cat >expect <<-\EOF && + UU source/bar + M source/baz + EOF + test_cmp expect actual + ) +' + # Testcase 12j, Directory rename to root causes rename-to-self # Commit O: {subdir/foo, bar, baz_1} # Commit A: {foo, bar, baz_1} @@ -5106,7 +5169,7 @@ test_setup_12n () { ) } -test_expect_failure '12n: Directory rename transitively makes rename back to self' ' +test_expect_success '12n: Directory rename transitively makes rename back to self' ' test_setup_12n && ( cd 12n && @@ -5166,7 +5229,7 @@ test_setup_12n2 () { ) } -test_expect_failure '12n2: Directory rename transitively makes rename back to self' ' +test_expect_success '12n2: Directory rename transitively makes rename back to self' ' test_setup_12n2 && ( cd 12n2 &&