merge-ort: avoid assuming all renames detected

In commit 8b09a900a1 ("merge-ort: restart merge with cached renames to reduce process entry cost", 2021-07-16), we noted that in the merge-ort steps of collect_merge_info() detect_and_process_renames() process_entries() that process_entries() was expensive, and we could often make it cheaper by changing this to collect_merge_info() detect_and_process_renames() <cache all the renames, and restart> collect_merge_info() detect_and_process_renames() process_entries() because the second collect_merge_info() would be cheaper (we could avoid traversing into some directories), the second detect_and_process_renames() would be free since we had already detected all renames, and then process_entries() has far fewer entries to handle. However, this was built on the assumption that the first detect_and_process_renames() actually detected all potential renames. If someone has merge.renameLimit set to some small value, that assumption is violated which manifests later with the following message: $ git -c merge.renameLimit=1 rebase upstream ... git: merge-ort.c:546: clear_or_reinit_internal_opts: Assertion `renames->cached_pairs_valid_side == 0' failed. Turn off this cache-renames-and-restart whenever we cannot detect all renames, and add a testcase that would have caught this problem. Reported-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Elijah Newren <newren@gmail.com> Tested-by: Taylor Blau <me@ttaylorr.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Elijah Newren committed Jan 17, 2022 at 18:25 UTC 9ae39fef7f3764537e029b57687f6a0a3163e810
2 files changed +71
merge-ort.c
+4
@@ -3031,6 +3031,10 @@ static int detect_and_process_renames(struct merge_options *opt,
3031 trace2_region_enter("merge", "regular renames", opt->repo);
3032 detection_run |= detect_regular_renames(opt, MERGE_SIDE1);
3033 detection_run |= detect_regular_renames(opt, MERGE_SIDE2);
3034 + if (renames->needed_limit) {
3035 + renames->cached_pairs_valid_side = 0;
3036 + renames->redo_after_renames = 0;
3037 + }
3038 if (renames->redo_after_renames && detection_run) {
3039 int i, side;
3040 struct diff_filepair *p;
t/t6429-merge-sequence-rename-caching.sh
+67
@@ -697,4 +697,71 @@ test_expect_success 'caching renames only on upstream side, part 2' '
697 )
698 '
699
700 +#
701 +# The following testcase just creates two simple renames (slightly modified
702 +# on both sides but without conflicting changes), and a directory full of
703 +# files that are otherwise uninteresting. The setup is as follows:
704 +#
705 +# base: unrelated/<BUNCH OF FILES>
706 +# numbers
707 +# values
708 +# upstream: modify: numbers
709 +# modify: values
710 +# topic: add: unrelated/foo
711 +# modify: numbers
712 +# modify: values
713 +# rename: numbers -> sequence
714 +# rename: values -> progression
715 +#
716 +# This is a trivial rename case, but we're curious what happens with a very
717 +# low renameLimit interacting with the restart optimization trying to notice
718 +# that unrelated/ looks like a trivial merge candidate.
719 +#
720 +test_expect_success 'avoid assuming we detected renames' '
721 + git init redo-weirdness &&
722 + (
723 + cd redo-weirdness &&
724 +
725 + mkdir unrelated &&
726 + for i in $(test_seq 1 10)
727 + do
728 + >unrelated/$i
729 + done &&
730 + test_seq 2 10 >numbers &&
731 + test_seq 12 20 >values &&
732 + git add numbers values unrelated/ &&
733 + git commit -m orig &&
734 +
735 + git branch upstream &&
736 + git branch topic &&
737 +
738 + git switch upstream &&
739 + test_seq 1 10 >numbers &&
740 + test_seq 11 20 >values &&
741 + git add numbers &&
742 + git commit -m "Some tweaks" &&
743 +
744 + git switch topic &&
745 +
746 + >unrelated/foo &&
747 + test_seq 2 12 >numbers &&
748 + test_seq 12 22 >values &&
749 + git add numbers values unrelated/ &&
750 + git mv numbers sequence &&
751 + git mv values progression &&
752 + git commit -m A &&
753 +
754 + #
755 + # Actual testing
756 + #
757 +
758 + git switch --detach topic^0 &&
759 +
760 + test_must_fail git -c merge.renameLimit=1 rebase upstream &&
761 +
762 + git ls-files -u >actual &&
763 + ! test_file_is_empty actual
764 + )
765 +'
766 +
767 test_done