On Tue, Dec 15, 2020 at 6:23 AM Derrick Stolee <stolee@xxxxxxxxx> wrote: > > On 12/14/2020 11:21 AM, Elijah Newren via GitGitGadget wrote: > > From: Elijah Newren <newren@xxxxxxxxx> > > > > 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. > > Thanks for adding this warning about the change to process_entry(). > > > @@ -657,6 +657,7 @@ static int process_renames(struct merge_options *opt, > > unsigned int old_sidemask; > > int target_index, other_source_index; > > int source_deleted, collision, type_changed; > > + const char *rename_branch = NULL, *delete_branch = NULL; > > Ah, here they are! > > > + if (source_deleted) { > > + if (target_index == 1) { > > + rename_branch = opt->branch1; > > + delete_branch = opt->branch2; > > + } else { > > + rename_branch = opt->branch2; > > + delete_branch = opt->branch1; > > + } > > } > > > > assert(source_deleted || oldinfo->filemask & old_sidemask); > > @@ -838,13 +847,26 @@ static int process_renames(struct merge_options *opt, > > "to %s in %s, but deleted in %s."), > > oldpath, newpath, rename_branch, delete_branch); > > This context line is the previous use of rename_branch and delete_branch. > Perhaps the declarations, initialization, and first-use here are worth > their own patch? > > > } else { > > + /* > > + * a few different cases...start by copying the > > + * existing stage(s) from oldinfo over the newinfo > > + * and update the pathname(s). > > + */ > > + memcpy(&newinfo->stages[0], &oldinfo->stages[0], > > + sizeof(newinfo->stages[0])); > > + newinfo->filemask |= (1 << MERGE_BASE); > > + newinfo->pathnames[0] = oldpath; > > if (type_changed) { > > /* rename vs. typechange */ > > die("Not yet implemented"); > > } else if (source_deleted) { > > /* rename/delete */ > > + newinfo->path_conflict = 1; > > + path_msg(opt, newpath, 0, > > + _("CONFLICT (rename/delete): %s renamed" > > + " to %s in %s, but deleted in %s."), > > + oldpath, newpath, > > + rename_branch, delete_branch); > > Since the primary purpose of rename_branch and delete_branch appears to > be for these error messages, then likely the previous error message about > a rename/delete should just be promoted into this patch instead of the > previous. > > In fact, the error messages are the exact same, but with slightly different > lines due to wrapping: > > path_msg(opt, newpath, 0, > _("CONFLICT (rename/delete): %s renamed " > "to %s in %s, but deleted in %s."), > oldpath, newpath, rename_branch, delete_branch); > > and > > path_msg(opt, newpath, 0, > _("CONFLICT (rename/delete): %s renamed" > " to %s in %s, but deleted in %s."), > oldpath, newpath, > rename_branch, delete_branch); > > I wonder if there is a way to group these together? Perhaps the nested > if/else if/else blocks could store a "conflict state" value that says > which CONFLICT message to print after the complicated branching is done. > > Alternatively, this message appears to be written in the following case: > > source_deleted && !type_changed > > your if/else if/else block could be rearranged as follows: > > if (collision && !source_deleted) > /* collision: rename/add or rename/rename(2to1) */ > else if (!type_change && source_deleted) > /* rename/delete or rename/add/delete or rename/rename(2to1)/delete */ > else if (!collision) > /* a few different cases */ > > Of course, the thing I am missing is that copy of oldinfo->stages[0] into > newinfo->stages[0] along with changes to the filemask and pathnames! That > is likely why you need the two different markers, because the cases truly > are different in that subtle way. Yeah, there is that subtlety and another one -- the rename/add/delete case will also later trigger the "add/add" conflict type within process_entries() for this same path, whereas the rename/delete case from this patch won't. The combination is enough of a difference that I'm worried that trying to make both types run through the same code block might blur the differences and pose a landmine for future folks coming to edit the code; it'd make it too easy to break one or the other conflict type. If the sharing was done a different way, namely saving the basic message in some variable before either if-block and then both places just pass that string to path_msg() instead of both having it typed out, then that'd probably make sense, but then we're not really saving much. > > /* normal rename */ > > die("Not yet implemented"); > > @@ -1380,12 +1402,21 @@ static void process_entry(struct merge_options *opt, > > modify_branch = (side == 1) ? opt->branch1 : opt->branch2; > > delete_branch = (side == 1) ? opt->branch2 : opt->branch1; > > > > - path_msg(opt, path, 0, > > - _("CONFLICT (modify/delete): %s deleted in %s " > > - "and modified in %s. Version %s of %s left " > > - "in tree."), > > - path, delete_branch, modify_branch, > > - modify_branch, path); > > + if (ci->path_conflict && > > + oideq(&ci->stages[0].oid, &ci->stages[side].oid)) { > > + /* > > + * This came from a rename/delete; no action to take, > > + * but avoid printing "modify/delete" conflict notice > > + * since the contents were not modified. > > + */ > > + } else { > > + path_msg(opt, path, 0, > > + _("CONFLICT (modify/delete): %s deleted in %s " > > + "and modified in %s. Version %s of %s left " > > + "in tree."), > > + path, delete_branch, modify_branch, > > + modify_branch, path); > > + } > > Thanks, > -Stolee