merge-recursive: avoid clobbering untracked files with directory renames

Reviewed-by: Stefan Beller <sbeller@google.com> Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Apr 19, 2018 at 10:58 UTC 79c47598f5c8c0008ae9281f20c2a041d4cabd93
2 files changed +43 -5
merge-recursive.c
+40 -2
@@ -1151,6 +1151,26 @@ static int conflict_rename_dir(struct merge_options *o,
1151 {
1152 const struct diff_filespec *dest = pair->two;
1153
1154 + if (!o->call_depth && would_lose_untracked(dest->path)) {
1155 + char *alt_path = unique_path(o, dest->path, rename_branch);
1156 +
1157 + output(o, 1, _("Error: Refusing to lose untracked file at %s; "
1158 + "writing to %s instead."),
1159 + dest->path, alt_path);
1160 + /*
1161 + * Write the file in worktree at alt_path, but not in the
1162 + * index. Instead, write to dest->path for the index but
1163 + * only at the higher appropriate stage.
1164 + */
1165 + if (update_file(o, 0, &dest->oid, dest->mode, alt_path))
1166 + return -1;
1167 + free(alt_path);
1168 + return update_stages(o, dest->path, NULL,
1169 + rename_branch == o->branch1 ? dest : NULL,
1170 + rename_branch == o->branch1 ? NULL : dest);
1171 + }
1172 +
1173 + /* Update dest->path both in index and in worktree */
1174 if (update_file(o, 1, &dest->oid, dest->mode, dest->path))
1175 return -1;
1176 return 0;
@@ -1169,7 +1189,8 @@ static int handle_change_delete(struct merge_options *o,
1189 const char *update_path = path;
1190 int ret = 0;
1191
1172 - if (dir_in_way(path, !o->call_depth, 0)) {
1192 + if (dir_in_way(path, !o->call_depth, 0) ||
1193 + (!o->call_depth && would_lose_untracked(path))) {
1194 update_path = alt_path = unique_path(o, path, change_branch);
1195 }
1196
@@ -1295,6 +1316,12 @@ static int handle_file(struct merge_options *o,
1316 dst_name = unique_path(o, rename->path, cur_branch);
1317 output(o, 1, _("%s is a directory in %s adding as %s instead"),
1318 rename->path, other_branch, dst_name);
1319 + } else if (!o->call_depth &&
1320 + would_lose_untracked(rename->path)) {
1321 + dst_name = unique_path(o, rename->path, cur_branch);
1322 + output(o, 1, _("Refusing to lose untracked file at %s; "
1323 + "adding as %s instead"),
1324 + rename->path, dst_name);
1325 }
1326 }
1327 if ((ret = update_file(o, 0, &rename->oid, rename->mode, dst_name)))
@@ -1420,7 +1447,18 @@ static int conflict_rename_rename_2to1(struct merge_options *o,
1447 char *new_path2 = unique_path(o, path, ci->branch2);
1448 output(o, 1, _("Renaming %s to %s and %s to %s instead"),
1449 a->path, new_path1, b->path, new_path2);
1423 - remove_file(o, 0, path, 0);
1450 + if (would_lose_untracked(path))
1451 + /*
1452 + * Only way we get here is if both renames were from
1453 + * a directory rename AND user had an untracked file
1454 + * at the location where both files end up after the
1455 + * two directory renames. See testcase 10d of t6043.
1456 + */
1457 + output(o, 1, _("Refusing to lose untracked file at "
1458 + "%s, even though it's in the way."),
1459 + path);
1460 + else
1461 + remove_file(o, 0, path, 0);
1462 ret = update_file(o, 0, &mfi_c1.oid, mfi_c1.mode, new_path1);
1463 if (!ret)
1464 ret = update_file(o, 0, &mfi_c2.oid, mfi_c2.mode,
t/t6043-merge-rename-directories.sh
+3 -3
@@ -2992,7 +2992,7 @@ test_expect_success '10b-setup: Overwrite untracked with dir rename + delete' '
2992 )
2993 '
2994
2995 -test_expect_failure '10b-check: Overwrite untracked with dir rename + delete' '
2995 +test_expect_success '10b-check: Overwrite untracked with dir rename + delete' '
2996 (
2997 cd 10b &&
2998
@@ -3070,7 +3070,7 @@ test_expect_success '10c-setup: Overwrite untracked with dir rename/rename(1to2)
3070 )
3071 '
3072
3073 -test_expect_failure '10c-check: Overwrite untracked with dir rename/rename(1to2)' '
3073 +test_expect_success '10c-check: Overwrite untracked with dir rename/rename(1to2)' '
3074 (
3075 cd 10c &&
3076
@@ -3145,7 +3145,7 @@ test_expect_success '10d-setup: Delete untracked with dir rename/rename(2to1)' '
3145 )
3146 '
3147
3148 -test_expect_failure '10d-check: Delete untracked with dir rename/rename(2to1)' '
3148 +test_expect_success '10d-check: Delete untracked with dir rename/rename(2to1)' '
3149 (
3150 cd 10d &&
3151