git_traverse/tree/breadthfirst.rs
1use std::collections::VecDeque;
2
3use git_hash::ObjectId;
4
5/// The error is part of the item returned by the [`traverse()`][impl_::traverse()] function.
6#[derive(Debug, thiserror::Error)]
7#[allow(missing_docs)]
8pub enum Error {
9 #[error("The tree {oid} could not be found")]
10 NotFound { oid: ObjectId },
11 #[error("The delegate cancelled the operation")]
12 Cancelled,
13 #[error(transparent)]
14 ObjectDecode(#[from] git_object::decode::Error),
15}
16
17/// The state used and potentially shared by multiple tree traversals.
18#[derive(Default, Clone)]
19pub struct State {
20 next: VecDeque<ObjectId>,
21 buf: Vec<u8>,
22}
23
24impl State {
25 fn clear(&mut self) {
26 self.next.clear();
27 self.buf.clear();
28 }
29}
30
31pub(crate) mod impl_ {
32 use std::borrow::BorrowMut;
33
34 use git_hash::oid;
35 use git_object::{tree::EntryMode, TreeRefIter};
36
37 use super::{Error, State};
38 use crate::tree::Visit;
39
40 /// Start a breadth-first iteration over the `root` trees entries.
41 ///
42 /// * `root`
43 /// * the tree to iterate in a nested fashion.
44 /// * `state` - all state used for the iteration. If multiple iterations are performed, allocations can be minimized by reusing
45 /// this state.
46 /// * `find` - a way to lookup new object data during traversal by their ObjectId, writing their data into buffer and returning
47 /// an iterator over entries if the object is present and is a tree. Caching should be implemented within this function
48 /// as needed. The return value is `Option<TreeIter>` which degenerates all error information. Not finding a commit should also
49 /// be considered an errors as all objects in the tree DAG should be present in the database. Hence [`Error::NotFound`] should
50 /// be escalated into a more specific error if its encountered by the caller.
51 /// * `delegate` - A way to observe entries and control the iteration while allowing the optimizer to let you pay only for what you use.
52 pub fn traverse<StateMut, Find, V>(
53 root: TreeRefIter<'_>,
54 mut state: StateMut,
55 mut find: Find,
56 delegate: &mut V,
57 ) -> Result<(), Error>
58 where
59 Find: for<'a> FnMut(&oid, &'a mut Vec<u8>) -> Option<TreeRefIter<'a>>,
60 StateMut: BorrowMut<State>,
61 V: Visit,
62 {
63 let state = state.borrow_mut();
64 state.clear();
65 let mut tree = root;
66 loop {
67 for entry in tree {
68 let entry = entry?;
69 match entry.mode {
70 EntryMode::Tree => {
71 use crate::tree::visit::Action::*;
72 delegate.push_path_component(entry.filename);
73 let action = delegate.visit_tree(&entry);
74 match action {
75 Skip => {}
76 Continue => {
77 delegate.pop_path_component();
78 delegate.push_back_tracked_path_component(entry.filename);
79 state.next.push_back(entry.oid.to_owned())
80 }
81 Cancel => {
82 return Err(Error::Cancelled);
83 }
84 }
85 }
86 _non_tree => {
87 delegate.push_path_component(entry.filename);
88 if delegate.visit_nontree(&entry).cancelled() {
89 return Err(Error::Cancelled);
90 }
91 }
92 }
93 delegate.pop_path_component();
94 }
95 match state.next.pop_front() {
96 Some(oid) => {
97 delegate.pop_front_tracked_path_and_set_current();
98 match find(&oid, &mut state.buf) {
99 Some(tree_iter) => tree = tree_iter,
100 None => return Err(Error::NotFound { oid: oid.to_owned() }),
101 }
102 }
103 None => break Ok(()),
104 }
105 }
106 }
107}