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