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