Skip to content

Commit 48c9cb9

Browse files
newrengitster
authored andcommitted
merge-recursive: improve rename/rename(1to2)/add[/add] handling
When we have a rename/rename(1to2) conflict, each of the renames can collide with a file addition. Each of these rename/add conflicts suffered from the same kinds of problems that normal rename/add suffered from. Make the code use handle_file_conflicts() as well so that we get all the same fixes and consistent behavior between the different conflict types. Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent dcf2815 commit 48c9cb9

File tree

3 files changed

+113
-94
lines changed

3 files changed

+113
-94
lines changed

merge-recursive.c

Lines changed: 77 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,96 +1709,34 @@ static int handle_rename_add(struct merge_options *o,
17091709
ci->dst_entry1->stages[other_stage].mode);
17101710
}
17111711

1712-
static int handle_file(struct merge_options *o,
1713-
struct diff_filespec *rename,
1714-
int stage,
1715-
struct rename_conflict_info *ci)
1716-
{
1717-
char *dst_name = rename->path;
1718-
struct stage_data *dst_entry;
1719-
const char *cur_branch, *other_branch;
1720-
struct diff_filespec other;
1721-
struct diff_filespec *add;
1722-
int ret;
1723-
1724-
if (stage == 2) {
1725-
dst_entry = ci->dst_entry1;
1726-
cur_branch = ci->branch1;
1727-
other_branch = ci->branch2;
1728-
} else {
1729-
dst_entry = ci->dst_entry2;
1730-
cur_branch = ci->branch2;
1731-
other_branch = ci->branch1;
1732-
}
1733-
1734-
add = filespec_from_entry(&other, dst_entry, stage ^ 1);
1735-
if (add) {
1736-
int ren_src_was_dirty = was_dirty(o, rename->path);
1737-
char *add_name = unique_path(o, rename->path, other_branch);
1738-
if (update_file(o, 0, &add->oid, add->mode, add_name))
1739-
return -1;
1740-
1741-
if (ren_src_was_dirty) {
1742-
output(o, 1, _("Refusing to lose dirty file at %s"),
1743-
rename->path);
1744-
}
1745-
/*
1746-
* Because the double negatives somehow keep confusing me...
1747-
* 1) update_wd iff !ren_src_was_dirty.
1748-
* 2) no_wd iff !update_wd
1749-
* 3) so, no_wd == !!ren_src_was_dirty == ren_src_was_dirty
1750-
*/
1751-
remove_file(o, 0, rename->path, ren_src_was_dirty);
1752-
dst_name = unique_path(o, rename->path, cur_branch);
1753-
} else {
1754-
if (dir_in_way(rename->path, !o->call_depth, 0)) {
1755-
dst_name = unique_path(o, rename->path, cur_branch);
1756-
output(o, 1, _("%s is a directory in %s adding as %s instead"),
1757-
rename->path, other_branch, dst_name);
1758-
} else if (!o->call_depth &&
1759-
would_lose_untracked(rename->path)) {
1760-
dst_name = unique_path(o, rename->path, cur_branch);
1761-
output(o, 1, _("Refusing to lose untracked file at %s; "
1762-
"adding as %s instead"),
1763-
rename->path, dst_name);
1764-
}
1765-
}
1766-
if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
1767-
; /* fall through, do allow dst_name to be released */
1768-
else if (stage == 2)
1769-
ret = update_stages(o, rename->path, NULL, rename, add);
1770-
else
1771-
ret = update_stages(o, rename->path, NULL, add, rename);
1772-
1773-
if (dst_name != rename->path)
1774-
free(dst_name);
1775-
1776-
return ret;
1777-
}
1778-
17791712
static int handle_rename_rename_1to2(struct merge_options *o,
17801713
struct rename_conflict_info *ci)
17811714
{
17821715
/* One file was renamed in both branches, but to different names. */
1716+
struct merge_file_info mfi;
1717+
struct diff_filespec other;
1718+
struct diff_filespec *add;
17831719
struct diff_filespec *one = ci->pair1->one;
17841720
struct diff_filespec *a = ci->pair1->two;
17851721
struct diff_filespec *b = ci->pair2->two;
1722+
char *path_desc;
17861723

17871724
output(o, 1, _("CONFLICT (rename/rename): "
17881725
"Rename \"%s\"->\"%s\" in branch \"%s\" "
17891726
"rename \"%s\"->\"%s\" in \"%s\"%s"),
17901727
one->path, a->path, ci->branch1,
17911728
one->path, b->path, ci->branch2,
17921729
o->call_depth ? _(" (left unresolved)") : "");
1793-
if (o->call_depth) {
1794-
struct merge_file_info mfi;
1795-
struct diff_filespec other;
1796-
struct diff_filespec *add;
1797-
if (merge_mode_and_contents(o, one, a, b, one->path,
1798-
ci->branch1, ci->branch2,
1799-
o->call_depth * 2, &mfi))
1800-
return -1;
18011730

1731+
path_desc = xstrfmt("%s and %s, both renamed from %s",
1732+
a->path, b->path, one->path);
1733+
if (merge_mode_and_contents(o, one, a, b, path_desc,
1734+
ci->branch1, ci->branch2,
1735+
o->call_depth * 2, &mfi))
1736+
return -1;
1737+
free(path_desc);
1738+
1739+
if (o->call_depth) {
18021740
/*
18031741
* FIXME: For rename/add-source conflicts (if we could detect
18041742
* such), this is wrong. We should instead find a unique
@@ -1830,8 +1768,70 @@ static int handle_rename_rename_1to2(struct merge_options *o,
18301768
}
18311769
else
18321770
remove_file_from_cache(b->path);
1833-
} else if (handle_file(o, a, 2, ci) || handle_file(o, b, 3, ci))
1834-
return -1;
1771+
} else {
1772+
/*
1773+
* For each destination path, we need to see if there is a
1774+
* rename/add collision. If not, we can write the file out
1775+
* to the specified location.
1776+
*/
1777+
add = filespec_from_entry(&other, ci->dst_entry1, 2 ^ 1);
1778+
if (add) {
1779+
if (handle_file_collision(o, a->path,
1780+
NULL, NULL,
1781+
ci->branch1, ci->branch2,
1782+
&mfi.oid, mfi.mode,
1783+
&add->oid, add->mode) < 0)
1784+
return -1;
1785+
} else {
1786+
char *new_path = NULL;
1787+
if (dir_in_way(a->path, !o->call_depth, 0)) {
1788+
new_path = unique_path(o, a->path, ci->branch1);
1789+
output(o, 1, _("%s is a directory in %s adding "
1790+
"as %s instead"),
1791+
a->path, ci->branch2, new_path);
1792+
} else if (would_lose_untracked(a->path)) {
1793+
new_path = unique_path(o, a->path, ci->branch1);
1794+
output(o, 1, _("Refusing to lose untracked file"
1795+
" at %s; adding as %s instead"),
1796+
a->path, new_path);
1797+
}
1798+
1799+
if (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : a->path))
1800+
return -1;
1801+
free(new_path);
1802+
if (update_stages(o, a->path, NULL, a, NULL))
1803+
return -1;
1804+
}
1805+
1806+
add = filespec_from_entry(&other, ci->dst_entry2, 3 ^ 1);
1807+
if (add) {
1808+
if (handle_file_collision(o, b->path,
1809+
NULL, NULL,
1810+
ci->branch1, ci->branch2,
1811+
&add->oid, add->mode,
1812+
&mfi.oid, mfi.mode) < 0)
1813+
return -1;
1814+
} else {
1815+
char *new_path = NULL;
1816+
if (dir_in_way(b->path, !o->call_depth, 0)) {
1817+
new_path = unique_path(o, b->path, ci->branch2);
1818+
output(o, 1, _("%s is a directory in %s adding "
1819+
"as %s instead"),
1820+
b->path, ci->branch1, new_path);
1821+
} else if (would_lose_untracked(b->path)) {
1822+
new_path = unique_path(o, b->path, ci->branch2);
1823+
output(o, 1, _("Refusing to lose untracked file"
1824+
" at %s; adding as %s instead"),
1825+
b->path, new_path);
1826+
}
1827+
1828+
if (update_file(o, 0, &mfi.oid, mfi.mode, new_path ? new_path : b->path))
1829+
return -1;
1830+
free(new_path);
1831+
if (update_stages(o, b->path, NULL, NULL, b))
1832+
return -1;
1833+
}
1834+
}
18351835

18361836
return 0;
18371837
}

