gix_traverse/tree/
breadthfirst.rs

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