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 if let Some((source, mapping)) = 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 .next()
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 = Some(match_);
116 } else {
117 return match_.into();
124 }
125 }
126 groups
127 .iter()
128 .rev()
129 .find_map(|group| group.pattern_matching_relative_path(relative_path, is_dir, case))
130 .or(dir_match)
131 }
132
133 pub(crate) fn matching_exclude_pattern_no_dir(
136 &self,
137 relative_path: &BStr,
138 is_dir: Option<bool>,
139 case: Case,
140 ) -> Option<(usize, usize, usize)> {
141 let groups = self.match_groups();
142 groups.iter().enumerate().rev().find_map(|(gidx, group)| {
143 let basename_pos = relative_path.rfind(b"/").map(|p| p + 1);
144 group
145 .patterns
146 .iter()
147 .enumerate()
148 .rev()
149 .find_map(|(plidx, pl)| {
150 gix_ignore::search::pattern_idx_matching_relative_path(
151 pl,
152 relative_path,
153 basename_pos,
154 is_dir,
155 case,
156 )
157 .map(|idx| (plidx, idx))
158 })
159 .map(|(plidx, pidx)| (gidx, plidx, pidx))
160 })
161 }
162
163 #[allow(clippy::too_many_arguments)]
164 pub(crate) fn push_directory(
165 &mut self,
166 root: &Path,
167 dir: &Path,
168 rela_dir: &BStr,
169 buf: &mut Vec<u8>,
170 id_mappings: &[PathIdMapping],
171 objects: &dyn gix_object::Find,
172 case: Case,
173 stats: &mut Statistics,
174 ) -> std::io::Result<()> {
175 self.matched_directory_patterns_stack
176 .push(self.matching_exclude_pattern_no_dir(rela_dir, Some(true), case));
177
178 let ignore_path_relative = gix_path::join_bstr_unix_pathsep(rela_dir, ".gitignore");
179 let ignore_file_in_index = id_mappings.binary_search_by(|t| t.0.as_bstr().cmp(ignore_path_relative.as_ref()));
180 match self.source {
181 Source::IdMapping => {
182 match ignore_file_in_index {
183 Ok(idx) => {
184 let ignore_blob = objects
185 .find_blob(&id_mappings[idx].1, buf)
186 .map_err(std::io::Error::other)?;
187 let ignore_path = gix_path::from_bstring(ignore_path_relative.into_owned());
188 self.stack
189 .add_patterns_buffer(ignore_blob.data, ignore_path, Some(Path::new("")), self.parse);
190 stats.patterns_buffers += 1;
191 }
192 Err(_) => {
193 self.stack.patterns.push(Default::default());
195 }
196 }
197 }
198 Source::WorktreeThenIdMappingIfNotSkipped => {
199 let follow_symlinks = ignore_file_in_index.is_err();
200 let added = gix_glob::search::add_patterns_file(
201 &mut self.stack.patterns,
202 dir.join(".gitignore"),
203 follow_symlinks,
204 Some(root),
205 buf,
206 self.parse,
207 )?;
208 stats.pattern_files += usize::from(added);
209 stats.tried_pattern_files += 1;
210 if !added {
211 match ignore_file_in_index {
212 Ok(idx) => {
213 let ignore_blob = objects
214 .find_blob(&id_mappings[idx].1, buf)
215 .map_err(std::io::Error::other)?;
216 let ignore_path = gix_path::from_bstring(ignore_path_relative.into_owned());
217 self.stack.add_patterns_buffer(
218 ignore_blob.data,
219 ignore_path,
220 Some(Path::new("")),
221 self.parse,
222 );
223 stats.patterns_buffers += 1;
224 }
225 Err(_) => {
226 self.stack.patterns.push(Default::default());
228 }
229 }
230 }
231 }
232 }
233 Ok(())
234 }
235}