t/t6042-merge-rename-corner-cases.sh

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -684,22 +684,35 @@ test_expect_success 'rename/rename/add-dest merge still knows about conflicting
684684
git ls-files -u c >out &&
685685
test_line_count = 2 out &&
686686
git ls-files -o >out &&
687-
test_line_count = 5 out &&
687+
test_line_count = 1 out &&
688688
689689
git rev-parse >expect \
690690
A:a C:b B:b C:c B:c &&
691691
git rev-parse >actual \
692692
:1:a :2:b :3:b :2:c :3:c &&
693693
test_cmp expect actual &&
694694
695-
git rev-parse >expect \
696-
C:c B:c C:b B:b &&
697-
git hash-object >actual \
698-
c~HEAD c~B\^0 b~HEAD b~B\^0 &&
699-
test_cmp expect actual &&
695+
# Record some contents for re-doing merges
696+
git cat-file -p A:a >stuff &&
697+
git cat-file -p C:b >important_info &&
698+
git cat-file -p B:c >precious_data &&
699+
>empty &&
700700
701-
test_path_is_missing b &&
702-
test_path_is_missing c
701+
# Test the merge in b
702+
test_must_fail git merge-file \
703+
-L "HEAD" \
704+
-L "" \
705+
-L "B^0" \
706+
important_info empty stuff &&
707+
test_cmp important_info b &&
708+
709+
# Test the merge in c
710+
test_must_fail git merge-file \
711+
-L "HEAD" \
712+
-L "" \
713+
-L "B^0" \
714+
stuff empty precious_data &&
715+
test_cmp stuff c
703716
)
704717
'
705718

t/t6043-merge-rename-directories.sh

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1078,7 +1078,7 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam
10781078
git ls-files -u >out &&
10791079
test_line_count = 6 out &&
10801080
git ls-files -o >out &&
1081-
test_line_count = 3 out &&
1081+
test_line_count = 1 out &&
10821082
10831083
git rev-parse >actual \
10841084
:0:y/b :0:y/c :0:y/e &&
@@ -1094,9 +1094,9 @@ test_expect_success '5c-check: Transitive rename would cause rename/rename/renam
10941094
test_cmp expect actual &&
10951095
10961096
git hash-object >actual \
1097-
w/d~HEAD w/d~B^0 z/d &&
1097+
z/d &&
10981098
git rev-parse >expect \
1099-
O:x/d B:w/d O:x/d &&
1099+
O:x/d &&
11001100
test_cmp expect actual &&
11011101
test_path_is_missing x/d &&
11021102
test_path_is_file y/d &&
@@ -3672,7 +3672,7 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena
36723672
git ls-files -u >out &&
36733673
test_line_count = 4 out &&
36743674
git ls-files -o >out &&
3675-
test_line_count = 4 out &&
3675+
test_line_count = 3 out &&
36763676
36773677
echo different >expected &&
36783678
echo mods >>expected &&
@@ -3684,11 +3684,17 @@ test_expect_success '11e-check: Avoid deleting not-uptodate with dir rename/rena
36843684
O:z/a O:z/b O:x/d O:x/c O:x/c A:y/c O:x/c &&
36853685
test_cmp expect actual &&
36863686
3687-
git hash-object >actual \
3688-
y/c~B^0 y/c~HEAD &&
3689-
git rev-parse >expect \
3690-
O:x/c A:y/c &&
3691-
test_cmp expect actual
3687+
# See if y/c~merged has expected contents; requires manually
3688+
# doing the expected file merge
3689+
git cat-file -p A:y/c >c1 &&
3690+
git cat-file -p B:z/c >c2 &&
3691+
>empty &&
3692+
test_must_fail git merge-file \
3693+
-L "HEAD" \
3694+
-L "" \
3695+
-L "B^0" \
3696+
c1 empty c2 &&
3697+
test_cmp c1 y/c~merged
36923698
)
36933699
'
36943700

0 commit comments

Comments
 (0)