use std::{cmp::Reverse, collections::VecDeque}; use gix_date::SecondsSinceUnixEpoch;use gix_hash::ObjectId;use smallvec::SmallVec; #[derive(Default, Debug, Copy, Clone)]/// The order with which to prioritize the search.pub enum CommitTimeOrder { #[default] /// Sort commits by newest first. NewestFirst, /// Sort commits by oldest first. #[doc(alias = "Sort::REVERSE", alias = "git2")] OldestFirst,} /// Specify how to sort commits during a [simple](super::Simple) traversal.////// ### Sample History////// The following history will be referred to for explaining how the sort order works, with the number denoting the commit timestamp/// (*their X-alignment doesn't matter*).////// ```text/// ---1----2----4----7 <- second parent of 8/// \ \/// 3----5----6----8---/// ```#[derive(Default, Debug, Copy, Clone)]pub enum Sorting { /// Commits are sorted as they are mentioned in the commit graph. /// /// In the *sample history* the order would be `8, 6, 7, 5, 4, 3, 2, 1`. /// /// ### Note /// /// This is not to be confused with `git log/rev-list --topo-order`, which is notably different from /// as it avoids overlapping branches. #[default] BreadthFirst, /// Commits are sorted by their commit time in the order specified, either newest or oldest first. /// /// The sorting applies to all currently queued commit ids and thus is full. /// /// In the *sample history* the order would be `8, 7, 6, 5, 4, 3, 2, 1` for [`NewestFirst`](CommitTimeOrder::NewestFirst), /// or `1, 2, 3, 4, 5, 6, 7, 8` for [`OldestFirst`](CommitTimeOrder::OldestFirst). /// /// # Performance /// /// This mode benefits greatly from having an object_cache in `find()` /// to avoid having to lookup each commit twice. ByCommitTime(CommitTimeOrder), /// This sorting is similar to [`ByCommitTime`](Sorting::ByCommitTime), but adds a cutoff to not return commits older than /// a given time, stopping the iteration once no younger commits is queued to be traversed. /// /// As the query is usually repeated with different cutoff dates, this search mode benefits greatly from an object cache. /// /// In the *sample history* and a cut-off date of 4, the returned list of commits would be `8, 7, 6, 4`. ByCommitTimeCutoff { /// The order in which to prioritize lookups. order: CommitTimeOrder, /// The number of seconds since unix epoch, the same value obtained by any `gix_date::Time` structure and the way git counts time. seconds: gix_date::SecondsSinceUnixEpoch, },} /// The error is part of the item returned by the [Ancestors](super::Simple) iterator.#[derive(Debug, thiserror::Error)]#[allow(missing_docs)]pub enum Error { #[error(transparent)] Find(#[from] gix_object::find::existing_iter::Error), #[error(transparent)] ObjectDecode(#[from] gix_object::decode::Error),} use Result as Either; type QueueKey<T> = Either<T, Reverse<T>>;type CommitDateQueue = gix_revwalk::PriorityQueue<QueueKey<SecondsSinceUnixEpoch>, (ObjectId, CommitState)>;type Candidates = VecDeque<crate::commit::Info>; /// The state used and potentially shared by multiple graph traversals.#[derive(Clone)]pub(super) struct State { next: VecDeque<(ObjectId, CommitState)>, queue: CommitDateQueue, buf: Vec<u8>, seen: gix_revwalk::graph::IdMap<CommitState>, parents_buf: Vec<u8>, parent_ids: SmallVec<[(ObjectId, SecondsSinceUnixEpoch); 2]>, /// The list (FIFO) of thus far interesting commits. /// /// As they may turn hidden later, we have to keep them until the conditions are met to return them. /// If `None`, there is nothing to do with hidden commits. candidates: Option<Candidates>,} #[derive(Debug, Clone, Copy)]enum CommitState { /// The commit may be returned, it hasn't been hidden yet. Interesting, /// The commit should not be returned. Hidden,} impl CommitState { pub fn is_hidden(&self) -> bool { matches!(self, CommitState::Hidden) } pub fn is_interesting(&self) -> bool { matches!(self, CommitState::Interesting) }} ///mod init { use std::cmp::Reverse; use super::{ super::{simple::Sorting, Either, Info, ParentIds, Parents, Simple}, collect_parents, Candidates, CommitDateQueue, CommitState, CommitTimeOrder, Error, State, }; use gix_date::SecondsSinceUnixEpoch; use gix_hash::{oid, ObjectId}; use gix_hashtable::hash_map::Entry; use gix_object::{CommitRefIter, FindExt}; use std::collections::VecDeque; use Err as Oldest; use Ok as Newest; impl Default for State { fn default() -> Self { State { next: Default::default(), queue: gix_revwalk::PriorityQueue::new(), buf: vec![], seen: Default::default(), parents_buf: vec![], parent_ids: Default::default(), candidates: None, } } } impl State { fn clear(&mut self) { let Self { next, queue, buf, seen, parents_buf: _, parent_ids: _, candidates, } = self; next.clear(); queue.clear(); buf.clear(); seen.clear(); *candidates = None; } } fn to_queue_key(i: i64, order: CommitTimeOrder) -> super::QueueKey<i64> { match order { CommitTimeOrder::NewestFirst => Newest(i), CommitTimeOrder::OldestFirst => Oldest(Reverse(i)), } } /// Builder impl<Find, Predicate> Simple<Find, Predicate> where Find: gix_object::Find, { /// Set the `sorting` method. pub fn sorting(mut self, sorting: Sorting) -> Result<Self, Error> { self.sorting = sorting; match self.sorting { Sorting::BreadthFirst => { self.queue_to_vecdeque(); } Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => { let state = &mut self.state; for (commit_id, commit_state) in state.next.drain(..) { add_to_queue( commit_id, commit_state, order, sorting.cutoff_time(), &mut state.queue, &self.objects, &mut state.buf, )?; } } } Ok(self) } /// Change our commit parent handling mode to the given one. pub fn parents(mut self, mode: Parents) -> Self { self.parents = mode; if matches!(self.parents, Parents::First) { self.queue_to_vecdeque(); } self } /// Hide the given `tips`, along with all commits reachable by them so that they will not be returned /// by the traversal. /// /// Note that this will force the traversal into a non-intermediate mode and queue return candidates, /// to be released when it's clear that they truly are not hidden. /// /// Note that hidden objects are expected to exist. pub fn hide(mut self, tips: impl IntoIterator<Item = ObjectId>) -> Result<Self, Error> { self.state.candidates = Some(VecDeque::new()); let state = &mut self.state; for id_to_ignore in tips { let previous = state.seen.insert(id_to_ignore, CommitState::Hidden); // If there was something, it will pick up whatever commit-state we have set last // upon iteration. Also, hidden states always override everything else. if previous.is_none() { // Assure we *start* traversing hidden variants of a commit first, give them a head-start. match self.sorting { Sorting::BreadthFirst => { state.next.push_front((id_to_ignore, CommitState::Hidden)); } Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => { add_to_queue( id_to_ignore, CommitState::Hidden, order, self.sorting.cutoff_time(), &mut state.queue, &self.objects, &mut state.buf, )?; } } } } if !self .state .seen .values() .any(|state| matches!(state, CommitState::Hidden)) { self.state.candidates = None; } Ok(self) } /// Set the commitgraph as `cache` to greatly accelerate any traversal. /// /// The cache will be used if possible, but we will fall back without error to using the object /// database for commit lookup. If the cache is corrupt, we will fall back to the object database as well. pub fn commit_graph(mut self, cache: Option<gix_commitgraph::Graph>) -> Self { self.cache = cache; self } fn queue_to_vecdeque(&mut self) { let state = &mut self.state; state.next.extend( std::mem::replace(&mut state.queue, gix_revwalk::PriorityQueue::new()) .into_iter_unordered() .map(|(_time, id)| id), ); } } fn add_to_queue( commit_id: ObjectId, commit_state: CommitState, order: CommitTimeOrder, cutoff_time: Option<SecondsSinceUnixEpoch>, queue: &mut CommitDateQueue, objects: &impl gix_object::Find, buf: &mut Vec<u8>, ) -> Result<(), Error> { let commit_iter = objects.find_commit_iter(&commit_id, buf)?; let time = commit_iter.committer()?.seconds(); let key = to_queue_key(time, order); match (cutoff_time, order) { (Some(cutoff_time), _) if time >= cutoff_time => { queue.insert(key, (commit_id, commit_state)); } (Some(_), _) => {} (None, _) => { queue.insert(key, (commit_id, commit_state)); } } Ok(()) } /// Lifecycle impl<Find> Simple<Find, fn(&oid) -> bool> where Find: gix_object::Find, { /// Create a new instance. /// /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over commit tokens if the object is present and is a commit. Caching should be implemented within this function /// as needed. /// * `tips` /// * the starting points of the iteration, usually commits /// * each commit they lead to will only be returned once, including the tip that started it pub fn new(tips: impl IntoIterator<Item = impl Into<ObjectId>>, find: Find) -> Self { Self::filtered(tips, find, |_| true) } } /// Lifecycle impl<Find, Predicate> Simple<Find, Predicate> where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { /// Create a new instance with commit filtering enabled. /// /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over commit tokens if the object is present and is a commit. Caching should be implemented within this function /// as needed. /// * `tips` /// * the starting points of the iteration, usually commits /// * each commit they lead to will only be returned once, including the tip that started it /// * `predicate` - indicate whether a given commit should be included in the result as well /// as whether its parent commits should be traversed. pub fn filtered( tips: impl IntoIterator<Item = impl Into<ObjectId>>, find: Find, mut predicate: Predicate, ) -> Self { let tips = tips.into_iter(); let mut state = State::default(); { state.clear(); state.next.reserve(tips.size_hint().0); for tip in tips.map(Into::into) { let commit_state = CommitState::Interesting; let seen = state.seen.insert(tip, commit_state); // We know there can only be duplicate interesting ones. if seen.is_none() && predicate(&tip) { state.next.push_back((tip, commit_state)); } } } Self { objects: find, cache: None, predicate, state, parents: Default::default(), sorting: Default::default(), } } } /// Access impl<Find, Predicate> Simple<Find, Predicate> { /// Return an iterator for accessing data of the current commit, parsed lazily. pub fn commit_iter(&self) -> CommitRefIter<'_> { CommitRefIter::from_bytes(self.commit_data()) } /// Return the current commits' raw data, which can be parsed using [`gix_object::CommitRef::from_bytes()`]. pub fn commit_data(&self) -> &[u8] { &self.state.buf } } impl<Find, Predicate> Iterator for Simple<Find, Predicate> where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { type Item = Result<Info, Error>; fn next(&mut self) -> Option<Self::Item> { if matches!(self.parents, Parents::First) { self.next_by_topology() } else { match self.sorting { Sorting::BreadthFirst => self.next_by_topology(), Sorting::ByCommitTime(order) => self.next_by_commit_date(order, None), Sorting::ByCommitTimeCutoff { seconds, order } => self.next_by_commit_date(order, seconds.into()), } } .or_else(|| { self.state .candidates .as_mut() .and_then(|candidates| candidates.pop_front().map(Ok)) }) } } impl Sorting { /// If not topo sort, provide the cutoff date if present. fn cutoff_time(&self) -> Option<SecondsSinceUnixEpoch> { match self { Sorting::ByCommitTimeCutoff { seconds, .. } => Some(*seconds), _ => None, } } } /// Utilities impl<Find, Predicate> Simple<Find, Predicate> where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { fn next_by_commit_date( &mut self, order: CommitTimeOrder, cutoff: Option<SecondsSinceUnixEpoch>, ) -> Option<Result<Info, Error>> { let state = &mut self.state; let next = &mut state.queue; 'skip_hidden: loop { let (commit_time, (oid, _queued_commit_state)) = match next.pop()? { (Newest(t) | Oldest(Reverse(t)), o) => (t, o), }; let mut parents: ParentIds = Default::default(); // Always use the state that is actually stored, as we may change the type as we go. let commit_state = *state.seen.get(&oid).expect("every commit we traverse has state added"); if can_deplete_candidates_early( next.iter_unordered().map(|t| t.1), commit_state, state.candidates.as_ref(), ) { return None; } match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) { Ok(Either::CachedCommit(commit)) => { if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) { // drop corrupt caches and try again with ODB self.cache = None; return self.next_by_commit_date(order, cutoff); } for (id, parent_commit_time) in state.parent_ids.drain(..) { parents.push(id); insert_into_seen_and_queue( &mut state.seen, id, &mut state.candidates, commit_state, &mut self.predicate, next, order, cutoff, || parent_commit_time, ); } } Ok(Either::CommitRefIter(commit_iter)) => { for token in commit_iter { match token { Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue, Ok(gix_object::commit::ref_iter::Token::Parent { id }) => { parents.push(id); insert_into_seen_and_queue( &mut state.seen, id, &mut state.candidates, commit_state, &mut self.predicate, next, order, cutoff, || { let parent = self.objects.find_commit_iter(id.as_ref(), &mut state.parents_buf).ok(); parent .and_then(|parent| { parent.committer().ok().map(|committer| committer.seconds()) }) .unwrap_or_default() }, ); } Ok(_unused_token) => break, Err(err) => return Some(Err(err.into())), } } } Err(err) => return Some(Err(err.into())), } match commit_state { CommitState::Interesting => { let info = Info { id: oid,Lines 1–500 of 751. Load more
use std::{cmp::Reverse, collections::VecDeque}; use gix_date::SecondsSinceUnixEpoch; use gix_hash::ObjectId; use smallvec::SmallVec; #[derive(Default, Debug, Copy, Clone)] /// The order with which to prioritize the search. pub enum CommitTimeOrder { #[default] /// Sort commits by newest first. NewestFirst, /// Sort commits by oldest first. #[doc(alias = "Sort::REVERSE", alias = "git2")] OldestFirst, } /// Specify how to sort commits during a [simple](super::Simple) traversal. /// /// ### Sample History /// /// The following history will be referred to for explaining how the sort order works, with the number denoting the commit timestamp /// (*their X-alignment doesn't matter*). /// /// ```text /// ---1----2----4----7 <- second parent of 8 /// \ \ /// 3----5----6----8--- /// ``` #[derive(Default, Debug, Copy, Clone)] pub enum Sorting { /// Commits are sorted as they are mentioned in the commit graph. /// /// In the *sample history* the order would be `8, 6, 7, 5, 4, 3, 2, 1`. /// /// ### Note /// /// This is not to be confused with `git log/rev-list --topo-order`, which is notably different from /// as it avoids overlapping branches. #[default] BreadthFirst, /// Commits are sorted by their commit time in the order specified, either newest or oldest first. /// /// The sorting applies to all currently queued commit ids and thus is full. /// /// In the *sample history* the order would be `8, 7, 6, 5, 4, 3, 2, 1` for [`NewestFirst`](CommitTimeOrder::NewestFirst), /// or `1, 2, 3, 4, 5, 6, 7, 8` for [`OldestFirst`](CommitTimeOrder::OldestFirst). /// /// # Performance /// /// This mode benefits greatly from having an object_cache in `find()` /// to avoid having to lookup each commit twice. ByCommitTime(CommitTimeOrder), /// This sorting is similar to [`ByCommitTime`](Sorting::ByCommitTime), but adds a cutoff to not return commits older than /// a given time, stopping the iteration once no younger commits is queued to be traversed. /// /// As the query is usually repeated with different cutoff dates, this search mode benefits greatly from an object cache. /// /// In the *sample history* and a cut-off date of 4, the returned list of commits would be `8, 7, 6, 4`. ByCommitTimeCutoff { /// The order in which to prioritize lookups. order: CommitTimeOrder, /// The number of seconds since unix epoch, the same value obtained by any `gix_date::Time` structure and the way git counts time. seconds: gix_date::SecondsSinceUnixEpoch, }, } /// The error is part of the item returned by the [Ancestors](super::Simple) iterator. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] Find(#[from] gix_object::find::existing_iter::Error), #[error(transparent)] ObjectDecode(#[from] gix_object::decode::Error), } use Result as Either; type QueueKey<T> = Either<T, Reverse<T>>; type CommitDateQueue = gix_revwalk::PriorityQueue<QueueKey<SecondsSinceUnixEpoch>, (ObjectId, CommitState)>; type Candidates = VecDeque<crate::commit::Info>; /// The state used and potentially shared by multiple graph traversals. #[derive(Clone)] pub(super) struct State { next: VecDeque<(ObjectId, CommitState)>, queue: CommitDateQueue, buf: Vec<u8>, seen: gix_revwalk::graph::IdMap<CommitState>, parents_buf: Vec<u8>, parent_ids: SmallVec<[(ObjectId, SecondsSinceUnixEpoch); 2]>, /// The list (FIFO) of thus far interesting commits. /// /// As they may turn hidden later, we have to keep them until the conditions are met to return them. /// If `None`, there is nothing to do with hidden commits. candidates: Option<Candidates>, } #[derive(Debug, Clone, Copy)] enum CommitState { /// The commit may be returned, it hasn't been hidden yet. Interesting, /// The commit should not be returned. Hidden, } impl CommitState { pub fn is_hidden(&self) -> bool { matches!(self, CommitState::Hidden) } pub fn is_interesting(&self) -> bool { matches!(self, CommitState::Interesting) } } /// mod init { use std::cmp::Reverse; use super::{ super::{simple::Sorting, Either, Info, ParentIds, Parents, Simple}, collect_parents, Candidates, CommitDateQueue, CommitState, CommitTimeOrder, Error, State, }; use gix_date::SecondsSinceUnixEpoch; use gix_hash::{oid, ObjectId}; use gix_hashtable::hash_map::Entry; use gix_object::{CommitRefIter, FindExt}; use std::collections::VecDeque; use Err as Oldest; use Ok as Newest; impl Default for State { fn default() -> Self { State { next: Default::default(), queue: gix_revwalk::PriorityQueue::new(), buf: vec![], seen: Default::default(), parents_buf: vec![], parent_ids: Default::default(), candidates: None, } } } impl State { fn clear(&mut self) { let Self { next, queue, buf, seen, parents_buf: _, parent_ids: _, candidates, } = self; next.clear(); queue.clear(); buf.clear(); seen.clear(); *candidates = None; } } fn to_queue_key(i: i64, order: CommitTimeOrder) -> super::QueueKey<i64> { match order { CommitTimeOrder::NewestFirst => Newest(i), CommitTimeOrder::OldestFirst => Oldest(Reverse(i)), } } /// Builder impl<Find, Predicate> Simple<Find, Predicate> where Find: gix_object::Find, { /// Set the `sorting` method. pub fn sorting(mut self, sorting: Sorting) -> Result<Self, Error> { self.sorting = sorting; match self.sorting { Sorting::BreadthFirst => { self.queue_to_vecdeque(); } Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => { let state = &mut self.state; for (commit_id, commit_state) in state.next.drain(..) { add_to_queue( commit_id, commit_state, order, sorting.cutoff_time(), &mut state.queue, &self.objects, &mut state.buf, )?; } } } Ok(self) } /// Change our commit parent handling mode to the given one. pub fn parents(mut self, mode: Parents) -> Self { self.parents = mode; if matches!(self.parents, Parents::First) { self.queue_to_vecdeque(); } self } /// Hide the given `tips`, along with all commits reachable by them so that they will not be returned /// by the traversal. /// /// Note that this will force the traversal into a non-intermediate mode and queue return candidates, /// to be released when it's clear that they truly are not hidden. /// /// Note that hidden objects are expected to exist. pub fn hide(mut self, tips: impl IntoIterator<Item = ObjectId>) -> Result<Self, Error> { self.state.candidates = Some(VecDeque::new()); let state = &mut self.state; for id_to_ignore in tips { let previous = state.seen.insert(id_to_ignore, CommitState::Hidden); // If there was something, it will pick up whatever commit-state we have set last // upon iteration. Also, hidden states always override everything else. if previous.is_none() { // Assure we *start* traversing hidden variants of a commit first, give them a head-start. match self.sorting { Sorting::BreadthFirst => { state.next.push_front((id_to_ignore, CommitState::Hidden)); } Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => { add_to_queue( id_to_ignore, CommitState::Hidden, order, self.sorting.cutoff_time(), &mut state.queue, &self.objects, &mut state.buf, )?; } } } } if !self .state .seen .values() .any(|state| matches!(state, CommitState::Hidden)) { self.state.candidates = None; } Ok(self) } /// Set the commitgraph as `cache` to greatly accelerate any traversal. /// /// The cache will be used if possible, but we will fall back without error to using the object /// database for commit lookup. If the cache is corrupt, we will fall back to the object database as well. pub fn commit_graph(mut self, cache: Option<gix_commitgraph::Graph>) -> Self { self.cache = cache; self } fn queue_to_vecdeque(&mut self) { let state = &mut self.state; state.next.extend( std::mem::replace(&mut state.queue, gix_revwalk::PriorityQueue::new()) .into_iter_unordered() .map(|(_time, id)| id), ); } } fn add_to_queue( commit_id: ObjectId, commit_state: CommitState, order: CommitTimeOrder, cutoff_time: Option<SecondsSinceUnixEpoch>, queue: &mut CommitDateQueue, objects: &impl gix_object::Find, buf: &mut Vec<u8>, ) -> Result<(), Error> { let commit_iter = objects.find_commit_iter(&commit_id, buf)?; let time = commit_iter.committer()?.seconds(); let key = to_queue_key(time, order); match (cutoff_time, order) { (Some(cutoff_time), _) if time >= cutoff_time => { queue.insert(key, (commit_id, commit_state)); } (Some(_), _) => {} (None, _) => { queue.insert(key, (commit_id, commit_state)); } } Ok(()) } /// Lifecycle impl<Find> Simple<Find, fn(&oid) -> bool> where Find: gix_object::Find, { /// Create a new instance. /// /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over commit tokens if the object is present and is a commit. Caching should be implemented within this function /// as needed. /// * `tips` /// * the starting points of the iteration, usually commits /// * each commit they lead to will only be returned once, including the tip that started it pub fn new(tips: impl IntoIterator<Item = impl Into<ObjectId>>, find: Find) -> Self { Self::filtered(tips, find, |_| true) } } /// Lifecycle impl<Find, Predicate> Simple<Find, Predicate> where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { /// Create a new instance with commit filtering enabled. /// /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over commit tokens if the object is present and is a commit. Caching should be implemented within this function /// as needed. /// * `tips` /// * the starting points of the iteration, usually commits /// * each commit they lead to will only be returned once, including the tip that started it /// * `predicate` - indicate whether a given commit should be included in the result as well /// as whether its parent commits should be traversed. pub fn filtered( tips: impl IntoIterator<Item = impl Into<ObjectId>>, find: Find, mut predicate: Predicate, ) -> Self { let tips = tips.into_iter(); let mut state = State::default(); { state.clear(); state.next.reserve(tips.size_hint().0); for tip in tips.map(Into::into) { let commit_state = CommitState::Interesting; let seen = state.seen.insert(tip, commit_state); // We know there can only be duplicate interesting ones. if seen.is_none() && predicate(&tip) { state.next.push_back((tip, commit_state)); } } } Self { objects: find, cache: None, predicate, state, parents: Default::default(), sorting: Default::default(), } } } /// Access impl<Find, Predicate> Simple<Find, Predicate> { /// Return an iterator for accessing data of the current commit, parsed lazily. pub fn commit_iter(&self) -> CommitRefIter<'_> { CommitRefIter::from_bytes(self.commit_data()) } /// Return the current commits' raw data, which can be parsed using [`gix_object::CommitRef::from_bytes()`]. pub fn commit_data(&self) -> &[u8] { &self.state.buf } } impl<Find, Predicate> Iterator for Simple<Find, Predicate> where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { type Item = Result<Info, Error>; fn next(&mut self) -> Option<Self::Item> { if matches!(self.parents, Parents::First) { self.next_by_topology() } else { match self.sorting { Sorting::BreadthFirst => self.next_by_topology(), Sorting::ByCommitTime(order) => self.next_by_commit_date(order, None), Sorting::ByCommitTimeCutoff { seconds, order } => self.next_by_commit_date(order, seconds.into()), } } .or_else(|| { self.state .candidates .as_mut() .and_then(|candidates| candidates.pop_front().map(Ok)) }) } } impl Sorting { /// If not topo sort, provide the cutoff date if present. fn cutoff_time(&self) -> Option<SecondsSinceUnixEpoch> { match self { Sorting::ByCommitTimeCutoff { seconds, .. } => Some(*seconds), _ => None, } } } /// Utilities impl<Find, Predicate> Simple<Find, Predicate> where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { fn next_by_commit_date( &mut self, order: CommitTimeOrder, cutoff: Option<SecondsSinceUnixEpoch>, ) -> Option<Result<Info, Error>> { let state = &mut self.state; let next = &mut state.queue; 'skip_hidden: loop { let (commit_time, (oid, _queued_commit_state)) = match next.pop()? { (Newest(t) | Oldest(Reverse(t)), o) => (t, o), }; let mut parents: ParentIds = Default::default(); // Always use the state that is actually stored, as we may change the type as we go. let commit_state = *state.seen.get(&oid).expect("every commit we traverse has state added"); if can_deplete_candidates_early( next.iter_unordered().map(|t| t.1), commit_state, state.candidates.as_ref(), ) { return None; } match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) { Ok(Either::CachedCommit(commit)) => { if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) { // drop corrupt caches and try again with ODB self.cache = None; return self.next_by_commit_date(order, cutoff); } for (id, parent_commit_time) in state.parent_ids.drain(..) { parents.push(id); insert_into_seen_and_queue( &mut state.seen, id, &mut state.candidates, commit_state, &mut self.predicate, next, order, cutoff, || parent_commit_time, ); } } Ok(Either::CommitRefIter(commit_iter)) => { for token in commit_iter { match token { Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue, Ok(gix_object::commit::ref_iter::Token::Parent { id }) => { parents.push(id); insert_into_seen_and_queue( &mut state.seen, id, &mut state.candidates, commit_state, &mut self.predicate, next, order, cutoff, || { let parent = self.objects.find_commit_iter(id.as_ref(), &mut state.parents_buf).ok(); parent .and_then(|parent| { parent.committer().ok().map(|committer| committer.seconds()) }) .unwrap_or_default() }, ); } Ok(_unused_token) => break, Err(err) => return Some(Err(err.into())), } } } Err(err) => return Some(Err(err.into())), } match commit_state { CommitState::Interesting => { let info = Info { id: oid,