@@ -78,13 +78,13 @@ pub enum Error {
7878use Result as Either ;
7979
8080type QueueKey < T > = Either < T , Reverse < T > > ;
81- type CommitDateQueue = gix_revwalk:: PriorityQueue < QueueKey < SecondsSinceUnixEpoch > , ObjectId > ;
81+ type CommitDateQueue = gix_revwalk:: PriorityQueue < QueueKey < SecondsSinceUnixEpoch > , ( ObjectId , CommitState ) > ;
8282type Candidates = VecDeque < crate :: commit:: Info > ;
8383
8484/// The state used and potentially shared by multiple graph traversals.
8585#[ derive( Clone ) ]
8686pub ( super ) struct State {
87- next : VecDeque < ObjectId > ,
87+ next : VecDeque < ( ObjectId , CommitState ) > ,
8888 queue : CommitDateQueue ,
8989 buf : Vec < u8 > ,
9090 seen : gix_revwalk:: graph:: IdMap < CommitState > ,
@@ -184,9 +184,10 @@ mod init {
184184 }
185185 Sorting :: ByCommitTime ( order) | Sorting :: ByCommitTimeCutoff { order, .. } => {
186186 let state = & mut self . state ;
187- for commit_id in state. next . drain ( ..) {
187+ for ( commit_id, commit_state ) in state. next . drain ( ..) {
188188 add_to_queue (
189189 commit_id,
190+ commit_state,
190191 order,
191192 sorting. cutoff_time ( ) ,
192193 & mut state. queue ,
@@ -226,11 +227,12 @@ mod init {
226227 // Assure we *start* traversing hidden variants of a commit first, give them a head-start.
227228 match self . sorting {
228229 Sorting :: BreadthFirst => {
229- state. next . push_front ( id_to_ignore) ;
230+ state. next . push_front ( ( id_to_ignore, CommitState :: Hidden ) ) ;
230231 }
231232 Sorting :: ByCommitTime ( order) | Sorting :: ByCommitTimeCutoff { order, .. } => {
232233 add_to_queue (
233234 id_to_ignore,
235+ CommitState :: Hidden ,
234236 order,
235237 self . sorting . cutoff_time ( ) ,
236238 & mut state. queue ,
@@ -273,6 +275,7 @@ mod init {
273275
274276 fn add_to_queue (
275277 commit_id : ObjectId ,
278+ commit_state : CommitState ,
276279 order : CommitTimeOrder ,
277280 cutoff_time : Option < SecondsSinceUnixEpoch > ,
278281 queue : & mut CommitDateQueue ,
@@ -284,11 +287,11 @@ mod init {
284287 let key = to_queue_key ( time, order) ;
285288 match ( cutoff_time, order) {
286289 ( Some ( cutoff_time) , _) if time >= cutoff_time => {
287- queue. insert ( key, commit_id) ;
290+ queue. insert ( key, ( commit_id, commit_state ) ) ;
288291 }
289292 ( Some ( _) , _) => { }
290293 ( None , _) => {
291- queue. insert ( key, commit_id) ;
294+ queue. insert ( key, ( commit_id, commit_state ) ) ;
292295 }
293296 }
294297 Ok ( ( ) )
@@ -339,10 +342,11 @@ mod init {
339342 state. clear ( ) ;
340343 state. next . reserve ( tips. size_hint ( ) . 0 ) ;
341344 for tip in tips. map ( Into :: into) {
342- let seen = state. seen . insert ( tip, CommitState :: Interesting ) ;
345+ let commit_state = CommitState :: Interesting ;
346+ let seen = state. seen . insert ( tip, commit_state) ;
343347 // We know there can only be duplicate interesting ones.
344348 if seen. is_none ( ) && predicate ( & tip) {
345- state. next . push_back ( tip) ;
349+ state. next . push_back ( ( tip, commit_state ) ) ;
346350 }
347351 }
348352 }
@@ -418,16 +422,23 @@ mod init {
418422 cutoff : Option < SecondsSinceUnixEpoch > ,
419423 ) -> Option < Result < Info , Error > > {
420424 let state = & mut self . state ;
425+ let next = & mut state. queue ;
421426
422427 ' skip_hidden: loop {
423- let ( commit_time, oid) = match state . queue . pop ( ) ? {
428+ let ( commit_time, ( oid, _queued_commit_state ) ) = match next . pop ( ) ? {
424429 ( Newest ( t) | Oldest ( Reverse ( t) ) , o) => ( t, o) ,
425430 } ;
426431 let mut parents: ParentIds = Default :: default ( ) ;
427- // TODO(perf): can avoid this lookup by storing state on `queue` respectively.
428- // ALSO: need to look ahead for early aborts, i.e. if there is only hidden left to traverse.
429- // Maybe this can be counted?
432+
433+ // Always use the state that is actually stored, as we may change the type as we go.
430434 let commit_state = * state. seen . get ( & oid) . expect ( "every commit we traverse has state added" ) ;
435+ if can_deplete_candidates_early (
436+ next. iter_unordered ( ) . map ( |t| t. 1 ) ,
437+ commit_state,
438+ state. candidates . as_ref ( ) ,
439+ ) {
440+ return None ;
441+ }
431442 match super :: super :: find ( self . cache . as_ref ( ) , & self . objects , & oid, & mut state. buf ) {
432443 Ok ( Either :: CachedCommit ( commit) ) => {
433444 if !collect_parents ( & mut state. parent_ids , self . cache . as_ref ( ) , commit. iter_parents ( ) ) {
@@ -443,7 +454,7 @@ mod init {
443454 & mut state. candidates ,
444455 commit_state,
445456 & mut self . predicate ,
446- & mut state . queue ,
457+ next ,
447458 order,
448459 cutoff,
449460 || parent_commit_time,
@@ -462,7 +473,7 @@ mod init {
462473 & mut state. candidates ,
463474 commit_state,
464475 & mut self . predicate ,
465- & mut state . queue ,
476+ next ,
466477 order,
467478 cutoff,
468479 || {
@@ -505,6 +516,28 @@ mod init {
505516 }
506517 }
507518
519+ /// Returns `true` if we have only hidden cursors queued for traversal, assuming that we don't see interesting ones ever again.
520+ ///
521+ /// `unqueued_commit_state` is the state of the commit that is currently being processed.
522+ fn can_deplete_candidates_early (
523+ mut queued_states : impl Iterator < Item = CommitState > ,
524+ unqueued_commit_state : CommitState ,
525+ candidates : Option < & Candidates > ,
526+ ) -> bool {
527+ if candidates. is_none ( ) {
528+ return false ;
529+ }
530+ if unqueued_commit_state. is_interesting ( ) {
531+ return false ;
532+ }
533+
534+ let mut is_empty = true ;
535+ queued_states. all ( |state| {
536+ is_empty = false ;
537+ state. is_hidden ( )
538+ } ) && !is_empty
539+ }
540+
508541 /// Utilities
509542 impl < Find , Predicate > Simple < Find , Predicate >
510543 where
@@ -513,14 +546,16 @@ mod init {
513546 {
514547 fn next_by_topology ( & mut self ) -> Option < Result < Info , Error > > {
515548 let state = & mut self . state ;
549+ let next = & mut state. next ;
516550 ' skip_hidden: loop {
517- let oid = state . next . pop_front ( ) ?;
551+ let ( oid, _queued_commit_state ) = next. pop_front ( ) ?;
518552 let mut parents: ParentIds = Default :: default ( ) ;
519- // TODO(perf): can avoid this lookup by storing state on `next` respectively.
520- // ALSO: need to look ahead for early aborts, i.e. if there is only hidden left to traverse.
521- // Maybe this can be counted?
522- let commit_state = * state. seen . get ( & oid) . expect ( "every commit we traverse has state added" ) ;
523553
554+ // Always use the state that is actually stored, as we may change the type as we go.
555+ let commit_state = * state. seen . get ( & oid) . expect ( "every commit we traverse has state added" ) ;
556+ if can_deplete_candidates_early ( next. iter ( ) . map ( |t| t. 1 ) , commit_state, state. candidates . as_ref ( ) ) {
557+ return None ;
558+ }
524559 match super :: super :: find ( self . cache . as_ref ( ) , & self . objects , & oid, & mut state. buf ) {
525560 Ok ( Either :: CachedCommit ( commit) ) => {
526561 if !collect_parents ( & mut state. parent_ids , self . cache . as_ref ( ) , commit. iter_parents ( ) ) {
@@ -537,7 +572,7 @@ mod init {
537572 & mut state. candidates ,
538573 commit_state,
539574 & mut self . predicate ,
540- & mut state . next ,
575+ next,
541576 ) ;
542577 if commit_state. is_interesting ( ) && matches ! ( self . parents, Parents :: First ) {
543578 break ;
@@ -556,7 +591,7 @@ mod init {
556591 & mut state. candidates ,
557592 commit_state,
558593 & mut self . predicate ,
559- & mut state . next ,
594+ next,
560595 ) ;
561596 if commit_state. is_interesting ( ) && matches ! ( self . parents, Parents :: First ) {
562597 break ;
@@ -608,7 +643,7 @@ mod init {
608643 candidates : & mut Option < Candidates > ,
609644 commit_state : CommitState ,
610645 predicate : & mut impl FnMut ( & oid ) -> bool ,
611- next : & mut VecDeque < ObjectId > ,
646+ next : & mut VecDeque < ( ObjectId , CommitState ) > ,
612647 ) {
613648 let enqueue = match seen. entry ( parent_id) {
614649 Entry :: Occupied ( mut e) => {
@@ -627,7 +662,7 @@ mod init {
627662 }
628663 } ;
629664 if enqueue {
630- next. push_back ( parent_id) ;
665+ next. push_back ( ( parent_id, commit_state ) ) ;
631666 }
632667 }
633668
@@ -665,7 +700,7 @@ mod init {
665700 let key = to_queue_key ( parent_commit_time, order) ;
666701 match cutoff {
667702 Some ( cutoff_older_than) if parent_commit_time < cutoff_older_than => { }
668- Some ( _) | None => queue. insert ( key, parent_id) ,
703+ Some ( _) | None => queue. insert ( key, ( parent_id, commit_state ) ) ,
669704 }
670705 }
671706 }
0 commit comments