gix_worktree/stack/mod.rs
1#![allow(missing_docs)]
2use std::path::{Path, PathBuf};
3
4use bstr::{BStr, ByteSlice};
5
6use super::Stack;
7use crate::PathIdMapping;
8
9/// Various aggregate numbers collected from when the corresponding [`Stack`] was instantiated.
10#[derive(Default, Clone, Copy, Debug)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct Statistics {
13 /// The amount of platforms created to do further matching.
14 pub platforms: usize,
15 /// Information about the stack delegate.
16 pub delegate: delegate::Statistics,
17 /// Information about attributes
18 #[cfg(feature = "attributes")]
19 pub attributes: state::attributes::Statistics,
20 /// Information about the ignore stack
21 pub ignore: state::ignore::Statistics,
22}
23
24#[derive(Clone)]
25pub enum State {
26 /// Useful for checkout where directories need creation, but we need to access attributes as well.
27 #[cfg(feature = "attributes")]
28 CreateDirectoryAndAttributesStack {
29 /// If there is a symlink or a file in our path, try to unlink it before creating the directory.
30 unlink_on_collision: bool,
31 /// Options to control how newly created path components should be validated.
32 validate: gix_validate::path::component::Options,
33 /// State to handle attribute information
34 attributes: state::Attributes,
35 },
36 /// Used when adding files, requiring access to both attributes and ignore information, for example during add operations.
37 #[cfg(feature = "attributes")]
38 AttributesAndIgnoreStack {
39 /// State to handle attribute information
40 attributes: state::Attributes,
41 /// State to handle exclusion information
42 ignore: state::Ignore,
43 },
44 /// Used when only attributes are required, typically with fully virtual worktrees.
45 #[cfg(feature = "attributes")]
46 AttributesStack(state::Attributes),
47 /// Used when providing worktree status information.
48 IgnoreStack(state::Ignore),
49}
50
51#[must_use]
52pub struct Platform<'a> {
53 parent: &'a Stack,
54 is_dir: Option<bool>,
55}
56
57/// Initialization
58impl Stack {
59 /// Create a new instance with `worktree_root` being the base for all future paths we match.
60 /// `state` defines the capabilities of the cache.
61 /// The `case` configures attribute and exclusion case sensitivity at *query time*, which should match the case that
62 /// `state` might be configured with.
63 /// `buf` is used when reading files, and `id_mappings` should have been created with [`State::id_mappings_from_index()`].
64 pub fn new(
65 worktree_root: impl Into<PathBuf>,
66 state: State,
67 case: gix_glob::pattern::Case,
68 buf: Vec<u8>,
69 id_mappings: Vec<PathIdMapping>,
70 ) -> Self {
71 let root = worktree_root.into();
72 Stack {
73 stack: gix_fs::Stack::new(root),
74 state,
75 case,
76 buf,
77 id_mappings,
78 statistics: Statistics::default(),
79 }
80 }
81
82 /// Create a new stack that takes into consideration the `ignore_case` result of a filesystem probe in `root`. It takes a configured
83 /// `state` to control what it can do, while initializing attribute or ignore files that are to be queried from the ODB using
84 /// `index` and `path_backing`.
85 ///
86 /// This is the easiest way to correctly setup a stack.
87 pub fn from_state_and_ignore_case(
88 root: impl Into<PathBuf>,
89 ignore_case: bool,
90 state: State,
91 index: &gix_index::State,
92 path_backing: &gix_index::PathStorageRef,
93 ) -> Self {
94 let case = if ignore_case {
95 gix_glob::pattern::Case::Fold
96 } else {
97 gix_glob::pattern::Case::Sensitive
98 };
99 let attribute_files = state.id_mappings_from_index(index, path_backing, case);
100 Stack::new(root, state, case, Vec::with_capacity(512), attribute_files)
101 }
102}
103
104/// Entry points for attribute query
105impl Stack {
106 /// Append the `relative` path to the root directory of the cache and efficiently create leading directories, while assuring that no
107 /// symlinks are in that path.
108 /// Unless `mode` is known with `Some(gix_index::entry::Mode::DIR|COMMIT)`,
109 /// then `relative` points to a directory itself in which case the entire resulting path is created as directory.
110 /// If it's not known it is assumed to be a file.
111 /// `objects` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()], with mappnigs
112 ///
113 /// Provide access to cached information for that `relative` path via the returned platform.
114 pub fn at_path(
115 &mut self,
116 relative: impl AsRef<Path>,
117 mode: Option<gix_index::entry::Mode>,
118 objects: &dyn gix_object::Find,
119 ) -> std::io::Result<Platform<'_>> {
120 self.statistics.platforms += 1;
121 let mut delegate = StackDelegate {
122 state: &mut self.state,
123 buf: &mut self.buf,
124 mode,
125 id_mappings: &self.id_mappings,
126 objects,
127 case: self.case,
128 statistics: &mut self.statistics,
129 };
130 self.stack
131 .make_relative_path_current(relative.as_ref(), &mut delegate)?;
132 Ok(Platform {
133 parent: self,
134 is_dir: mode_is_dir(mode),
135 })
136 }
137
138 /// Obtain a platform for lookups from a repo-`relative` path, typically obtained from an index entry. `mode` should reflect
139 /// the kind of item set here, or left at `None` if unknown.
140 /// `objects` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()].
141 /// All effects are similar to [`at_path()`][Self::at_path()].
142 ///
143 /// If `relative` ends with `/` and `mode` is `None`, it is automatically assumed set to be a directory.
144 pub fn at_entry<'r>(
145 &mut self,
146 relative: impl Into<&'r BStr>,
147 mode: Option<gix_index::entry::Mode>,
148 objects: &dyn gix_object::Find,
149 ) -> std::io::Result<Platform<'_>> {
150 let relative = relative.into();
151 let relative_path = gix_path::try_from_bstr(relative).map_err(|_err| {
152 std::io::Error::new(
153 std::io::ErrorKind::Other,
154 format!("The path \"{relative}\" contained invalid UTF-8 and could not be turned into a path"),
155 )
156 })?;
157
158 self.at_path(
159 relative_path,
160 mode.or_else(|| relative.ends_with_str("/").then_some(gix_index::entry::Mode::DIR)),
161 objects,
162 )
163 }
164}
165
166fn mode_is_dir(mode: Option<gix_index::entry::Mode>) -> Option<bool> {
167 mode.map(|m|
168 // This applies to directories and commits (submodules are directories on disk)
169 m.is_sparse() || m.is_submodule())
170}
171
172/// Mutation
173impl Stack {
174 /// Reset the statistics after returning them.
175 pub fn take_statistics(&mut self) -> Statistics {
176 std::mem::take(&mut self.statistics)
177 }
178
179 /// Return our state for applying changes.
180 pub fn state_mut(&mut self) -> &mut State {
181 &mut self.state
182 }
183
184 /// Change the `case` of the next match to the given one.
185 pub fn set_case(&mut self, case: gix_glob::pattern::Case) -> &mut Self {
186 self.case = case;
187 self
188 }
189}
190
191/// Access
192impl Stack {
193 /// Return the statistics we gathered thus far.
194 pub fn statistics(&self) -> &Statistics {
195 &self.statistics
196 }
197 /// Return the state for introspection.
198 pub fn state(&self) -> &State {
199 &self.state
200 }
201
202 /// Return the base path against which all entries or paths should be relative to when querying.
203 ///
204 /// Note that this path _may_ not be canonicalized.
205 pub fn base(&self) -> &Path {
206 self.stack.root()
207 }
208}
209
210///
211pub mod delegate;
212use delegate::StackDelegate;
213
214mod platform;
215///
216pub mod state;