Skip to content

Commit eb36a3d

Browse files
committed
feat(traversal): Add sorting mode to ancestor traversal #270
1 parent bc77534 commit eb36a3d

File tree

4 files changed

+250
-80
lines changed

4 files changed

+250
-80
lines changed

git-object/src/commit/ref_iter.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,15 @@ impl<'a> CommitRefIter<'a> {
6565
_ => None,
6666
})
6767
}
68+
69+
/// Returns the committer signature if there is no decoding error.
70+
/// Errors are coerced into options, hiding whether there was an error or not. The caller knows if there was an error or not.
71+
pub fn committer(&mut self) -> Option<git_actor::SignatureRef<'_>> {
72+
self.find_map(|t| match t {
73+
Ok(Token::Committer { signature }) => Some(signature),
74+
_ => None,
75+
})
76+
}
6877
}
6978

7079
impl<'a> CommitRefIter<'a> {

git-traverse/src/commit.rs

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ pub struct Ancestors<Find, Predicate, StateMut> {
44
predicate: Predicate,
55
state: StateMut,
66
mode: Parents,
7+
sorting: Sorting,
78
}
89

910
/// Specify how to handle commit parents during traversal.
11+
#[derive(Copy, Clone)]
1012
pub enum Parents {
1113
/// Traverse all parents, useful for traversing the entire ancestry.
1214
All,
@@ -20,6 +22,22 @@ impl Default for Parents {
2022
}
2123
}
2224

25+
/// Specify how to sort commits during traversal.
26+
#[derive(Copy, Clone)]
27+
pub enum Sorting {
28+
/// Default order, sort commit looking up the first reachable parent
29+
GraphOrder,
30+
/// Order commit looking up the most recent parent, since only parents are looked up
31+
/// this ordering is partial
32+
ByCommitterDate,
33+
}
34+
35+
impl Default for Sorting {
36+
fn default() -> Self {
37+
Sorting::GraphOrder
38+
}
39+
}
40+
2341
///
2442
pub mod ancestors {
2543
use std::{
@@ -31,7 +49,7 @@ pub mod ancestors {
3149
use git_object::CommitRefIter;
3250
use quick_error::quick_error;
3351

34-
use crate::commit::{Ancestors, Parents};
52+
use crate::commit::{Ancestors, Parents, Sorting};
3553

3654
quick_error! {
3755
/// The error is part of the item returned by the [Ancestors] iterator.
@@ -55,6 +73,8 @@ pub mod ancestors {
5573
next: VecDeque<ObjectId>,
5674
buf: Vec<u8>,
5775
seen: BTreeSet<ObjectId>,
76+
parents_with_date: Vec<(ObjectId, u32)>,
77+
parents_buf: Vec<u8>,
5878
}
5979

6080
impl State {
@@ -71,6 +91,12 @@ pub mod ancestors {
7191
self.mode = mode;
7292
self
7393
}
94+
95+
/// Set the sorting method, either topological or by author date
96+
pub fn sorting(mut self, sorting: Sorting) -> Self {
97+
self.sorting = sorting;
98+
self
99+
}
74100
}
75101

76102
impl<Find, StateMut> Ancestors<Find, fn(&oid) -> bool, StateMut>
@@ -138,6 +164,7 @@ pub mod ancestors {
138164
predicate,
139165
state,
140166
mode: Default::default(),
167+
sorting: Default::default(),
141168
}
142169
}
143170
}
@@ -151,6 +178,80 @@ pub mod ancestors {
151178
type Item = Result<ObjectId, Error>;
152179

153180
fn next(&mut self) -> Option<Self::Item> {
181+
match self.sorting {
182+
Sorting::GraphOrder => self.graph_sort_next(),
183+
Sorting::ByCommitterDate => self.next_by_commit_date(),
184+
}
185+
}
186+
}
187+
188+
impl<Find, Predicate, StateMut> Ancestors<Find, Predicate, StateMut>
189+
where
190+
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<CommitRefIter<'a>>,
191+
Predicate: FnMut(&oid) -> bool,
192+
StateMut: BorrowMut<State>,
193+
{
194+
fn next_by_commit_date(&mut self) -> Option<Result<ObjectId, Error>> {
195+
let state = self.state.borrow_mut();
196+
state.parents_with_date.clear();
197+
state.parents_buf.clear();
198+
let res = state.next.pop_front();
199+
200+
if let Some(oid) = res {
201+
match (self.find)(&oid, &mut state.buf) {
202+
Some(mut commit_iter) => {
203+
if let Some(Err(decode_tree_err)) = commit_iter.next() {
204+
return Some(Err(decode_tree_err.into()));
205+
}
206+
207+
for token in commit_iter {
208+
match token {
209+
Ok(git_object::commit::ref_iter::Token::Parent { id }) => {
210+
let parent = (self.find)(id.as_ref(), &mut state.parents_buf);
211+
212+
let parent_committer_date = parent
213+
.and_then(|mut parent| parent.committer().map(|committer| committer.time));
214+
215+
if let Some(parent_committer_date) = parent_committer_date {
216+
state.parents_with_date.push((id, parent_committer_date.time));
217+
}
218+
219+
if matches!(self.mode, Parents::First) {
220+
break;
221+
}
222+
}
223+
Ok(_unused_token) => break,
224+
Err(err) => return Some(Err(err.into())),
225+
}
226+
}
227+
}
228+
None => return Some(Err(Error::NotFound { oid })),
229+
}
230+
}
231+
232+
state
233+
.parents_with_date
234+
.sort_by(|(_, time), (_, other_time)| time.cmp(&other_time).reverse());
235+
for parent in &state.parents_with_date {
236+
let id = parent.0;
237+
let was_inserted = state.seen.insert(id);
238+
239+
if was_inserted && (self.predicate)(&id) {
240+
state.next.push_back(id);
241+
}
242+
}
243+
244+
res.map(Ok)
245+
}
246+
}
247+
248+
impl<Find, Predicate, StateMut> Ancestors<Find, Predicate, StateMut>
249+
where
250+
Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<CommitRefIter<'a>>,
251+
Predicate: FnMut(&oid) -> bool,
252+
StateMut: BorrowMut<State>,
253+
{
254+
fn graph_sort_next(&mut self) -> Option<Result<ObjectId, Error>> {
154255
let state = self.state.borrow_mut();
155256
let res = state.next.pop_front();
156257
if let Some(oid) = res {

0 commit comments

Comments
 (0)