@@ -2,8 +2,20 @@ use gix_date::SecondsSinceUnixEpoch;
2
2
use gix_hash:: ObjectId ;
3
3
use gix_hashtable:: HashSet ;
4
4
use smallvec:: SmallVec ;
5
+ use std:: cmp:: Reverse ;
5
6
use std:: collections:: VecDeque ;
6
7
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
+
7
19
/// Specify how to sort commits during a [simple](super::Simple) traversal.
8
20
///
9
21
/// ### Sample History
@@ -20,32 +32,35 @@ use std::collections::VecDeque;
20
32
pub enum Sorting {
21
33
/// Commits are sorted as they are mentioned in the commit graph.
22
34
///
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`.
24
36
///
25
37
/// ### Note
26
38
///
27
39
/// This is not to be confused with `git log/rev-list --topo-order`, which is notably different from
28
40
/// as it avoids overlapping branches.
29
41
#[ default]
30
42
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.
32
44
///
33
45
/// The sorting applies to all currently queued commit ids and thus is full.
34
46
///
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).
36
49
///
37
50
/// # Performance
38
51
///
39
52
/// This mode benefits greatly from having an object_cache in `find()`
40
53
/// 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
43
56
/// a given time, stopping the iteration once no younger commits is queued to be traversed.
44
57
///
45
58
/// As the query is usually repeated with different cutoff dates, this search mode benefits greatly from an object cache.
46
59
///
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 ,
49
64
/// The amount of seconds since unix epoch, the same value obtained by any `gix_date::Time` structure and the way git counts time.
50
65
seconds : gix_date:: SecondsSinceUnixEpoch ,
51
66
} ,
@@ -61,11 +76,14 @@ pub enum Error {
61
76
ObjectDecode ( #[ from] gix_object:: decode:: Error ) ,
62
77
}
63
78
79
+ use Result as Either ;
80
+ type QueueKey < T > = Either < T , Reverse < T > > ;
81
+
64
82
/// The state used and potentially shared by multiple graph traversals.
65
83
#[ derive( Clone ) ]
66
84
pub ( super ) struct State {
67
85
next : VecDeque < ObjectId > ,
68
- queue : gix_revwalk:: PriorityQueue < SecondsSinceUnixEpoch , ObjectId > ,
86
+ queue : gix_revwalk:: PriorityQueue < QueueKey < SecondsSinceUnixEpoch > , ObjectId > ,
69
87
buf : Vec < u8 > ,
70
88
seen : HashSet < ObjectId > ,
71
89
parents_buf : Vec < u8 > ,
@@ -77,10 +95,13 @@ mod init {
77
95
use gix_date:: SecondsSinceUnixEpoch ;
78
96
use gix_hash:: { oid, ObjectId } ;
79
97
use gix_object:: { CommitRefIter , FindExt } ;
98
+ use std:: cmp:: Reverse ;
99
+ use Err as Oldest ;
100
+ use Ok as Newest ;
80
101
81
102
use super :: {
82
103
super :: { simple:: Sorting , Either , Info , ParentIds , Parents , Simple } ,
83
- collect_parents, Error , State ,
104
+ collect_parents, CommitTimeOrder , Error , State ,
84
105
} ;
85
106
86
107
impl Default for State {
@@ -105,6 +126,13 @@ mod init {
105
126
}
106
127
}
107
128
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
+
108
136
/// Builder
109
137
impl < Find , Predicate > Simple < Find , Predicate >
110
138
where
@@ -117,19 +145,20 @@ mod init {
117
145
Sorting :: BreadthFirst => {
118
146
self . queue_to_vecdeque ( ) ;
119
147
}
120
- Sorting :: ByCommitTimeNewestFirst | Sorting :: ByCommitTimeNewestFirstCutoffOlderThan { .. } => {
148
+ Sorting :: ByCommitTime ( order ) | Sorting :: ByCommitTimeCutoff { order , .. } => {
121
149
let cutoff_time = self . sorting . cutoff_time ( ) ;
122
150
let state = & mut self . state ;
123
151
for commit_id in state. next . drain ( ..) {
124
152
let commit_iter = self . objects . find_commit_iter ( & commit_id, & mut state. buf ) ?;
125
153
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) ;
129
158
}
130
- Some ( _) => { }
131
- None => {
132
- state. queue . insert ( time , commit_id) ;
159
+ ( Some ( _ ) , _) => { }
160
+ ( None , _ ) => {
161
+ state. queue . insert ( key , commit_id) ;
133
162
}
134
163
}
135
164
}
@@ -254,10 +283,8 @@ mod init {
254
283
} else {
255
284
match self . sorting {
256
285
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 ( ) ) ,
261
288
}
262
289
}
263
290
}
@@ -267,7 +294,7 @@ mod init {
267
294
/// If not topo sort, provide the cutoff date if present.
268
295
fn cutoff_time ( & self ) -> Option < SecondsSinceUnixEpoch > {
269
296
match self {
270
- Sorting :: ByCommitTimeNewestFirstCutoffOlderThan { seconds } => Some ( * seconds) ,
297
+ Sorting :: ByCommitTimeCutoff { seconds, .. } => Some ( * seconds) ,
271
298
_ => None ,
272
299
}
273
300
}
@@ -281,18 +308,21 @@ mod init {
281
308
{
282
309
fn next_by_commit_date (
283
310
& mut self ,
284
- cutoff_older_than : Option < SecondsSinceUnixEpoch > ,
311
+ order : CommitTimeOrder ,
312
+ cutoff : Option < SecondsSinceUnixEpoch > ,
285
313
) -> Option < Result < Info , Error > > {
286
314
let state = & mut self . state ;
287
315
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
+ } ;
289
319
let mut parents: ParentIds = Default :: default ( ) ;
290
320
match super :: super :: find ( self . cache . as_ref ( ) , & self . objects , & oid, & mut state. buf ) {
291
321
Ok ( Either :: CachedCommit ( commit) ) => {
292
322
if !collect_parents ( & mut state. parent_ids , self . cache . as_ref ( ) , commit. iter_parents ( ) ) {
293
323
// drop corrupt caches and try again with ODB
294
324
self . cache = None ;
295
- return self . next_by_commit_date ( cutoff_older_than ) ;
325
+ return self . next_by_commit_date ( order , cutoff ) ;
296
326
}
297
327
for ( id, parent_commit_time) in state. parent_ids . drain ( ..) {
298
328
parents. push ( id) ;
@@ -301,9 +331,10 @@ mod init {
301
331
continue ;
302
332
}
303
333
304
- match cutoff_older_than {
334
+ let key = to_queue_key ( parent_commit_time, order) ;
335
+ match cutoff {
305
336
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) ,
307
338
}
308
339
}
309
340
}
@@ -323,9 +354,10 @@ mod init {
323
354
. and_then ( |parent| parent. committer ( ) . ok ( ) . map ( |committer| committer. time . seconds ) )
324
355
. unwrap_or_default ( ) ;
325
356
326
- match cutoff_older_than {
357
+ let time = to_queue_key ( parent_commit_time, order) ;
358
+ match cutoff {
327
359
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) ,
329
361
}
330
362
}
331
363
Ok ( _unused_token) => break ,
0 commit comments