Skip to content

Commit 20f9b3f

Browse files
authored
Merge pull request #1610 from nrdxp/traverse/oldest-first
feat: add option to traverse commits from oldest to newest
2 parents 7bf2f4f + 6862c27 commit 20f9b3f

File tree

10 files changed

+268
-105
lines changed

10 files changed

+268
-105
lines changed

examples/log.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ fn run(args: Args) -> anyhow::Result<()> {
7979
Sorting::BreadthFirst
8080
} else {
8181
// else if args.newest_first {
82-
Sorting::ByCommitTimeNewestFirst
82+
Sorting::ByCommitTime(Default::default())
8383
};
8484

8585
let mut min_parents = args.min_parents.unwrap_or(0);

gitoxide-core/src/repository/commitgraph/list.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub(crate) mod function {
3030
.context("Need committish as starting point")?
3131
.id()
3232
.ancestors()
33-
.sorting(Sorting::ByCommitTimeNewestFirst)
33+
.sorting(Sorting::ByCommitTime(Default::default()))
3434
.all()?;
3535
for commit in commits {
3636
let commit = commit?;

gitoxide-core/src/repository/revision/list.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ pub(crate) mod function {
5252
.context("Need committish as starting point")?
5353
.id()
5454
.ancestors()
55-
.sorting(Sorting::ByCommitTimeNewestFirst)
55+
.sorting(Sorting::ByCommitTime(Default::default()))
5656
.all()?;
5757

5858
let mut vg = match text {

gix-traverse/src/commit/simple.rs

+60-28
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,20 @@ use gix_date::SecondsSinceUnixEpoch;
22
use gix_hash::ObjectId;
33
use gix_hashtable::HashSet;
44
use smallvec::SmallVec;
5+
use std::cmp::Reverse;
56
use 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;
2032
pub 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)]
6684
pub(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

Comments
 (0)