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