Skip to content

Commit d15468f

Browse files
nrdxpByron
andcommitted
feat: Support for 'fast-tracking' reaching the beginning of the commit-graph during traversals.
It's implemented by sorting commits oldest first when choosing the next one to traverse, which can greatly reduce the time it takes to reach the first commit of a graph. Co-authored-by: Sebastian Thiel <[email protected]>
1 parent 7419552 commit d15468f

File tree

5 files changed

+47
-35
lines changed

5 files changed

+47
-35
lines changed

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

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

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

Lines changed: 2 additions & 2 deletions
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(crate::revision::walk::Sorting::ByCommitTimeNewestFirst)
195+
.sorting(crate::revision::walk::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(crate::revision::walk::Sorting::ByCommitTimeNewestFirst)
248+
.sorting(crate::revision::walk::Sorting::ByCommitTime(Default::default()))
249249
.all()
250250
{
251251
Ok(iter) => {

gix/src/revision/walk.rs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use gix_hash::ObjectId;
22
use gix_object::FindExt;
3+
use gix_traverse::commit::simple::CommitTimeOrder;
34

45
use crate::{ext::ObjectIdExt, revision, Repository};
56

@@ -39,24 +40,27 @@ pub enum Sorting {
3940
/// as it avoids overlapping branches.
4041
#[default]
4142
BreadthFirst,
42-
/// 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.
4344
///
4445
/// The sorting applies to all currently queued commit ids and thus is full.
4546
///
46-
/// In the *sample history* the order would be `8, 7, 6, 4, 5, 2, 3, 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).
4749
///
4850
/// # Performance
4951
///
5052
/// This mode benefits greatly from having an [object cache](crate::Repository::object_cache_size) configured
5153
/// to avoid having to look up each commit twice.
52-
ByCommitTimeNewestFirst,
53-
/// 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::ByCommitTimeCutoff), but adds a cutoff to not return commits older than
5456
/// a given time, stopping the iteration once no younger commits is queued to be traversed.
5557
///
5658
/// As the query is usually repeated with different cutoff dates, this search mode benefits greatly from an object cache.
5759
///
5860
/// In the *sample history* and a cut-off date of 4, the returned list of commits would be `8, 7, 6, 4`
59-
ByCommitTimeNewestFirstCutoffOlderThan {
61+
ByCommitTimeCutoff {
62+
/// The order in wich to prioritize lookups
63+
order: CommitTimeOrder,
6064
/// The amount of seconds since unix epoch to use as cut-off time.
6165
seconds: gix_date::SecondsSinceUnixEpoch,
6266
},
@@ -66,9 +70,9 @@ impl Sorting {
6670
fn into_simple(self) -> Option<gix_traverse::commit::simple::Sorting> {
6771
Some(match self {
6872
Sorting::BreadthFirst => gix_traverse::commit::simple::Sorting::BreadthFirst,
69-
Sorting::ByCommitTimeNewestFirst => gix_traverse::commit::simple::Sorting::ByCommitTimeNewestFirst,
70-
Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds } => {
71-
gix_traverse::commit::simple::Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds }
73+
Sorting::ByCommitTime(order) => gix_traverse::commit::simple::Sorting::ByCommitTime(order),
74+
Sorting::ByCommitTimeCutoff { seconds, order } => {
75+
gix_traverse::commit::simple::Sorting::ByCommitTimeCutoff { order, seconds }
7276
}
7377
})
7478
}
@@ -208,15 +212,16 @@ impl<'repo> Platform<'repo> {
208212
/// Prune the commit with the given `ids` such that they won't be returned, and such that none of their ancestors is returned either.
209213
///
210214
/// Note that this forces the [sorting](Self::sorting) to
211-
/// [`ByCommitTimeNewestFirstCutoffOlderThan`](Sorting::ByCommitTimeNewestFirstCutoffOlderThan) configured with
215+
/// [`ByCommitTimeCutoff`](Sorting::ByCommitTimeCutoff) configured with
212216
/// the oldest available commit time, ensuring that no commits older than the oldest of `ids` will be returned either.
213217
///
214218
/// Also note that commits that can't be accessed or are missing are simply ignored for the purpose of obtaining the cutoff date.
215219
#[doc(alias = "hide", alias = "git2")]
216220
pub fn with_pruned(mut self, ids: impl IntoIterator<Item = impl Into<ObjectId>>) -> Self {
217-
let mut cutoff = match self.sorting {
218-
Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds } => Some(seconds),
219-
Sorting::BreadthFirst | Sorting::ByCommitTimeNewestFirst => None,
221+
let (mut cutoff, order) = match self.sorting {
222+
Sorting::ByCommitTimeCutoff { seconds, order } => (Some(seconds), order),
223+
Sorting::ByCommitTime(order) => (None, order),
224+
Sorting::BreadthFirst => (None, CommitTimeOrder::default()),
220225
};
221226
for id in ids.into_iter() {
222227
let id = id.into();
@@ -231,7 +236,7 @@ impl<'repo> Platform<'repo> {
231236
}
232237

233238
if let Some(cutoff) = cutoff {
234-
self.sorting = Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds: cutoff }
239+
self.sorting = Sorting::ByCommitTimeCutoff { seconds: cutoff, order }
235240
}
236241
self
237242
}

gix/tests/id/mod.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ mod ancestors {
8585
let commits_by_commit_date = head
8686
.ancestors()
8787
.use_commit_graph(!use_commit_graph)
88-
.sorting(gix::revision::walk::Sorting::ByCommitTimeNewestFirst)
88+
.sorting(gix::revision::walk::Sorting::ByCommitTime(Default::default()))
8989
.all()?
9090
.map(|c| c.map(gix::revision::walk::Info::detach))
9191
.collect::<Result<Vec<_>, _>>()?;
@@ -119,7 +119,7 @@ mod ancestors {
119119
let head = repo.head()?.into_peeled_id()?;
120120
let commits = head
121121
.ancestors()
122-
.sorting(gix::revision::walk::Sorting::ByCommitTimeNewestFirst) // assure we have time set
122+
.sorting(gix::revision::walk::Sorting::ByCommitTime(Default::default())) // assure we have time set
123123
.use_commit_graph(use_commit_graph)
124124
.all()?
125125
.collect::<Result<Vec<_>, _>>()?;
@@ -162,8 +162,11 @@ mod ancestors {
162162
for use_commit_graph in [false, true] {
163163
for sorting in [
164164
gix::revision::walk::Sorting::BreadthFirst,
165-
gix::revision::walk::Sorting::ByCommitTimeNewestFirst,
166-
gix::revision::walk::Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds: 0 },
165+
gix::revision::walk::Sorting::ByCommitTime(Default::default()),
166+
gix::revision::walk::Sorting::ByCommitTimeCutoff {
167+
order: Default::default(),
168+
seconds: 0,
169+
},
167170
] {
168171
let commits_graph_order = head
169172
.ancestors()

gix/tests/repository/shallow.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ fn yes() -> crate::Result {
4444
}
4545

4646
mod traverse {
47+
use gix_traverse::commit::simple::CommitTimeOrder;
4748
use serial_test::parallel;
4849

4950
use crate::util::{hex_to_id, named_subrepo_opts};
@@ -53,8 +54,11 @@ mod traverse {
5354
fn boundary_is_detected_triggering_no_error() -> crate::Result {
5455
for sorting in [
5556
gix::revision::walk::Sorting::BreadthFirst,
56-
gix::revision::walk::Sorting::ByCommitTimeNewestFirst,
57-
gix::revision::walk::Sorting::ByCommitTimeNewestFirstCutoffOlderThan { seconds: 0 },
57+
gix::revision::walk::Sorting::ByCommitTime(CommitTimeOrder::NewestFirst),
58+
gix::revision::walk::Sorting::ByCommitTimeCutoff {
59+
order: CommitTimeOrder::NewestFirst,
60+
seconds: 0,
61+
},
5862
] {
5963
for toggle in [false, true] {
6064
for name in ["shallow.git", "shallow"] {
@@ -97,7 +101,7 @@ mod traverse {
97101
.head_id()?
98102
.ancestors()
99103
.use_commit_graph(toggle)
100-
.sorting(gix::revision::walk::Sorting::ByCommitTimeNewestFirst)
104+
.sorting(gix::revision::walk::Sorting::ByCommitTime(CommitTimeOrder::NewestFirst))
101105
.all()?
102106
.map(|c| c.map(|c| c.id))
103107
.collect::<Result<_, _>>()?;

0 commit comments

Comments
 (0)