gix_worktree/stack/state/
ignore.rs1use std::path::Path;
2
3use bstr::{BStr, ByteSlice};
4use gix_glob::pattern::Case;
5use gix_object::FindExt;
6
7use crate::{
8 PathIdMapping,
9 stack::state::{Ignore, IgnoreMatchGroup},
10};
11
12pub use gix_ignore::search::Ignore as ParseIgnore;
14
15#[derive(Default, Debug, Clone, Copy)]
17pub enum Source {
18 IdMapping,
27 #[default]
30 WorktreeThenIdMappingIfNotSkipped,
31}
32
33impl Source {
34 pub fn adjust_for_bare(self, is_bare: bool) -> Self {
36 if is_bare { Source::IdMapping } else { self }
37 }
38}
39
40#[derive(Default, Clone, Copy, Debug)]
42#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
43pub struct Statistics {
44 pub patterns_buffers: usize,
46 pub pattern_files: usize,
48 pub tried_pattern_files: usize,
50}
51
52impl Ignore {
53 pub fn new(
61 overrides: IgnoreMatchGroup,
62 globals: IgnoreMatchGroup,
63 exclude_file_name_for_directories: Option<&BStr>,
64 source: Source,
65 parse: gix_ignore::search::Ignore,
66 ) -> Self {
67 Ignore {
68 overrides,
69 globals,
70 stack: Default::default(),
71 matched_directory_patterns_stack: Vec::with_capacity(6),
72 exclude_file_name_for_directories: exclude_file_name_for_directories
73 .map_or_else(|| ".gitignore".into(), ToOwned::to_owned),
74 source,
75 parse,
76 }
77 }
78}
79
80impl Ignore {
81 pub(crate) fn pop_directory(&mut self) {
82 self.matched_directory_patterns_stack.pop().expect("something to pop");
83 self.stack.patterns.pop().expect("something to pop");
84 }
85 pub(crate) fn match_groups(&self) -> [&IgnoreMatchGroup; 3] {
87 [&self.globals, &self.stack, &self.overrides]
88 }
89
90 pub(crate) fn matching_exclude_pattern(
91 &self,
92 relative_path: &BStr,
93 is_dir: Option<bool>,
94 case: Case,
95 ) -> Option<gix_ignore::search::Match<'_>> {
96 let groups = self.match_groups();
97 let mut dir_match = None;
98 for (source, mapping) in self
99 .matched_directory_patterns_stack
100 .iter()
101 .rev()
102 .filter_map(|v| *v)
103 .map(|(gidx, plidx, pidx)| {
104 let list = &groups[gidx].patterns[plidx];
105 (list.source.as_deref(), &list.patterns[pidx])
106 })
107 {
108 let match_ = gix_ignore::search::Match {
109 pattern: &mapping.pattern,
110 sequence_number: mapping.sequence_number,
111 kind: mapping.value,
112 source,
113 };
114 if mapping.pattern.is_negative() {
115 dir_match.get_or_insert(match_);
116 } else {
117 return match_.into();
122 }
123 }
124 groups
125 .iter()
126 .rev()
127 .find_map(|group| group.pattern_matching_relative_path(relative_path, is_dir, case))
128 .or(dir_match)
129 }
130
131 pub(crate) fn matching_exclude_pattern_no_dir(
134 &self,
135 relative_path: &BStr,
136 is_dir: Option<bool>,
137 case: Case,
138 ) -> Option<(usize, usize, usize)> {
139 let groups = self.match_groups();
140 groups.iter().enumerate().rev().find_map(|(gidx, group)| {
141 let basename_pos = relative_path.rfind(b"/").map(|p| p + 1);
142 group
143 .patterns
144 .iter()
145 .enumerate()
146 .rev()
147 .find_map(|(plidx, pl)| {
148 gix_ignore::search::pattern_idx_matching_relative_path(
149 pl,
150 relative_path,
151 basename_pos,
152 is_dir,
153 case,
154 )
155 .map(|idx| (plidx, idx))
156 })
157 .map(|(plidx, pidx)| (gidx, plidx, pidx))
158 })
159 }
160
161 #[expect(clippy::too_many_arguments)]
162 pub(crate) fn push_directory(
163 &mut self,
164 root: &Path,
165 dir: &Path,
166 rela_dir: &BStr,
167 buf: &mut Vec<u8>,
168 id_mappings: &[PathIdMapping],
169 objects: &dyn gix_object::Find,
170 case: Case,
171 stats: &mut Statistics,
172 ) -> std::io::Result<()> {
173 self.matched_directory_patterns_stack
174 .push(self.matching_exclude_pattern_no_dir(rela_dir, Some(true), case));
175
176 let ignore_path_relative = gix_path::join_bstr_unix_pathsep(rela_dir, ".gitignore");
177 let ignore_file_in_index = id_mappings.binary_search_by(|t| t.0.as_bstr().cmp(ignore_path_relative.as_ref()));
178 match self.source {
179 Source::IdMapping => {
180 match ignore_file_in_index {
181 Ok(idx) => {
182 let ignore_blob = objects
183 .find_blob(&id_mappings[idx].1, buf)
184 .map_err(std::io::Error::other)?;
185 let ignore_path =
186 gix_path::from_bstring(ignore_path_relative.into_owned()).map_err(std::io::Error::other)?;
187 self.stack.add_patterns_buffer(
188 ignore_blob.data,
189 ignore_path,
190 Some(Path::new("")),
191 self.parse,
192 )?;
193 stats.patterns_buffers += 1;
194 }
195 Err(_) => {
196 self.stack.patterns.push(Default::default());
198 }
199 }
200 }
201 Source::WorktreeThenIdMappingIfNotSkipped => {
202 let added = gix_glob::search::add_patterns_file(
203 &mut self.stack.patterns,
204 dir.join(".gitignore"),
205 false,
206 Some(root),
207 buf,
208 self.parse,
209 )?;
210 stats.pattern_files += usize::from(added);
211 stats.tried_pattern_files += 1;
212 if !added {
213 match ignore_file_in_index {
214 Ok(idx) => {
215 let ignore_blob = objects
216 .find_blob(&id_mappings[idx].1, buf)
217 .map_err(std::io::Error::other)?;
218 let ignore_path = gix_path::from_bstring(ignore_path_relative.into_owned())
219 .map_err(std::io::Error::other)?;
220 self.stack.add_patterns_buffer(
221 ignore_blob.data,
222 ignore_path,
223 Some(Path::new("")),
224 self.parse,
225 )?;
226 stats.patterns_buffers += 1;
227 }
228 Err(_) => {
229 self.stack.patterns.push(Default::default());
231 }
232 }
233 }
234 }
235 }
236 Ok(())
237 }
238}