Skip to content

Commit 2fd9eda

Browse files
newrengitster
authored andcommitted
merge-ort: precompute whether directory rename detection is needed
The point of directory rename detection is that if one side of history renames a directory, and the other side adds new files under the old directory, then the merge can move those new files into the new directory. This leads to the following important observation: * If the other side does not add any new files under the old directory, we do not need to detect any renames for that directory. Similarly, directory rename detection had an important requirement: * If a directory still exists on one side of history, it has not been renamed on that side of history. (See section 4 of t6423 or Documentation/technical/directory-rename-detection.txt for more details). Using these two bits of information, we note that directory rename detection is only needed in cases where (1) directories exist in the merge base and on one side of history (i.e. dirmask == 3 or dirmask == 5), and (2) where there is some new file added to that directory on the side where it still exists (thus where the file has filemask == 2 or filemask == 4, respectively). This has to be done in two steps, because we have the dirmask when we are first considering the directory, and won't get the filemasks for the files within it until we recurse into that directory. So, we save dir_rename_mask = dirmask - 1 when we hit a directory that is missing on one side, and then later look for cases of filemask == dir_rename_mask One final note is that as soon as we hit a directory that needs directory rename detection, we will need to detect renames in all subdirectories of that directory as well due to the "majority rules" decision when files are renamed into different directory hierarchies. We arbitrarily use the special value of 0x07 to record when we've hit such a directory. The combination of all the above mean that we introduce a variable named dir_rename_mask (couldn't think of a better name) which has one of the following values as we traverse into a directory: * 0x00: directory rename detection not needed * 0x02 or 0x04: directory rename detection only needed if files added * 0x07: directory rename detection definitely needed We then pass this value through to add_pairs() so that it can mark location_relevant as true only when dir_rename_mask is 0x07. Signed-off-by: Elijah Newren <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent a68e6ce commit 2fd9eda

File tree

1 file changed

+61
-6
lines changed

1 file changed

+61
-6
lines changed

merge-ort.c

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ struct rename_info {
108108
*/
109109
struct strset relevant_sources[3];
110110

111+
/*
112+
* dir_rename_mask:
113+
* 0: optimization removing unmodified potential rename source okay
114+
* 2 or 4: optimization okay, but must check for files added to dir
115+
* 7: optimization forbidden; need rename source in case of dir rename
116+
*/
117+
unsigned dir_rename_mask:3;
118+
111119
/*
112120
* callback_data_*: supporting data structures for alternate traversal
113121
*
@@ -419,6 +427,8 @@ static void clear_or_reinit_internal_opts(struct merge_options_internal *opti,
419427
strmap_clear(&opti->output, 0);
420428
}
421429

430+
renames->dir_rename_mask = 0;
431+
422432
/* Clean out callback_data as well. */
423433
FREE_AND_NULL(renames->callback_data);
424434
renames->callback_data_nr = renames->callback_data_alloc = 0;
@@ -520,12 +530,16 @@ static int traverse_trees_wrapper_callback(int n,
520530
{
521531
struct merge_options *opt = info->data;
522532
struct rename_info *renames = &opt->priv->renames;
533+
unsigned filemask = mask & ~dirmask;
523534

524535
assert(n==3);
525536

526537
if (!renames->callback_data_traverse_path)
527538
renames->callback_data_traverse_path = xstrdup(info->traverse_path);
528539

540+
if (filemask && filemask == renames->dir_rename_mask)
541+
renames->dir_rename_mask = 0x07;
542+
529543
ALLOC_GROW(renames->callback_data, renames->callback_data_nr + 1,
530544
renames->callback_data_alloc);
531545
renames->callback_data[renames->callback_data_nr].mask = mask;
@@ -544,7 +558,6 @@ static int traverse_trees_wrapper_callback(int n,
544558
* additional details before the "real" traversal
545559
* - loop through the saved entries and call the original callback on them
546560
*/
547-
MAYBE_UNUSED
548561
static int traverse_trees_wrapper(struct index_state *istate,
549562
int n,
550563
struct tree_desc *t,
@@ -556,6 +569,8 @@ static int traverse_trees_wrapper(struct index_state *istate,
556569
struct merge_options *opt = info->data;
557570
struct rename_info *renames = &opt->priv->renames;
558571

572+
assert(renames->dir_rename_mask == 2 || renames->dir_rename_mask == 4);
573+
559574
old_callback_data_traverse_path = renames->callback_data_traverse_path;
560575
old_fn = info->fn;
561576
old_offset = renames->callback_data_nr;
@@ -647,15 +662,16 @@ static void add_pair(struct merge_options *opt,
647662
const char *pathname,
648663
unsigned side,
649664
unsigned is_add /* if false, is_delete */,
650-
unsigned match_mask)
665+
unsigned match_mask,
666+
unsigned dir_rename_mask)
651667
{
652668
struct diff_filespec *one, *two;
653669
struct rename_info *renames = &opt->priv->renames;
654670
int names_idx = is_add ? side : 0;
655671

656672
if (!is_add) {
657673
unsigned content_relevant = (match_mask == 0);
658-
unsigned location_relevant = 1; /* FIXME: compute this */
674+
unsigned location_relevant = (dir_rename_mask == 0x07);
659675

660676
if (content_relevant || location_relevant)
661677
strset_add(&renames->relevant_sources[side], pathname);
@@ -679,6 +695,36 @@ static void collect_rename_info(struct merge_options *opt,
679695
struct rename_info *renames = &opt->priv->renames;
680696
unsigned side;
681697

698+
/*
699+
* Update dir_rename_mask (determines ignore-rename-source validity)
700+
*
701+
* dir_rename_mask helps us keep track of when directory rename
702+
* detection may be relevant. Basically, whenver a directory is
703+
* removed on one side of history, and a file is added to that
704+
* directory on the other side of history, directory rename
705+
* detection is relevant (meaning we have to detect renames for all
706+
* files within that directory to deduce where the directory
707+
* moved). Also, whenever a directory needs directory rename
708+
* detection, due to the "majority rules" choice for where to move
709+
* it (see t6423 testcase 1f), we also need to detect renames for
710+
* all files within subdirectories of that directory as well.
711+
*
712+
* Here we haven't looked at files within the directory yet, we are
713+
* just looking at the directory itself. So, if we aren't yet in
714+
* a case where a parent directory needed directory rename detection
715+
* (i.e. dir_rename_mask != 0x07), and if the directory was removed
716+
* on one side of history, record the mask of the other side of
717+
* history in dir_rename_mask.
718+
*/
719+
if (renames->dir_rename_mask != 0x07 &&
720+
(dirmask == 3 || dirmask == 5)) {
721+
/* simple sanity check */
722+
assert(renames->dir_rename_mask == 0 ||
723+
renames->dir_rename_mask == (dirmask & ~1));
724+
/* update dir_rename_mask; have it record mask of new side */
725+
renames->dir_rename_mask = (dirmask & ~1);
726+
}
727+
682728
/* Update dirs_removed, as needed */
683729
if (dirmask == 1 || dirmask == 3 || dirmask == 5) {
684730
/* absent_mask = 0x07 - dirmask; sides = absent_mask/2 */
@@ -698,12 +744,14 @@ static void collect_rename_info(struct merge_options *opt,
698744
/* Check for deletion on side */
699745
if ((filemask & 1) && !(filemask & side_mask))
700746
add_pair(opt, names, fullname, side, 0 /* delete */,
701-
match_mask & filemask);
747+
match_mask & filemask,
748+
renames->dir_rename_mask);
702749

703750
/* Check for addition on side */
704751
if (!(filemask & 1) && (filemask & side_mask))
705752
add_pair(opt, names, fullname, side, 1 /* add */,
706-
match_mask & filemask);
753+
match_mask & filemask,
754+
renames->dir_rename_mask);
707755
}
708756
}
709757

@@ -721,12 +769,14 @@ static int collect_merge_info_callback(int n,
721769
*/
722770
struct merge_options *opt = info->data;
723771
struct merge_options_internal *opti = opt->priv;
772+
struct rename_info *renames = &opt->priv->renames;
724773
struct string_list_item pi; /* Path Info */
725774
struct conflict_info *ci; /* typed alias to pi.util (which is void*) */
726775
struct name_entry *p;
727776
size_t len;
728777
char *fullpath;
729778
const char *dirname = opti->current_dir_name;
779+
unsigned prev_dir_rename_mask = renames->dir_rename_mask;
730780
unsigned filemask = mask & ~dirmask;
731781
unsigned match_mask = 0; /* will be updated below */
732782
unsigned mbase_null = !(mask & 1);
@@ -867,8 +917,13 @@ static int collect_merge_info_callback(int n,
867917

868918
original_dir_name = opti->current_dir_name;
869919
opti->current_dir_name = pi.string;
870-
ret = traverse_trees(NULL, 3, t, &newinfo);
920+
if (renames->dir_rename_mask == 0 ||
921+
renames->dir_rename_mask == 0x07)
922+
ret = traverse_trees(NULL, 3, t, &newinfo);
923+
else
924+
ret = traverse_trees_wrapper(NULL, 3, t, &newinfo);
871925
opti->current_dir_name = original_dir_name;
926+
renames->dir_rename_mask = prev_dir_rename_mask;
872927

873928
for (i = MERGE_BASE; i <= MERGE_SIDE2; i++)
874929
free(buf[i]);

0 commit comments

Comments
 (0)