Skip to content

Commit b2a7942

Browse files
newrengitster
authored andcommitted
merge-recursive: increase marker length with depth of recursion
Later patches in this series will modify file collision conflict handling (e.g. from rename/add and rename/rename(2to1) conflicts) so that multiply nested conflict markers can arise even before considering conflicts in the virtual merge base. Including the virtual merge base will provide a way to get triply (or higher) nested conflict markers. This new way to get nested conflict markers will force the need for a more general mechanism to extend the length of conflict markers in order to differentiate between different nestings. Along with this change to conflict marker length handling, we want to make sure that we don't regress handling for other types of conflicts with nested conflict markers. Add a more involved testcase using merge.conflictstyle=diff3, where not only does the virtual merge base contain conflicts, but its virtual merge base does as well (i.e. a case with triply nested conflict markers). While there are multiple reasonable ways to handle nested conflict markers in the virtual merge base for this type of situation, the easiest approach that dovetails well with the new needs for the file collision conflict handling is to require that the length of the conflict markers increase with each subsequent nesting. Subsequent patches which change the rename/add and rename/rename(2to1) conflict handling will modify the extra_marker_size flag appropriately for their new needs. Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent b28eeb3 commit b2a7942

File tree

4 files changed

+172
-9
lines changed

4 files changed

+172
-9
lines changed

