gix_worktree/stack/
delegate.rs1use crate::{stack::State, PathIdMapping};
2
3#[derive(Default, Clone, Copy, Debug)]
5#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
6pub struct Statistics {
7 pub num_mkdir_calls: usize,
11 pub push_element: usize,
13 pub push_directory: usize,
15 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}