@@ -2,8 +2,20 @@ use gix_date::SecondsSinceUnixEpoch;
22use gix_hash:: ObjectId ;
33use gix_hashtable:: HashSet ;
44use smallvec:: SmallVec ;
5+ use std:: cmp:: Reverse ;
56use std:: collections:: VecDeque ;
67
8+ #[ derive( Default , Debug , Copy , Clone ) ]
9+ /// The order with which to prioritize the search.
10+ pub enum CommitTimeOrder {
11+ #[ default]
12+ /// Sort commits by newest first.
13+ NewestFirst ,
14+ /// Sort commits by oldest first.
15+ #[ doc( alias = "Sort::REVERSE" , alias = "git2" ) ]
16+ OldestFirst ,
17+ }
18+
719/// Specify how to sort commits during a [simple](super::Simple) traversal.
820///
921/// ### Sample History
@@ -20,32 +32,35 @@ use std::collections::VecDeque;
2032pub enum Sorting {
2133 /// Commits are sorted as they are mentioned in the commit graph.
2234 ///
23- /// In the *sample history* the order would be `8, 6, 7, 5, 4, 3, 2, 1`
35+ /// In the *sample history* the order would be `8, 6, 7, 5, 4, 3, 2, 1`.
2436 ///
2537 /// ### Note
2638 ///
2739 /// This is not to be confused with `git log/rev-list --topo-order`, which is notably different from
2840 /// as it avoids overlapping branches.
2941 #[ default]
3042 BreadthFirst ,
31- /// Commits are sorted by their commit time in descending order, that is newest first.
43+ /// Commits are sorted by their commit time in the order specified, either newest or oldest first.
3244 ///
3345 /// The sorting applies to all currently queued commit ids and thus is full.
3446 ///
35- /// In the *sample history* the order would be `8, 7, 6, 5, 4, 3, 2, 1`
47+ /// In the *sample history* the order would be `8, 7, 6, 5, 4, 3, 2, 1` for [`NewestFirst`](CommitTimeOrder::NewestFirst),
48+ /// or `1, 2, 3, 4, 5, 6, 7, 8` for [`OldestFirst`](CommitTimeOrder::OldestFirst).
3649 ///
3750 /// # Performance
3851 ///
3952 /// This mode benefits greatly from having an object_cache in `find()`
4053 /// to avoid having to lookup each commit twice.
41- ByCommitTimeNewestFirst ,
42- /// This sorting is similar to `ByCommitTimeNewestFirst` , but adds a cutoff to not return commits older than
54+ ByCommitTime ( CommitTimeOrder ) ,
55+ /// This sorting is similar to [`ByCommitTime`](Sorting::ByCommitTime) , but adds a cutoff to not return commits older than
4356 /// a given time, stopping the iteration once no younger commits is queued to be traversed.
4457 ///
4558 /// As the query is usually repeated with different cutoff dates, this search mode benefits greatly from an object cache.
4659 ///
47- /// In the *sample history* and a cut-off date of 4, the returned list of commits would be `8, 7, 6, 4`
48- ByCommitTimeNewestFirstCutoffOlderThan {
60+ /// In the *sample history* and a cut-off date of 4, the returned list of commits would be `8, 7, 6, 4`.
61+ ByCommitTimeCutoff {
62+ /// The order in which to prioritize lookups.
63+ order : CommitTimeOrder ,
4964 /// The amount of seconds since unix epoch, the same value obtained by any `gix_date::Time` structure and the way git counts time.
5065 seconds : gix_date:: SecondsSinceUnixEpoch ,
5166 } ,
@@ -61,11 +76,14 @@ pub enum Error {
6176 ObjectDecode ( #[ from] gix_object:: decode:: Error ) ,
6277}
6378
79+ use Result as Either ;
80+ type QueueKey < T > = Either < T , Reverse < T > > ;
81+
6482/// The state used and potentially shared by multiple graph traversals.
6583#[ derive( Clone ) ]
6684pub ( super ) struct State {
6785 next : VecDeque < ObjectId > ,
68- queue : gix_revwalk:: PriorityQueue < SecondsSinceUnixEpoch , ObjectId > ,
86+ queue : gix_revwalk:: PriorityQueue < QueueKey < SecondsSinceUnixEpoch > , ObjectId > ,
6987 buf : Vec < u8 > ,
7088 seen : HashSet < ObjectId > ,
7189 parents_buf : Vec < u8 > ,
@@ -77,10 +95,13 @@ mod init {
7795 use gix_date:: SecondsSinceUnixEpoch ;
7896 use gix_hash:: { oid, ObjectId } ;
7997 use gix_object:: { CommitRefIter , FindExt } ;
98+ use std:: cmp:: Reverse ;
99+ use Err as Oldest ;
100+ use Ok as Newest ;
80101
81102 use super :: {
82103 super :: { simple:: Sorting , Either , Info , ParentIds , Parents , Simple } ,
83- collect_parents, Error , State ,
104+ collect_parents, CommitTimeOrder , Error , State ,
84105 } ;
85106
86107 impl Default for State {
@@ -105,6 +126,13 @@ mod init {
105126 }
106127 }
107128
129+ fn to_queue_key ( i : i64 , order : CommitTimeOrder ) -> super :: QueueKey < i64 > {
130+ match order {
131+ CommitTimeOrder :: NewestFirst => Newest ( i) ,
132+ CommitTimeOrder :: OldestFirst => Oldest ( Reverse ( i) ) ,
133+ }
134+ }
135+
108136 /// Builder
109137 impl < Find , Predicate > Simple < Find , Predicate >
110138 where
@@ -117,19 +145,20 @@ mod init {
117145 Sorting :: BreadthFirst => {
118146 self . queue_to_vecdeque ( ) ;
119147 }
120- Sorting :: ByCommitTimeNewestFirst | Sorting :: ByCommitTimeNewestFirstCutoffOlderThan { .. } => {
148+ Sorting :: ByCommitTime ( order ) | Sorting :: ByCommitTimeCutoff { order , .. } => {
121149 let cutoff_time = self . sorting . cutoff_time ( ) ;
122150 let state = & mut self . state ;
123151 for commit_id in state. next . drain ( ..) {
124152 let commit_iter = self . objects . find_commit_iter ( & commit_id, & mut state. buf ) ?;
125153 let time = commit_iter. committer ( ) ?. time . seconds ;
126- match cutoff_time {
127- Some ( cutoff_time) if time >= cutoff_time => {
128- state. queue . insert ( time, commit_id) ;
154+ let key = to_queue_key ( time, order) ;
155+ match ( cutoff_time, order) {
156+ ( Some ( cutoff_time) , _) if time >= cutoff_time => {
157+ state. queue . insert ( key, commit_id) ;
129158 }
130- Some ( _) => { }
131- None => {
132- state. queue . insert ( time , commit_id) ;
159+ ( Some ( _ ) , _) => { }
160+ ( None , _ ) => {
161+ state. queue . insert ( key , commit_id) ;
133162 }
134163 }
135164 }
@@ -254,10 +283,8 @@ mod init {
254283 } else {
255284 match self . sorting {
256285 Sorting :: BreadthFirst => self . next_by_topology ( ) ,
257- Sorting :: ByCommitTimeNewestFirst => self . next_by_commit_date ( None ) ,
258- Sorting :: ByCommitTimeNewestFirstCutoffOlderThan { seconds } => {
259- self . next_by_commit_date ( seconds. into ( ) )
260- }
286+ Sorting :: ByCommitTime ( order) => self . next_by_commit_date ( order, None ) ,
287+ Sorting :: ByCommitTimeCutoff { seconds, order } => self . next_by_commit_date ( order, seconds. into ( ) ) ,
261288 }
262289 }
263290 }
@@ -267,7 +294,7 @@ mod init {
267294 /// If not topo sort, provide the cutoff date if present.
268295 fn cutoff_time ( & self ) -> Option < SecondsSinceUnixEpoch > {
269296 match self {
270- Sorting :: ByCommitTimeNewestFirstCutoffOlderThan { seconds } => Some ( * seconds) ,
297+ Sorting :: ByCommitTimeCutoff { seconds, .. } => Some ( * seconds) ,
271298 _ => None ,
272299 }
273300 }
@@ -281,18 +308,21 @@ mod init {
281308 {
282309 fn next_by_commit_date (
283310 & mut self ,
284- cutoff_older_than : Option < SecondsSinceUnixEpoch > ,
311+ order : CommitTimeOrder ,
312+ cutoff : Option < SecondsSinceUnixEpoch > ,
285313 ) -> Option < Result < Info , Error > > {
286314 let state = & mut self . state ;
287315
288- let ( commit_time, oid) = state. queue . pop ( ) ?;
316+ let ( commit_time, oid) = match state. queue . pop ( ) ? {
317+ ( Newest ( t) | Oldest ( Reverse ( t) ) , o) => ( t, o) ,
318+ } ;
289319 let mut parents: ParentIds = Default :: default ( ) ;
290320 match super :: super :: find ( self . cache . as_ref ( ) , & self . objects , & oid, & mut state. buf ) {
291321 Ok ( Either :: CachedCommit ( commit) ) => {
292322 if !collect_parents ( & mut state. parent_ids , self . cache . as_ref ( ) , commit. iter_parents ( ) ) {
293323 // drop corrupt caches and try again with ODB
294324 self . cache = None ;
295- return self . next_by_commit_date ( cutoff_older_than ) ;
325+ return self . next_by_commit_date ( order , cutoff ) ;
296326 }
297327 for ( id, parent_commit_time) in state. parent_ids . drain ( ..) {
298328 parents. push ( id) ;
@@ -301,9 +331,10 @@ mod init {
301331 continue ;
302332 }
303333
304- match cutoff_older_than {
334+ let key = to_queue_key ( parent_commit_time, order) ;
335+ match cutoff {
305336 Some ( cutoff_older_than) if parent_commit_time < cutoff_older_than => continue ,
306- Some ( _) | None => state. queue . insert ( parent_commit_time , id) ,
337+ Some ( _) | None => state. queue . insert ( key , id) ,
307338 }
308339 }
309340 }
@@ -323,9 +354,10 @@ mod init {
323354 . and_then ( |parent| parent. committer ( ) . ok ( ) . map ( |committer| committer. time . seconds ) )
324355 . unwrap_or_default ( ) ;
325356
326- match cutoff_older_than {
357+ let time = to_queue_key ( parent_commit_time, order) ;
358+ match cutoff {
327359 Some ( cutoff_older_than) if parent_commit_time < cutoff_older_than => continue ,
328- Some ( _) | None => state. queue . insert ( parent_commit_time , id) ,
360+ Some ( _) | None => state. queue . insert ( time , id) ,
329361 }
330362 }
331363 Ok ( _unused_token) => break ,
0 commit comments