Skip to content

Commit 59b8014

Browse files
committed
patch: patch to allow efficient OldestFirst
minimum viable diff take from: GitoxideLabs#1610
1 parent 3b4b22c commit 59b8014

File tree

3 files changed

+96
-44
lines changed

3 files changed

+96
-44
lines changed

gix-traverse/src/commit/simple.rs

+81-29
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,19 @@ 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+
OldestFirst,
16+
}
17+
718
/// Specify how to sort commits during a [simple](super::Simple) traversal.
819
///
920
/// ### Sample History
@@ -28,24 +39,27 @@ pub enum Sorting {
2839
/// as it avoids overlapping branches.
2940
#[default]
3041
BreadthFirst,
31-
/// Commits are sorted by their commit time in descending order, that is newest first.
42+
/// Commits are sorted by their commit time in the order specified, either newest or oldest first.
3243
///
3344
/// The sorting applies to all currently queued commit ids and thus is full.
3445
///
35-
/// In the *sample history* the order would be `8, 7, 6, 5, 4, 3, 2, 1`
46+
/// In the *sample history* the order would be `8, 7, 6, 5, 4, 3, 2, 1` for NewestFirst
47+
/// Or `1, 2, 3, 4, 5, 6, 7, 8` for OldestFirst
3648
///
3749
/// # Performance
3850
///
3951
/// This mode benefits greatly from having an object_cache in `find()`
4052
/// 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
53+
ByCommitTime(CommitTimeOrder),
54+
/// This sorting is similar to `ByCommitTime`, but adds a cutoff to not return commits older than
4355
/// a given time, stopping the iteration once no younger commits is queued to be traversed.
4456
///
4557
/// As the query is usually repeated with different cutoff dates, this search mode benefits greatly from an object cache.
4658
///
4759
/// 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+
ByCommitTimeCutoff {
61+
/// The order in wich to prioritize lookups
62+
order: CommitTimeOrder,
4963
/// The amount of seconds since unix epoch, the same value obtained by any `gix_date::Time` structure and the way git counts time.
5064
seconds: gix_date::SecondsSinceUnixEpoch,
5165
},
@@ -61,27 +75,32 @@ pub enum Error {
6175
ObjectDecode(#[from] gix_object::decode::Error),
6276
}
6377

78+
use Result as Either;
79+
type QueueKey<T> = Either<T, Reverse<T>>;
80+
6481
/// The state used and potentially shared by multiple graph traversals.
6582
#[derive(Clone)]
6683
pub(super) struct State {
6784
next: VecDeque<ObjectId>,
68-
queue: gix_revwalk::PriorityQueue<SecondsSinceUnixEpoch, ObjectId>,
85+
queue: gix_revwalk::PriorityQueue<QueueKey<SecondsSinceUnixEpoch>, ObjectId>,
6986
buf: Vec<u8>,
7087
seen: HashSet<ObjectId>,
7188
parents_buf: Vec<u8>,
7289
parent_ids: SmallVec<[(ObjectId, SecondsSinceUnixEpoch); 2]>,
7390
}
7491

7592
///
76-
#[allow(clippy::empty_docs)]
7793
mod init {
7894
use gix_date::SecondsSinceUnixEpoch;
7995
use gix_hash::{oid, ObjectId};
8096
use gix_object::{CommitRefIter, FindExt};
97+
use std::cmp::Reverse;
98+
use Err as Oldest;
99+
use Ok as Newest;
81100

82101
use super::{
83102
super::{simple::Sorting, Either, Info, ParentIds, Parents, Simple},
84-
collect_parents, Error, State,
103+
collect_parents, CommitTimeOrder, Error, State,
85104
};
86105

87106
impl Default for State {
@@ -106,6 +125,14 @@ mod init {
106125
}
107126
}
108127

128+
fn order_time(i: i64, order: CommitTimeOrder) -> super::QueueKey<i64> {
129+
if let CommitTimeOrder::NewestFirst = order {
130+
Newest(i)
131+
} else {
132+
Oldest(Reverse(i))
133+
}
134+
}
135+
109136
/// Builder
110137
impl<Find, Predicate> Simple<Find, Predicate>
111138
where
@@ -118,19 +145,23 @@ mod init {
118145
Sorting::BreadthFirst => {
119146
self.queue_to_vecdeque();
120147
}
121-
Sorting::ByCommitTimeNewestFirst | Sorting::ByCommitTimeNewestFirstCutoffOlderThan { .. } => {
148+
Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => {
122149
let cutoff_time = self.sorting.cutoff_time();
123150
let state = &mut self.state;
124151
for commit_id in state.next.drain(..) {
125152
let commit_iter = self.objects.find_commit_iter(&commit_id, &mut state.buf)?;
126153
let time = commit_iter.committer()?.time.seconds;
127-
match cutoff_time {
128-
Some(cutoff_time) if time >= cutoff_time => {
129-
state.queue.insert(time, commit_id);
154+
let ordered_time = order_time(time, order);
155+
match (cutoff_time, order) {
156+
(Some(cutoff_time), CommitTimeOrder::NewestFirst) if time >= cutoff_time => {
157+
state.queue.insert(ordered_time, commit_id);
158+
}
159+
(Some(cutoff_time), CommitTimeOrder::OldestFirst) if time <= cutoff_time => {
160+
state.queue.insert(ordered_time, commit_id);
130161
}
131-
Some(_) => {}
132-
None => {
133-
state.queue.insert(time, commit_id);
162+
(Some(_), _) => {}
163+
(None, _) => {
164+
state.queue.insert(ordered_time, commit_id);
134165
}
135166
}
136167
}
@@ -255,10 +286,8 @@ mod init {
255286
} else {
256287
match self.sorting {
257288
Sorting::BreadthFirst => self.next_by_topology(),
258-
Sorting::ByCommitTimeNewestFirst => self.next_by_commit_date(None),
259-
Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds } => {
260-
self.next_by_commit_date(seconds.into())
261-
}
289+
Sorting::ByCommitTime(order) => self.next_by_commit_date(order, None),
290+
Sorting::ByCommitTimeCutoff { seconds, order } => self.next_by_commit_date(order, seconds.into()),
262291
}
263292
}
264293
}
@@ -268,7 +297,7 @@ mod init {
268297
/// If not topo sort, provide the cutoff date if present.
269298
fn cutoff_time(&self) -> Option<SecondsSinceUnixEpoch> {
270299
match self {
271-
Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds } => Some(*seconds),
300+
Sorting::ByCommitTimeCutoff { seconds, .. } => Some(*seconds),
272301
_ => None,
273302
}
274303
}
@@ -282,18 +311,21 @@ mod init {
282311
{
283312
fn next_by_commit_date(
284313
&mut self,
285-
cutoff_older_than: Option<SecondsSinceUnixEpoch>,
314+
order: CommitTimeOrder,
315+
cutoff: Option<SecondsSinceUnixEpoch>,
286316
) -> Option<Result<Info, Error>> {
287317
let state = &mut self.state;
288318

289-
let (commit_time, oid) = state.queue.pop()?;
319+
let (commit_time, oid) = match state.queue.pop()? {
320+
(Newest(t) | Oldest(Reverse(t)), o) => (t, o),
321+
};
290322
let mut parents: ParentIds = Default::default();
291323
match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) {
292324
Ok(Either::CachedCommit(commit)) => {
293325
if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) {
294326
// drop corrupt caches and try again with ODB
295327
self.cache = None;
296-
return self.next_by_commit_date(cutoff_older_than);
328+
return self.next_by_commit_date(order, cutoff);
297329
}
298330
for (id, parent_commit_time) in state.parent_ids.drain(..) {
299331
parents.push(id);
@@ -302,9 +334,19 @@ mod init {
302334
continue;
303335
}
304336

305-
match cutoff_older_than {
306-
Some(cutoff_older_than) if parent_commit_time < cutoff_older_than => continue,
307-
Some(_) | None => state.queue.insert(parent_commit_time, id),
337+
let time = order_time(parent_commit_time, order);
338+
match (cutoff, order) {
339+
(Some(cutoff_older_than), CommitTimeOrder::NewestFirst)
340+
if parent_commit_time < cutoff_older_than =>
341+
{
342+
continue
343+
}
344+
(Some(cutoff_newer_than), CommitTimeOrder::OldestFirst)
345+
if parent_commit_time > cutoff_newer_than =>
346+
{
347+
continue
348+
}
349+
(Some(_) | None, _) => state.queue.insert(time, id),
308350
}
309351
}
310352
}
@@ -324,9 +366,19 @@ mod init {
324366
.and_then(|parent| parent.committer().ok().map(|committer| committer.time.seconds))
325367
.unwrap_or_default();
326368

327-
match cutoff_older_than {
328-
Some(cutoff_older_than) if parent_commit_time < cutoff_older_than => continue,
329-
Some(_) | None => state.queue.insert(parent_commit_time, id),
369+
let time = order_time(parent_commit_time, order);
370+
match (cutoff, order) {
371+
(Some(cutoff_older_than), CommitTimeOrder::NewestFirst)
372+
if parent_commit_time < cutoff_older_than =>
373+
{
374+
continue
375+
}
376+
(Some(cutoff_newer_than), CommitTimeOrder::OldestFirst)
377+
if parent_commit_time > cutoff_newer_than =>
378+
{
379+
continue
380+
}
381+
(Some(_) | None, _) => state.queue.insert(time, id),
330382
}
331383
}
332384
Ok(_unused_token) => break,

gix/src/remote/connection/fetch/update_refs/mod.rs

+13-13
Original file line numberDiff line numberDiff line change
@@ -155,19 +155,19 @@ pub(crate) fn update(
155155
.find_object(local_id)?
156156
.try_into_commit()
157157
.map_err(|_| ())
158-
.and_then(|c| {
159-
c.committer().map(|a| a.time.seconds).map_err(|_| ())
160-
}).and_then(|local_commit_time|
161-
remote_id
162-
.to_owned()
163-
.ancestors(&repo.objects)
164-
.sorting(
165-
gix_traverse::commit::simple::Sorting::ByCommitTimeNewestFirstCutoffOlderThan {
166-
seconds: local_commit_time
167-
},
168-
)
169-
.map_err(|_| ())
170-
);
158+
.and_then(|c| c.committer().map(|a| a.time.seconds).map_err(|_| ()))
159+
.and_then(|local_commit_time| {
160+
remote_id
161+
.to_owned()
162+
.ancestors(&repo.objects)
163+
.sorting(
164+
gix_traverse::commit::simple::Sorting::ByCommitTimeCutoff {
165+
order: Default::default(),
166+
seconds: local_commit_time,
167+
},
168+
)
169+
.map_err(|_| ())
170+
});
171171
match ancestors {
172172
Ok(mut ancestors) => {
173173
ancestors.any(|cid| cid.map_or(false, |c| c.id == local_id))

gix/src/revision/spec/parse/delegate/navigate.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
192192
match oid
193193
.attach(repo)
194194
.ancestors()
195-
.sorting(gix_traverse::commit::simple::Sorting::ByCommitTimeNewestFirst)
195+
.sorting(gix_traverse::commit::simple::Sorting::ByCommitTime(Default::default()))
196196
.all()
197197
{
198198
Ok(iter) => {
@@ -245,7 +245,7 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
245245
.filter(|r| r.id().header().ok().map_or(false, |obj| obj.kind().is_commit()))
246246
.filter_map(|r| r.detach().peeled),
247247
)
248-
.sorting(gix_traverse::commit::simple::Sorting::ByCommitTimeNewestFirst)
248+
.sorting(gix_traverse::commit::simple::Sorting::ByCommitTime(Default::default()))
249249
.all()
250250
{
251251
Ok(iter) => {

0 commit comments

Comments
 (0)