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