gix_worktree/stack/
delegate.rs

1use crate::{stack::State, PathIdMapping};
2
3/// Various aggregate numbers related to the stack delegate itself.
4#[derive(Default, Clone, Copy, Debug)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Statistics {
7    /// The amount of `std::fs::create_dir` calls.
8    ///
9    /// This only happens if we are in the respective mode to create leading directories efficiently.
10    pub num_mkdir_calls: usize,
11    /// Amount of calls to push a path element.
12    pub push_element: usize,
13    /// Amount of calls to push a directory.
14    pub push_directory: usize,
15    /// Amount of calls to pop a directory.
16    pub pop_directory: usize,
17}
18
19pub(crate) struct StackDelegate<'a, 'find> {
20    pub state: &'a mut State,
21    pub buf: &'a mut Vec<u8>,
22    #[cfg_attr(not(feature = "attributes"), allow(dead_code))]
23    pub mode: Option<gix_index::entry::Mode>,
24    pub id_mappings: &'a Vec<PathIdMapping>,
25    pub objects: &'find dyn gix_object::Find,
26    pub case: gix_glob::pattern::Case,
27    pub statistics: &'a mut super::Statistics,
28}
29
30impl gix_fs::stack::Delegate for StackDelegate<'_, '_> {
31    fn push_directory(&mut self, stack: &gix_fs::Stack) -> std::io::Result<()> {
32        self.statistics.delegate.push_directory += 1;
33        let rela_dir_bstr = gix_path::into_bstr(stack.current_relative());
34        let rela_dir = gix_path::to_unix_separators_on_windows(rela_dir_bstr);
35        match &mut self.state {
36            #[cfg(feature = "attributes")]
37            State::CreateDirectoryAndAttributesStack { attributes, .. } | State::AttributesStack(attributes) => {
38                attributes.push_directory(
39                    stack.root(),
40                    stack.current(),
41                    &rela_dir,
42                    self.buf,
43                    self.id_mappings,
44                    self.objects,
45                    &mut self.statistics.attributes,
46                )?;
47            }
48            #[cfg(feature = "attributes")]
49            State::AttributesAndIgnoreStack { ignore, attributes } => {
50                attributes.push_directory(
51                    stack.root(),
52                    stack.current(),
53                    &rela_dir,
54                    self.buf,
55                    self.id_mappings,
56                    self.objects,
57                    &mut self.statistics.attributes,
58                )?;
59                ignore.push_directory(
60                    stack.root(),
61                    stack.current(),
62                    &rela_dir,
63                    self.buf,
64                    self.id_mappings,
65                    self.objects,
66                    self.case,
67                    &mut self.statistics.ignore,
68                )?;
69            }
70            State::IgnoreStack(ignore) => ignore.push_directory(
71                stack.root(),
72                stack.current(),
73                &rela_dir,
74                self.buf,
75                self.id_mappings,
76                self.objects,
77                self.case,
78                &mut self.statistics.ignore,
79            )?,
80        }
81        Ok(())
82    }
83
84    #[cfg_attr(not(feature = "attributes"), allow(unused_variables))]
85    fn push(&mut self, is_last_component: bool, stack: &gix_fs::Stack) -> std::io::Result<()> {
86        self.statistics.delegate.push_element += 1;
87        match &mut self.state {
88            #[cfg(feature = "attributes")]
89            State::CreateDirectoryAndAttributesStack {
90                unlink_on_collision,
91                validate,
92                attributes: _,
93            } => {
94                validate_last_component(stack, self.mode, *validate)?;
95                create_leading_directory(
96                    is_last_component,
97                    stack,
98                    self.mode,
99                    &mut self.statistics.delegate.num_mkdir_calls,
100                    *unlink_on_collision,
101                )?;
102            }
103            #[cfg(feature = "attributes")]
104            State::AttributesAndIgnoreStack { .. } | State::AttributesStack(_) => {}
105            State::IgnoreStack(_) => {}
106        }
107        Ok(())
108    }
109
110    fn pop_directory(&mut self) {
111        self.statistics.delegate.pop_directory += 1;
112        match &mut self.state {
113            #[cfg(feature = "attributes")]
114            State::CreateDirectoryAndAttributesStack { attributes, .. } | State::AttributesStack(attributes) => {
115                attributes.pop_directory();
116            }
117            #[cfg(feature = "attributes")]
118            State::AttributesAndIgnoreStack { attributes, ignore } => {
119                attributes.pop_directory();
120                ignore.pop_directory();
121            }
122            State::IgnoreStack(ignore) => {
123                ignore.pop_directory();
124            }
125        }
126    }
127}
128
129#[cfg(feature = "attributes")]
130fn validate_last_component(
131    stack: &gix_fs::Stack,
132    mode: Option<gix_index::entry::Mode>,
133    opts: gix_validate::path::component::Options,
134) -> std::io::Result<()> {
135    let Some(last_component) = stack.current_relative().components().next_back() else {
136        return Ok(());
137    };
138    let last_component = gix_path::try_into_bstr(std::borrow::Cow::Borrowed(last_component.as_os_str().as_ref()))
139        .map_err(|_err| {
140            std::io::Error::other(format!(
141                "Path component {last_component:?} of path \"{}\" contained invalid UTF-8 and could not be validated",
142                stack.current_relative().display()
143            ))
144        })?;
145
146    if let Err(err) = gix_validate::path::component(
147        last_component.as_ref(),
148        mode.and_then(|m| {
149            (m == gix_index::entry::Mode::SYMLINK).then_some(gix_validate::path::component::Mode::Symlink)
150        }),
151        opts,
152    ) {
153        return Err(std::io::Error::other(err));
154    }
155    Ok(())
156}
157
158#[cfg(feature = "attributes")]
159fn create_leading_directory(
160    is_last_component: bool,
161    stack: &gix_fs::Stack,
162    mode: Option<gix_index::entry::Mode>,
163    mkdir_calls: &mut usize,
164    unlink_on_collision: bool,
165) -> std::io::Result<()> {
166    if is_last_component && !crate::stack::mode_is_dir(mode).unwrap_or(false) {
167        return Ok(());
168    }
169    *mkdir_calls += 1;
170    match std::fs::create_dir(stack.current()) {
171        Ok(()) => Ok(()),
172        Err(err) if err.kind() == std::io::ErrorKind::AlreadyExists => {
173            let meta = stack.current().symlink_metadata()?;
174            if meta.is_dir() {
175                Ok(())
176            } else if unlink_on_collision {
177                if meta.file_type().is_symlink() {
178                    gix_fs::symlink::remove(stack.current())?;
179                } else {
180                    std::fs::remove_file(stack.current())?;
181                }
182                *mkdir_calls += 1;
183                std::fs::create_dir(stack.current())
184            } else {
185                Err(err)
186            }
187        }
188        Err(err) => Err(err),
189    }
190}