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