Skip to content

Commit bbafc9c

Browse files
newrengitster
authored andcommitted
merge-recursive: improve handling for rename/rename(2to1) conflicts
This makes the rename/rename(2to1) conflicts use the new handle_file_collision() function. Since that function was based originally on the rename/rename(2to1) handling code, the main differences here are in what was added. In particular: * Instead of storing files at collide_path~HEAD and collide_path~MERGE, the files are two-way merged and recorded at collide_path. * Instead of recording the version of the renamed file that existed on the renamed side in the index (thus ignoring any changes that were made to the file on the side of history without the rename), we do a three-way content merge on the renamed path, then store that at either stage 2 or stage 3. * Note that since the content merge for each rename may have conflicts, and then we have to merge the two renamed files, we can end up with nested conflict markers. Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7f86716 commit bbafc9c

File tree

4 files changed

+89
-148
lines changed

4 files changed

+89
-148
lines changed

merge-recursive.c

Lines changed: 14 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -696,27 +696,6 @@ static int update_stages(struct merge_options *opt, const char *path,
696696
return 0;
697697
}
698698

699-
static int update_stages_for_stage_data(struct merge_options *opt,
700-
const char *path,
701-
const struct stage_data *stage_data)
702-
{
703-
struct diff_filespec o, a, b;
704-
705-
o.mode = stage_data->stages[1].mode;
706-
oidcpy(&o.oid, &stage_data->stages[1].oid);
707-
708-
a.mode = stage_data->stages[2].mode;
709-
oidcpy(&a.oid, &stage_data->stages[2].oid);
710-
711-
b.mode = stage_data->stages[3].mode;
712-
oidcpy(&b.oid, &stage_data->stages[3].oid);
713-
714-
return update_stages(opt, path,
715-
is_null_oid(&o.oid) ? NULL : &o,
716-
is_null_oid(&a.oid) ? NULL : &a,
717-
is_null_oid(&b.oid) ? NULL : &b);
718-
}
719-
720699
static void update_entry(struct stage_data *entry,
721700
struct diff_filespec *o,
722701
struct diff_filespec *a,
@@ -1870,89 +1849,29 @@ static int handle_rename_rename_2to1(struct merge_options *o,
18701849
char *path_side_2_desc;
18711850
struct merge_file_info mfi_c1;
18721851
struct merge_file_info mfi_c2;
1873-
int ret;
18741852

18751853
output(o, 1, _("CONFLICT (rename/rename): "
18761854
"Rename %s->%s in %s. "
18771855
"Rename %s->%s in %s"),
18781856
a->path, c1->path, ci->branch1,
18791857
b->path, c2->path, ci->branch2);
18801858

1881-
remove_file(o, 1, a->path, o->call_depth || would_lose_untracked(a->path));
1882-
remove_file(o, 1, b->path, o->call_depth || would_lose_untracked(b->path));
1883-
18841859
path_side_1_desc = xstrfmt("version of %s from %s", path, a->path);
18851860
path_side_2_desc = xstrfmt("version of %s from %s", path, b->path);
18861861
if (merge_mode_and_contents(o, a, c1, &ci->ren1_other, path_side_1_desc,
18871862
o->branch1, o->branch2,
1888-
o->call_depth * 2, &mfi_c1) ||
1863+
1 + o->call_depth * 2, &mfi_c1) ||
18891864
merge_mode_and_contents(o, b, &ci->ren2_other, c2, path_side_2_desc,
18901865
o->branch1, o->branch2,
1891-
o->call_depth * 2, &mfi_c2))
1866+
1 + o->call_depth * 2, &mfi_c2))
18921867
return -1;
18931868
free(path_side_1_desc);
18941869
free(path_side_2_desc);
18951870

