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