Skip to content

Commit b45302a

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

File tree

4 files changed

+251
-80
lines changed

4 files changed

+251
-80
lines changed

git-object/src/commit/ref_iter.rs

+9
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

+103-1
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,7 @@ pub mod ancestors {
5573
next: VecDeque<ObjectId>,
5674
buf: Vec<u8>,
5775
seen: BTreeSet<ObjectId>,
76+
parents_with_date: Vec<(ObjectId, u32)>,
5877
}
5978

6079
impl State {
@@ -71,6 +90,12 @@ pub mod ancestors {
7190
self.mode = mode;
7291
self
7392
}
93+
94+
/// Set the sorting method, either topological or by author date
95+
pub fn sorting(mut self, sorting: Sorting) -> Self {
96+
self.sorting = sorting;
97+
self
98+
}
7499
}
75100

76101
impl<Find, StateMut> Ancestors<Find, fn(&oid) -> bool, StateMut>
@@ -138,6 +163,7 @@ pub mod ancestors {
138163
predicate,
139164
state,
140165
mode: Default::default(),
166+
sorting: Default::default(),
141167
}
142168
}
143169
}
@@ -151,6 +177,82 @@ pub mod ancestors {
151177
type Item = Result<ObjectId, Error>;
152178

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

0 commit comments

Comments
 (0)