1896-
if (o->call_depth) {
1897-
/*
1898-
* If mfi_c1.clean && mfi_c2.clean, then it might make
1899-
* sense to do a two-way merge of those results. But, I
1900-
* think in all cases, it makes sense to have the virtual
1901-
* merge base just undo the renames; they can be detected
1902-
* again later for the non-recursive merge.
1903-
*/
1904-
remove_file(o, 0, path, 0);
1905-
ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, a->path);
1906-
if (!ret)
1907-
ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
1908-
b->path);
1909-
} else {
1910-
char *new_path1 = unique_path(o, path, ci->branch1);
1911-
char *new_path2 = unique_path(o, path, ci->branch2);
1912-
output(o, 1, _("Renaming %s to %s and %s to %s instead"),
1913-
a->path, new_path1, b->path, new_path2);
1914-
if (was_dirty(o, path))
1915-
output(o, 1, _("Refusing to lose dirty file at %s"),
1916-
path);
1917-
else if (would_lose_untracked(path))
1918-
/*
1919-
* Only way we get here is if both renames were from
1920-
* a directory rename AND user had an untracked file
1921-
* at the location where both files end up after the
1922-
* two directory renames. See testcase 10d of t6043.
1923-
*/
1924-
output(o, 1, _("Refusing to lose untracked file at "
1925-
"%s, even though it's in the way."),
1926-
path);
1927-
else
1928-
remove_file(o, 0, path, 0);
1929-
ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
1930-
if (!ret)
1931-
ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
1932-
new_path2);
1933-
/*
1934-
* unpack_trees() actually populates the index for us for
1935-
* "normal" rename/rename(2to1) situtations so that the
1936-
* correct entries are at the higher stages, which would
1937-
* make the call below to update_stages_for_stage_data
1938-
* unnecessary. However, if either of the renames came
1939-
* from a directory rename, then unpack_trees() will not
1940-
* have gotten the right data loaded into the index, so we
1941-
* need to do so now. (While it'd be tempting to move this
1942-
* call to update_stages_for_stage_data() to
1943-
* apply_directory_rename_modifications(), that would break
1944-
* our intermediate calls to would_lose_untracked() since
1945-
* those rely on the current in-memory index. See also the
1946-
* big "NOTE" in update_stages()).
1947-
*/
1948-
if (update_stages_for_stage_data(o, path, ci->dst_entry1))
1949-
ret = -1;
1950-
1951-
free(new_path2);
1952-
free(new_path1);
1953-
}
1954-
1955-
return ret;
1871+
return handle_file_collision(o, path, a->path, b->path,
1872+
ci->branch1, ci->branch2,
1873+
&mfi_c1.oid, mfi_c1.mode,
1874+
&mfi_c2.oid, mfi_c2.mode);
19561875
}
19571876

19581877
/*
@@ -3361,9 +3280,14 @@ static int process_entry(struct merge_options *o,
33613280
clean_merge = -1;
33623281
break;
33633282
case RENAME_TWO_FILES_TO_ONE:
3364-
clean_merge = 0;
3365-
if (handle_rename_rename_2to1(o, conflict_info))
3366-
clean_merge = -1;
3283+
/*
3284+
* Probably unclean merge, but if the two renamed
3285+
* files merge cleanly and the two resulting files
3286+
* can then be two-way merged cleanly, I guess it's
3287+
* a clean merge?
3288+
*/
3289+
clean_merge = handle_rename_rename_2to1(o,
3290+
conflict_info);
33673291
break;
33683292
default:
33693293
entry->processed = 0;

t/t6036-recursive-corner-cases.sh

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,12 @@ test_expect_success 'merge simple rename+criss-cross with no modifications' '
6464
git ls-files -u >out &&
6565
test_line_count = 2 out &&
6666
git ls-files -o >out &&
67-
test_line_count = 3 out &&
67+
test_line_count = 1 out &&
6868
6969
git rev-parse >expect \
70-
L2:three R2:three \
7170
L2:three R2:three &&
7271
git rev-parse >actual \
7372
:2:three :3:three &&
74-
git hash-object >>actual \
75-
three~HEAD three~R2^0 &&
7673
test_cmp expect actual
7774
)
7875
'
@@ -140,15 +137,12 @@ test_expect_success 'merge criss-cross + rename merges with basic modification'
140137
git ls-files -u >out &&
141138
test_line_count = 2 out &&
142139
git ls-files -o >out &&
143-
test_line_count = 3 out &&
140+
test_line_count = 1 out &&
144141
145142
git rev-parse >expect \
146-
L2:three R2:three \
147143
L2:three R2:three &&
148144
git rev-parse >actual \
149145
:2:three :3:three &&
150-
git hash-object >>actual \
151-
three~HEAD three~R2^0 &&
152146
test_cmp expect actual
153147
)
154148
'
@@ -1512,7 +1506,7 @@ test_expect_success 'setup nested conflicts' '
15121506
)
15131507
'
15141508

