diffcore-rename: provide basic implementation of idx_possible_rename()
Add a new struct dir_rename_info with various values we need inside our idx_possible_rename() function introduced in the previous commit. Add a basic implementation for this function showing how we plan to use the variables, but which will just return early with a value of -1 (not found) when those variables are not set up. Future commits will do the work necessary to set up those other variables so that idx_possible_rename() does not always return -1. Reviewed-by: Derrick Stolee <dstolee@microsoft.com> Signed-off-by: Elijah Newren <newren@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Elijah Newren committed
Feb 27, 2021 at 00:30 UTC
bde8b9f34c57f0ad567890de181ff94741ef2cbd
1 file changed
+94
-6
diffcore-rename.c
+94
-6
@@ -367,6 +367,19 @@ static int find_exact_renames(struct diff_options *options)
367
return renames;
368
}
369
370
+struct dir_rename_info {
371
+ struct strintmap idx_map;
372
+ struct strmap dir_rename_guess;
373
+ struct strmap *dir_rename_count;
374
+ unsigned setup;
375
+};
376
+
377
+static char *get_dirname(const char *filename)
378
+{
379
+ char *slash = strrchr(filename, '/');
380
+ return slash ? xstrndup(filename, slash - filename) : xstrdup("");
381
+}
382
+
383
static const char *get_basename(const char *filename)
384
{
385
/*
@@ -379,14 +392,86 @@ static const char *get_basename(const char *filename)
392
return base ? base + 1 : filename;
393
}
394
382
-static int idx_possible_rename(char *filename)
395
+static int idx_possible_rename(char *filename, struct dir_rename_info *info)
396
{
384
- /* Unconditionally return -1, "not found", for now */
385
- return -1;
397
+ /*
398
+ * Our comparison of files with the same basename (see
399
+ * find_basename_matches() below), is only helpful when after exact
400
+ * rename detection we have exactly one file with a given basename
401
+ * among the rename sources and also only exactly one file with
402
+ * that basename among the rename destinations. When we have
403
+ * multiple files with the same basename in either set, we do not
404
+ * know which to compare against. However, there are some
405
+ * filenames that occur in large numbers (particularly
406
+ * build-related filenames such as 'Makefile', '.gitignore', or
407
+ * 'build.gradle' that potentially exist within every single
408
+ * subdirectory), and for performance we want to be able to quickly
409
+ * find renames for these files too.
410
+ *
411
+ * The reason basename comparisons are a useful heuristic was that it
412
+ * is common for people to move files across directories while keeping
413
+ * their filename the same. If we had a way of determining or even
414
+ * making a good educated guess about which directory these non-unique
415
+ * basename files had moved the file to, we could check it.
416
+ * Luckily...
417
+ *
418
+ * When an entire directory is in fact renamed, we have two factors
419
+ * helping us out:
420
+ * (a) the original directory disappeared giving us a hint
421
+ * about when we can apply an extra heuristic.
422
+ * (a) we often have several files within that directory and
423
+ * subdirectories that are renamed without changes
424
+ * So, rules for a heuristic:
425
+ * (0) If there basename matches are non-unique (the condition under
426
+ * which this function is called) AND
427
+ * (1) the directory in which the file was found has disappeared
428
+ * (i.e. dirs_removed is non-NULL and has a relevant entry) THEN
429
+ * (2) use exact renames of files within the directory to determine
430
+ * where the directory is likely to have been renamed to. IF
431
+ * there is at least one exact rename from within that
432
+ * directory, we can proceed.
433
+ * (3) If there are multiple places the directory could have been
434
+ * renamed to based on exact renames, ignore all but one of them.
435
+ * Just use the destination with the most renames going to it.
436
+ * (4) Check if applying that directory rename to the original file
437
+ * would result in a destination filename that is in the
438
+ * potential rename set. If so, return the index of the
439
+ * destination file (the index within rename_dst).
440
+ * (5) Compare the original file and returned destination for
441
+ * similarity, and if they are sufficiently similar, record the
442
+ * rename.
443
+ *
444
+ * This function, idx_possible_rename(), is only responsible for (4).
445
+ * The conditions/steps in (1)-(3) will be handled via setting up
446
+ * dir_rename_count and dir_rename_guess in a future
447
+ * initialize_dir_rename_info() function. Steps (0) and (5) are
448
+ * handled by the caller of this function.
449
+ */
450
+ char *old_dir, *new_dir;
451
+ struct strbuf new_path = STRBUF_INIT;
452
+ int idx;
453
+
454
+ if (!info->setup)
455
+ return -1;
456
+
457
+ old_dir = get_dirname(filename);
458
+ new_dir = strmap_get(&info->dir_rename_guess, old_dir);
459
+ free(old_dir);
460
+ if (!new_dir)
461
+ return -1;
462
+
463
+ strbuf_addstr(&new_path, new_dir);
464
+ strbuf_addch(&new_path, '/');
465
+ strbuf_addstr(&new_path, get_basename(filename));
466
+
467
+ idx = strintmap_get(&info->idx_map, new_path.buf);
468
+ strbuf_release(&new_path);
469
+ return idx;
470
}
471
472
static int find_basename_matches(struct diff_options *options,
389
- int minimum_score)
473
+ int minimum_score,
474
+ struct dir_rename_info *info)
475
{
476
/*
477
* When I checked in early 2020, over 76% of file renames in linux
@@ -494,7 +579,7 @@ static int find_basename_matches(struct diff_options *options,
579
dst_index = strintmap_get(&dests, base);
580
if (src_index == -1 || dst_index == -1) {
581
src_index = i;
497
- dst_index = idx_possible_rename(filename);
582
+ dst_index = idx_possible_rename(filename, info);
583
}
584
if (dst_index == -1)
585
continue;
@@ -677,8 +762,10 @@ void diffcore_rename(struct diff_options *options)
762
int num_destinations, dst_cnt;
763
int num_sources, want_copies;
764
struct progress *progress = NULL;
765
+ struct dir_rename_info info;
766
767
trace2_region_enter("diff", "setup", options->repo);
768
+ info.setup = 0;
769
want_copies = (detect_rename == DIFF_DETECT_COPY);
770
if (!minimum_score)
771
minimum_score = DEFAULT_RENAME_SCORE;
@@ -774,7 +861,8 @@ void diffcore_rename(struct diff_options *options)
861
/* Utilize file basenames to quickly find renames. */
862
trace2_region_enter("diff", "basename matches", options->repo);
863
rename_count += find_basename_matches(options,
777
- min_basename_score);
864
+ min_basename_score,
865
+ &info);
866
trace2_region_leave("diff", "basename matches", options->repo);
867
868
/*