Skip to main content

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            reject_terminal_symlinks: false,
76            case,
77            buf,
78            id_mappings,
79            statistics: Statistics::default(),
80        }
81    }
82
83    /// Enable Windows-only no-follow validation of terminal non-directory entries for checkout stacks.
84    ///
85    /// Call this before the first [`at_path()`](Self::at_path()) or [`at_entry()`](Self::at_entry()) invocation.
86    pub fn enable_terminal_symlink_check(&mut self) {
87        self.reject_terminal_symlinks = true;
88    }
89
90    /// Create a new stack that takes into consideration the `ignore_case` result of a filesystem probe in `root`. It takes a configured
91    /// `state` to control what it can do, while initializing attribute or ignore files that are to be queried from the ODB using
92    /// `index` and `path_backing`.
93    ///
94    /// This is the easiest way to correctly setup a stack.
95    pub fn from_state_and_ignore_case(
96        root: impl Into<PathBuf>,
97        ignore_case: bool,
98        state: State,
99        index: &gix_index::State,
100        path_backing: &gix_index::PathStorageRef,
101    ) -> Self {
102        let case = if ignore_case {
103            gix_glob::pattern::Case::Fold
104        } else {
105            gix_glob::pattern::Case::Sensitive
106        };
107        let attribute_files = state.id_mappings_from_index(index, path_backing, case);
108        Stack::new(root, state, case, Vec::with_capacity(512), attribute_files)
109    }
110}
111
112/// Entry points for attribute query
113impl Stack {
114    /// Append the `relative` path to the root directory of the cache and efficiently create leading directories, while assuring that no
115    /// symlinks are in that path.
116    /// Unless `mode` is known with `Some(gix_index::entry::Mode::DIR|COMMIT)`,
117    /// then `relative` points to a directory itself in which case the entire resulting path is created as directory.
118    /// If it's not known it is assumed to be a file.
119    /// `objects` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()], with mappnigs
120    ///
121    /// Provide access to cached information for that `relative` path via the returned platform.
122    pub fn at_path(
123        &mut self,
124        relative: impl ToNormalPathComponents,
125        mode: Option<gix_index::entry::Mode>,
126        objects: &dyn gix_object::Find,
127    ) -> std::io::Result<Platform<'_>> {
128        self.statistics.platforms += 1;
129        let mut delegate = StackDelegate {
130            state: &mut self.state,
131            buf: &mut self.buf,
132            mode,
133            id_mappings: &self.id_mappings,
134            objects,
135            case: self.case,
136            reject_temrinal_symlinks: self.reject_terminal_symlinks,
137            statistics: &mut self.statistics,
138        };
139        self.stack.make_relative_path_current(relative, &mut delegate)?;
140        Ok(Platform {
141            parent: self,
142            is_dir: mode_is_dir(mode),
143        })
144    }
145
146    /// Obtain a platform for lookups from a repo-`relative` path, typically obtained from an index entry. `mode` should reflect
147    /// the kind of item set here, or left at `None` if unknown.
148    /// `objects` maybe used to lookup objects from an [id mapping][crate::stack::State::id_mappings_from_index()].
149    /// All effects are similar to [`at_path()`][Self::at_path()].
150    ///
151    /// If `relative` ends with `/` and `mode` is `None`, it is automatically assumed set to be a directory.
152    pub fn at_entry<'r>(
153        &mut self,
154        relative: impl Into<&'r BStr>,
155        mode: Option<gix_index::entry::Mode>,
156        objects: &dyn gix_object::Find,
157    ) -> std::io::Result<Platform<'_>> {
158        let relative = relative.into();
159        self.at_path(
160            relative,
161            mode.or_else(|| relative.ends_with_str("/").then_some(gix_index::entry::Mode::DIR)),
162            objects,
163        )
164    }
165}
166
167fn mode_is_dir(mode: Option<gix_index::entry::Mode>) -> Option<bool> {
168    mode.map(|m|
169        // This applies to directories and commits (submodules are directories on disk)
170        m.is_sparse() || m.is_submodule())
171}
172
173/// Mutation
174impl Stack {
175    /// Reset the statistics after returning them.
176    pub fn take_statistics(&mut self) -> Statistics {
177        std::mem::take(&mut self.statistics)
178    }
179
180    /// Return our state for applying changes.
181    pub fn state_mut(&mut self) -> &mut State {
182        &mut self.state
183    }
184
185    /// Change the `case` of the next match to the given one.
186    pub fn set_case(&mut self, case: gix_glob::pattern::Case) -> &mut Self {
187        self.case = case;
188        self
189    }
190}
191
192/// Access
193impl Stack {
194    /// Return the statistics we gathered thus far.
195    pub fn statistics(&self) -> &Statistics {
196        &self.statistics
197    }
198    /// Return the state for introspection.
199    pub fn state(&self) -> &State {
200        &self.state
201    }
202
203    /// Return the base path against which all entries or paths should be relative to when querying.
204    ///
205    /// Note that this path _may_ not be canonicalized.
206    pub fn base(&self) -> &Path {
207        self.stack.root()
208    }
209}
210
211///
212pub mod delegate;
213use delegate::StackDelegate;
214use gix_fs::stack::ToNormalPathComponents;
215
216mod platform;
217///
218pub mod state;