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