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