Skip to content

Commit ab25a3a

Browse files
committed
feat!: revision::Walk yields revision::Info structs instead of Ids.
This enables re-use of information that was already obtained, like the parents of a commit and possibly its commit-time.
1 parent 329479c commit ab25a3a

File tree

6 files changed

+127
-38
lines changed

6 files changed

+127
-38
lines changed

gix/examples/stats.rs

+19-17
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
1+
use gix::prelude::ObjectIdExt;
12
use gix::Reference;
3+
use std::io::Write;
24

35
fn main() -> Result<(), Box<dyn std::error::Error>> {
46
let mut repo = gix::discover(".")?;
57
println!("Repo: {}", repo.work_dir().unwrap_or_else(|| repo.git_dir()).display());
6-
let mut max_commit_size = 0;
7-
let mut avg_commit_size = 0;
8+
let mut max_parents = 0;
9+
let mut avg_parents = 0;
810
repo.object_cache_size(32 * 1024);
9-
let commit_ids = repo
11+
let mut most_recent_commit_id = None;
12+
let num_commits = repo
1013
.head()?
1114
.into_fully_peeled_id()
12-
.ok_or("There are no commits - nothing to do here.")??
15+
.ok_or("Cannot provide meaningful stats on empty repos")??
1316
.ancestors()
1417
.all()?
15-
.inspect(|id| {
16-
if let Ok(Ok(object)) = id.as_ref().map(|id| id.object()) {
17-
avg_commit_size += object.data.len();
18-
if object.data.len() > max_commit_size {
19-
max_commit_size = object.data.len();
20-
}
18+
.map_while(Result::ok)
19+
.inspect(|commit| {
20+
if most_recent_commit_id.is_none() {
21+
most_recent_commit_id = Some(commit.id);
2122
}
23+
avg_parents += commit.parent_ids.len();
24+
max_parents = max_parents.max(commit.parent_ids.len());
2225
})
23-
.collect::<Result<Vec<_>, _>>()?;
24-
println!("Num Commits: {}", commit_ids.len());
25-
println!("Max commit Size: {max_commit_size}");
26-
println!("Avg commit Size: {}", avg_commit_size / commit_ids.len());
27-
assert!(!commit_ids.is_empty(), "checked that before");
26+
.count();
27+
println!("Num Commits: {num_commits}");
28+
println!("Max parents: {max_parents}");
29+
println!("Avg parents: {}", avg_parents / num_commits);
2830

29-
let last_commit_id = &commit_ids[0];
3031
println!("Most recent commit message");
3132

32-
let object = last_commit_id.object()?;
33+
let object = most_recent_commit_id.expect("already checked").attach(&repo).object()?;
3334
let commit = object.into_commit();
3435
println!("{}", commit.message_raw()?);
36+
std::io::stdout().flush()?;
3537

3638
let tree = commit.tree()?;
3739

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

+5-5
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
6565
.filter_map(Result::ok)
6666
.next()
6767
{
68-
Some(id) => replacements.push((*obj, id.detach())),
68+
Some(commit) => replacements.push((*obj, commit.id)),
6969
None => errors.push((
7070
*obj,
7171
Error::AncestorOutOfRange {
@@ -193,8 +193,8 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
193193
let mut matched = false;
194194
let mut count = 0;
195195
let commits = iter.map(|res| {
196-
res.map_err(Error::from).and_then(|commit_id| {
197-
commit_id.object().map_err(Error::from).map(|obj| obj.into_commit())
196+
res.map_err(Error::from).and_then(|commit| {
197+
commit.id().object().map_err(Error::from).map(|obj| obj.into_commit())
198198
})
199199
});
200200
for commit in commits {
@@ -250,8 +250,8 @@ impl<'repo> delegate::Navigate for Delegate<'repo> {
250250
let mut matched = false;
251251
let mut count = 0;
252252
let commits = iter.map(|res| {
253-
res.map_err(Error::from).and_then(|commit_id| {
254-
commit_id.object().map_err(Error::from).map(|obj| obj.into_commit())
253+
res.map_err(Error::from).and_then(|commit| {
254+
commit.id().object().map_err(Error::from).map(|obj| obj.into_commit())
255255
})
256256
});
257257
for commit in commits {

gix/src/revision/walk.rs

+77-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use gix_hash::ObjectId;
22
use gix_odb::FindExt;
33

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

67
/// The error returned by [`Platform::all()`].
@@ -13,6 +14,73 @@ pub enum Error {
1314
ShallowCommits(#[from] crate::shallow::open::Error),
1415
}
1516

17+
/// Information about a commit that we obtained naturally as part of the iteration.
18+
#[derive(Debug, Clone)]
19+
pub struct Info<'repo> {
20+
/// The detached id of the commit.
21+
pub id: gix_hash::ObjectId,
22+
/// All parent ids we have encountered. Note that these will be at most one if [`Parents::First`] is enabled.
23+
pub parent_ids: gix_traverse::commit::ParentIds,
24+
/// The time at which the commit was created. It's only `Some(_)` if sorting is not [`Sorting::BreadthFirst`], as the walk
25+
/// needs to require the commit-date.
26+
pub commit_time: Option<u64>,
27+
28+
repo: &'repo Repository,
29+
}
30+
31+
/// Access
32+
impl<'repo> Info<'repo> {
33+
/// Provide an attached version of our [`id`][Info::id] field.
34+
pub fn id(&self) -> crate::Id<'repo> {
35+
self.id.attach(self.repo)
36+
}
37+
38+
/// Read the whole object from the object database.
39+
///
40+
/// Note that this is an expensive operation which shouldn't be performed unless one needs more than parent ids
41+
/// and commit time.
42+
pub fn object(&self) -> Result<crate::Commit<'repo>, crate::object::find::existing::Error> {
43+
Ok(self.id().object()?.into_commit())
44+
}
45+
46+
/// Provide an iterator yielding attached versions of our [`parent_ids`][Info::parent_ids] field.
47+
pub fn parent_ids(&self) -> impl Iterator<Item = crate::Id<'repo>> + '_ {
48+
self.parent_ids.iter().map(|id| id.attach(self.repo))
49+
}
50+
51+
/// Returns the commit-time of this commit.
52+
///
53+
/// ### Panics
54+
///
55+
/// If the iteration wasn't ordered by date.
56+
pub fn commit_time(&self) -> u64 {
57+
self.commit_time.expect("traversal involving date caused it to be set")
58+
}
59+
}
60+
61+
/// Initialization and detachment
62+
impl<'repo> Info<'repo> {
63+
/// Create a new instance that represents `info`, but is attached to `repo` as well.
64+
pub fn new(info: gix_traverse::commit::Info, repo: &'repo Repository) -> Self {
65+
Info {
66+
id: info.id,
67+
parent_ids: info.parent_ids,
68+
commit_time: info.commit_time,
69+
repo,
70+
}
71+
}
72+
/// Consume this instance and remove the reference to the underlying repository.
73+
///
74+
/// This is useful for sending instances across threads, for example.
75+
pub fn detach(self) -> gix_traverse::commit::Info {
76+
gix_traverse::commit::Info {
77+
id: self.id,
78+
parent_ids: self.parent_ids,
79+
commit_time: self.commit_time,
80+
}
81+
}
82+
}
83+
1684
/// A platform to traverse the revision graph by adding starting points as well as points which shouldn't be crossed,
1785
/// returned by [`Repository::rev_walk()`].
1886
///
@@ -41,7 +109,7 @@ impl<'repo> Platform<'repo> {
41109

42110
/// Create-time builder methods
43111
impl<'repo> Platform<'repo> {
44-
/// Set the sort mode for commits to the given value. The default is to order by topology.
112+
/// Set the sort mode for commits to the given value. The default is to order topologically breadth-first.
45113
pub fn sorting(mut self, sorting: gix_traverse::commit::Sorting) -> Self {
46114
self.sorting = sorting;
47115
self
@@ -117,9 +185,7 @@ impl<'repo> Platform<'repo> {
117185
)
118186
.sorting(sorting)?
119187
.parents(parents)
120-
.commit_graph(use_commit_graph.then(|| self.repo.commit_graph().ok()).flatten())
121-
// TODO: use wrapped items instead
122-
.map(|res| res.map(|item| item.id)),
188+
.commit_graph(use_commit_graph.then(|| self.repo.commit_graph().ok()).flatten()),
123189
),
124190
})
125191
}
@@ -135,20 +201,21 @@ impl<'repo> Platform<'repo> {
135201
}
136202

137203
pub(crate) mod iter {
138-
use crate::{ext::ObjectIdExt, Id};
139-
140204
/// The iterator returned by [`crate::revision::walk::Platform::all()`].
141205
pub struct Walk<'repo> {
142206
pub(crate) repo: &'repo crate::Repository,
143-
pub(crate) inner:
144-
Box<dyn Iterator<Item = Result<gix_hash::ObjectId, gix_traverse::commit::ancestors::Error>> + 'repo>,
207+
pub(crate) inner: Box<
208+
dyn Iterator<Item = Result<gix_traverse::commit::Info, gix_traverse::commit::ancestors::Error>> + 'repo,
209+
>,
145210
}
146211

147212
impl<'repo> Iterator for Walk<'repo> {
148-
type Item = Result<Id<'repo>, gix_traverse::commit::ancestors::Error>;
213+
type Item = Result<super::Info<'repo>, gix_traverse::commit::ancestors::Error>;
149214

150215
fn next(&mut self) -> Option<Self::Item> {
151-
self.inner.next().map(|res| res.map(|id| id.attach(self.repo)))
216+
self.inner
217+
.next()
218+
.map(|res| res.map(|info| super::Info::new(info, self.repo)))
152219
}
153220
}
154221
}

gix/tests/id/mod.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,18 @@ mod ancestors {
7575
fn all() -> crate::Result {
7676
let repo = crate::repo("make_repo_with_fork_and_dates.sh")?.to_thread_local();
7777
let head = repo.head()?.into_fully_peeled_id().expect("born")?;
78-
let commits_graph_order = head.ancestors().all()?.collect::<Result<Vec<_>, _>>()?;
78+
let commits_graph_order = head
79+
.ancestors()
80+
.all()?
81+
.map(|c| c.map(|c| c.detach()))
82+
.collect::<Result<Vec<_>, _>>()?;
7983
assert_eq!(commits_graph_order.len(), 4, "need a specific amount of commits");
8084

8185
let commits_by_commit_date = head
8286
.ancestors()
8387
.sorting(commit::Sorting::ByCommitTimeNewestFirst)
8488
.all()?
89+
.map(|c| c.map(|c| c.detach()))
8590
.collect::<Result<Vec<_>, _>>()?;
8691
assert_eq!(
8792
commits_by_commit_date.len(),
@@ -123,6 +128,7 @@ mod ancestors {
123128
id != hex_to_id("9902e3c3e8f0c569b4ab295ddf473e6de763e1e7")
124129
&& id != hex_to_id("bcb05040a6925f2ff5e10d3ae1f9264f2e8c43ac")
125130
})?
131+
.map(|c| c.map(|c| c.id))
126132
.collect::<Result<Vec<_>, _>>()?;
127133
assert_eq!(
128134
commits_graph_order,

gix/tests/repository/object.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,18 @@ mod find {
125125
}
126126
for commit_id in repo.head()?.peeled()?.id().expect("born").ancestors().all()? {
127127
let commit = commit_id?;
128-
assert_eq!(commit.object()?.kind, gix_object::Kind::Commit);
128+
assert_eq!(commit.id().object()?.kind, gix_object::Kind::Commit);
129129
if round == 2 {
130130
assert_eq!(
131-
commit.object()?.kind,
131+
commit.id().object()?.kind,
132132
gix_object::Kind::Commit,
133133
"repeated request triggers cache and doesn't fail"
134134
);
135135
}
136-
assert_eq!(commit.try_object()?.expect("exists").kind, gix_object::Kind::Commit,);
136+
assert_eq!(
137+
commit.id().try_object()?.expect("exists").kind,
138+
gix_object::Kind::Commit,
139+
);
137140
}
138141
}
139142
Ok(())

gix/tests/repository/shallow.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@ fn no() -> crate::Result {
99
let repo = named_subrepo_opts("make_shallow_repo.sh", name, crate::restricted())?;
1010
assert!(!repo.is_shallow());
1111
assert!(repo.shallow_commits()?.is_none());
12-
let commits: Vec<_> = repo.head_id()?.ancestors().all()?.collect::<Result<_, _>>()?;
12+
let commits: Vec<_> = repo
13+
.head_id()?
14+
.ancestors()
15+
.all()?
16+
.map(|c| c.map(|c| c.id))
17+
.collect::<Result<_, _>>()?;
1318
let expected = if name == "base" {
1419
vec![
1520
hex_to_id("30887839de28edf7ab66c860e5c58b4d445f6b12"),
@@ -49,7 +54,12 @@ mod traverse {
4954
fn boundary_is_detected_triggering_no_error() -> crate::Result {
5055
for name in ["shallow.git", "shallow"] {
5156
let repo = named_subrepo_opts("make_shallow_repo.sh", name, crate::restricted())?;
52-
let commits: Vec<_> = repo.head_id()?.ancestors().all()?.collect::<Result<_, _>>()?;
57+
let commits: Vec<_> = repo
58+
.head_id()?
59+
.ancestors()
60+
.all()?
61+
.map(|c| c.map(|c| c.id))
62+
.collect::<Result<_, _>>()?;
5363
assert_eq!(commits, [hex_to_id("30887839de28edf7ab66c860e5c58b4d445f6b12")]);
5464
}
5565
Ok(())
@@ -78,6 +88,7 @@ mod traverse {
7888
.ancestors()
7989
.sorting(Sorting::ByCommitTimeNewestFirst)
8090
.all()?
91+
.map(|c| c.map(|c| c.id))
8192
.collect::<Result<_, _>>()?;
8293
assert_eq!(
8394
commits,

0 commit comments

Comments
 (0)