merge-ort: add implementation of rename/delete conflicts

Implement rename/delete conflicts, i.e. one side renames a file and the other deletes the file. This code replaces the following from merge-recurisve.c: * the code relevant to RENAME_DELETE in process_renames() * the RENAME_DELETE case of process_entry() * handle_rename_delete() Also, there is some shared code from merge-recursive.c for multiple different rename cases which we will no longer need for this case (or other rename cases): * handle_change_delete() * setup_rename_conflict_info() The consolidation of five separate codepaths into one is made possible by a change in design: process_renames() tweaks the conflict_info entries within opt->priv->paths such that process_entry() can then handle all the non-rename conflict types (directory/file, modify/delete, etc.) orthogonally. This means we're much less likely to miss special implementation of some kind of combination of conflict types (see commits brought in by 66c62eaec6 ("Merge branch 'en/merge-tests'", 2020-11-18), especially commit ef52778708 ("merge tests: expect improved directory/file conflict handling in ort", 2020-10-26) for more details). That, together with letting worktree/index updating be handled orthogonally in the merge_switch_to_result() function, dramatically simplifies the code for various special rename cases. To be fair, there is a _slight_ tweak to process_entry() here, because rename/delete cases will also trigger the modify/delete codepath. However, we only want a modify/delete message to be printed for a rename/delete conflict if there is a content change in the renamed file in addition to the rename. So process_renames() and process_entry() aren't quite fully orthogonal, but they are pretty close. Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Dec 15, 2020 at 18:28 UTC 2e91ddd24e7240460fd2a9aee1962345ed858b6b
1 file changed +40 -8
merge-ort.c
+40 -8
@@ -657,6 +657,7 @@ static int process_renames(struct merge_options *opt,
657 unsigned int old_sidemask;
658 int target_index, other_source_index;
659 int source_deleted, collision, type_changed;
660 + const char *rename_branch = NULL, *delete_branch = NULL;
661
662 old_ent = strmap_get_entry(&opt->priv->paths, pair->one->path);
663 oldpath = old_ent->key;
@@ -779,6 +780,15 @@ static int process_renames(struct merge_options *opt,
780 /* special handling so later blocks can handle this */
781 die("Not yet implemented");
782 }
783 + if (source_deleted) {
784 + if (target_index == 1) {
785 + rename_branch = opt->branch1;
786 + delete_branch = opt->branch2;
787 + } else {
788 + rename_branch = opt->branch2;
789 + delete_branch = opt->branch1;
790 + }
791 + }
792
793 assert(source_deleted || oldinfo->filemask & old_sidemask);
794
@@ -790,13 +800,26 @@ static int process_renames(struct merge_options *opt,
800 /* rename/add/delete or rename/rename(2to1)/delete */
801 die("Not yet implemented");
802 } else {
793 - /* a few different cases... */
803 + /*
804 + * a few different cases...start by copying the
805 + * existing stage(s) from oldinfo over the newinfo
806 + * and update the pathname(s).
807 + */
808 + memcpy(&newinfo->stages[0], &oldinfo->stages[0],
809 + sizeof(newinfo->stages[0]));
810 + newinfo->filemask |= (1 << MERGE_BASE);
811 + newinfo->pathnames[0] = oldpath;
812 if (type_changed) {
813 /* rename vs. typechange */
814 die("Not yet implemented");
815 } else if (source_deleted) {
816 /* rename/delete */
799 - die("Not yet implemented");
817 + newinfo->path_conflict = 1;
818 + path_msg(opt, newpath, 0,
819 + _("CONFLICT (rename/delete): %s renamed"
820 + " to %s in %s, but deleted in %s."),
821 + oldpath, newpath,
822 + rename_branch, delete_branch);
823 } else {
824 /* normal rename */
825 die("Not yet implemented");
@@ -1332,12 +1355,21 @@ static void process_entry(struct merge_options *opt,
1355 modify_branch = (side == 1) ? opt->branch1 : opt->branch2;
1356 delete_branch = (side == 1) ? opt->branch2 : opt->branch1;
1357
1335 - path_msg(opt, path, 0,
1336 - _("CONFLICT (modify/delete): %s deleted in %s "
1337 - "and modified in %s. Version %s of %s left "
1338 - "in tree."),
1339 - path, delete_branch, modify_branch,
1340 - modify_branch, path);
1358 + if (ci->path_conflict &&
1359 + oideq(&ci->stages[0].oid, &ci->stages[side].oid)) {
1360 + /*
1361 + * This came from a rename/delete; no action to take,
1362 + * but avoid printing "modify/delete" conflict notice
1363 + * since the contents were not modified.
1364 + */
1365 + } else {
1366 + path_msg(opt, path, 0,
1367 + _("CONFLICT (modify/delete): %s deleted in %s "
1368 + "and modified in %s. Version %s of %s left "
1369 + "in tree."),
1370 + path, delete_branch, modify_branch,
1371 + modify_branch, path);
1372 + }
1373 } else if (ci->filemask == 2 || ci->filemask == 4) {
1374 /* Added on one side */
1375 int side = (ci->filemask == 4) ? 2 : 1;