##// END OF EJS Templates
rust-matchers: raw regular expression builder...
Georges Racinet -
r52364:5633de95 stable
parent child Browse files
Show More
@@ -1,2114 +1,2122 b''
1 1 // matchers.rs
2 2 //
3 3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
4 4 //
5 5 // This software may be used and distributed according to the terms of the
6 6 // GNU General Public License version 2 or any later version.
7 7
8 8 //! Structs and types for matching files and directories.
9 9
10 10 use format_bytes::format_bytes;
11 11 use once_cell::sync::OnceCell;
12 12
13 13 use crate::{
14 14 dirstate::dirs_multiset::DirsChildrenMultiset,
15 15 filepatterns::{
16 16 build_single_regex, filter_subincludes, get_patterns_from_file,
17 17 PatternFileWarning, PatternResult,
18 18 },
19 19 utils::{
20 20 files::find_dirs,
21 21 hg_path::{HgPath, HgPathBuf, HgPathError},
22 22 Escaped,
23 23 },
24 24 DirsMultiset, FastHashMap, IgnorePattern, PatternError, PatternSyntax,
25 25 };
26 26
27 27 use crate::dirstate::status::IgnoreFnType;
28 28 use crate::filepatterns::normalize_path_bytes;
29 29 use std::collections::HashSet;
30 30 use std::fmt::{Display, Error, Formatter};
31 31 use std::path::{Path, PathBuf};
32 32 use std::{borrow::ToOwned, collections::BTreeSet};
33 33
34 34 #[derive(Debug, PartialEq)]
35 35 pub enum VisitChildrenSet {
36 36 /// Don't visit anything
37 37 Empty,
38 38 /// Only visit this directory
39 39 This,
40 40 /// Visit this directory and these subdirectories
41 41 /// TODO Should we implement a `NonEmptyHashSet`?
42 42 Set(HashSet<HgPathBuf>),
43 43 /// Visit this directory and all subdirectories
44 44 Recursive,
45 45 }
46 46
47 47 pub trait Matcher: core::fmt::Debug {
48 48 /// Explicitly listed files
49 49 fn file_set(&self) -> Option<&HashSet<HgPathBuf>>;
50 50 /// Returns whether `filename` is in `file_set`
51 51 fn exact_match(&self, filename: &HgPath) -> bool;
52 52 /// Returns whether `filename` is matched by this matcher
53 53 fn matches(&self, filename: &HgPath) -> bool;
54 54 /// Decides whether a directory should be visited based on whether it
55 55 /// has potential matches in it or one of its subdirectories, and
56 56 /// potentially lists which subdirectories of that directory should be
57 57 /// visited. This is based on the match's primary, included, and excluded
58 58 /// patterns.
59 59 ///
60 60 /// # Example
61 61 ///
62 62 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
63 63 /// return the following values (assuming the implementation of
64 64 /// visit_children_set is capable of recognizing this; some implementations
65 65 /// are not).
66 66 ///
67 67 /// ```text
68 68 /// ```ignore
69 69 /// '' -> {'foo', 'qux'}
70 70 /// 'baz' -> set()
71 71 /// 'foo' -> {'bar'}
72 72 /// // Ideally this would be `Recursive`, but since the prefix nature of
73 73 /// // matchers is applied to the entire matcher, we have to downgrade this
74 74 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
75 75 /// // `RootFilesIn'-kind matcher being mixed in.
76 76 /// 'foo/bar' -> 'this'
77 77 /// 'qux' -> 'this'
78 78 /// ```
79 79 /// # Important
80 80 ///
81 81 /// Most matchers do not know if they're representing files or
82 82 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
83 83 /// file or a directory, so `visit_children_set('dir')` for most matchers
84 84 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
85 85 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
86 86 /// it may return `VisitChildrenSet::This`.
87 87 /// Do not rely on the return being a `HashSet` indicating that there are
88 88 /// no files in this dir to investigate (or equivalently that if there are
89 89 /// files to investigate in 'dir' that it will always return
90 90 /// `VisitChildrenSet::This`).
91 91 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet;
92 92 /// Matcher will match everything and `files_set()` will be empty:
93 93 /// optimization might be possible.
94 94 fn matches_everything(&self) -> bool;
95 95 /// Matcher will match exactly the files in `files_set()`: optimization
96 96 /// might be possible.
97 97 fn is_exact(&self) -> bool;
98 98 }
99 99
100 100 /// Matches everything.
101 101 ///```
102 102 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
103 103 ///
104 104 /// let matcher = AlwaysMatcher;
105 105 ///
106 106 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
107 107 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
108 108 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
109 109 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
110 110 /// ```
111 111 #[derive(Debug)]
112 112 pub struct AlwaysMatcher;
113 113
114 114 impl Matcher for AlwaysMatcher {
115 115 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
116 116 None
117 117 }
118 118 fn exact_match(&self, _filename: &HgPath) -> bool {
119 119 false
120 120 }
121 121 fn matches(&self, _filename: &HgPath) -> bool {
122 122 true
123 123 }
124 124 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
125 125 VisitChildrenSet::Recursive
126 126 }
127 127 fn matches_everything(&self) -> bool {
128 128 true
129 129 }
130 130 fn is_exact(&self) -> bool {
131 131 false
132 132 }
133 133 }
134 134
135 135 /// Matches nothing.
136 136 #[derive(Debug)]
137 137 pub struct NeverMatcher;
138 138
139 139 impl Matcher for NeverMatcher {
140 140 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
141 141 None
142 142 }
143 143 fn exact_match(&self, _filename: &HgPath) -> bool {
144 144 false
145 145 }
146 146 fn matches(&self, _filename: &HgPath) -> bool {
147 147 false
148 148 }
149 149 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
150 150 VisitChildrenSet::Empty
151 151 }
152 152 fn matches_everything(&self) -> bool {
153 153 false
154 154 }
155 155 fn is_exact(&self) -> bool {
156 156 true
157 157 }
158 158 }
159 159
160 160 /// Matches the input files exactly. They are interpreted as paths, not
161 161 /// patterns.
162 162 ///
163 163 ///```
164 164 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::{HgPath, HgPathBuf} };
165 165 ///
166 166 /// let files = vec![HgPathBuf::from_bytes(b"a.txt"), HgPathBuf::from_bytes(br"re:.*\.c$")];
167 167 /// let matcher = FileMatcher::new(files).unwrap();
168 168 ///
169 169 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
170 170 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
171 171 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
172 172 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
173 173 /// ```
174 174 #[derive(Debug)]
175 175 pub struct FileMatcher {
176 176 files: HashSet<HgPathBuf>,
177 177 dirs: DirsMultiset,
178 178 sorted_visitchildrenset_candidates: OnceCell<BTreeSet<HgPathBuf>>,
179 179 }
180 180
181 181 impl FileMatcher {
182 182 pub fn new(files: Vec<HgPathBuf>) -> Result<Self, HgPathError> {
183 183 let dirs = DirsMultiset::from_manifest(&files)?;
184 184 Ok(Self {
185 185 files: HashSet::from_iter(files),
186 186 dirs,
187 187 sorted_visitchildrenset_candidates: OnceCell::new(),
188 188 })
189 189 }
190 190 fn inner_matches(&self, filename: &HgPath) -> bool {
191 191 self.files.contains(filename.as_ref())
192 192 }
193 193 }
194 194
195 195 impl Matcher for FileMatcher {
196 196 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
197 197 Some(&self.files)
198 198 }
199 199 fn exact_match(&self, filename: &HgPath) -> bool {
200 200 self.inner_matches(filename)
201 201 }
202 202 fn matches(&self, filename: &HgPath) -> bool {
203 203 self.inner_matches(filename)
204 204 }
205 205 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
206 206 if self.files.is_empty() || !self.dirs.contains(directory) {
207 207 return VisitChildrenSet::Empty;
208 208 }
209 209
210 210 let compute_candidates = || -> BTreeSet<HgPathBuf> {
211 211 let mut candidates: BTreeSet<HgPathBuf> =
212 212 self.dirs.iter().cloned().collect();
213 213 candidates.extend(self.files.iter().cloned());
214 214 candidates.remove(HgPath::new(b""));
215 215 candidates
216 216 };
217 217 let candidates =
218 218 if directory.as_ref().is_empty() {
219 219 compute_candidates()
220 220 } else {
221 221 let sorted_candidates = self
222 222 .sorted_visitchildrenset_candidates
223 223 .get_or_init(compute_candidates);
224 224 let directory_bytes = directory.as_ref().as_bytes();
225 225 let start: HgPathBuf =
226 226 format_bytes!(b"{}/", directory_bytes).into();
227 227 let start_len = start.len();
228 228 // `0` sorts after `/`
229 229 let end = format_bytes!(b"{}0", directory_bytes).into();
230 230 BTreeSet::from_iter(sorted_candidates.range(start..end).map(
231 231 |c| HgPathBuf::from_bytes(&c.as_bytes()[start_len..]),
232 232 ))
233 233 };
234 234
235 235 // `self.dirs` includes all of the directories, recursively, so if
236 236 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
237 237 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
238 238 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
239 239 // subdir will be in there without a slash.
240 240 VisitChildrenSet::Set(
241 241 candidates
242 242 .into_iter()
243 243 .filter_map(|c| {
244 244 if c.bytes().all(|b| *b != b'/') {
245 245 Some(c)
246 246 } else {
247 247 None
248 248 }
249 249 })
250 250 .collect(),
251 251 )
252 252 }
253 253 fn matches_everything(&self) -> bool {
254 254 false
255 255 }
256 256 fn is_exact(&self) -> bool {
257 257 true
258 258 }
259 259 }
260 260
261 261 /// Matches a set of (kind, pat, source) against a 'root' directory.
262 262 /// (Currently the 'root' directory is effectively always empty)
263 263 /// ```
264 264 /// use hg::{
265 265 /// matchers::{PatternMatcher, Matcher},
266 266 /// IgnorePattern,
267 267 /// PatternSyntax,
268 268 /// utils::hg_path::{HgPath, HgPathBuf}
269 269 /// };
270 270 /// use std::collections::HashSet;
271 271 /// use std::path::Path;
272 272 /// ///
273 273 /// let ignore_patterns : Vec<IgnorePattern> =
274 274 /// vec![IgnorePattern::new(PatternSyntax::Regexp, br".*\.c$", Path::new("")),
275 275 /// IgnorePattern::new(PatternSyntax::Path, b"foo/a", Path::new("")),
276 276 /// IgnorePattern::new(PatternSyntax::RelPath, b"b", Path::new("")),
277 277 /// IgnorePattern::new(PatternSyntax::Glob, b"*.h", Path::new("")),
278 278 /// ];
279 279 /// let matcher = PatternMatcher::new(ignore_patterns).unwrap();
280 280 /// ///
281 281 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true); // matches re:.*\.c$
282 282 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
283 283 /// assert_eq!(matcher.matches(HgPath::new(b"foo/a")), true); // matches path:foo/a
284 284 /// assert_eq!(matcher.matches(HgPath::new(b"a")), false); // does not match path:b, since 'root' is 'foo'
285 285 /// assert_eq!(matcher.matches(HgPath::new(b"b")), true); // matches relpath:b, since 'root' is 'foo'
286 286 /// assert_eq!(matcher.matches(HgPath::new(b"lib.h")), true); // matches glob:*.h
287 287 /// assert_eq!(matcher.file_set().unwrap(),
288 288 /// &HashSet::from([HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"foo/a"),
289 289 /// HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"b")]));
290 290 /// assert_eq!(matcher.exact_match(HgPath::new(b"foo/a")), true);
291 291 /// assert_eq!(matcher.exact_match(HgPath::new(b"b")), true);
292 292 /// assert_eq!(matcher.exact_match(HgPath::new(b"lib.h")), false); // exact matches are for (rel)path kinds
293 293 /// ```
294 294 pub struct PatternMatcher<'a> {
295 295 patterns: Vec<u8>,
296 296 match_fn: IgnoreFnType<'a>,
297 297 /// Whether all the patterns match a prefix (i.e. recursively)
298 298 prefix: bool,
299 299 files: HashSet<HgPathBuf>,
300 300 dirs: DirsMultiset,
301 301 }
302 302
303 303 impl core::fmt::Debug for PatternMatcher<'_> {
304 304 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
305 305 f.debug_struct("PatternMatcher")
306 306 .field("patterns", &String::from_utf8_lossy(&self.patterns))
307 307 .field("prefix", &self.prefix)
308 308 .field("files", &self.files)
309 309 .field("dirs", &self.dirs)
310 310 .finish()
311 311 }
312 312 }
313 313
314 314 impl<'a> PatternMatcher<'a> {
315 315 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
316 316 let (files, _) = roots_and_dirs(&ignore_patterns);
317 317 let dirs = DirsMultiset::from_manifest(&files)?;
318 318 let files: HashSet<HgPathBuf> = HashSet::from_iter(files);
319 319
320 320 let prefix = ignore_patterns.iter().all(|k| {
321 321 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
322 322 });
323 323 let (patterns, match_fn) = build_match(ignore_patterns, b"$")?;
324 324
325 325 Ok(Self {
326 326 patterns,
327 327 match_fn,
328 328 prefix,
329 329 files,
330 330 dirs,
331 331 })
332 332 }
333 333 }
334 334
335 335 impl<'a> Matcher for PatternMatcher<'a> {
336 336 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
337 337 Some(&self.files)
338 338 }
339 339
340 340 fn exact_match(&self, filename: &HgPath) -> bool {
341 341 self.files.contains(filename)
342 342 }
343 343
344 344 fn matches(&self, filename: &HgPath) -> bool {
345 345 if self.files.contains(filename) {
346 346 return true;
347 347 }
348 348 (self.match_fn)(filename)
349 349 }
350 350
351 351 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
352 352 if self.prefix && self.files.contains(directory) {
353 353 return VisitChildrenSet::Recursive;
354 354 }
355 355 let path_or_parents_in_set = find_dirs(directory)
356 356 .any(|parent_dir| self.files.contains(parent_dir));
357 357 if self.dirs.contains(directory) || path_or_parents_in_set {
358 358 VisitChildrenSet::This
359 359 } else {
360 360 VisitChildrenSet::Empty
361 361 }
362 362 }
363 363
364 364 fn matches_everything(&self) -> bool {
365 365 false
366 366 }
367 367
368 368 fn is_exact(&self) -> bool {
369 369 false
370 370 }
371 371 }
372 372
373 373 /// Matches files that are included in the ignore rules.
374 374 /// ```
375 375 /// use hg::{
376 376 /// matchers::{IncludeMatcher, Matcher},
377 377 /// IgnorePattern,
378 378 /// PatternSyntax,
379 379 /// utils::hg_path::HgPath
380 380 /// };
381 381 /// use std::path::Path;
382 382 /// ///
383 383 /// let ignore_patterns =
384 384 /// vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
385 385 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
386 386 /// ///
387 387 /// assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
388 388 /// assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
389 389 /// assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
390 390 /// assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
391 391 /// ///
392 392 /// let ignore_patterns =
393 393 /// vec![IgnorePattern::new(PatternSyntax::RootFiles, b"dir/subdir", Path::new(""))];
394 394 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
395 395 /// ///
396 396 /// assert!(!matcher.matches(HgPath::new(b"file")));
397 397 /// assert!(!matcher.matches(HgPath::new(b"dir/file")));
398 398 /// assert!(matcher.matches(HgPath::new(b"dir/subdir/file")));
399 399 /// assert!(!matcher.matches(HgPath::new(b"dir/subdir/subsubdir/file")));
400 400 /// ```
401 401 pub struct IncludeMatcher<'a> {
402 402 patterns: Vec<u8>,
403 403 match_fn: IgnoreFnType<'a>,
404 404 /// Whether all the patterns match a prefix (i.e. recursively)
405 405 prefix: bool,
406 406 roots: HashSet<HgPathBuf>,
407 407 dirs: HashSet<HgPathBuf>,
408 408 parents: HashSet<HgPathBuf>,
409 409 }
410 410
411 411 impl core::fmt::Debug for IncludeMatcher<'_> {
412 412 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
413 413 f.debug_struct("IncludeMatcher")
414 414 .field("patterns", &String::from_utf8_lossy(&self.patterns))
415 415 .field("prefix", &self.prefix)
416 416 .field("roots", &self.roots)
417 417 .field("dirs", &self.dirs)
418 418 .field("parents", &self.parents)
419 419 .finish()
420 420 }
421 421 }
422 422
423 423 impl<'a> Matcher for IncludeMatcher<'a> {
424 424 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
425 425 None
426 426 }
427 427
428 428 fn exact_match(&self, _filename: &HgPath) -> bool {
429 429 false
430 430 }
431 431
432 432 fn matches(&self, filename: &HgPath) -> bool {
433 433 (self.match_fn)(filename)
434 434 }
435 435
436 436 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
437 437 let dir = directory;
438 438 if self.prefix && self.roots.contains(dir) {
439 439 return VisitChildrenSet::Recursive;
440 440 }
441 441 if self.roots.contains(HgPath::new(b""))
442 442 || self.roots.contains(dir)
443 443 || self.dirs.contains(dir)
444 444 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
445 445 {
446 446 return VisitChildrenSet::This;
447 447 }
448 448
449 449 if self.parents.contains(dir.as_ref()) {
450 450 let multiset = self.get_all_parents_children();
451 451 if let Some(children) = multiset.get(dir) {
452 452 return VisitChildrenSet::Set(
453 453 children.iter().map(HgPathBuf::from).collect(),
454 454 );
455 455 }
456 456 }
457 457 VisitChildrenSet::Empty
458 458 }
459 459
460 460 fn matches_everything(&self) -> bool {
461 461 false
462 462 }
463 463
464 464 fn is_exact(&self) -> bool {
465 465 false
466 466 }
467 467 }
468 468
469 469 /// The union of multiple matchers. Will match if any of the matchers match.
470 470 #[derive(Debug)]
471 471 pub struct UnionMatcher {
472 472 matchers: Vec<Box<dyn Matcher + Sync>>,
473 473 }
474 474
475 475 impl Matcher for UnionMatcher {
476 476 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
477 477 None
478 478 }
479 479
480 480 fn exact_match(&self, _filename: &HgPath) -> bool {
481 481 false
482 482 }
483 483
484 484 fn matches(&self, filename: &HgPath) -> bool {
485 485 self.matchers.iter().any(|m| m.matches(filename))
486 486 }
487 487
488 488 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
489 489 let mut result = HashSet::new();
490 490 let mut this = false;
491 491 for matcher in self.matchers.iter() {
492 492 let visit = matcher.visit_children_set(directory);
493 493 match visit {
494 494 VisitChildrenSet::Empty => continue,
495 495 VisitChildrenSet::This => {
496 496 this = true;
497 497 // Don't break, we might have an 'all' in here.
498 498 continue;
499 499 }
500 500 VisitChildrenSet::Set(set) => {
501 501 result.extend(set);
502 502 }
503 503 VisitChildrenSet::Recursive => {
504 504 return visit;
505 505 }
506 506 }
507 507 }
508 508 if this {
509 509 return VisitChildrenSet::This;
510 510 }
511 511 if result.is_empty() {
512 512 VisitChildrenSet::Empty
513 513 } else {
514 514 VisitChildrenSet::Set(result)
515 515 }
516 516 }
517 517
518 518 fn matches_everything(&self) -> bool {
519 519 // TODO Maybe if all are AlwaysMatcher?
520 520 false
521 521 }
522 522
523 523 fn is_exact(&self) -> bool {
524 524 false
525 525 }
526 526 }
527 527
528 528 impl UnionMatcher {
529 529 pub fn new(matchers: Vec<Box<dyn Matcher + Sync>>) -> Self {
530 530 Self { matchers }
531 531 }
532 532 }
533 533
534 534 #[derive(Debug)]
535 535 pub struct IntersectionMatcher {
536 536 m1: Box<dyn Matcher + Sync>,
537 537 m2: Box<dyn Matcher + Sync>,
538 538 files: Option<HashSet<HgPathBuf>>,
539 539 }
540 540
541 541 impl Matcher for IntersectionMatcher {
542 542 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
543 543 self.files.as_ref()
544 544 }
545 545
546 546 fn exact_match(&self, filename: &HgPath) -> bool {
547 547 self.files.as_ref().map_or(false, |f| f.contains(filename))
548 548 }
549 549
550 550 fn matches(&self, filename: &HgPath) -> bool {
551 551 self.m1.matches(filename) && self.m2.matches(filename)
552 552 }
553 553
554 554 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
555 555 let m1_set = self.m1.visit_children_set(directory);
556 556 if m1_set == VisitChildrenSet::Empty {
557 557 return VisitChildrenSet::Empty;
558 558 }
559 559 let m2_set = self.m2.visit_children_set(directory);
560 560 if m2_set == VisitChildrenSet::Empty {
561 561 return VisitChildrenSet::Empty;
562 562 }
563 563
564 564 if m1_set == VisitChildrenSet::Recursive {
565 565 return m2_set;
566 566 } else if m2_set == VisitChildrenSet::Recursive {
567 567 return m1_set;
568 568 }
569 569
570 570 match (&m1_set, &m2_set) {
571 571 (VisitChildrenSet::Recursive, _) => m2_set,
572 572 (_, VisitChildrenSet::Recursive) => m1_set,
573 573 (VisitChildrenSet::This, _) | (_, VisitChildrenSet::This) => {
574 574 VisitChildrenSet::This
575 575 }
576 576 (VisitChildrenSet::Set(m1), VisitChildrenSet::Set(m2)) => {
577 577 let set: HashSet<_> = m1.intersection(m2).cloned().collect();
578 578 if set.is_empty() {
579 579 VisitChildrenSet::Empty
580 580 } else {
581 581 VisitChildrenSet::Set(set)
582 582 }
583 583 }
584 584 _ => unreachable!(),
585 585 }
586 586 }
587 587
588 588 fn matches_everything(&self) -> bool {
589 589 self.m1.matches_everything() && self.m2.matches_everything()
590 590 }
591 591
592 592 fn is_exact(&self) -> bool {
593 593 self.m1.is_exact() || self.m2.is_exact()
594 594 }
595 595 }
596 596
597 597 impl IntersectionMatcher {
598 598 pub fn new(
599 599 mut m1: Box<dyn Matcher + Sync>,
600 600 mut m2: Box<dyn Matcher + Sync>,
601 601 ) -> Self {
602 602 let files = if m1.is_exact() || m2.is_exact() {
603 603 if !m1.is_exact() {
604 604 std::mem::swap(&mut m1, &mut m2);
605 605 }
606 606 m1.file_set().map(|m1_files| {
607 607 m1_files.iter().cloned().filter(|f| m2.matches(f)).collect()
608 608 })
609 609 } else {
610 610 // without exact input file sets, we can't do an exact
611 611 // intersection, so we must over-approximate by
612 612 // unioning instead
613 613 m1.file_set().map(|m1_files| match m2.file_set() {
614 614 Some(m2_files) => m1_files.union(m2_files).cloned().collect(),
615 615 None => m1_files.iter().cloned().collect(),
616 616 })
617 617 };
618 618 Self { m1, m2, files }
619 619 }
620 620 }
621 621
622 622 #[derive(Debug)]
623 623 pub struct DifferenceMatcher {
624 624 base: Box<dyn Matcher + Sync>,
625 625 excluded: Box<dyn Matcher + Sync>,
626 626 files: Option<HashSet<HgPathBuf>>,
627 627 }
628 628
629 629 impl Matcher for DifferenceMatcher {
630 630 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
631 631 self.files.as_ref()
632 632 }
633 633
634 634 fn exact_match(&self, filename: &HgPath) -> bool {
635 635 self.files.as_ref().map_or(false, |f| f.contains(filename))
636 636 }
637 637
638 638 fn matches(&self, filename: &HgPath) -> bool {
639 639 self.base.matches(filename) && !self.excluded.matches(filename)
640 640 }
641 641
642 642 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
643 643 let excluded_set = self.excluded.visit_children_set(directory);
644 644 if excluded_set == VisitChildrenSet::Recursive {
645 645 return VisitChildrenSet::Empty;
646 646 }
647 647 let base_set = self.base.visit_children_set(directory);
648 648 // Possible values for base: 'recursive', 'this', set(...), set()
649 649 // Possible values for excluded: 'this', set(...), set()
650 650 // If excluded has nothing under here that we care about, return base,
651 651 // even if it's 'recursive'.
652 652 if excluded_set == VisitChildrenSet::Empty {
653 653 return base_set;
654 654 }
655 655 match base_set {
656 656 VisitChildrenSet::This | VisitChildrenSet::Recursive => {
657 657 // Never return 'recursive' here if excluded_set is any kind of
658 658 // non-empty (either 'this' or set(foo)), since excluded might
659 659 // return set() for a subdirectory.
660 660 VisitChildrenSet::This
661 661 }
662 662 set => {
663 663 // Possible values for base: set(...), set()
664 664 // Possible values for excluded: 'this', set(...)
665 665 // We ignore excluded set results. They're possibly incorrect:
666 666 // base = path:dir/subdir
667 667 // excluded=rootfilesin:dir,
668 668 // visit_children_set(''):
669 669 // base returns {'dir'}, excluded returns {'dir'}, if we
670 670 // subtracted we'd return set(), which is *not* correct, we
671 671 // still need to visit 'dir'!
672 672 set
673 673 }
674 674 }
675 675 }
676 676
677 677 fn matches_everything(&self) -> bool {
678 678 false
679 679 }
680 680
681 681 fn is_exact(&self) -> bool {
682 682 self.base.is_exact()
683 683 }
684 684 }
685 685
686 686 impl DifferenceMatcher {
687 687 pub fn new(
688 688 base: Box<dyn Matcher + Sync>,
689 689 excluded: Box<dyn Matcher + Sync>,
690 690 ) -> Self {
691 691 let base_is_exact = base.is_exact();
692 692 let base_files = base.file_set().map(ToOwned::to_owned);
693 693 let mut new = Self {
694 694 base,
695 695 excluded,
696 696 files: None,
697 697 };
698 698 if base_is_exact {
699 699 new.files = base_files.map(|files| {
700 700 files.iter().cloned().filter(|f| new.matches(f)).collect()
701 701 });
702 702 }
703 703 new
704 704 }
705 705 }
706 706
707 707 /// Wraps [`regex::bytes::Regex`] to improve performance in multithreaded
708 708 /// contexts.
709 709 ///
710 710 /// The `status` algorithm makes heavy use of threads, and calling `is_match`
711 711 /// from many threads at once is prone to contention, probably within the
712 712 /// scratch space needed as the regex DFA is built lazily.
713 713 ///
714 714 /// We are in the process of raising the issue upstream, but for now
715 715 /// the workaround used here is to store the `Regex` in a lazily populated
716 716 /// thread-local variable, sharing the initial read-only compilation, but
717 717 /// not the lazy dfa scratch space mentioned above.
718 718 ///
719 719 /// This reduces the contention observed with 16+ threads, but does not
720 720 /// completely remove it. Hopefully this can be addressed upstream.
721 721 struct RegexMatcher {
722 722 /// Compiled at the start of the status algorithm, used as a base for
723 723 /// cloning in each thread-local `self.local`, thus sharing the expensive
724 724 /// first compilation.
725 725 base: regex::bytes::Regex,
726 726 /// Thread-local variable that holds the `Regex` that is actually queried
727 727 /// from each thread.
728 728 local: thread_local::ThreadLocal<regex::bytes::Regex>,
729 729 }
730 730
731 731 impl RegexMatcher {
732 732 /// Returns whether the path matches the stored `Regex`.
733 733 pub fn is_match(&self, path: &HgPath) -> bool {
734 734 self.local
735 735 .get_or(|| self.base.clone())
736 736 .is_match(path.as_bytes())
737 737 }
738 738 }
739 739
740 /// Returns a function that matches an `HgPath` against the given regex
741 /// pattern.
740 /// Return a `RegexBuilder` from a bytes pattern
742 741 ///
743 /// This can fail when the pattern is invalid or not supported by the
744 /// underlying engine (the `regex` crate), for instance anything with
745 /// back-references.
746 #[logging_timer::time("trace")]
747 fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
742 /// This works around the fact that even if it works on byte haysacks,
743 /// [`regex::bytes::Regex`] still uses UTF-8 patterns.
744 pub fn re_bytes_builder(pattern: &[u8]) -> regex::bytes::RegexBuilder {
748 745 use std::io::Write;
749 746
750 747 // The `regex` crate adds `.*` to the start and end of expressions if there
751 748 // are no anchors, so add the start anchor.
752 749 let mut escaped_bytes = vec![b'^', b'(', b'?', b':'];
753 750 for byte in pattern {
754 751 if *byte > 127 {
755 752 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
756 753 } else {
757 754 escaped_bytes.push(*byte);
758 755 }
759 756 }
760 757 escaped_bytes.push(b')');
761 758
762 759 // Avoid the cost of UTF8 checking
763 760 //
764 761 // # Safety
765 762 // This is safe because we escaped all non-ASCII bytes.
766 763 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
767 let re = regex::bytes::RegexBuilder::new(&pattern_string)
764 regex::bytes::RegexBuilder::new(&pattern_string)
765 }
766
767 /// Returns a function that matches an `HgPath` against the given regex
768 /// pattern.
769 ///
770 /// This can fail when the pattern is invalid or not supported by the
771 /// underlying engine (the `regex` crate), for instance anything with
772 /// back-references.
773 #[logging_timer::time("trace")]
774 fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
775 let re = re_bytes_builder(pattern)
768 776 .unicode(false)
769 777 // Big repos with big `.hgignore` will hit the default limit and
770 778 // incur a significant performance hit. One repo's `hg status` hit
771 779 // multiple *minutes*.
772 780 .dfa_size_limit(50 * (1 << 20))
773 781 .build()
774 782 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
775 783
776 784 Ok(RegexMatcher {
777 785 base: re,
778 786 local: Default::default(),
779 787 })
780 788 }
781 789
782 790 /// Returns the regex pattern and a function that matches an `HgPath` against
783 791 /// said regex formed by the given ignore patterns.
784 792 fn build_regex_match<'a>(
785 793 ignore_patterns: &[IgnorePattern],
786 794 glob_suffix: &[u8],
787 795 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
788 796 let mut regexps = vec![];
789 797 let mut exact_set = HashSet::new();
790 798
791 799 for pattern in ignore_patterns {
792 800 if let Some(re) = build_single_regex(pattern, glob_suffix)? {
793 801 regexps.push(re);
794 802 } else {
795 803 let exact = normalize_path_bytes(&pattern.pattern);
796 804 exact_set.insert(HgPathBuf::from_bytes(&exact));
797 805 }
798 806 }
799 807
800 808 let full_regex = regexps.join(&b'|');
801 809
802 810 // An empty pattern would cause the regex engine to incorrectly match the
803 811 // (empty) root directory
804 812 let func = if !(regexps.is_empty()) {
805 813 let matcher = re_matcher(&full_regex)?;
806 814 let func = move |filename: &HgPath| {
807 815 exact_set.contains(filename) || matcher.is_match(filename)
808 816 };
809 817 Box::new(func) as IgnoreFnType
810 818 } else {
811 819 let func = move |filename: &HgPath| exact_set.contains(filename);
812 820 Box::new(func) as IgnoreFnType
813 821 };
814 822
815 823 Ok((full_regex, func))
816 824 }
817 825
818 826 /// Returns roots and directories corresponding to each pattern.
819 827 ///
820 828 /// This calculates the roots and directories exactly matching the patterns and
821 829 /// returns a tuple of (roots, dirs). It does not return other directories
822 830 /// which may also need to be considered, like the parent directories.
823 831 fn roots_and_dirs(
824 832 ignore_patterns: &[IgnorePattern],
825 833 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
826 834 let mut roots = Vec::new();
827 835 let mut dirs = Vec::new();
828 836
829 837 for ignore_pattern in ignore_patterns {
830 838 let IgnorePattern {
831 839 syntax, pattern, ..
832 840 } = ignore_pattern;
833 841 match syntax {
834 842 PatternSyntax::RootGlob | PatternSyntax::Glob => {
835 843 let mut root = HgPathBuf::new();
836 844 for p in pattern.split(|c| *c == b'/') {
837 845 if p.iter()
838 846 .any(|c| matches!(*c, b'[' | b'{' | b'*' | b'?'))
839 847 {
840 848 break;
841 849 }
842 850 root.push(HgPathBuf::from_bytes(p).as_ref());
843 851 }
844 852 roots.push(root);
845 853 }
846 854 PatternSyntax::Path
847 855 | PatternSyntax::RelPath
848 856 | PatternSyntax::FilePath => {
849 857 let pat = HgPath::new(if pattern == b"." {
850 858 &[] as &[u8]
851 859 } else {
852 860 pattern
853 861 });
854 862 roots.push(pat.to_owned());
855 863 }
856 864 PatternSyntax::RootFiles => {
857 865 let pat = if pattern == b"." {
858 866 &[] as &[u8]
859 867 } else {
860 868 pattern
861 869 };
862 870 dirs.push(HgPathBuf::from_bytes(pat));
863 871 }
864 872 _ => {
865 873 roots.push(HgPathBuf::new());
866 874 }
867 875 }
868 876 }
869 877 (roots, dirs)
870 878 }
871 879
872 880 /// Paths extracted from patterns
873 881 #[derive(Debug, PartialEq)]
874 882 struct RootsDirsAndParents {
875 883 /// Directories to match recursively
876 884 pub roots: HashSet<HgPathBuf>,
877 885 /// Directories to match non-recursively
878 886 pub dirs: HashSet<HgPathBuf>,
879 887 /// Implicitly required directories to go to items in either roots or dirs
880 888 pub parents: HashSet<HgPathBuf>,
881 889 }
882 890
883 891 /// Extract roots, dirs and parents from patterns.
884 892 fn roots_dirs_and_parents(
885 893 ignore_patterns: &[IgnorePattern],
886 894 ) -> PatternResult<RootsDirsAndParents> {
887 895 let (roots, dirs) = roots_and_dirs(ignore_patterns);
888 896
889 897 let mut parents = HashSet::new();
890 898
891 899 parents.extend(
892 900 DirsMultiset::from_manifest(&dirs)?
893 901 .iter()
894 902 .map(ToOwned::to_owned),
895 903 );
896 904 parents.extend(
897 905 DirsMultiset::from_manifest(&roots)?
898 906 .iter()
899 907 .map(ToOwned::to_owned),
900 908 );
901 909
902 910 Ok(RootsDirsAndParents {
903 911 roots: HashSet::from_iter(roots),
904 912 dirs: HashSet::from_iter(dirs),
905 913 parents,
906 914 })
907 915 }
908 916
909 917 /// Returns a function that checks whether a given file (in the general sense)
910 918 /// should be matched.
911 919 fn build_match<'a>(
912 920 ignore_patterns: Vec<IgnorePattern>,
913 921 glob_suffix: &[u8],
914 922 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
915 923 let mut match_funcs: Vec<IgnoreFnType<'a>> = vec![];
916 924 // For debugging and printing
917 925 let mut patterns = vec![];
918 926
919 927 let (subincludes, ignore_patterns) = filter_subincludes(ignore_patterns)?;
920 928
921 929 if !subincludes.is_empty() {
922 930 // Build prefix-based matcher functions for subincludes
923 931 let mut submatchers = FastHashMap::default();
924 932 let mut prefixes = vec![];
925 933
926 934 for sub_include in subincludes {
927 935 let matcher = IncludeMatcher::new(sub_include.included_patterns)?;
928 936 let match_fn =
929 937 Box::new(move |path: &HgPath| matcher.matches(path));
930 938 prefixes.push(sub_include.prefix.clone());
931 939 submatchers.insert(sub_include.prefix.clone(), match_fn);
932 940 }
933 941
934 942 let match_subinclude = move |filename: &HgPath| {
935 943 for prefix in prefixes.iter() {
936 944 if let Some(rel) = filename.relative_to(prefix) {
937 945 if (submatchers[prefix])(rel) {
938 946 return true;
939 947 }
940 948 }
941 949 }
942 950 false
943 951 };
944 952
945 953 match_funcs.push(Box::new(match_subinclude));
946 954 }
947 955
948 956 if !ignore_patterns.is_empty() {
949 957 // Either do dumb matching if all patterns are rootfiles, or match
950 958 // with a regex.
951 959 if ignore_patterns
952 960 .iter()
953 961 .all(|k| k.syntax == PatternSyntax::RootFiles)
954 962 {
955 963 let dirs: HashSet<_> = ignore_patterns
956 964 .iter()
957 965 .map(|k| k.pattern.to_owned())
958 966 .collect();
959 967 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
960 968
961 969 let match_func = move |path: &HgPath| -> bool {
962 970 let path = path.as_bytes();
963 971 let i = path.iter().rposition(|a| *a == b'/');
964 972 let dir = if let Some(i) = i { &path[..i] } else { b"." };
965 973 dirs.contains(dir)
966 974 };
967 975 match_funcs.push(Box::new(match_func));
968 976
969 977 patterns.extend(b"rootfilesin: ");
970 978 dirs_vec.sort();
971 979 patterns.extend(dirs_vec.escaped_bytes());
972 980 } else {
973 981 let (new_re, match_func) =
974 982 build_regex_match(&ignore_patterns, glob_suffix)?;
975 983 patterns = new_re;
976 984 match_funcs.push(match_func)
977 985 }
978 986 }
979 987
980 988 Ok(if match_funcs.len() == 1 {
981 989 (patterns, match_funcs.remove(0))
982 990 } else {
983 991 (
984 992 patterns,
985 993 Box::new(move |f: &HgPath| -> bool {
986 994 match_funcs.iter().any(|match_func| match_func(f))
987 995 }),
988 996 )
989 997 })
990 998 }
991 999
992 1000 /// Parses all "ignore" files with their recursive includes and returns a
993 1001 /// function that checks whether a given file (in the general sense) should be
994 1002 /// ignored.
995 1003 pub fn get_ignore_matcher<'a>(
996 1004 mut all_pattern_files: Vec<PathBuf>,
997 1005 root_dir: &Path,
998 1006 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
999 1007 ) -> PatternResult<(IncludeMatcher<'a>, Vec<PatternFileWarning>)> {
1000 1008 let mut all_patterns = vec![];
1001 1009 let mut all_warnings = vec![];
1002 1010
1003 1011 // Sort to make the ordering of calls to `inspect_pattern_bytes`
1004 1012 // deterministic even if the ordering of `all_pattern_files` is not (such
1005 1013 // as when a iteration order of a Python dict or Rust HashMap is involved).
1006 1014 // Sort by "string" representation instead of the default by component
1007 1015 // (with a Rust-specific definition of a component)
1008 1016 all_pattern_files
1009 1017 .sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
1010 1018
1011 1019 for pattern_file in &all_pattern_files {
1012 1020 let (patterns, warnings) = get_patterns_from_file(
1013 1021 pattern_file,
1014 1022 root_dir,
1015 1023 inspect_pattern_bytes,
1016 1024 )?;
1017 1025
1018 1026 all_patterns.extend(patterns.to_owned());
1019 1027 all_warnings.extend(warnings);
1020 1028 }
1021 1029 let matcher = IncludeMatcher::new(all_patterns)?;
1022 1030 Ok((matcher, all_warnings))
1023 1031 }
1024 1032
1025 1033 /// Parses all "ignore" files with their recursive includes and returns a
1026 1034 /// function that checks whether a given file (in the general sense) should be
1027 1035 /// ignored.
1028 1036 pub fn get_ignore_function<'a>(
1029 1037 all_pattern_files: Vec<PathBuf>,
1030 1038 root_dir: &Path,
1031 1039 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
1032 1040 ) -> PatternResult<(IgnoreFnType<'a>, Vec<PatternFileWarning>)> {
1033 1041 let res =
1034 1042 get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes);
1035 1043 res.map(|(matcher, all_warnings)| {
1036 1044 let res: IgnoreFnType<'a> =
1037 1045 Box::new(move |path: &HgPath| matcher.matches(path));
1038 1046
1039 1047 (res, all_warnings)
1040 1048 })
1041 1049 }
1042 1050
1043 1051 impl<'a> IncludeMatcher<'a> {
1044 1052 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
1045 1053 let RootsDirsAndParents {
1046 1054 roots,
1047 1055 dirs,
1048 1056 parents,
1049 1057 } = roots_dirs_and_parents(&ignore_patterns)?;
1050 1058 let prefix = ignore_patterns.iter().all(|k| {
1051 1059 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
1052 1060 });
1053 1061 let (patterns, match_fn) = build_match(ignore_patterns, b"(?:/|$)")?;
1054 1062
1055 1063 Ok(Self {
1056 1064 patterns,
1057 1065 match_fn,
1058 1066 prefix,
1059 1067 roots,
1060 1068 dirs,
1061 1069 parents,
1062 1070 })
1063 1071 }
1064 1072
1065 1073 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
1066 1074 // TODO cache
1067 1075 let thing = self
1068 1076 .dirs
1069 1077 .iter()
1070 1078 .chain(self.roots.iter())
1071 1079 .chain(self.parents.iter());
1072 1080 DirsChildrenMultiset::new(thing, Some(&self.parents))
1073 1081 }
1074 1082
1075 1083 pub fn debug_get_patterns(&self) -> &[u8] {
1076 1084 self.patterns.as_ref()
1077 1085 }
1078 1086 }
1079 1087
1080 1088 impl<'a> Display for IncludeMatcher<'a> {
1081 1089 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
1082 1090 // XXX What about exact matches?
1083 1091 // I'm not sure it's worth it to clone the HashSet and keep it
1084 1092 // around just in case someone wants to display the matcher, plus
1085 1093 // it's going to be unreadable after a few entries, but we need to
1086 1094 // inform in this display that exact matches are being used and are
1087 1095 // (on purpose) missing from the `includes`.
1088 1096 write!(
1089 1097 f,
1090 1098 "IncludeMatcher(includes='{}')",
1091 1099 String::from_utf8_lossy(&self.patterns.escaped_bytes())
1092 1100 )
1093 1101 }
1094 1102 }
1095 1103
1096 1104 #[cfg(test)]
1097 1105 mod tests {
1098 1106 use super::*;
1099 1107 use pretty_assertions::assert_eq;
1100 1108 use std::path::Path;
1101 1109
1102 1110 #[test]
1103 1111 fn test_roots_and_dirs() {
1104 1112 let pats = vec![
1105 1113 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1106 1114 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1107 1115 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1108 1116 ];
1109 1117 let (roots, dirs) = roots_and_dirs(&pats);
1110 1118
1111 1119 assert_eq!(
1112 1120 roots,
1113 1121 vec!(
1114 1122 HgPathBuf::from_bytes(b"g/h"),
1115 1123 HgPathBuf::from_bytes(b"g/h"),
1116 1124 HgPathBuf::new()
1117 1125 ),
1118 1126 );
1119 1127 assert_eq!(dirs, vec!());
1120 1128 }
1121 1129
1122 1130 #[test]
1123 1131 fn test_roots_dirs_and_parents() {
1124 1132 let pats = vec![
1125 1133 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1126 1134 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1127 1135 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1128 1136 ];
1129 1137
1130 1138 let mut roots = HashSet::new();
1131 1139 roots.insert(HgPathBuf::from_bytes(b"g/h"));
1132 1140 roots.insert(HgPathBuf::new());
1133 1141
1134 1142 let dirs = HashSet::new();
1135 1143
1136 1144 let mut parents = HashSet::new();
1137 1145 parents.insert(HgPathBuf::new());
1138 1146 parents.insert(HgPathBuf::from_bytes(b"g"));
1139 1147
1140 1148 assert_eq!(
1141 1149 roots_dirs_and_parents(&pats).unwrap(),
1142 1150 RootsDirsAndParents {
1143 1151 roots,
1144 1152 dirs,
1145 1153 parents
1146 1154 }
1147 1155 );
1148 1156 }
1149 1157
1150 1158 #[test]
1151 1159 fn test_filematcher_visit_children_set() {
1152 1160 // Visitchildrenset
1153 1161 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/foo.txt")];
1154 1162 let matcher = FileMatcher::new(files).unwrap();
1155 1163
1156 1164 let mut set = HashSet::new();
1157 1165 set.insert(HgPathBuf::from_bytes(b"dir"));
1158 1166 assert_eq!(
1159 1167 matcher.visit_children_set(HgPath::new(b"")),
1160 1168 VisitChildrenSet::Set(set)
1161 1169 );
1162 1170
1163 1171 let mut set = HashSet::new();
1164 1172 set.insert(HgPathBuf::from_bytes(b"subdir"));
1165 1173 assert_eq!(
1166 1174 matcher.visit_children_set(HgPath::new(b"dir")),
1167 1175 VisitChildrenSet::Set(set)
1168 1176 );
1169 1177
1170 1178 let mut set = HashSet::new();
1171 1179 set.insert(HgPathBuf::from_bytes(b"foo.txt"));
1172 1180 assert_eq!(
1173 1181 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1174 1182 VisitChildrenSet::Set(set)
1175 1183 );
1176 1184
1177 1185 assert_eq!(
1178 1186 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1179 1187 VisitChildrenSet::Empty
1180 1188 );
1181 1189 assert_eq!(
1182 1190 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
1183 1191 VisitChildrenSet::Empty
1184 1192 );
1185 1193 assert_eq!(
1186 1194 matcher.visit_children_set(HgPath::new(b"folder")),
1187 1195 VisitChildrenSet::Empty
1188 1196 );
1189 1197 }
1190 1198
1191 1199 #[test]
1192 1200 fn test_filematcher_visit_children_set_files_and_dirs() {
1193 1201 let files = vec![
1194 1202 HgPathBuf::from_bytes(b"rootfile.txt"),
1195 1203 HgPathBuf::from_bytes(b"a/file1.txt"),
1196 1204 HgPathBuf::from_bytes(b"a/b/file2.txt"),
1197 1205 // No file in a/b/c
1198 1206 HgPathBuf::from_bytes(b"a/b/c/d/file4.txt"),
1199 1207 ];
1200 1208 let matcher = FileMatcher::new(files).unwrap();
1201 1209
1202 1210 let mut set = HashSet::new();
1203 1211 set.insert(HgPathBuf::from_bytes(b"a"));
1204 1212 set.insert(HgPathBuf::from_bytes(b"rootfile.txt"));
1205 1213 assert_eq!(
1206 1214 matcher.visit_children_set(HgPath::new(b"")),
1207 1215 VisitChildrenSet::Set(set)
1208 1216 );
1209 1217
1210 1218 let mut set = HashSet::new();
1211 1219 set.insert(HgPathBuf::from_bytes(b"b"));
1212 1220 set.insert(HgPathBuf::from_bytes(b"file1.txt"));
1213 1221 assert_eq!(
1214 1222 matcher.visit_children_set(HgPath::new(b"a")),
1215 1223 VisitChildrenSet::Set(set)
1216 1224 );
1217 1225
1218 1226 let mut set = HashSet::new();
1219 1227 set.insert(HgPathBuf::from_bytes(b"c"));
1220 1228 set.insert(HgPathBuf::from_bytes(b"file2.txt"));
1221 1229 assert_eq!(
1222 1230 matcher.visit_children_set(HgPath::new(b"a/b")),
1223 1231 VisitChildrenSet::Set(set)
1224 1232 );
1225 1233
1226 1234 let mut set = HashSet::new();
1227 1235 set.insert(HgPathBuf::from_bytes(b"d"));
1228 1236 assert_eq!(
1229 1237 matcher.visit_children_set(HgPath::new(b"a/b/c")),
1230 1238 VisitChildrenSet::Set(set)
1231 1239 );
1232 1240 let mut set = HashSet::new();
1233 1241 set.insert(HgPathBuf::from_bytes(b"file4.txt"));
1234 1242 assert_eq!(
1235 1243 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
1236 1244 VisitChildrenSet::Set(set)
1237 1245 );
1238 1246
1239 1247 assert_eq!(
1240 1248 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
1241 1249 VisitChildrenSet::Empty
1242 1250 );
1243 1251 assert_eq!(
1244 1252 matcher.visit_children_set(HgPath::new(b"folder")),
1245 1253 VisitChildrenSet::Empty
1246 1254 );
1247 1255 }
1248 1256
1249 1257 #[test]
1250 1258 fn test_patternmatcher() {
1251 1259 // VisitdirPrefix
1252 1260 let m = PatternMatcher::new(vec![IgnorePattern::new(
1253 1261 PatternSyntax::Path,
1254 1262 b"dir/subdir",
1255 1263 Path::new(""),
1256 1264 )])
1257 1265 .unwrap();
1258 1266 assert_eq!(
1259 1267 m.visit_children_set(HgPath::new(b"")),
1260 1268 VisitChildrenSet::This
1261 1269 );
1262 1270 assert_eq!(
1263 1271 m.visit_children_set(HgPath::new(b"dir")),
1264 1272 VisitChildrenSet::This
1265 1273 );
1266 1274 assert_eq!(
1267 1275 m.visit_children_set(HgPath::new(b"dir/subdir")),
1268 1276 VisitChildrenSet::Recursive
1269 1277 );
1270 1278 // OPT: This should probably be Recursive if its parent is?
1271 1279 assert_eq!(
1272 1280 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1273 1281 VisitChildrenSet::This
1274 1282 );
1275 1283 assert_eq!(
1276 1284 m.visit_children_set(HgPath::new(b"folder")),
1277 1285 VisitChildrenSet::Empty
1278 1286 );
1279 1287
1280 1288 // VisitchildrensetPrefix
1281 1289 let m = PatternMatcher::new(vec![IgnorePattern::new(
1282 1290 PatternSyntax::Path,
1283 1291 b"dir/subdir",
1284 1292 Path::new(""),
1285 1293 )])
1286 1294 .unwrap();
1287 1295 assert_eq!(
1288 1296 m.visit_children_set(HgPath::new(b"")),
1289 1297 VisitChildrenSet::This
1290 1298 );
1291 1299 assert_eq!(
1292 1300 m.visit_children_set(HgPath::new(b"dir")),
1293 1301 VisitChildrenSet::This
1294 1302 );
1295 1303 assert_eq!(
1296 1304 m.visit_children_set(HgPath::new(b"dir/subdir")),
1297 1305 VisitChildrenSet::Recursive
1298 1306 );
1299 1307 // OPT: This should probably be Recursive if its parent is?
1300 1308 assert_eq!(
1301 1309 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1302 1310 VisitChildrenSet::This
1303 1311 );
1304 1312 assert_eq!(
1305 1313 m.visit_children_set(HgPath::new(b"folder")),
1306 1314 VisitChildrenSet::Empty
1307 1315 );
1308 1316
1309 1317 // VisitdirRootfilesin
1310 1318 let m = PatternMatcher::new(vec![IgnorePattern::new(
1311 1319 PatternSyntax::RootFiles,
1312 1320 b"dir/subdir",
1313 1321 Path::new(""),
1314 1322 )])
1315 1323 .unwrap();
1316 1324 assert_eq!(
1317 1325 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1318 1326 VisitChildrenSet::Empty
1319 1327 );
1320 1328 assert_eq!(
1321 1329 m.visit_children_set(HgPath::new(b"folder")),
1322 1330 VisitChildrenSet::Empty
1323 1331 );
1324 1332 // FIXME: These should probably be This.
1325 1333 assert_eq!(
1326 1334 m.visit_children_set(HgPath::new(b"")),
1327 1335 VisitChildrenSet::Empty
1328 1336 );
1329 1337 assert_eq!(
1330 1338 m.visit_children_set(HgPath::new(b"dir")),
1331 1339 VisitChildrenSet::Empty
1332 1340 );
1333 1341 assert_eq!(
1334 1342 m.visit_children_set(HgPath::new(b"dir/subdir")),
1335 1343 VisitChildrenSet::Empty
1336 1344 );
1337 1345
1338 1346 // VisitchildrensetRootfilesin
1339 1347 let m = PatternMatcher::new(vec![IgnorePattern::new(
1340 1348 PatternSyntax::RootFiles,
1341 1349 b"dir/subdir",
1342 1350 Path::new(""),
1343 1351 )])
1344 1352 .unwrap();
1345 1353 assert_eq!(
1346 1354 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1347 1355 VisitChildrenSet::Empty
1348 1356 );
1349 1357 assert_eq!(
1350 1358 m.visit_children_set(HgPath::new(b"folder")),
1351 1359 VisitChildrenSet::Empty
1352 1360 );
1353 1361 // FIXME: These should probably be {'dir'}, {'subdir'} and This,
1354 1362 // respectively, or at least This for all three.
1355 1363 assert_eq!(
1356 1364 m.visit_children_set(HgPath::new(b"")),
1357 1365 VisitChildrenSet::Empty
1358 1366 );
1359 1367 assert_eq!(
1360 1368 m.visit_children_set(HgPath::new(b"dir")),
1361 1369 VisitChildrenSet::Empty
1362 1370 );
1363 1371 assert_eq!(
1364 1372 m.visit_children_set(HgPath::new(b"dir/subdir")),
1365 1373 VisitChildrenSet::Empty
1366 1374 );
1367 1375
1368 1376 // VisitdirGlob
1369 1377 let m = PatternMatcher::new(vec![IgnorePattern::new(
1370 1378 PatternSyntax::Glob,
1371 1379 b"dir/z*",
1372 1380 Path::new(""),
1373 1381 )])
1374 1382 .unwrap();
1375 1383 assert_eq!(
1376 1384 m.visit_children_set(HgPath::new(b"")),
1377 1385 VisitChildrenSet::This
1378 1386 );
1379 1387 // FIXME: This probably should be This
1380 1388 assert_eq!(
1381 1389 m.visit_children_set(HgPath::new(b"dir")),
1382 1390 VisitChildrenSet::Empty
1383 1391 );
1384 1392 assert_eq!(
1385 1393 m.visit_children_set(HgPath::new(b"folder")),
1386 1394 VisitChildrenSet::Empty
1387 1395 );
1388 1396 // OPT: these should probably be False.
1389 1397 assert_eq!(
1390 1398 m.visit_children_set(HgPath::new(b"dir/subdir")),
1391 1399 VisitChildrenSet::This
1392 1400 );
1393 1401 assert_eq!(
1394 1402 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1395 1403 VisitChildrenSet::This
1396 1404 );
1397 1405
1398 1406 // VisitchildrensetGlob
1399 1407 let m = PatternMatcher::new(vec![IgnorePattern::new(
1400 1408 PatternSyntax::Glob,
1401 1409 b"dir/z*",
1402 1410 Path::new(""),
1403 1411 )])
1404 1412 .unwrap();
1405 1413 assert_eq!(
1406 1414 m.visit_children_set(HgPath::new(b"")),
1407 1415 VisitChildrenSet::This
1408 1416 );
1409 1417 assert_eq!(
1410 1418 m.visit_children_set(HgPath::new(b"folder")),
1411 1419 VisitChildrenSet::Empty
1412 1420 );
1413 1421 // FIXME: This probably should be This
1414 1422 assert_eq!(
1415 1423 m.visit_children_set(HgPath::new(b"dir")),
1416 1424 VisitChildrenSet::Empty
1417 1425 );
1418 1426 // OPT: these should probably be Empty
1419 1427 assert_eq!(
1420 1428 m.visit_children_set(HgPath::new(b"dir/subdir")),
1421 1429 VisitChildrenSet::This
1422 1430 );
1423 1431 assert_eq!(
1424 1432 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1425 1433 VisitChildrenSet::This
1426 1434 );
1427 1435
1428 1436 // VisitdirFilepath
1429 1437 let m = PatternMatcher::new(vec![IgnorePattern::new(
1430 1438 PatternSyntax::FilePath,
1431 1439 b"dir/z",
1432 1440 Path::new(""),
1433 1441 )])
1434 1442 .unwrap();
1435 1443 assert_eq!(
1436 1444 m.visit_children_set(HgPath::new(b"")),
1437 1445 VisitChildrenSet::This
1438 1446 );
1439 1447 assert_eq!(
1440 1448 m.visit_children_set(HgPath::new(b"dir")),
1441 1449 VisitChildrenSet::This
1442 1450 );
1443 1451 assert_eq!(
1444 1452 m.visit_children_set(HgPath::new(b"folder")),
1445 1453 VisitChildrenSet::Empty
1446 1454 );
1447 1455 assert_eq!(
1448 1456 m.visit_children_set(HgPath::new(b"dir/subdir")),
1449 1457 VisitChildrenSet::Empty
1450 1458 );
1451 1459 assert_eq!(
1452 1460 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1453 1461 VisitChildrenSet::Empty
1454 1462 );
1455 1463
1456 1464 // VisitchildrensetFilepath
1457 1465 let m = PatternMatcher::new(vec![IgnorePattern::new(
1458 1466 PatternSyntax::FilePath,
1459 1467 b"dir/z",
1460 1468 Path::new(""),
1461 1469 )])
1462 1470 .unwrap();
1463 1471 assert_eq!(
1464 1472 m.visit_children_set(HgPath::new(b"")),
1465 1473 VisitChildrenSet::This
1466 1474 );
1467 1475 assert_eq!(
1468 1476 m.visit_children_set(HgPath::new(b"folder")),
1469 1477 VisitChildrenSet::Empty
1470 1478 );
1471 1479 assert_eq!(
1472 1480 m.visit_children_set(HgPath::new(b"dir")),
1473 1481 VisitChildrenSet::This
1474 1482 );
1475 1483 assert_eq!(
1476 1484 m.visit_children_set(HgPath::new(b"dir/subdir")),
1477 1485 VisitChildrenSet::Empty
1478 1486 );
1479 1487 assert_eq!(
1480 1488 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1481 1489 VisitChildrenSet::Empty
1482 1490 );
1483 1491 }
1484 1492
1485 1493 #[test]
1486 1494 fn test_includematcher() {
1487 1495 // VisitchildrensetPrefix
1488 1496 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1489 1497 PatternSyntax::RelPath,
1490 1498 b"dir/subdir",
1491 1499 Path::new(""),
1492 1500 )])
1493 1501 .unwrap();
1494 1502
1495 1503 let mut set = HashSet::new();
1496 1504 set.insert(HgPathBuf::from_bytes(b"dir"));
1497 1505 assert_eq!(
1498 1506 matcher.visit_children_set(HgPath::new(b"")),
1499 1507 VisitChildrenSet::Set(set)
1500 1508 );
1501 1509
1502 1510 let mut set = HashSet::new();
1503 1511 set.insert(HgPathBuf::from_bytes(b"subdir"));
1504 1512 assert_eq!(
1505 1513 matcher.visit_children_set(HgPath::new(b"dir")),
1506 1514 VisitChildrenSet::Set(set)
1507 1515 );
1508 1516 assert_eq!(
1509 1517 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1510 1518 VisitChildrenSet::Recursive
1511 1519 );
1512 1520 // OPT: This should probably be 'all' if its parent is?
1513 1521 assert_eq!(
1514 1522 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1515 1523 VisitChildrenSet::This
1516 1524 );
1517 1525 assert_eq!(
1518 1526 matcher.visit_children_set(HgPath::new(b"folder")),
1519 1527 VisitChildrenSet::Empty
1520 1528 );
1521 1529
1522 1530 // VisitchildrensetRootfilesin
1523 1531 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1524 1532 PatternSyntax::RootFiles,
1525 1533 b"dir/subdir",
1526 1534 Path::new(""),
1527 1535 )])
1528 1536 .unwrap();
1529 1537
1530 1538 let mut set = HashSet::new();
1531 1539 set.insert(HgPathBuf::from_bytes(b"dir"));
1532 1540 assert_eq!(
1533 1541 matcher.visit_children_set(HgPath::new(b"")),
1534 1542 VisitChildrenSet::Set(set)
1535 1543 );
1536 1544
1537 1545 let mut set = HashSet::new();
1538 1546 set.insert(HgPathBuf::from_bytes(b"subdir"));
1539 1547 assert_eq!(
1540 1548 matcher.visit_children_set(HgPath::new(b"dir")),
1541 1549 VisitChildrenSet::Set(set)
1542 1550 );
1543 1551
1544 1552 assert_eq!(
1545 1553 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1546 1554 VisitChildrenSet::This
1547 1555 );
1548 1556 assert_eq!(
1549 1557 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1550 1558 VisitChildrenSet::Empty
1551 1559 );
1552 1560 assert_eq!(
1553 1561 matcher.visit_children_set(HgPath::new(b"folder")),
1554 1562 VisitChildrenSet::Empty
1555 1563 );
1556 1564
1557 1565 // VisitchildrensetGlob
1558 1566 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1559 1567 PatternSyntax::Glob,
1560 1568 b"dir/z*",
1561 1569 Path::new(""),
1562 1570 )])
1563 1571 .unwrap();
1564 1572
1565 1573 let mut set = HashSet::new();
1566 1574 set.insert(HgPathBuf::from_bytes(b"dir"));
1567 1575 assert_eq!(
1568 1576 matcher.visit_children_set(HgPath::new(b"")),
1569 1577 VisitChildrenSet::Set(set)
1570 1578 );
1571 1579 assert_eq!(
1572 1580 matcher.visit_children_set(HgPath::new(b"folder")),
1573 1581 VisitChildrenSet::Empty
1574 1582 );
1575 1583 assert_eq!(
1576 1584 matcher.visit_children_set(HgPath::new(b"dir")),
1577 1585 VisitChildrenSet::This
1578 1586 );
1579 1587 // OPT: these should probably be set().
1580 1588 assert_eq!(
1581 1589 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1582 1590 VisitChildrenSet::This
1583 1591 );
1584 1592 assert_eq!(
1585 1593 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1586 1594 VisitChildrenSet::This
1587 1595 );
1588 1596
1589 1597 // VisitchildrensetFilePath
1590 1598 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1591 1599 PatternSyntax::FilePath,
1592 1600 b"dir/z",
1593 1601 Path::new(""),
1594 1602 )])
1595 1603 .unwrap();
1596 1604
1597 1605 let mut set = HashSet::new();
1598 1606 set.insert(HgPathBuf::from_bytes(b"dir"));
1599 1607 assert_eq!(
1600 1608 matcher.visit_children_set(HgPath::new(b"")),
1601 1609 VisitChildrenSet::Set(set)
1602 1610 );
1603 1611 assert_eq!(
1604 1612 matcher.visit_children_set(HgPath::new(b"folder")),
1605 1613 VisitChildrenSet::Empty
1606 1614 );
1607 1615 let mut set = HashSet::new();
1608 1616 set.insert(HgPathBuf::from_bytes(b"z"));
1609 1617 assert_eq!(
1610 1618 matcher.visit_children_set(HgPath::new(b"dir")),
1611 1619 VisitChildrenSet::Set(set)
1612 1620 );
1613 1621 // OPT: these should probably be set().
1614 1622 assert_eq!(
1615 1623 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1616 1624 VisitChildrenSet::Empty
1617 1625 );
1618 1626 assert_eq!(
1619 1627 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1620 1628 VisitChildrenSet::Empty
1621 1629 );
1622 1630
1623 1631 // Test multiple patterns
1624 1632 let matcher = IncludeMatcher::new(vec![
1625 1633 IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")),
1626 1634 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1627 1635 ])
1628 1636 .unwrap();
1629 1637
1630 1638 assert_eq!(
1631 1639 matcher.visit_children_set(HgPath::new(b"")),
1632 1640 VisitChildrenSet::This
1633 1641 );
1634 1642
1635 1643 // Test multiple patterns
1636 1644 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1637 1645 PatternSyntax::Glob,
1638 1646 b"**/*.exe",
1639 1647 Path::new(""),
1640 1648 )])
1641 1649 .unwrap();
1642 1650
1643 1651 assert_eq!(
1644 1652 matcher.visit_children_set(HgPath::new(b"")),
1645 1653 VisitChildrenSet::This
1646 1654 );
1647 1655 }
1648 1656
1649 1657 #[test]
1650 1658 fn test_unionmatcher() {
1651 1659 // Path + Rootfiles
1652 1660 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1653 1661 PatternSyntax::RelPath,
1654 1662 b"dir/subdir",
1655 1663 Path::new(""),
1656 1664 )])
1657 1665 .unwrap();
1658 1666 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1659 1667 PatternSyntax::RootFiles,
1660 1668 b"dir",
1661 1669 Path::new(""),
1662 1670 )])
1663 1671 .unwrap();
1664 1672 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1665 1673
1666 1674 let mut set = HashSet::new();
1667 1675 set.insert(HgPathBuf::from_bytes(b"dir"));
1668 1676 assert_eq!(
1669 1677 matcher.visit_children_set(HgPath::new(b"")),
1670 1678 VisitChildrenSet::Set(set)
1671 1679 );
1672 1680 assert_eq!(
1673 1681 matcher.visit_children_set(HgPath::new(b"dir")),
1674 1682 VisitChildrenSet::This
1675 1683 );
1676 1684 assert_eq!(
1677 1685 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1678 1686 VisitChildrenSet::Recursive
1679 1687 );
1680 1688 assert_eq!(
1681 1689 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1682 1690 VisitChildrenSet::Empty
1683 1691 );
1684 1692 assert_eq!(
1685 1693 matcher.visit_children_set(HgPath::new(b"folder")),
1686 1694 VisitChildrenSet::Empty
1687 1695 );
1688 1696 assert_eq!(
1689 1697 matcher.visit_children_set(HgPath::new(b"folder")),
1690 1698 VisitChildrenSet::Empty
1691 1699 );
1692 1700
1693 1701 // OPT: These next two could be 'all' instead of 'this'.
1694 1702 assert_eq!(
1695 1703 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1696 1704 VisitChildrenSet::This
1697 1705 );
1698 1706 assert_eq!(
1699 1707 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1700 1708 VisitChildrenSet::This
1701 1709 );
1702 1710
1703 1711 // Path + unrelated Path
1704 1712 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1705 1713 PatternSyntax::RelPath,
1706 1714 b"dir/subdir",
1707 1715 Path::new(""),
1708 1716 )])
1709 1717 .unwrap();
1710 1718 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1711 1719 PatternSyntax::RelPath,
1712 1720 b"folder",
1713 1721 Path::new(""),
1714 1722 )])
1715 1723 .unwrap();
1716 1724 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1717 1725
1718 1726 let mut set = HashSet::new();
1719 1727 set.insert(HgPathBuf::from_bytes(b"folder"));
1720 1728 set.insert(HgPathBuf::from_bytes(b"dir"));
1721 1729 assert_eq!(
1722 1730 matcher.visit_children_set(HgPath::new(b"")),
1723 1731 VisitChildrenSet::Set(set)
1724 1732 );
1725 1733 let mut set = HashSet::new();
1726 1734 set.insert(HgPathBuf::from_bytes(b"subdir"));
1727 1735 assert_eq!(
1728 1736 matcher.visit_children_set(HgPath::new(b"dir")),
1729 1737 VisitChildrenSet::Set(set)
1730 1738 );
1731 1739
1732 1740 assert_eq!(
1733 1741 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1734 1742 VisitChildrenSet::Recursive
1735 1743 );
1736 1744 assert_eq!(
1737 1745 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1738 1746 VisitChildrenSet::Empty
1739 1747 );
1740 1748
1741 1749 assert_eq!(
1742 1750 matcher.visit_children_set(HgPath::new(b"folder")),
1743 1751 VisitChildrenSet::Recursive
1744 1752 );
1745 1753 // OPT: These next two could be 'all' instead of 'this'.
1746 1754 assert_eq!(
1747 1755 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1748 1756 VisitChildrenSet::This
1749 1757 );
1750 1758 assert_eq!(
1751 1759 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1752 1760 VisitChildrenSet::This
1753 1761 );
1754 1762
1755 1763 // Path + subpath
1756 1764 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1757 1765 PatternSyntax::RelPath,
1758 1766 b"dir/subdir/x",
1759 1767 Path::new(""),
1760 1768 )])
1761 1769 .unwrap();
1762 1770 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1763 1771 PatternSyntax::RelPath,
1764 1772 b"dir/subdir",
1765 1773 Path::new(""),
1766 1774 )])
1767 1775 .unwrap();
1768 1776 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1769 1777
1770 1778 let mut set = HashSet::new();
1771 1779 set.insert(HgPathBuf::from_bytes(b"dir"));
1772 1780 assert_eq!(
1773 1781 matcher.visit_children_set(HgPath::new(b"")),
1774 1782 VisitChildrenSet::Set(set)
1775 1783 );
1776 1784 let mut set = HashSet::new();
1777 1785 set.insert(HgPathBuf::from_bytes(b"subdir"));
1778 1786 assert_eq!(
1779 1787 matcher.visit_children_set(HgPath::new(b"dir")),
1780 1788 VisitChildrenSet::Set(set)
1781 1789 );
1782 1790
1783 1791 assert_eq!(
1784 1792 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1785 1793 VisitChildrenSet::Recursive
1786 1794 );
1787 1795 assert_eq!(
1788 1796 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1789 1797 VisitChildrenSet::Empty
1790 1798 );
1791 1799
1792 1800 assert_eq!(
1793 1801 matcher.visit_children_set(HgPath::new(b"folder")),
1794 1802 VisitChildrenSet::Empty
1795 1803 );
1796 1804 assert_eq!(
1797 1805 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1798 1806 VisitChildrenSet::Recursive
1799 1807 );
1800 1808 // OPT: this should probably be 'all' not 'this'.
1801 1809 assert_eq!(
1802 1810 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1803 1811 VisitChildrenSet::This
1804 1812 );
1805 1813 }
1806 1814
1807 1815 #[test]
1808 1816 fn test_intersectionmatcher() {
1809 1817 // Include path + Include rootfiles
1810 1818 let m1 = Box::new(
1811 1819 IncludeMatcher::new(vec![IgnorePattern::new(
1812 1820 PatternSyntax::RelPath,
1813 1821 b"dir/subdir",
1814 1822 Path::new(""),
1815 1823 )])
1816 1824 .unwrap(),
1817 1825 );
1818 1826 let m2 = Box::new(
1819 1827 IncludeMatcher::new(vec![IgnorePattern::new(
1820 1828 PatternSyntax::RootFiles,
1821 1829 b"dir",
1822 1830 Path::new(""),
1823 1831 )])
1824 1832 .unwrap(),
1825 1833 );
1826 1834 let matcher = IntersectionMatcher::new(m1, m2);
1827 1835
1828 1836 let mut set = HashSet::new();
1829 1837 set.insert(HgPathBuf::from_bytes(b"dir"));
1830 1838 assert_eq!(
1831 1839 matcher.visit_children_set(HgPath::new(b"")),
1832 1840 VisitChildrenSet::Set(set)
1833 1841 );
1834 1842 assert_eq!(
1835 1843 matcher.visit_children_set(HgPath::new(b"dir")),
1836 1844 VisitChildrenSet::This
1837 1845 );
1838 1846 assert_eq!(
1839 1847 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1840 1848 VisitChildrenSet::Empty
1841 1849 );
1842 1850 assert_eq!(
1843 1851 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1844 1852 VisitChildrenSet::Empty
1845 1853 );
1846 1854 assert_eq!(
1847 1855 matcher.visit_children_set(HgPath::new(b"folder")),
1848 1856 VisitChildrenSet::Empty
1849 1857 );
1850 1858 assert_eq!(
1851 1859 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1852 1860 VisitChildrenSet::Empty
1853 1861 );
1854 1862 assert_eq!(
1855 1863 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1856 1864 VisitChildrenSet::Empty
1857 1865 );
1858 1866
1859 1867 // Non intersecting paths
1860 1868 let m1 = Box::new(
1861 1869 IncludeMatcher::new(vec![IgnorePattern::new(
1862 1870 PatternSyntax::RelPath,
1863 1871 b"dir/subdir",
1864 1872 Path::new(""),
1865 1873 )])
1866 1874 .unwrap(),
1867 1875 );
1868 1876 let m2 = Box::new(
1869 1877 IncludeMatcher::new(vec![IgnorePattern::new(
1870 1878 PatternSyntax::RelPath,
1871 1879 b"folder",
1872 1880 Path::new(""),
1873 1881 )])
1874 1882 .unwrap(),
1875 1883 );
1876 1884 let matcher = IntersectionMatcher::new(m1, m2);
1877 1885
1878 1886 assert_eq!(
1879 1887 matcher.visit_children_set(HgPath::new(b"")),
1880 1888 VisitChildrenSet::Empty
1881 1889 );
1882 1890 assert_eq!(
1883 1891 matcher.visit_children_set(HgPath::new(b"dir")),
1884 1892 VisitChildrenSet::Empty
1885 1893 );
1886 1894 assert_eq!(
1887 1895 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1888 1896 VisitChildrenSet::Empty
1889 1897 );
1890 1898 assert_eq!(
1891 1899 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1892 1900 VisitChildrenSet::Empty
1893 1901 );
1894 1902 assert_eq!(
1895 1903 matcher.visit_children_set(HgPath::new(b"folder")),
1896 1904 VisitChildrenSet::Empty
1897 1905 );
1898 1906 assert_eq!(
1899 1907 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1900 1908 VisitChildrenSet::Empty
1901 1909 );
1902 1910 assert_eq!(
1903 1911 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1904 1912 VisitChildrenSet::Empty
1905 1913 );
1906 1914
1907 1915 // Nested paths
1908 1916 let m1 = Box::new(
1909 1917 IncludeMatcher::new(vec![IgnorePattern::new(
1910 1918 PatternSyntax::RelPath,
1911 1919 b"dir/subdir/x",
1912 1920 Path::new(""),
1913 1921 )])
1914 1922 .unwrap(),
1915 1923 );
1916 1924 let m2 = Box::new(
1917 1925 IncludeMatcher::new(vec![IgnorePattern::new(
1918 1926 PatternSyntax::RelPath,
1919 1927 b"dir/subdir",
1920 1928 Path::new(""),
1921 1929 )])
1922 1930 .unwrap(),
1923 1931 );
1924 1932 let matcher = IntersectionMatcher::new(m1, m2);
1925 1933
1926 1934 let mut set = HashSet::new();
1927 1935 set.insert(HgPathBuf::from_bytes(b"dir"));
1928 1936 assert_eq!(
1929 1937 matcher.visit_children_set(HgPath::new(b"")),
1930 1938 VisitChildrenSet::Set(set)
1931 1939 );
1932 1940
1933 1941 let mut set = HashSet::new();
1934 1942 set.insert(HgPathBuf::from_bytes(b"subdir"));
1935 1943 assert_eq!(
1936 1944 matcher.visit_children_set(HgPath::new(b"dir")),
1937 1945 VisitChildrenSet::Set(set)
1938 1946 );
1939 1947 let mut set = HashSet::new();
1940 1948 set.insert(HgPathBuf::from_bytes(b"x"));
1941 1949 assert_eq!(
1942 1950 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1943 1951 VisitChildrenSet::Set(set)
1944 1952 );
1945 1953 assert_eq!(
1946 1954 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1947 1955 VisitChildrenSet::Empty
1948 1956 );
1949 1957 assert_eq!(
1950 1958 matcher.visit_children_set(HgPath::new(b"folder")),
1951 1959 VisitChildrenSet::Empty
1952 1960 );
1953 1961 assert_eq!(
1954 1962 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1955 1963 VisitChildrenSet::Empty
1956 1964 );
1957 1965 // OPT: this should probably be 'all' not 'this'.
1958 1966 assert_eq!(
1959 1967 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1960 1968 VisitChildrenSet::This
1961 1969 );
1962 1970
1963 1971 // Diverging paths
1964 1972 let m1 = Box::new(
1965 1973 IncludeMatcher::new(vec![IgnorePattern::new(
1966 1974 PatternSyntax::RelPath,
1967 1975 b"dir/subdir/x",
1968 1976 Path::new(""),
1969 1977 )])
1970 1978 .unwrap(),
1971 1979 );
1972 1980 let m2 = Box::new(
1973 1981 IncludeMatcher::new(vec![IgnorePattern::new(
1974 1982 PatternSyntax::RelPath,
1975 1983 b"dir/subdir/z",
1976 1984 Path::new(""),
1977 1985 )])
1978 1986 .unwrap(),
1979 1987 );
1980 1988 let matcher = IntersectionMatcher::new(m1, m2);
1981 1989
1982 1990 // OPT: these next two could probably be Empty as well.
1983 1991 let mut set = HashSet::new();
1984 1992 set.insert(HgPathBuf::from_bytes(b"dir"));
1985 1993 assert_eq!(
1986 1994 matcher.visit_children_set(HgPath::new(b"")),
1987 1995 VisitChildrenSet::Set(set)
1988 1996 );
1989 1997 // OPT: these next two could probably be Empty as well.
1990 1998 let mut set = HashSet::new();
1991 1999 set.insert(HgPathBuf::from_bytes(b"subdir"));
1992 2000 assert_eq!(
1993 2001 matcher.visit_children_set(HgPath::new(b"dir")),
1994 2002 VisitChildrenSet::Set(set)
1995 2003 );
1996 2004 assert_eq!(
1997 2005 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1998 2006 VisitChildrenSet::Empty
1999 2007 );
2000 2008 assert_eq!(
2001 2009 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2002 2010 VisitChildrenSet::Empty
2003 2011 );
2004 2012 assert_eq!(
2005 2013 matcher.visit_children_set(HgPath::new(b"folder")),
2006 2014 VisitChildrenSet::Empty
2007 2015 );
2008 2016 assert_eq!(
2009 2017 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2010 2018 VisitChildrenSet::Empty
2011 2019 );
2012 2020 assert_eq!(
2013 2021 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2014 2022 VisitChildrenSet::Empty
2015 2023 );
2016 2024 }
2017 2025
2018 2026 #[test]
2019 2027 fn test_differencematcher() {
2020 2028 // Two alwaysmatchers should function like a nevermatcher
2021 2029 let m1 = AlwaysMatcher;
2022 2030 let m2 = AlwaysMatcher;
2023 2031 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2024 2032
2025 2033 for case in &[
2026 2034 &b""[..],
2027 2035 b"dir",
2028 2036 b"dir/subdir",
2029 2037 b"dir/subdir/z",
2030 2038 b"dir/foo",
2031 2039 b"dir/subdir/x",
2032 2040 b"folder",
2033 2041 ] {
2034 2042 assert_eq!(
2035 2043 matcher.visit_children_set(HgPath::new(case)),
2036 2044 VisitChildrenSet::Empty
2037 2045 );
2038 2046 }
2039 2047
2040 2048 // One always and one never should behave the same as an always
2041 2049 let m1 = AlwaysMatcher;
2042 2050 let m2 = NeverMatcher;
2043 2051 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2044 2052
2045 2053 for case in &[
2046 2054 &b""[..],
2047 2055 b"dir",
2048 2056 b"dir/subdir",
2049 2057 b"dir/subdir/z",
2050 2058 b"dir/foo",
2051 2059 b"dir/subdir/x",
2052 2060 b"folder",
2053 2061 ] {
2054 2062 assert_eq!(
2055 2063 matcher.visit_children_set(HgPath::new(case)),
2056 2064 VisitChildrenSet::Recursive
2057 2065 );
2058 2066 }
2059 2067
2060 2068 // Two include matchers
2061 2069 let m1 = Box::new(
2062 2070 IncludeMatcher::new(vec![IgnorePattern::new(
2063 2071 PatternSyntax::RelPath,
2064 2072 b"dir/subdir",
2065 2073 Path::new("/repo"),
2066 2074 )])
2067 2075 .unwrap(),
2068 2076 );
2069 2077 let m2 = Box::new(
2070 2078 IncludeMatcher::new(vec![IgnorePattern::new(
2071 2079 PatternSyntax::RootFiles,
2072 2080 b"dir",
2073 2081 Path::new("/repo"),
2074 2082 )])
2075 2083 .unwrap(),
2076 2084 );
2077 2085
2078 2086 let matcher = DifferenceMatcher::new(m1, m2);
2079 2087
2080 2088 let mut set = HashSet::new();
2081 2089 set.insert(HgPathBuf::from_bytes(b"dir"));
2082 2090 assert_eq!(
2083 2091 matcher.visit_children_set(HgPath::new(b"")),
2084 2092 VisitChildrenSet::Set(set)
2085 2093 );
2086 2094
2087 2095 let mut set = HashSet::new();
2088 2096 set.insert(HgPathBuf::from_bytes(b"subdir"));
2089 2097 assert_eq!(
2090 2098 matcher.visit_children_set(HgPath::new(b"dir")),
2091 2099 VisitChildrenSet::Set(set)
2092 2100 );
2093 2101 assert_eq!(
2094 2102 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
2095 2103 VisitChildrenSet::Recursive
2096 2104 );
2097 2105 assert_eq!(
2098 2106 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2099 2107 VisitChildrenSet::Empty
2100 2108 );
2101 2109 assert_eq!(
2102 2110 matcher.visit_children_set(HgPath::new(b"folder")),
2103 2111 VisitChildrenSet::Empty
2104 2112 );
2105 2113 assert_eq!(
2106 2114 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2107 2115 VisitChildrenSet::This
2108 2116 );
2109 2117 assert_eq!(
2110 2118 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2111 2119 VisitChildrenSet::This
2112 2120 );
2113 2121 }
2114 2122 }
General Comments 0
You need to be logged in to leave comments. Login now