1515-
test_expect_failure 'check nested conflicts' '
1509+
test_expect_success 'check nested conflicts' '
15161510
(
15171511
cd nested_conflicts &&
15181512

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

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -464,17 +464,28 @@ test_expect_success 'handle rename/rename (2to1) conflict correctly' '
464464
git ls-files -u c >out &&
465465
test_line_count = 2 out &&
466466
git ls-files -o >out &&
467-
test_line_count = 3 out &&
467+
test_line_count = 1 out &&
468468
469469
test_path_is_missing a &&
470470
test_path_is_missing b &&
471-
test_path_is_file c~HEAD &&
472-
test_path_is_file c~C^0 &&
473471
474-
git rev-parse >expect \
475-
C:a B:b &&
476-
git hash-object >actual \
477-
c~HEAD c~C^0 &&
472+
git rev-parse >expect \
473+
C:a B:b &&
474+
git rev-parse >actual \
475+
:2:c :3:c &&
476+
test_cmp expect actual &&
477+
478+
# Test that the two-way merge in new_a is as expected
479+
git cat-file -p :2:c >>ours &&
480+
git cat-file -p :3:c >>theirs &&
481+
>empty &&
482+
test_must_fail git merge-file \
483+
-L "HEAD" \
484+
-L "" \
485+
-L "C^0" \
486+
ours empty theirs &&
487+
git hash-object c >actual &&
488+
git hash-object ours >expect &&
478489
test_cmp expect actual
479490
)
480491
'
@@ -940,7 +951,6 @@ test_expect_failure 'mod6-check: chains of rename/rename(1to2) and rename/rename
940951
test_conflicts_with_adds_and_renames() {
941952
sideL=$1
942953
sideR=$2
943-
expect=$3
944954

945955
# Setup:
946956
# L
@@ -1048,7 +1058,7 @@ test_conflicts_with_adds_and_renames() {
10481058
)
10491059
'
10501060

1051-
test_expect_$expect "check simple $sideL/$sideR conflict" '
1061+
test_expect_success "check simple $sideL/$sideR conflict" '
10521062
(
10531063
cd simple_${sideL}_${sideR} &&
10541064
@@ -1094,10 +1104,10 @@ test_conflicts_with_adds_and_renames() {
10941104
'
10951105
}
10961106

1097-
test_conflicts_with_adds_and_renames rename rename failure
1098-
test_conflicts_with_adds_and_renames rename add success
1099-
test_conflicts_with_adds_and_renames add rename success
1100-
test_conflicts_with_adds_and_renames add add success
1107+
test_conflicts_with_adds_and_renames rename rename
1108+
test_conflicts_with_adds_and_renames rename add
1109+
test_conflicts_with_adds_and_renames add rename
1110+
test_conflicts_with_adds_and_renames add add
11011111

11021112
# Setup:
11031113
# L
@@ -1168,7 +1178,7 @@ test_expect_success 'setup nested conflicts from rename/rename(2to1)' '
11681178
)
11691179
'
11701180

1171-
test_expect_failure 'check nested conflicts from rename/rename(2to1)' '
1181+
test_expect_success 'check nested conflicts from rename/rename(2to1)' '
11721182
(
11731183
cd nested_conflicts_from_rename_rename &&
11741184

0 commit comments

Comments
 (0)