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