gix_pathspec/search/
matching.rs1use bstr::{BStr, BString, ByteSlice};
2use gix_glob::pattern::Case;
3
4use crate::{
5 search::{Match, MatchKind, MatchKind::*, Spec},
6 MagicSignature, Pattern, Search, SearchMode,
7};
8
9impl Search {
10 pub fn pattern_matching_relative_path(
27 &mut self,
28 relative_path: &BStr,
29 is_dir: Option<bool>,
30 attributes: &mut dyn FnMut(&BStr, Case, bool, &mut gix_attributes::search::Outcome) -> bool,
31 ) -> Option<Match<'_>> {
32 static MATCH_ALL_STAND_IN: Pattern = Pattern {
33 path: BString::new(Vec::new()),
34 signature: MagicSignature::empty(),
35 search_mode: SearchMode::ShellGlob,
36 attributes: Vec::new(),
37 prefix_len: 0,
38 nil: true,
39 };
40 if relative_path.is_empty() {
41 return Some(Match {
42 pattern: &MATCH_ALL_STAND_IN,
43 sequence_number: 0,
44 kind: Always,
45 });
46 }
47 let basename_not_important = None;
48 if relative_path
49 .get(..self.common_prefix_len)
50 .is_none_or(|rela_path_prefix| rela_path_prefix != self.common_prefix())
51 {
52 return None;
53 }
54
55 let is_dir = is_dir.unwrap_or(false);
56 let patterns_len = self.patterns.len();
57 let res = self.patterns.iter_mut().find_map(|mapping| {
58 let ignore_case = mapping.value.pattern.signature.contains(MagicSignature::ICASE);
59 let prefix = mapping.value.pattern.prefix_directory();
60 if ignore_case && !prefix.is_empty() {
61 let pattern_requirement_is_met = relative_path.get(prefix.len()).map_or_else(|| is_dir, |b| *b == b'/');
62 if !pattern_requirement_is_met
63 || relative_path.get(..prefix.len()).map(ByteSlice::as_bstr) != Some(prefix)
64 {
65 return None;
66 }
67 }
68
69 let case = if ignore_case { Case::Fold } else { Case::Sensitive };
70 let mut is_match = mapping.value.pattern.always_matches();
71 let mut how = Always;
72 if !is_match {
73 is_match = if mapping.pattern.first_wildcard_pos.is_none() {
74 match_verbatim(mapping, relative_path, is_dir, case, &mut how)
75 } else {
76 let wildmatch_mode = match mapping.value.pattern.search_mode {
77 SearchMode::ShellGlob => Some(gix_glob::wildmatch::Mode::empty()),
78 SearchMode::Literal => None,
79 SearchMode::PathAwareGlob => Some(gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL),
80 };
81 match wildmatch_mode {
82 Some(wildmatch_mode) => {
83 let is_match = mapping.pattern.matches_repo_relative_path(
84 relative_path,
85 basename_not_important,
86 Some(is_dir),
87 case,
88 wildmatch_mode,
89 );
90 if !is_match {
91 match_verbatim(mapping, relative_path, is_dir, case, &mut how)
92 } else {
93 how = mapping.pattern.first_wildcard_pos.map_or(Verbatim, |_| WildcardMatch);
94 true
95 }
96 }
97 None => match_verbatim(mapping, relative_path, is_dir, case, &mut how),
98 }
99 }
100 }
101
102 if let Some(attrs) = mapping.value.attrs_match.as_mut() {
103 if !attributes(relative_path, Case::Sensitive, is_dir, attrs) {
104 return None;
106 }
107 for (actual, expected) in attrs.iter_selected().zip(mapping.value.pattern.attributes.iter()) {
108 if actual.assignment != expected.as_ref() {
109 return None;
110 }
111 }
112 }
113
114 is_match.then_some(Match {
115 pattern: &mapping.value.pattern,
116 sequence_number: mapping.sequence_number,
117 kind: how,
118 })
119 });
120
121 if res.is_none() && self.all_patterns_are_excluded {
122 Some(Match {
123 pattern: &MATCH_ALL_STAND_IN,
124 sequence_number: patterns_len,
125 kind: Always,
126 })
127 } else {
128 res
129 }
130 }
131
132 pub fn can_match_relative_path(&self, relative_path: &BStr, is_dir: Option<bool>) -> bool {
141 if self.patterns.is_empty() || relative_path.is_empty() {
142 return true;
143 }
144 let common_prefix_len = self.common_prefix_len.min(relative_path.len());
145 if relative_path
146 .get(..common_prefix_len)
147 .is_none_or(|rela_path_prefix| rela_path_prefix != self.common_prefix()[..common_prefix_len])
148 {
149 return false;
150 }
151 for mapping in &self.patterns {
152 let pattern = &mapping.value.pattern;
153 if mapping.pattern.first_wildcard_pos == Some(0) && !pattern.is_excluded() {
154 return true;
155 }
156 let max_usable_pattern_len = mapping.pattern.first_wildcard_pos.unwrap_or_else(|| pattern.path.len());
157 let common_len = max_usable_pattern_len.min(relative_path.len());
158
159 let ignore_case = pattern.signature.contains(MagicSignature::ICASE);
160 let mut is_match = pattern.always_matches();
161 if !is_match && common_len != 0 {
162 let pattern_path = pattern.path[..common_len].as_bstr();
163 let longest_possible_relative_path = &relative_path[..common_len];
164 is_match = if ignore_case {
165 pattern_path.eq_ignore_ascii_case(longest_possible_relative_path)
166 } else {
167 pattern_path == longest_possible_relative_path
168 };
169
170 if is_match {
171 is_match = if common_len < max_usable_pattern_len {
172 pattern.path.get(common_len) == Some(&b'/')
173 } else if relative_path.len() > max_usable_pattern_len
174 && mapping.pattern.first_wildcard_pos.is_none()
175 {
176 relative_path.get(common_len) == Some(&b'/')
177 } else {
178 is_match
179 };
180 if let Some(is_dir) = is_dir.filter(|_| pattern.signature.contains(MagicSignature::MUST_BE_DIR)) {
181 is_match = if is_dir {
182 matches!(pattern.path.get(common_len), None | Some(&b'/'))
183 } else {
184 relative_path.get(common_len) == Some(&b'/')
185 };
186 }
187 }
188 }
189 if is_match && (!pattern.is_excluded() || pattern.always_matches()) {
190 return !pattern.is_excluded();
191 }
192 }
193
194 self.all_patterns_are_excluded
195 }
196
197 pub fn directory_matches_prefix(&self, relative_path: &BStr, leading: bool) -> bool {
203 if self.patterns.is_empty() || relative_path.is_empty() {
204 return true;
205 }
206 let common_prefix_len = self.common_prefix_len.min(relative_path.len());
207 if relative_path
208 .get(..common_prefix_len)
209 .is_none_or(|rela_path_prefix| rela_path_prefix != self.common_prefix()[..common_prefix_len])
210 {
211 return false;
212 }
213 for mapping in &self.patterns {
214 let pattern = &mapping.value.pattern;
215 if mapping.pattern.first_wildcard_pos.is_some() && pattern.is_excluded() {
216 return true;
217 }
218 let mut rightmost_idx = mapping.pattern.first_wildcard_pos.map_or_else(
219 || pattern.path.len(),
220 |idx| pattern.path[..idx].rfind_byte(b'/').unwrap_or(idx),
221 );
222 let ignore_case = pattern.signature.contains(MagicSignature::ICASE);
223 let mut is_match = pattern.always_matches();
224 if !is_match {
225 let plen = relative_path.len();
226 if leading && rightmost_idx > plen {
227 if let Some(idx) = pattern.path[..plen]
228 .rfind_byte(b'/')
229 .or_else(|| pattern.path[plen..].find_byte(b'/').map(|idx| idx + plen))
230 {
231 rightmost_idx = idx;
232 }
233 }
234 if let Some(relative_path) = relative_path.get(..rightmost_idx) {
235 let pattern_path = pattern.path[..rightmost_idx].as_bstr();
236 is_match = if ignore_case {
237 pattern_path.eq_ignore_ascii_case(relative_path)
238 } else {
239 pattern_path == relative_path
240 };
241 }
242 }
243 if is_match && (!pattern.is_excluded() || pattern.always_matches()) {
244 return !pattern.is_excluded();
245 }
246 }
247
248 self.all_patterns_are_excluded
249 }
250}
251
252fn match_verbatim(
253 mapping: &gix_glob::search::pattern::Mapping<Spec>,
254 relative_path: &BStr,
255 is_dir: bool,
256 case: Case,
257 how: &mut MatchKind,
258) -> bool {
259 let pattern_len = mapping.value.pattern.path.len();
260 let mut relative_path_ends_with_slash_at_pattern_len = false;
261 let (match_is_allowed, probably_how) = relative_path.get(pattern_len).map_or_else(
262 || (relative_path.len() == pattern_len, Verbatim),
263 |b| {
264 relative_path_ends_with_slash_at_pattern_len = *b == b'/';
265 (relative_path_ends_with_slash_at_pattern_len, Prefix)
266 },
267 );
268 *how = probably_how;
269 let pattern_requirement_is_met = !mapping.pattern.mode.contains(gix_glob::pattern::Mode::MUST_BE_DIR)
270 || (relative_path_ends_with_slash_at_pattern_len || is_dir);
271
272 if match_is_allowed && pattern_requirement_is_met {
273 let dir_or_file = &relative_path[..mapping.value.pattern.path.len()];
274 match case {
275 Case::Sensitive => mapping.value.pattern.path == dir_or_file,
276 Case::Fold => mapping.value.pattern.path.eq_ignore_ascii_case(dir_or_file),
277 }
278 } else {
279 false
280 }
281}