Re: [PATCH 05/11] merge-ort: add basic outline for process_renames()

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 12/9/2020 2:41 PM, Elijah Newren via GitGitGadget wrote:
> From: Elijah Newren <newren@xxxxxxxxx>
> 
> Add code which determines which kind of special rename case each rename
> corresponds to, but leave the handling of each type unimplemented for
> now.  Future commits will implement each one.
> 
> There is some tenuous resemblance to merge-recursive's
> process_renames(), but comparing the two is very unlikely to yield any
> insights.  merge-ort's process_renames() is a bit complex and I would
> prefer if I could simplify it more, but it is far easier to grok than
> merge-recursive's function of the same name in my opinion.  Plus,
> merge-ort handles more rename conflict types than merge-recursive does.
> 
> Signed-off-by: Elijah Newren <newren@xxxxxxxxx>
> ---
>  merge-ort.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 97 insertions(+), 1 deletion(-)
> 
> diff --git a/merge-ort.c b/merge-ort.c
> index 3cdf8124b85..faec29db955 100644
> --- a/merge-ort.c
> +++ b/merge-ort.c
> @@ -620,7 +620,103 @@ static int handle_content_merge(struct merge_options *opt,
>  static int process_renames(struct merge_options *opt,
>  			   struct diff_queue_struct *renames)
>  {
> -	die("Not yet implemented.");
> +	int clean_merge = 1, i;
> +
> +	for (i = 0; i < renames->nr; ++i) {
> +		const char *oldpath = NULL, *newpath;

This "= NULL" is not necessary, since you initialize it to
old_ent->key unconditionally.

> +		struct diff_filepair *pair = renames->queue[i];
> +		struct conflict_info *oldinfo = NULL, *newinfo = NULL;

These, too.

> +		struct strmap_entry *old_ent, *new_ent;
> +		unsigned int old_sidemask;
> +		int target_index, other_source_index;
> +		int source_deleted, collision, type_changed;
> +
> +		old_ent = strmap_get_entry(&opt->priv->paths, pair->one->path);
> +		oldpath = old_ent->key;
> +		oldinfo = old_ent->value;
> +
> +		new_ent = strmap_get_entry(&opt->priv->paths, pair->two->path);
> +		newpath = new_ent->key;
> +		newinfo = new_ent->value;

This is moving data around. I wonder if there is any possibility that
old_ent or new_ent could be NULL here, and we should check for that?
(The "any possibility" is probably "is there a chance of a bug in the
earlier logic that didn't cause a failure yet, but would cause a SEGFAULT
here?".)

> +		/*
> +		 * diff_filepairs have copies of pathnames, thus we have to
> +		 * use standard 'strcmp()' (negated) instead of '=='.
> +		 */
> +		if (i+1 < renames->nr &&

nit: I tend to prefer "i + 1".

> +		    !strcmp(oldpath, renames->queue[i+1]->one->path)) {
> +			/* Handle rename/rename(1to2) or rename/rename(1to1) */
> +			const char *pathnames[3];
> +
> +			pathnames[0] = oldpath;
> +			pathnames[1] = newpath;
> +			pathnames[2] = renames->queue[i+1]->two->path;
> +
> +			if (!strcmp(pathnames[1], pathnames[2])) {
> +				/* Both sides renamed the same way. */
> +				die("Not yet implemented");
> +
> +				/* We handled both renames, i.e. i+1 handled */
> +				i++;
> +				/* Move to next rename */
> +				continue;
> +			}
> +
> +			/* This is a rename/rename(1to2) */
> +			die("Not yet implemented");

Interesting that you chose to do some internal logic to split this
case, but have both die(). Perhaps that is wise, but also this could
have been a die() at the start, along with the pathnames[] initialization
in a later patch that implements the 1to1 case (leaving the 1to2 case
to die()).

> +			i++; /* We handled both renames, i.e. i+1 handled */
> +			continue;
> +		}
> +
> +		VERIFY_CI(oldinfo);
> +		VERIFY_CI(newinfo);
> +		target_index = pair->score; /* from append_rename_pairs() */

Hm. I don't see append_rename_pairs() anywhere else in the codebase.
Do you mean record_rename_pair()? But in that case, I don't understand
the following assertion:

> +		assert(target_index == 1 || target_index == 2)> +		other_source_index = 3-target_index;

nit: "3 - target_index"

> +		old_sidemask = (1 << other_source_index); /* 2 or 4 */
> +		source_deleted = (oldinfo->filemask == 1);

This oldinfo->filemask check made me go to the declaration to find

	/*
	 * For filemask and dirmask, see tree-walk.h's struct traverse_info,
	 * particularly the documentation above the "fn" member.  Note that
	 * filemask = mask & ~dirmask from that documentation.
	 */
	unsigned filemask:3;
	unsigned dirmask:3;

Perhaps I've missed my window to complain about this comment pointing to
a comment in another struct definition instead of something like:

	/*
	 * The ith bit corresponds to whether the ith entry is a file
	 * (filemask) or a directory (dirmask). Thus, filemask & dirmask
	 * is always zero and filemask | dirmask == 7 always.
	 */

And of course, looking at this struct provides the justification for
using "1" and "2" for the "sides" and wasting the 0th value, because
it is consistent with the three entries here, using 0 as the base.

Thus, the comment about not using the 0 position could use a reference
to these triples. I still think that using an enum would help here.

Coming back to the line in question, "filemask == 1" _does_ mean that
the file exists only in the base. Deleted indeed.

> +		collision = ((newinfo->filemask & old_sidemask) != 0);
> +		type_changed = !source_deleted &&
> +			(S_ISREG(oldinfo->stages[other_source_index].mode) !=
> +			 S_ISREG(newinfo->stages[target_index].mode));
> +		if (type_changed && collision) {
> +			/* special handling so later blocks can handle this */
> +			die("Not yet implemented");
> +		}
> +
> +		assert(source_deleted || oldinfo->filemask & old_sidemask);
> +
> +		/* Need to check for special types of rename conflicts... */
> +		if (collision && !source_deleted) {
> +			/* collision: rename/add or rename/rename(2to1) */
> +			die("Not yet implemented");
> +		} else if (collision && source_deleted) {
> +			/* rename/add/delete or rename/rename(2to1)/delete */

How did we get to three actions here? I'll probably learn when
it is implemented.

> +			die("Not yet implemented");
> +		} else {
> +			/* a few different cases... */
> +			if (type_changed) {
> +				/* rename vs. typechange */
> +				die("Not yet implemented");
> +			} else if (source_deleted) {
> +				/* rename/delete */
> +				die("Not yet implemented");
> +			} else {
> +				/* normal rename */
> +				die("Not yet implemented");
> +			}
> +		}
> +
> +		if (!type_changed) {
> +			/* Mark the original as resolved by removal */
> +			oldinfo->merged.is_null = 1;
> +			oldinfo->merged.clean = 1;
> +		}
> +
> +	}
> +
> +	return clean_merge;

I'm glad you separated out this case organization from the implementations.
It's still a big dense, so I'll probably need to revisit as I see you fill
in the rest.

Thanks,
-Stolee



[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux