gix_index/
init.rs

1///
2pub mod from_tree {
3    use std::collections::VecDeque;
4
5    use bstr::{BStr, BString, ByteSlice, ByteVec};
6    use gix_object::{tree, tree::EntryKind};
7    use gix_traverse::tree::{depthfirst, visit::Action, Visit};
8
9    use crate::{
10        entry::{Flags, Mode, Stat},
11        Entry, PathStorage, State, Version,
12    };
13
14    /// The error returned by [State::from_tree()].
15    #[derive(Debug, thiserror::Error)]
16    #[allow(missing_docs)]
17    pub enum Error {
18        #[error("The path \"{path}\" is invalid")]
19        InvalidComponent {
20            path: BString,
21            source: gix_validate::path::component::Error,
22        },
23        #[error(transparent)]
24        Traversal(#[from] gix_traverse::tree::depthfirst::Error),
25    }
26
27    /// Initialization
28    impl State {
29        /// Return a new and empty in-memory index assuming the given `object_hash`.
30        pub fn new(object_hash: gix_hash::Kind) -> Self {
31            State {
32                object_hash,
33                timestamp: filetime::FileTime::now(),
34                version: Version::V2,
35                entries: vec![],
36                path_backing: vec![],
37                is_sparse: false,
38                tree: None,
39                link: None,
40                resolve_undo: None,
41                untracked: None,
42                fs_monitor: None,
43                offset_table_at_decode_time: false,
44                end_of_index_at_decode_time: false,
45            }
46        }
47        /// Create an index [`State`] by traversing `tree` recursively, accessing sub-trees
48        /// with `objects`.
49        /// `validate` is used to determine which validations to perform on every path component we see.
50        ///
51        /// **No extension data is currently produced**.
52        pub fn from_tree<Find>(
53            tree: &gix_hash::oid,
54            objects: Find,
55            validate: gix_validate::path::component::Options,
56        ) -> Result<Self, Error>
57        where
58            Find: gix_object::Find,
59        {
60            let _span = gix_features::trace::coarse!("gix_index::State::from_tree()");
61            let mut delegate = CollectEntries::new(validate);
62            match depthfirst(tree.to_owned(), depthfirst::State::default(), &objects, &mut delegate) {
63                Ok(()) => {}
64                Err(gix_traverse::tree::breadthfirst::Error::Cancelled) => {
65                    let (path, err) = delegate
66                        .invalid_path
67                        .take()
68                        .expect("cancellation only happens on validation error");
69                    return Err(Error::InvalidComponent { path, source: err });
70                }
71                Err(err) => return Err(err.into()),
72            }
73
74            let CollectEntries {
75                entries,
76                path_backing,
77                path: _,
78                path_deque: _,
79                validate: _,
80                invalid_path,
81            } = delegate;
82
83            if let Some((path, err)) = invalid_path {
84                return Err(Error::InvalidComponent { path, source: err });
85            }
86
87            Ok(State {
88                object_hash: tree.kind(),
89                timestamp: filetime::FileTime::now(),
90                version: Version::V2,
91                entries,
92                path_backing,
93                is_sparse: false,
94                tree: None,
95                link: None,
96                resolve_undo: None,
97                untracked: None,
98                fs_monitor: None,
99                offset_table_at_decode_time: false,
100                end_of_index_at_decode_time: false,
101            })
102        }
103    }
104
105    struct CollectEntries {
106        entries: Vec<Entry>,
107        path_backing: PathStorage,
108        path: BString,
109        path_deque: VecDeque<BString>,
110        validate: gix_validate::path::component::Options,
111        invalid_path: Option<(BString, gix_validate::path::component::Error)>,
112    }
113
114    impl CollectEntries {
115        pub fn new(validate: gix_validate::path::component::Options) -> CollectEntries {
116            CollectEntries {
117                entries: Vec::new(),
118                path_backing: Vec::new(),
119                path: BString::default(),
120                path_deque: VecDeque::new(),
121                validate,
122                invalid_path: None,
123            }
124        }
125
126        fn push_element(&mut self, name: &BStr) {
127            if name.is_empty() {
128                return;
129            }
130            if !self.path.is_empty() {
131                self.path.push(b'/');
132            }
133            self.path.push_str(name);
134            if self.invalid_path.is_none() {
135                if let Err(err) = gix_validate::path::component(name, None, self.validate) {
136                    self.invalid_path = Some((self.path.clone(), err));
137                }
138            }
139        }
140
141        pub fn add_entry(&mut self, entry: &tree::EntryRef<'_>) {
142            let mode = match entry.mode.kind() {
143                EntryKind::Tree => unreachable!("visit_non_tree() called us"),
144                EntryKind::Blob => Mode::FILE,
145                EntryKind::BlobExecutable => Mode::FILE_EXECUTABLE,
146                EntryKind::Link => Mode::SYMLINK,
147                EntryKind::Commit => Mode::COMMIT,
148            };
149            // There are leaf-names that require special validation, specific to their mode.
150            // Double-validate just for this case, as the previous validation didn't know the mode yet.
151            if self.invalid_path.is_none() {
152                let start = self.path.rfind_byte(b'/').map(|pos| pos + 1).unwrap_or_default();
153                if let Err(err) = gix_validate::path::component(
154                    self.path[start..].as_ref(),
155                    (entry.mode.kind() == EntryKind::Link).then_some(gix_validate::path::component::Mode::Symlink),
156                    self.validate,
157                ) {
158                    self.invalid_path = Some((self.path.clone(), err));
159                }
160            }
161
162            let path_start = self.path_backing.len();
163            self.path_backing.extend_from_slice(&self.path);
164
165            let new_entry = Entry {
166                stat: Stat::default(),
167                id: entry.oid.into(),
168                flags: Flags::empty(),
169                mode,
170                path: path_start..self.path_backing.len(),
171            };
172
173            self.entries.push(new_entry);
174        }
175
176        fn determine_action(&self) -> Action {
177            if self.invalid_path.is_none() {
178                Action::Continue
179            } else {
180                Action::Cancel
181            }
182        }
183    }
184
185    impl Visit for CollectEntries {
186        fn pop_back_tracked_path_and_set_current(&mut self) {
187            self.path = self.path_deque.pop_back().unwrap_or_default();
188        }
189
190        fn pop_front_tracked_path_and_set_current(&mut self) {
191            self.path = self
192                .path_deque
193                .pop_front()
194                .expect("every call is matched with push_tracked_path_component");
195        }
196
197        fn push_back_tracked_path_component(&mut self, component: &BStr) {
198            self.push_element(component);
199            self.path_deque.push_back(self.path.clone());
200        }
201
202        fn push_path_component(&mut self, component: &BStr) {
203            self.push_element(component);
204        }
205
206        fn pop_path_component(&mut self) {
207            if let Some(pos) = self.path.rfind_byte(b'/') {
208                self.path.resize(pos, 0);
209            } else {
210                self.path.clear();
211            }
212        }
213
214        fn visit_tree(&mut self, _entry: &gix_object::tree::EntryRef<'_>) -> Action {
215            self.determine_action()
216        }
217
218        fn visit_nontree(&mut self, entry: &gix_object::tree::EntryRef<'_>) -> Action {
219            self.add_entry(entry);
220            self.determine_action()
221        }
222    }
223}