gix_pathspec/search/
matching.rs1use bstr::{BStr, BString, ByteSlice};
2use gix_glob::pattern::Case;
3
4use crate::search::MatchKind;
5use crate::search::MatchKind::*;
6use crate::{
7 search::{Match, Spec},
8 MagicSignature, Pattern, Search, SearchMode,
9};
10
11impl Search {
12 pub fn pattern_matching_relative_path(
29 &mut self,
30 relative_path: &BStr,
31 is_dir: Option<bool>,
32 attributes: &mut dyn FnMut(&BStr, Case, bool, &mut gix_attributes::search::Outcome) -> bool,
33 ) -> Option<Match<'_>> {
34 static MATCH_ALL_STAND_IN: Pattern = Pattern {
35 path: BString::new(Vec::new()),
36 signature: MagicSignature::empty(),
37 search_mode: SearchMode::ShellGlob,
38 attributes: Vec::new(),
39 prefix_len: 0,
40 nil: true,
41 };
42 if relative_path.is_empty() {
43 return Some(Match {
44 pattern: &MATCH_ALL_STAND_IN,
45 sequence_number: 0,
46 kind: Always,
47 });
48 }
49 let basename_not_important = None;
50 if relative_path
51 .get(..self.common_prefix_len)
52 .map_or(true, |rela_path_prefix| rela_path_prefix != self.common_prefix())
53 {
54 return None;
55 }
56
57 let is_dir = is_dir.unwrap_or(false);
58 let patterns_len = self.patterns.len();
59 let res = self.patterns.iter_mut().find_map(|mapping| {
60 let ignore_case = mapping.value.pattern.signature.contains(MagicSignature::ICASE);
61 let prefix = mapping.value.pattern.prefix_directory();
62 if ignore_case && !prefix.is_empty() {
63 let pattern_requirement_is_met = relative_path.get(prefix.len()).map_or_else(|| is_dir, |b| *b == b'/');
64 if !pattern_requirement_is_met
65 || relative_path.get(..prefix.len()).map(ByteSlice::as_bstr) != Some(prefix)
66 {
67 return None;
68 }
69 }
70
71 let case = if ignore_case { Case::Fold } else { Case::Sensitive };
72 let mut is_match = mapping.value.pattern.always_matches();
73 let mut how = Always;
74 if !is_match {
75 is_match = if mapping.pattern.first_wildcard_pos.is_none() {
76 match_verbatim(mapping, relative_path, is_dir, case, &mut how)
77 } else {
78 let wildmatch_mode = match mapping.value.pattern.search_mode {
79 SearchMode::ShellGlob => Some(gix_glob::wildmatch::Mode::empty()),
80 SearchMode::Literal => None,
81 SearchMode::PathAwareGlob => Some(gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL),
82 };
83 match wildmatch_mode {
84 Some(wildmatch_mode) => {
85 let is_match = mapping.pattern.matches_repo_relative_path(
86 relative_path,
87 basename_not_important,
88 Some(is_dir),
89 case,
90 wildmatch_mode,
91 );
92 if !is_match {
93 match_verbatim(mapping, relative_path, is_dir, case, &mut how)
94 } else {
95 how = mapping.pattern.first_wildcard_pos.map_or(Verbatim, |_| WildcardMatch);
96 true
97 }
98 }
99 None => match_verbatim(mapping, relative_path, is_dir, case, &mut how),
100 }
101 }
102 }
103
104 if let Some(attrs) = mapping.value.attrs_match.as_mut() {
105 if !attributes(relative_path, Case::Sensitive, is_dir, attrs) {
106 return None;
108 }
109 for (actual, expected) in attrs.iter_selected().zip(mapping.value.pattern.attributes.iter()) {
110 if actual.assignment != expected.as_ref() {
111 return None;
112 }
113 }
114 }
115
116 is_match.then_some(Match {
117 pattern: &mapping.value.pattern,
118 sequence_number: mapping.sequence_number,
119 kind: how,
120 })
121 });
122
123 if res.is_none() && self.all_patterns_are_excluded {
124 Some(Match {
125 pattern: &MATCH_ALL_STAND_IN,
126 sequence_number: patterns_len,
127 kind: Always,
128 })
129 } else {
130 res
131 }
132 }
133
134 pub fn can_match_relative_path(&self, relative_path: &BStr, is_dir: Option<bool>) -> bool {
143 if self.patterns.is_empty() || relative_path.is_empty() {
144 return true;
145 }
146 let common_prefix_len = self.common_prefix_len.min(relative_path.len());
147 if relative_path.get(..common_prefix_len).map_or(true, |rela_path_prefix| {
148 rela_path_prefix != self.common_prefix()[..common_prefix_len]
149 }) {
150 return false;
151 }
152 for mapping in &self.patterns {
153 let pattern = &mapping.value.pattern;
154 if mapping.pattern.first_wildcard_pos == Some(0) && !pattern.is_excluded() {
155 return true;
156 }
157 let max_usable_pattern_len = mapping.pattern.first_wildcard_pos.unwrap_or_else(|| pattern.path.len());
158 let common_len = max_usable_pattern_len.min(relative_path.len());
159
160 let ignore_case = pattern.signature.contains(MagicSignature::ICASE);
161 let mut is_match = pattern.always_matches();
162 if !is_match && common_len != 0 {
163 let pattern_path = pattern.path[..common_len].as_bstr();
164 let longest_possible_relative_path = &relative_path[..common_len];
165 is_match = if ignore_case {
166 pattern_path.eq_ignore_ascii_case(longest_possible_relative_path)
167 } else {
168 pattern_path == longest_possible_relative_path
169 };
170
171 if is_match {
172 is_match = if common_len < max_usable_pattern_len {
173 pattern.path.get(common_len) == Some(&b'/')
174 } else if relative_path.len() > max_usable_pattern_len
175 && mapping.pattern.first_wildcard_pos.is_none()
176 {
177 relative_path.get(common_len) == Some(&b'/')
178 } else {
179 is_match
180 };
181 if let Some(is_dir) = is_dir.filter(|_| pattern.signature.contains(MagicSignature::MUST_BE_DIR)) {
182 is_match = if is_dir {
183 matches!(pattern.path.get(common_len), None | Some(&b'/'))
184 } else {
185 relative_path.get(common_len) == Some(&b'/')
186 };
187 }
188 }
189 }
190 if is_match && (!pattern.is_excluded() || pattern.always_matches()) {
191 return !pattern.is_excluded();
192 }
193 }
194
195 self.all_patterns_are_excluded
196 }
197
198 pub fn directory_matches_prefix(&self, relative_path: &BStr, leading: bool) -> bool {
204 if self.patterns.is_empty() || relative_path.is_empty() {
205 return true;
206 }
207 let common_prefix_len = self.common_prefix_len.min(relative_path.len());
208 if relative_path.get(..common_prefix_len).map_or(true, |rela_path_prefix| {
209 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}