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