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