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
12#[derive(Default, Debug, Clone, Copy)]
14pub enum Source {
15 IdMapping,
24 #[default]
26 WorktreeThenIdMappingIfNotSkipped,
27}
28
29impl Source {
30 pub fn adjust_for_bare(self, is_bare: bool) -> Self {
32 if is_bare {
33 Source::IdMapping
34 } else {
35 self
36 }
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(
59 overrides: IgnoreMatchGroup,
60 globals: IgnoreMatchGroup,
61 exclude_file_name_for_directories: Option<&BStr>,
62 source: Source,
63 ) -> Self {
64 Ignore {
65 overrides,
66 globals,
67 stack: Default::default(),
68 matched_directory_patterns_stack: Vec::with_capacity(6),
69 exclude_file_name_for_directories: exclude_file_name_for_directories
70 .map_or_else(|| ".gitignore".into(), ToOwned::to_owned),
71 source,
72 }
73 }
74}
75
76impl Ignore {
77 pub(crate) fn pop_directory(&mut self) {
78 self.matched_directory_patterns_stack.pop().expect("something to pop");
79 self.stack.patterns.pop().expect("something to pop");
80 }
81 pub(crate) fn match_groups(&self) -> [&IgnoreMatchGroup; 3] {
83 [&self.globals, &self.stack, &self.overrides]
84 }
85
86 pub(crate) fn matching_exclude_pattern(
87 &self,
88 relative_path: &BStr,
89 is_dir: Option<bool>,
90 case: Case,
91 ) -> Option<gix_ignore::search::Match<'_>> {
92 let groups = self.match_groups();
93 let mut dir_match = None;
94 if let Some((source, mapping)) = self
95 .matched_directory_patterns_stack
96 .iter()
97 .rev()
98 .filter_map(|v| *v)
99 .map(|(gidx, plidx, pidx)| {
100 let list = &groups[gidx].patterns[plidx];
101 (list.source.as_deref(), &list.patterns[pidx])
102 })
103 .next()
104 {
105 let match_ = gix_ignore::search::Match {
106 pattern: &mapping.pattern,
107 sequence_number: mapping.sequence_number,
108 kind: mapping.value,
109 source,
110 };
111 if mapping.pattern.is_negative() {
112 dir_match = Some(match_);
113 } else {
114 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 #[allow(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::new(std::io::ErrorKind::Other, err))?;
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("")));
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 )?;
204 stats.pattern_files += usize::from(added);
205 stats.tried_pattern_files += 1;
206 if !added {
207 match ignore_file_in_index {
208 Ok(idx) => {
209 let ignore_blob = objects
210 .find_blob(&id_mappings[idx].1, buf)
211 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
212 let ignore_path = gix_path::from_bstring(ignore_path_relative.into_owned());
213 self.stack
214 .add_patterns_buffer(ignore_blob.data, ignore_path, Some(Path::new("")));
215 stats.patterns_buffers += 1;
216 }
217 Err(_) => {
218 self.stack.patterns.push(Default::default());
220 }
221 }
222 }
223 }
224 }
225 Ok(())
226 }
227}