@@ -341,6 +341,10 @@ impl<T: Change> Tracker<T> {
341
341
) -> Result < ( ) , emit:: Error > {
342
342
// we try to cheaply reduce the set of possibilities first, before possibly looking more exhaustively.
343
343
let needs_second_pass = !needs_exact_match ( percentage) ;
344
+
345
+ // https://github.com/git/git/blob/cc01bad4a9f566cf4453c7edd6b433851b0835e2/diffcore-rename.c#L350-L369
346
+ // We would need a hashmap to be OK to not use the limit here, otherwise the performance is too bad.
347
+ // This also means we don't find all renames if we hit the rename limit.
344
348
if self . match_pairs ( cb, None /* by identity */ , kind, out, diff_cache, objects, filter) ? == Action :: Cancel {
345
349
return Ok ( ( ) ) ;
346
350
}
@@ -384,6 +388,19 @@ impl<T: Change> Tracker<T> {
384
388
filter : Option < fn ( & T ) -> bool > ,
385
389
) -> Result < Action , emit:: Error > {
386
390
let mut dest_ofs = 0 ;
391
+ let mut num_checks = 0 ;
392
+ let max_checks = {
393
+ let limit = self . rewrites . limit . saturating_pow ( 2 ) ;
394
+ // There can be trees with a lot of entries and pathological search behaviour, as they can be repeated
395
+ // and then have a lot of similar hashes. This also means we have to search a lot of candidates which
396
+ // can be too slow despite best attempts. So play it save and detect such cases 'roughly' by amount of items.
397
+ if self . items . len ( ) < 100_000 {
398
+ 0
399
+ } else {
400
+ limit
401
+ }
402
+ } ;
403
+
387
404
while let Some ( ( mut dest_idx, dest) ) = self . items [ dest_ofs..] . iter ( ) . enumerate ( ) . find_map ( |( idx, item) | {
388
405
( !item. emitted
389
406
&& matches ! ( item. change. kind( ) , ChangeKind :: Addition )
@@ -403,6 +420,7 @@ impl<T: Change> Tracker<T> {
403
420
objects,
404
421
diff_cache,
405
422
& self . path_backing ,
423
+ & mut num_checks,
406
424
) ?
407
425
. map ( |( src_idx, src, diff) | {
408
426
let ( id, entry_mode) = src. change . id_and_entry_mode ( ) ;
@@ -420,6 +438,12 @@ impl<T: Change> Tracker<T> {
420
438
src_idx,
421
439
)
422
440
} ) ;
441
+ if max_checks != 0 && num_checks > max_checks {
442
+ gix_trace:: warn!(
443
+ "Cancelled rename matching as there were too many iterations ({num_checks} > {max_checks})"
444
+ ) ;
445
+ return Ok ( Action :: Cancel ) ;
446
+ }
423
447
let Some ( ( src, src_idx) ) = src else {
424
448
continue ;
425
449
} ;
@@ -631,6 +655,7 @@ fn find_match<'a, T: Change>(
631
655
objects : & impl gix_object:: FindObjectOrHeader ,
632
656
diff_cache : & mut crate :: blob:: Platform ,
633
657
path_backing : & [ u8 ] ,
658
+ num_checks : & mut usize ,
634
659
) -> Result < Option < SourceTuple < ' a , T > > , emit:: Error > {
635
660
let ( item_id, item_mode) = item. change . id_and_entry_mode ( ) ;
636
661
if needs_exact_match ( percentage) || item_mode. is_link ( ) {
@@ -651,6 +676,7 @@ fn find_match<'a, T: Change>(
651
676
}
652
677
let res = items[ range. clone ( ) ] . iter ( ) . enumerate ( ) . find_map ( |( mut src_idx, src) | {
653
678
src_idx += range. start ;
679
+ * num_checks += 1 ;
654
680
( src_idx != item_idx && src. is_source_for_destination_of ( kind, item_mode) ) . then_some ( ( src_idx, src, None ) )
655
681
} ) ;
656
682
if let Some ( src) = res {
@@ -685,6 +711,7 @@ fn find_match<'a, T: Change>(
685
711
) ?;
686
712
let prep = diff_cache. prepare_diff ( ) ?;
687
713
stats. num_similarity_checks += 1 ;
714
+ * num_checks += 1 ;
688
715
match prep. operation {
689
716
Operation :: InternalDiff { algorithm } => {
690
717
let tokens =
0 commit comments