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