Skip to main content

gix_worktree/stack/
mod.rs

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