ll-merge.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,9 @@ int ll_merge(mmbuffer_t *result_buf,
384384
if (opts->virtual_ancestor) {
385385
if (driver->recursive)
386386
driver = find_ll_merge_driver(driver->recursive);
387-
marker_size += 2;
387+
}
388+
if (opts->extra_marker_size) {
389+
marker_size += opts->extra_marker_size;
388390
}
389391
return driver->fn(driver, result_buf, path, ancestor, ancestor_label,
390392
ours, our_label, theirs, their_label,

ll-merge.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ struct ll_merge_options {
1111
unsigned virtual_ancestor : 1;
1212
unsigned variant : 2; /* favor ours, favor theirs, or union merge */
1313
unsigned renormalize : 1;
14+
unsigned extra_marker_size;
1415
long xdl_opts;
1516
};
1617

merge-recursive.c

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,14 +1058,16 @@ static int merge_3way(struct merge_options *o,
10581058
const struct diff_filespec *a,
10591059
const struct diff_filespec *b,
10601060
const char *branch1,
1061-
const char *branch2)
1061+
const char *branch2,
1062+
const int extra_marker_size)
10621063
{
10631064
mmfile_t orig, src1, src2;
10641065
struct ll_merge_options ll_opts = {0};
10651066
char *base_name, *name1, *name2;
10661067
int merge_status;
10671068

10681069
ll_opts.renormalize = o->renormalize;
1070+
ll_opts.extra_marker_size = extra_marker_size;
10691071
ll_opts.xdl_opts = o->xdl_opts;
10701072

10711073
if (o->call_depth) {
@@ -1300,6 +1302,7 @@ static int merge_mode_and_contents(struct merge_options *o,
13001302
const char *filename,
13011303
const char *branch1,
13021304
const char *branch2,
1305+
const int extra_marker_size,
13031306
struct merge_file_info *result)
13041307
{
13051308
if (o->branch1 != branch1) {
@@ -1310,7 +1313,8 @@ static int merge_mode_and_contents(struct merge_options *o,
13101313
*/
13111314
return merge_mode_and_contents(o, one, b, a,
13121315
filename,
1313-
branch2, branch1, result);
1316+
branch2, branch1,
1317+
extra_marker_size, result);
13141318
}
13151319

13161320
result->merge = 0;
@@ -1351,7 +1355,8 @@ static int merge_mode_and_contents(struct merge_options *o,
13511355
int ret = 0, merge_status;
13521356

13531357
merge_status = merge_3way(o, &result_buf, one, a, b,
1354-
branch1, branch2);
1358+
branch1, branch2,
1359+
extra_marker_size);
13551360

13561361
if ((merge_status < 0) || !result_buf.ptr)
13571362
ret = err(o, _("Failed to execute internal merge"));
@@ -1640,7 +1645,8 @@ static int handle_rename_rename_1to2(struct merge_options *o,
16401645
struct diff_filespec other;
16411646
struct diff_filespec *add;
16421647
if (merge_mode_and_contents(o, one, a, b, one->path,
1643-
ci->branch1, ci->branch2, &mfi))
1648+
ci->branch1, ci->branch2,
1649+
o->call_depth * 2, &mfi))
16441650
return -1;
16451651

16461652
/*
@@ -1707,9 +1713,11 @@ static int handle_rename_rename_2to1(struct merge_options *o,
17071713
path_side_1_desc = xstrfmt("version of %s from %s", path, a->path);
17081714
path_side_2_desc = xstrfmt("version of %s from %s", path, b->path);
17091715
if (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,
1710-
o->branch1, o->branch2, &mfi_c1) ||
1716+
o->branch1, o->branch2,
1717+
o->call_depth * 2, &mfi_c1) ||
17111718
merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,
1712-
o->branch1, o->branch2, &mfi_c2))
1719+
o->branch1, o->branch2,
1720+
o->call_depth * 2, &mfi_c2))
17131721
return -1;
17141722
free(path_side_1_desc);
17151723
free(path_side_2_desc);
@@ -2755,7 +2763,7 @@ static int process_renames(struct merge_options *o,
27552763

27562764
if (merge_mode_and_contents(o, &one, &a, &b, ren1_dst,
27572765
branch1, branch2,
2758-
&mfi)) {
2766+
o->call_depth * 2, &mfi)) {
27592767
clean_merge = -1;
27602768
goto cleanup_and_return;
27612769
}
@@ -3052,7 +3060,8 @@ static int handle_content_merge(struct merge_options *o,
30523060
df_conflict_remains = 1;
30533061
}
30543062
if (merge_mode_and_contents(o, &one, &a, &b, path,
3055-
o->branch1, o->branch2, &mfi))
3063+
o->branch1, o->branch2,
3064+
o->call_depth * 2, &mfi))
30563065
return -1;
30573066

30583067
/*

t/t6036-recursive-corner-cases.sh

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,4 +1596,155 @@ test_expect_failure 'check nested conflicts' '
15961596
)
15971597
'
15981598

1599+
# Setup:
1600+
# L1---L2---L3
1601+
# / \ / \ / \
1602+
# master X1 X2 ?
1603+
# \ / \ / \ /
1604+
# R1---R2---R3
1605+
#
1606+
# Where:
1607+
# master has one file named 'content'
1608+
# branches L1 and R1 both modify each of the two files in conflicting ways
1609+
#
1610+
# L<n> (n>1) is a merge of R<n-1> into L<n-1>
1611+
# R<n> (n>1) is a merge of L<n-1> into R<n-1>
1612+
# L<n> and R<n> resolve the conflicts differently.
1613+
#
1614+
# X<n> is an auto-generated merge-base used when merging L<n+1> and R<n+1>.
1615+
# By construction, X1 has conflict markers due to conflicting versions.
1616+
# X2, due to using merge.conflictstyle=3, has nested conflict markers.
1617+
#
1618+
# So, merging R3 into L3 using merge.conflictstyle=3 should show the
1619+
# nested conflict markers from X2 in the base version -- that means we
1620+
# have three levels of conflict markers. Can we distinguish all three?
1621+
1622+
test_expect_success 'setup virtual merge base with nested conflicts' '
1623+
test_create_repo virtual_merge_base_has_nested_conflicts &&
1624+
(
1625+
cd virtual_merge_base_has_nested_conflicts &&
1626+
1627+
# Create some related files now
1628+
for i in $(test_seq 1 10)
1629+
do
1630+
echo Random base content line $i
1631+
done >content &&
1632+
1633+
# Setup original commit
1634+
git add content &&
1635+
test_tick && git commit -m initial &&
1636+
1637+
git branch L &&
1638+
git branch R &&
1639+
1640+
# Create L1
1641+
git checkout L &&
1642+
echo left >>content &&
1643+
git add content &&
1644+
test_tick && git commit -m "version L1 of content" &&
1645+
git tag L1 &&
1646+
1647+
# Create R1
1648+
git checkout R &&
1649+
echo right >>content &&
1650+
git add content &&
1651+
test_tick && git commit -m "verson R1 of content" &&
1652+
git tag R1 &&
1653+
1654+
# Create L2
1655+
git checkout L &&
1656+
test_must_fail git -c merge.conflictstyle=diff3 merge R1 &&
1657+
git checkout L1 content &&
1658+
test_tick && git commit -m "version L2 of content" &&
1659+
git tag L2 &&
1660+
1661+
# Create R2
1662+
git checkout R &&
1663+
test_must_fail git -c merge.conflictstyle=diff3 merge L1 &&
1664+
git checkout R1 content &&
1665+
test_tick && git commit -m "version R2 of content" &&
1666+
git tag R2 &&
1667+
1668+
# Create L3
1669+
git checkout L &&
1670+
test_must_fail git -c merge.conflictstyle=diff3 merge R2 &&
1671+
git checkout L1 content &&
1672+
test_tick && git commit -m "version L3 of content" &&
1673+
git tag L3 &&
1674+
1675+
# Create R3
1676+
git checkout R &&
1677+
test_must_fail git -c merge.conflictstyle=diff3 merge L2 &&
1678+
git checkout R1 content &&
1679+
test_tick && git commit -m "version R3 of content" &&
1680+
git tag R3
1681+
)
1682+
'
1683+
1684+
test_expect_success 'check virtual merge base with nested conflicts' '
1685+
(
1686+
cd virtual_merge_base_has_nested_conflicts &&
1687+
1688+
git checkout L3^0 &&
1689+
1690+
# Merge must fail; there is a conflict
1691+
test_must_fail git -c merge.conflictstyle=diff3 merge -s recursive R3^0 &&
1692+
1693+
# Make sure the index has the right number of entries
1694+
git ls-files -s >out &&
1695+
test_line_count = 3 out &&
1696+
git ls-files -u >out &&
1697+
test_line_count = 3 out &&
1698+
# Ensure we have the correct number of untracked files
1699+
git ls-files -o >out &&
1700+
test_line_count = 1 out &&
1701+
1702+
# Compare :[23]:content to expected values
1703+
git rev-parse L1:content R1:content >expect &&
1704+
git rev-parse :2:content :3:content >actual &&
1705+
test_cmp expect actual &&
1706+
1707+
# Imitate X1 merge base, except without long enough conflict
1708+
# markers because a subsequent sed will modify them. Put
1709+
# result into vmb.
1710+
git cat-file -p master:content >base &&
1711+
git cat-file -p L:content >left &&
1712+
git cat-file -p R:content >right &&
1713+
cp left merged-once &&
1714+
test_must_fail git merge-file --diff3 \
1715+
-L "Temporary merge branch 1" \
1716+
-L "merged common ancestors" \
1717+
-L "Temporary merge branch 2" \
1718+
merged-once \
1719+
base \
1720+
right &&
1721+
sed -e "s/^\([<|=>]\)/\1\1\1/" merged-once >vmb &&
1722+
1723+
# Imitate X2 merge base, overwriting vmb. Note that we
1724+
# extend both sets of conflict markers to make them longer
1725+
# with the sed command.
1726+
cp left merged-twice &&
1727+
test_must_fail git merge-file --diff3 \
1728+
-L "Temporary merge branch 1" \
1729+
-L "merged common ancestors" \
1730+
-L "Temporary merge branch 2" \
1731+
merged-twice \
1732+
vmb \
1733+
right &&
1734+
sed -e "s/^\([<|=>]\)/\1\1\1/" merged-twice >vmb &&
1735+
1736+
# Compare :1:content to expected value
1737+
git cat-file -p :1:content >actual &&
1738+
test_cmp vmb actual &&
1739+
1740+
# Determine expected content in final outer merge, compare to
1741+
# what the merge generated.
1742+
cp -f left expect &&
1743+
test_must_fail git merge-file --diff3 \
1744+
-L "HEAD" -L "merged common ancestors" -L "R3^0" \
1745+
expect vmb right &&
1746+
test_cmp expect content
1747+
)
1748+
'
1749+
15991750
test_done

0 commit comments

Comments
 (0)