##// END OF EJS Templates
rust-matchers: add TODO about incomplete `Display` for `IncludeMatcher`...
Raphaël Gomès -
r45312:de0fb446 default
parent child Browse files
Show More
@@ -1,948 +1,954 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 #[cfg(feature = "with-re2")]
10 #[cfg(feature = "with-re2")]
11 use crate::re2::Re2;
11 use crate::re2::Re2;
12 use crate::{
12 use crate::{
13 dirstate::dirs_multiset::DirsChildrenMultiset,
13 dirstate::dirs_multiset::DirsChildrenMultiset,
14 filepatterns::{
14 filepatterns::{
15 build_single_regex, filter_subincludes, get_patterns_from_file,
15 build_single_regex, filter_subincludes, get_patterns_from_file,
16 PatternFileWarning, PatternResult, SubInclude,
16 PatternFileWarning, PatternResult, SubInclude,
17 },
17 },
18 utils::{
18 utils::{
19 files::find_dirs,
19 files::find_dirs,
20 hg_path::{HgPath, HgPathBuf},
20 hg_path::{HgPath, HgPathBuf},
21 Escaped,
21 Escaped,
22 },
22 },
23 DirsMultiset, DirstateMapError, FastHashMap, IgnorePattern, PatternError,
23 DirsMultiset, DirstateMapError, FastHashMap, IgnorePattern, PatternError,
24 PatternSyntax,
24 PatternSyntax,
25 };
25 };
26
26
27 use crate::filepatterns::normalize_path_bytes;
27 use crate::filepatterns::normalize_path_bytes;
28 use std::borrow::ToOwned;
28 use std::borrow::ToOwned;
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::iter::FromIterator;
31 use std::iter::FromIterator;
32 use std::ops::Deref;
32 use std::ops::Deref;
33 use std::path::{Path, PathBuf};
33 use std::path::{Path, PathBuf};
34
34
35 use micro_timer::timed;
35 use micro_timer::timed;
36
36
37 #[derive(Debug, PartialEq)]
37 #[derive(Debug, PartialEq)]
38 pub enum VisitChildrenSet<'a> {
38 pub enum VisitChildrenSet<'a> {
39 /// Don't visit anything
39 /// Don't visit anything
40 Empty,
40 Empty,
41 /// Only visit this directory
41 /// Only visit this directory
42 This,
42 This,
43 /// Visit this directory and these subdirectories
43 /// Visit this directory and these subdirectories
44 /// TODO Should we implement a `NonEmptyHashSet`?
44 /// TODO Should we implement a `NonEmptyHashSet`?
45 Set(HashSet<&'a HgPath>),
45 Set(HashSet<&'a HgPath>),
46 /// Visit this directory and all subdirectories
46 /// Visit this directory and all subdirectories
47 Recursive,
47 Recursive,
48 }
48 }
49
49
50 pub trait Matcher {
50 pub trait Matcher {
51 /// Explicitly listed files
51 /// Explicitly listed files
52 fn file_set(&self) -> Option<&HashSet<&HgPath>>;
52 fn file_set(&self) -> Option<&HashSet<&HgPath>>;
53 /// Returns whether `filename` is in `file_set`
53 /// Returns whether `filename` is in `file_set`
54 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool;
54 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool;
55 /// Returns whether `filename` is matched by this matcher
55 /// Returns whether `filename` is matched by this matcher
56 fn matches(&self, filename: impl AsRef<HgPath>) -> bool;
56 fn matches(&self, filename: impl AsRef<HgPath>) -> bool;
57 /// Decides whether a directory should be visited based on whether it
57 /// Decides whether a directory should be visited based on whether it
58 /// has potential matches in it or one of its subdirectories, and
58 /// has potential matches in it or one of its subdirectories, and
59 /// potentially lists which subdirectories of that directory should be
59 /// potentially lists which subdirectories of that directory should be
60 /// visited. This is based on the match's primary, included, and excluded
60 /// visited. This is based on the match's primary, included, and excluded
61 /// patterns.
61 /// patterns.
62 ///
62 ///
63 /// # Example
63 /// # Example
64 ///
64 ///
65 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
65 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
66 /// return the following values (assuming the implementation of
66 /// return the following values (assuming the implementation of
67 /// visit_children_set is capable of recognizing this; some implementations
67 /// visit_children_set is capable of recognizing this; some implementations
68 /// are not).
68 /// are not).
69 ///
69 ///
70 /// ```text
70 /// ```text
71 /// ```ignore
71 /// ```ignore
72 /// '' -> {'foo', 'qux'}
72 /// '' -> {'foo', 'qux'}
73 /// 'baz' -> set()
73 /// 'baz' -> set()
74 /// 'foo' -> {'bar'}
74 /// 'foo' -> {'bar'}
75 /// // Ideally this would be `Recursive`, but since the prefix nature of
75 /// // Ideally this would be `Recursive`, but since the prefix nature of
76 /// // matchers is applied to the entire matcher, we have to downgrade this
76 /// // matchers is applied to the entire matcher, we have to downgrade this
77 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
77 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
78 /// // `RootFilesIn'-kind matcher being mixed in.
78 /// // `RootFilesIn'-kind matcher being mixed in.
79 /// 'foo/bar' -> 'this'
79 /// 'foo/bar' -> 'this'
80 /// 'qux' -> 'this'
80 /// 'qux' -> 'this'
81 /// ```
81 /// ```
82 /// # Important
82 /// # Important
83 ///
83 ///
84 /// Most matchers do not know if they're representing files or
84 /// Most matchers do not know if they're representing files or
85 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
85 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
86 /// file or a directory, so `visit_children_set('dir')` for most matchers
86 /// file or a directory, so `visit_children_set('dir')` for most matchers
87 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
87 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
88 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
88 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
89 /// it may return `VisitChildrenSet::This`.
89 /// it may return `VisitChildrenSet::This`.
90 /// Do not rely on the return being a `HashSet` indicating that there are
90 /// Do not rely on the return being a `HashSet` indicating that there are
91 /// no files in this dir to investigate (or equivalently that if there are
91 /// no files in this dir to investigate (or equivalently that if there are
92 /// files to investigate in 'dir' that it will always return
92 /// files to investigate in 'dir' that it will always return
93 /// `VisitChildrenSet::This`).
93 /// `VisitChildrenSet::This`).
94 fn visit_children_set(
94 fn visit_children_set(
95 &self,
95 &self,
96 directory: impl AsRef<HgPath>,
96 directory: impl AsRef<HgPath>,
97 ) -> VisitChildrenSet;
97 ) -> VisitChildrenSet;
98 /// Matcher will match everything and `files_set()` will be empty:
98 /// Matcher will match everything and `files_set()` will be empty:
99 /// optimization might be possible.
99 /// optimization might be possible.
100 fn matches_everything(&self) -> bool;
100 fn matches_everything(&self) -> bool;
101 /// Matcher will match exactly the files in `files_set()`: optimization
101 /// Matcher will match exactly the files in `files_set()`: optimization
102 /// might be possible.
102 /// might be possible.
103 fn is_exact(&self) -> bool;
103 fn is_exact(&self) -> bool;
104 }
104 }
105
105
106 /// Matches everything.
106 /// Matches everything.
107 ///```
107 ///```
108 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
108 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
109 ///
109 ///
110 /// let matcher = AlwaysMatcher;
110 /// let matcher = AlwaysMatcher;
111 ///
111 ///
112 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
112 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
113 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
113 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
114 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
114 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
115 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
115 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
116 /// ```
116 /// ```
117 #[derive(Debug)]
117 #[derive(Debug)]
118 pub struct AlwaysMatcher;
118 pub struct AlwaysMatcher;
119
119
120 impl Matcher for AlwaysMatcher {
120 impl Matcher for AlwaysMatcher {
121 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
121 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
122 None
122 None
123 }
123 }
124 fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool {
124 fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool {
125 false
125 false
126 }
126 }
127 fn matches(&self, _filename: impl AsRef<HgPath>) -> bool {
127 fn matches(&self, _filename: impl AsRef<HgPath>) -> bool {
128 true
128 true
129 }
129 }
130 fn visit_children_set(
130 fn visit_children_set(
131 &self,
131 &self,
132 _directory: impl AsRef<HgPath>,
132 _directory: impl AsRef<HgPath>,
133 ) -> VisitChildrenSet {
133 ) -> VisitChildrenSet {
134 VisitChildrenSet::Recursive
134 VisitChildrenSet::Recursive
135 }
135 }
136 fn matches_everything(&self) -> bool {
136 fn matches_everything(&self) -> bool {
137 true
137 true
138 }
138 }
139 fn is_exact(&self) -> bool {
139 fn is_exact(&self) -> bool {
140 false
140 false
141 }
141 }
142 }
142 }
143
143
144 /// Matches the input files exactly. They are interpreted as paths, not
144 /// Matches the input files exactly. They are interpreted as paths, not
145 /// patterns.
145 /// patterns.
146 ///
146 ///
147 ///```
147 ///```
148 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath };
148 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath };
149 ///
149 ///
150 /// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")];
150 /// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")];
151 /// let matcher = FileMatcher::new(&files).unwrap();
151 /// let matcher = FileMatcher::new(&files).unwrap();
152 ///
152 ///
153 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
153 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
154 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
154 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
155 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
155 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
156 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
156 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
157 /// ```
157 /// ```
158 #[derive(Debug)]
158 #[derive(Debug)]
159 pub struct FileMatcher<'a> {
159 pub struct FileMatcher<'a> {
160 files: HashSet<&'a HgPath>,
160 files: HashSet<&'a HgPath>,
161 dirs: DirsMultiset,
161 dirs: DirsMultiset,
162 }
162 }
163
163
164 impl<'a> FileMatcher<'a> {
164 impl<'a> FileMatcher<'a> {
165 pub fn new(
165 pub fn new(
166 files: &'a [impl AsRef<HgPath>],
166 files: &'a [impl AsRef<HgPath>],
167 ) -> Result<Self, DirstateMapError> {
167 ) -> Result<Self, DirstateMapError> {
168 Ok(Self {
168 Ok(Self {
169 files: HashSet::from_iter(files.iter().map(|f| f.as_ref())),
169 files: HashSet::from_iter(files.iter().map(|f| f.as_ref())),
170 dirs: DirsMultiset::from_manifest(files)?,
170 dirs: DirsMultiset::from_manifest(files)?,
171 })
171 })
172 }
172 }
173 fn inner_matches(&self, filename: impl AsRef<HgPath>) -> bool {
173 fn inner_matches(&self, filename: impl AsRef<HgPath>) -> bool {
174 self.files.contains(filename.as_ref())
174 self.files.contains(filename.as_ref())
175 }
175 }
176 }
176 }
177
177
178 impl<'a> Matcher for FileMatcher<'a> {
178 impl<'a> Matcher for FileMatcher<'a> {
179 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
179 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
180 Some(&self.files)
180 Some(&self.files)
181 }
181 }
182 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool {
182 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool {
183 self.inner_matches(filename)
183 self.inner_matches(filename)
184 }
184 }
185 fn matches(&self, filename: impl AsRef<HgPath>) -> bool {
185 fn matches(&self, filename: impl AsRef<HgPath>) -> bool {
186 self.inner_matches(filename)
186 self.inner_matches(filename)
187 }
187 }
188 fn visit_children_set(
188 fn visit_children_set(
189 &self,
189 &self,
190 directory: impl AsRef<HgPath>,
190 directory: impl AsRef<HgPath>,
191 ) -> VisitChildrenSet {
191 ) -> VisitChildrenSet {
192 if self.files.is_empty() || !self.dirs.contains(&directory) {
192 if self.files.is_empty() || !self.dirs.contains(&directory) {
193 return VisitChildrenSet::Empty;
193 return VisitChildrenSet::Empty;
194 }
194 }
195 let dirs_as_set = self.dirs.iter().map(|k| k.deref()).collect();
195 let dirs_as_set = self.dirs.iter().map(|k| k.deref()).collect();
196
196
197 let mut candidates: HashSet<&HgPath> =
197 let mut candidates: HashSet<&HgPath> =
198 self.files.union(&dirs_as_set).map(|k| *k).collect();
198 self.files.union(&dirs_as_set).map(|k| *k).collect();
199 candidates.remove(HgPath::new(b""));
199 candidates.remove(HgPath::new(b""));
200
200
201 if !directory.as_ref().is_empty() {
201 if !directory.as_ref().is_empty() {
202 let directory = [directory.as_ref().as_bytes(), b"/"].concat();
202 let directory = [directory.as_ref().as_bytes(), b"/"].concat();
203 candidates = candidates
203 candidates = candidates
204 .iter()
204 .iter()
205 .filter_map(|c| {
205 .filter_map(|c| {
206 if c.as_bytes().starts_with(&directory) {
206 if c.as_bytes().starts_with(&directory) {
207 Some(HgPath::new(&c.as_bytes()[directory.len()..]))
207 Some(HgPath::new(&c.as_bytes()[directory.len()..]))
208 } else {
208 } else {
209 None
209 None
210 }
210 }
211 })
211 })
212 .collect();
212 .collect();
213 }
213 }
214
214
215 // `self.dirs` includes all of the directories, recursively, so if
215 // `self.dirs` includes all of the directories, recursively, so if
216 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
216 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
217 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
217 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
218 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
218 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
219 // subdir will be in there without a slash.
219 // subdir will be in there without a slash.
220 VisitChildrenSet::Set(
220 VisitChildrenSet::Set(
221 candidates
221 candidates
222 .iter()
222 .iter()
223 .filter_map(|c| {
223 .filter_map(|c| {
224 if c.bytes().all(|b| *b != b'/') {
224 if c.bytes().all(|b| *b != b'/') {
225 Some(*c)
225 Some(*c)
226 } else {
226 } else {
227 None
227 None
228 }
228 }
229 })
229 })
230 .collect(),
230 .collect(),
231 )
231 )
232 }
232 }
233 fn matches_everything(&self) -> bool {
233 fn matches_everything(&self) -> bool {
234 false
234 false
235 }
235 }
236 fn is_exact(&self) -> bool {
236 fn is_exact(&self) -> bool {
237 true
237 true
238 }
238 }
239 }
239 }
240
240
241 /// Matches files that are included in the ignore rules.
241 /// Matches files that are included in the ignore rules.
242 #[cfg_attr(
242 #[cfg_attr(
243 feature = "with-re2",
243 feature = "with-re2",
244 doc = r##"
244 doc = r##"
245 ```
245 ```
246 use hg::{
246 use hg::{
247 matchers::{IncludeMatcher, Matcher},
247 matchers::{IncludeMatcher, Matcher},
248 IgnorePattern,
248 IgnorePattern,
249 PatternSyntax,
249 PatternSyntax,
250 utils::hg_path::HgPath
250 utils::hg_path::HgPath
251 };
251 };
252 use std::path::Path;
252 use std::path::Path;
253 ///
253 ///
254 let ignore_patterns =
254 let ignore_patterns =
255 vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
255 vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
256 let (matcher, _) = IncludeMatcher::new(ignore_patterns, "").unwrap();
256 let (matcher, _) = IncludeMatcher::new(ignore_patterns, "").unwrap();
257 ///
257 ///
258 assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
258 assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
259 assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
259 assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
260 assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
260 assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
261 assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
261 assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
262 ```
262 ```
263 "##
263 "##
264 )]
264 )]
265 pub struct IncludeMatcher<'a> {
265 pub struct IncludeMatcher<'a> {
266 patterns: Vec<u8>,
266 patterns: Vec<u8>,
267 match_fn: Box<dyn for<'r> Fn(&'r HgPath) -> bool + 'a + Sync>,
267 match_fn: Box<dyn for<'r> Fn(&'r HgPath) -> bool + 'a + Sync>,
268 /// Whether all the patterns match a prefix (i.e. recursively)
268 /// Whether all the patterns match a prefix (i.e. recursively)
269 prefix: bool,
269 prefix: bool,
270 roots: HashSet<HgPathBuf>,
270 roots: HashSet<HgPathBuf>,
271 dirs: HashSet<HgPathBuf>,
271 dirs: HashSet<HgPathBuf>,
272 parents: HashSet<HgPathBuf>,
272 parents: HashSet<HgPathBuf>,
273 }
273 }
274
274
275 impl<'a> Matcher for IncludeMatcher<'a> {
275 impl<'a> Matcher for IncludeMatcher<'a> {
276 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
276 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
277 None
277 None
278 }
278 }
279
279
280 fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool {
280 fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool {
281 false
281 false
282 }
282 }
283
283
284 fn matches(&self, filename: impl AsRef<HgPath>) -> bool {
284 fn matches(&self, filename: impl AsRef<HgPath>) -> bool {
285 (self.match_fn)(filename.as_ref())
285 (self.match_fn)(filename.as_ref())
286 }
286 }
287
287
288 fn visit_children_set(
288 fn visit_children_set(
289 &self,
289 &self,
290 directory: impl AsRef<HgPath>,
290 directory: impl AsRef<HgPath>,
291 ) -> VisitChildrenSet {
291 ) -> VisitChildrenSet {
292 let dir = directory.as_ref();
292 let dir = directory.as_ref();
293 if self.prefix && self.roots.contains(dir) {
293 if self.prefix && self.roots.contains(dir) {
294 return VisitChildrenSet::Recursive;
294 return VisitChildrenSet::Recursive;
295 }
295 }
296 if self.roots.contains(HgPath::new(b""))
296 if self.roots.contains(HgPath::new(b""))
297 || self.roots.contains(dir)
297 || self.roots.contains(dir)
298 || self.dirs.contains(dir)
298 || self.dirs.contains(dir)
299 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
299 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
300 {
300 {
301 return VisitChildrenSet::This;
301 return VisitChildrenSet::This;
302 }
302 }
303
303
304 if self.parents.contains(directory.as_ref()) {
304 if self.parents.contains(directory.as_ref()) {
305 let multiset = self.get_all_parents_children();
305 let multiset = self.get_all_parents_children();
306 if let Some(children) = multiset.get(dir) {
306 if let Some(children) = multiset.get(dir) {
307 return VisitChildrenSet::Set(children.to_owned());
307 return VisitChildrenSet::Set(children.to_owned());
308 }
308 }
309 }
309 }
310 VisitChildrenSet::Empty
310 VisitChildrenSet::Empty
311 }
311 }
312
312
313 fn matches_everything(&self) -> bool {
313 fn matches_everything(&self) -> bool {
314 false
314 false
315 }
315 }
316
316
317 fn is_exact(&self) -> bool {
317 fn is_exact(&self) -> bool {
318 false
318 false
319 }
319 }
320 }
320 }
321
321
322 #[cfg(feature = "with-re2")]
322 #[cfg(feature = "with-re2")]
323 /// Returns a function that matches an `HgPath` against the given regex
323 /// Returns a function that matches an `HgPath` against the given regex
324 /// pattern.
324 /// pattern.
325 ///
325 ///
326 /// This can fail when the pattern is invalid or not supported by the
326 /// This can fail when the pattern is invalid or not supported by the
327 /// underlying engine `Re2`, for instance anything with back-references.
327 /// underlying engine `Re2`, for instance anything with back-references.
328 #[timed]
328 #[timed]
329 fn re_matcher(
329 fn re_matcher(
330 pattern: &[u8],
330 pattern: &[u8],
331 ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
331 ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
332 let regex = Re2::new(pattern);
332 let regex = Re2::new(pattern);
333 let regex = regex.map_err(|e| PatternError::UnsupportedSyntax(e))?;
333 let regex = regex.map_err(|e| PatternError::UnsupportedSyntax(e))?;
334 Ok(move |path: &HgPath| regex.is_match(path.as_bytes()))
334 Ok(move |path: &HgPath| regex.is_match(path.as_bytes()))
335 }
335 }
336
336
337 #[cfg(not(feature = "with-re2"))]
337 #[cfg(not(feature = "with-re2"))]
338 /// Returns a function that matches an `HgPath` against the given regex
338 /// Returns a function that matches an `HgPath` against the given regex
339 /// pattern.
339 /// pattern.
340 ///
340 ///
341 /// This can fail when the pattern is invalid or not supported by the
341 /// This can fail when the pattern is invalid or not supported by the
342 /// underlying engine (the `regex` crate), for instance anything with
342 /// underlying engine (the `regex` crate), for instance anything with
343 /// back-references.
343 /// back-references.
344 #[timed]
344 #[timed]
345 fn re_matcher(
345 fn re_matcher(
346 pattern: &[u8],
346 pattern: &[u8],
347 ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
347 ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
348 use std::io::Write;
348 use std::io::Write;
349
349
350 let mut escaped_bytes = vec![];
350 let mut escaped_bytes = vec![];
351 for byte in pattern {
351 for byte in pattern {
352 if *byte > 127 {
352 if *byte > 127 {
353 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
353 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
354 } else {
354 } else {
355 escaped_bytes.push(*byte);
355 escaped_bytes.push(*byte);
356 }
356 }
357 }
357 }
358
358
359 // Avoid the cost of UTF8 checking
359 // Avoid the cost of UTF8 checking
360 //
360 //
361 // # Safety
361 // # Safety
362 // This is safe because we escaped all non-ASCII bytes.
362 // This is safe because we escaped all non-ASCII bytes.
363 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
363 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
364 let re = regex::bytes::RegexBuilder::new(&pattern_string)
364 let re = regex::bytes::RegexBuilder::new(&pattern_string)
365 .unicode(false)
365 .unicode(false)
366 .build()
366 .build()
367 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
367 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
368
368
369 Ok(move |path: &HgPath| re.is_match(path.as_bytes()))
369 Ok(move |path: &HgPath| re.is_match(path.as_bytes()))
370 }
370 }
371
371
372 /// Returns the regex pattern and a function that matches an `HgPath` against
372 /// Returns the regex pattern and a function that matches an `HgPath` against
373 /// said regex formed by the given ignore patterns.
373 /// said regex formed by the given ignore patterns.
374 fn build_regex_match<'a>(
374 fn build_regex_match<'a>(
375 ignore_patterns: &'a [&'a IgnorePattern],
375 ignore_patterns: &'a [&'a IgnorePattern],
376 ) -> PatternResult<(Vec<u8>, Box<dyn Fn(&HgPath) -> bool + Sync>)> {
376 ) -> PatternResult<(Vec<u8>, Box<dyn Fn(&HgPath) -> bool + Sync>)> {
377 let mut regexps = vec![];
377 let mut regexps = vec![];
378 let mut exact_set = HashSet::new();
378 let mut exact_set = HashSet::new();
379
379
380 for pattern in ignore_patterns {
380 for pattern in ignore_patterns {
381 if let Some(re) = build_single_regex(pattern)? {
381 if let Some(re) = build_single_regex(pattern)? {
382 regexps.push(re);
382 regexps.push(re);
383 } else {
383 } else {
384 let exact = normalize_path_bytes(&pattern.pattern);
384 let exact = normalize_path_bytes(&pattern.pattern);
385 exact_set.insert(HgPathBuf::from_bytes(&exact));
385 exact_set.insert(HgPathBuf::from_bytes(&exact));
386 }
386 }
387 }
387 }
388
388
389 let full_regex = regexps.join(&b'|');
389 let full_regex = regexps.join(&b'|');
390
390
391 // An empty pattern would cause the regex engine to incorrectly match the
391 // An empty pattern would cause the regex engine to incorrectly match the
392 // (empty) root directory
392 // (empty) root directory
393 let func = if !(regexps.is_empty()) {
393 let func = if !(regexps.is_empty()) {
394 let matcher = re_matcher(&full_regex)?;
394 let matcher = re_matcher(&full_regex)?;
395 let func = move |filename: &HgPath| {
395 let func = move |filename: &HgPath| {
396 exact_set.contains(filename) || matcher(filename)
396 exact_set.contains(filename) || matcher(filename)
397 };
397 };
398 Box::new(func) as Box<dyn Fn(&HgPath) -> bool + Sync>
398 Box::new(func) as Box<dyn Fn(&HgPath) -> bool + Sync>
399 } else {
399 } else {
400 let func = move |filename: &HgPath| exact_set.contains(filename);
400 let func = move |filename: &HgPath| exact_set.contains(filename);
401 Box::new(func) as Box<dyn Fn(&HgPath) -> bool + Sync>
401 Box::new(func) as Box<dyn Fn(&HgPath) -> bool + Sync>
402 };
402 };
403
403
404 Ok((full_regex, func))
404 Ok((full_regex, func))
405 }
405 }
406
406
407 /// Returns roots and directories corresponding to each pattern.
407 /// Returns roots and directories corresponding to each pattern.
408 ///
408 ///
409 /// This calculates the roots and directories exactly matching the patterns and
409 /// This calculates the roots and directories exactly matching the patterns and
410 /// returns a tuple of (roots, dirs). It does not return other directories
410 /// returns a tuple of (roots, dirs). It does not return other directories
411 /// which may also need to be considered, like the parent directories.
411 /// which may also need to be considered, like the parent directories.
412 fn roots_and_dirs(
412 fn roots_and_dirs(
413 ignore_patterns: &[IgnorePattern],
413 ignore_patterns: &[IgnorePattern],
414 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
414 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
415 let mut roots = Vec::new();
415 let mut roots = Vec::new();
416 let mut dirs = Vec::new();
416 let mut dirs = Vec::new();
417
417
418 for ignore_pattern in ignore_patterns {
418 for ignore_pattern in ignore_patterns {
419 let IgnorePattern {
419 let IgnorePattern {
420 syntax, pattern, ..
420 syntax, pattern, ..
421 } = ignore_pattern;
421 } = ignore_pattern;
422 match syntax {
422 match syntax {
423 PatternSyntax::RootGlob | PatternSyntax::Glob => {
423 PatternSyntax::RootGlob | PatternSyntax::Glob => {
424 let mut root = vec![];
424 let mut root = vec![];
425
425
426 for p in pattern.split(|c| *c == b'/') {
426 for p in pattern.split(|c| *c == b'/') {
427 if p.iter().any(|c| match *c {
427 if p.iter().any(|c| match *c {
428 b'[' | b'{' | b'*' | b'?' => true,
428 b'[' | b'{' | b'*' | b'?' => true,
429 _ => false,
429 _ => false,
430 }) {
430 }) {
431 break;
431 break;
432 }
432 }
433 root.push(HgPathBuf::from_bytes(p));
433 root.push(HgPathBuf::from_bytes(p));
434 }
434 }
435 let buf =
435 let buf =
436 root.iter().fold(HgPathBuf::new(), |acc, r| acc.join(r));
436 root.iter().fold(HgPathBuf::new(), |acc, r| acc.join(r));
437 roots.push(buf);
437 roots.push(buf);
438 }
438 }
439 PatternSyntax::Path | PatternSyntax::RelPath => {
439 PatternSyntax::Path | PatternSyntax::RelPath => {
440 let pat = HgPath::new(if pattern == b"." {
440 let pat = HgPath::new(if pattern == b"." {
441 &[] as &[u8]
441 &[] as &[u8]
442 } else {
442 } else {
443 pattern
443 pattern
444 });
444 });
445 roots.push(pat.to_owned());
445 roots.push(pat.to_owned());
446 }
446 }
447 PatternSyntax::RootFiles => {
447 PatternSyntax::RootFiles => {
448 let pat = if pattern == b"." {
448 let pat = if pattern == b"." {
449 &[] as &[u8]
449 &[] as &[u8]
450 } else {
450 } else {
451 pattern
451 pattern
452 };
452 };
453 dirs.push(HgPathBuf::from_bytes(pat));
453 dirs.push(HgPathBuf::from_bytes(pat));
454 }
454 }
455 _ => {
455 _ => {
456 roots.push(HgPathBuf::new());
456 roots.push(HgPathBuf::new());
457 }
457 }
458 }
458 }
459 }
459 }
460 (roots, dirs)
460 (roots, dirs)
461 }
461 }
462
462
463 /// Paths extracted from patterns
463 /// Paths extracted from patterns
464 #[derive(Debug, PartialEq)]
464 #[derive(Debug, PartialEq)]
465 struct RootsDirsAndParents {
465 struct RootsDirsAndParents {
466 /// Directories to match recursively
466 /// Directories to match recursively
467 pub roots: HashSet<HgPathBuf>,
467 pub roots: HashSet<HgPathBuf>,
468 /// Directories to match non-recursively
468 /// Directories to match non-recursively
469 pub dirs: HashSet<HgPathBuf>,
469 pub dirs: HashSet<HgPathBuf>,
470 /// Implicitly required directories to go to items in either roots or dirs
470 /// Implicitly required directories to go to items in either roots or dirs
471 pub parents: HashSet<HgPathBuf>,
471 pub parents: HashSet<HgPathBuf>,
472 }
472 }
473
473
474 /// Extract roots, dirs and parents from patterns.
474 /// Extract roots, dirs and parents from patterns.
475 fn roots_dirs_and_parents(
475 fn roots_dirs_and_parents(
476 ignore_patterns: &[IgnorePattern],
476 ignore_patterns: &[IgnorePattern],
477 ) -> PatternResult<RootsDirsAndParents> {
477 ) -> PatternResult<RootsDirsAndParents> {
478 let (roots, dirs) = roots_and_dirs(ignore_patterns);
478 let (roots, dirs) = roots_and_dirs(ignore_patterns);
479
479
480 let mut parents = HashSet::new();
480 let mut parents = HashSet::new();
481
481
482 parents.extend(
482 parents.extend(
483 DirsMultiset::from_manifest(&dirs)
483 DirsMultiset::from_manifest(&dirs)
484 .map_err(|e| match e {
484 .map_err(|e| match e {
485 DirstateMapError::InvalidPath(e) => e,
485 DirstateMapError::InvalidPath(e) => e,
486 _ => unreachable!(),
486 _ => unreachable!(),
487 })?
487 })?
488 .iter()
488 .iter()
489 .map(|k| k.to_owned()),
489 .map(|k| k.to_owned()),
490 );
490 );
491 parents.extend(
491 parents.extend(
492 DirsMultiset::from_manifest(&roots)
492 DirsMultiset::from_manifest(&roots)
493 .map_err(|e| match e {
493 .map_err(|e| match e {
494 DirstateMapError::InvalidPath(e) => e,
494 DirstateMapError::InvalidPath(e) => e,
495 _ => unreachable!(),
495 _ => unreachable!(),
496 })?
496 })?
497 .iter()
497 .iter()
498 .map(|k| k.to_owned()),
498 .map(|k| k.to_owned()),
499 );
499 );
500
500
501 Ok(RootsDirsAndParents {
501 Ok(RootsDirsAndParents {
502 roots: HashSet::from_iter(roots),
502 roots: HashSet::from_iter(roots),
503 dirs: HashSet::from_iter(dirs),
503 dirs: HashSet::from_iter(dirs),
504 parents,
504 parents,
505 })
505 })
506 }
506 }
507
507
508 /// Returns a function that checks whether a given file (in the general sense)
508 /// Returns a function that checks whether a given file (in the general sense)
509 /// should be matched.
509 /// should be matched.
510 fn build_match<'a, 'b>(
510 fn build_match<'a, 'b>(
511 ignore_patterns: &'a [IgnorePattern],
511 ignore_patterns: &'a [IgnorePattern],
512 root_dir: impl AsRef<Path>,
512 root_dir: impl AsRef<Path>,
513 ) -> PatternResult<(
513 ) -> PatternResult<(
514 Vec<u8>,
514 Vec<u8>,
515 Box<dyn Fn(&HgPath) -> bool + 'b + Sync>,
515 Box<dyn Fn(&HgPath) -> bool + 'b + Sync>,
516 Vec<PatternFileWarning>,
516 Vec<PatternFileWarning>,
517 )> {
517 )> {
518 let mut match_funcs: Vec<Box<dyn Fn(&HgPath) -> bool + Sync>> = vec![];
518 let mut match_funcs: Vec<Box<dyn Fn(&HgPath) -> bool + Sync>> = vec![];
519 // For debugging and printing
519 // For debugging and printing
520 let mut patterns = vec![];
520 let mut patterns = vec![];
521 let mut all_warnings = vec![];
521 let mut all_warnings = vec![];
522
522
523 let (subincludes, ignore_patterns) =
523 let (subincludes, ignore_patterns) =
524 filter_subincludes(ignore_patterns, root_dir)?;
524 filter_subincludes(ignore_patterns, root_dir)?;
525
525
526 if !subincludes.is_empty() {
526 if !subincludes.is_empty() {
527 // Build prefix-based matcher functions for subincludes
527 // Build prefix-based matcher functions for subincludes
528 let mut submatchers = FastHashMap::default();
528 let mut submatchers = FastHashMap::default();
529 let mut prefixes = vec![];
529 let mut prefixes = vec![];
530
530
531 for SubInclude { prefix, root, path } in subincludes.into_iter() {
531 for SubInclude { prefix, root, path } in subincludes.into_iter() {
532 let (match_fn, warnings) =
532 let (match_fn, warnings) =
533 get_ignore_function(vec![path.to_path_buf()], root)?;
533 get_ignore_function(vec![path.to_path_buf()], root)?;
534 all_warnings.extend(warnings);
534 all_warnings.extend(warnings);
535 prefixes.push(prefix.to_owned());
535 prefixes.push(prefix.to_owned());
536 submatchers.insert(prefix.to_owned(), match_fn);
536 submatchers.insert(prefix.to_owned(), match_fn);
537 }
537 }
538
538
539 let match_subinclude = move |filename: &HgPath| {
539 let match_subinclude = move |filename: &HgPath| {
540 for prefix in prefixes.iter() {
540 for prefix in prefixes.iter() {
541 if let Some(rel) = filename.relative_to(prefix) {
541 if let Some(rel) = filename.relative_to(prefix) {
542 if (submatchers.get(prefix).unwrap())(rel) {
542 if (submatchers.get(prefix).unwrap())(rel) {
543 return true;
543 return true;
544 }
544 }
545 }
545 }
546 }
546 }
547 false
547 false
548 };
548 };
549
549
550 match_funcs.push(Box::new(match_subinclude));
550 match_funcs.push(Box::new(match_subinclude));
551 }
551 }
552
552
553 if !ignore_patterns.is_empty() {
553 if !ignore_patterns.is_empty() {
554 // Either do dumb matching if all patterns are rootfiles, or match
554 // Either do dumb matching if all patterns are rootfiles, or match
555 // with a regex.
555 // with a regex.
556 if ignore_patterns
556 if ignore_patterns
557 .iter()
557 .iter()
558 .all(|k| k.syntax == PatternSyntax::RootFiles)
558 .all(|k| k.syntax == PatternSyntax::RootFiles)
559 {
559 {
560 let dirs: HashSet<_> = ignore_patterns
560 let dirs: HashSet<_> = ignore_patterns
561 .iter()
561 .iter()
562 .map(|k| k.pattern.to_owned())
562 .map(|k| k.pattern.to_owned())
563 .collect();
563 .collect();
564 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
564 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
565
565
566 let match_func = move |path: &HgPath| -> bool {
566 let match_func = move |path: &HgPath| -> bool {
567 let path = path.as_bytes();
567 let path = path.as_bytes();
568 let i = path.iter().rfind(|a| **a == b'/');
568 let i = path.iter().rfind(|a| **a == b'/');
569 let dir = if let Some(i) = i {
569 let dir = if let Some(i) = i {
570 &path[..*i as usize]
570 &path[..*i as usize]
571 } else {
571 } else {
572 b"."
572 b"."
573 };
573 };
574 dirs.contains(dir.deref())
574 dirs.contains(dir.deref())
575 };
575 };
576 match_funcs.push(Box::new(match_func));
576 match_funcs.push(Box::new(match_func));
577
577
578 patterns.extend(b"rootfilesin: ");
578 patterns.extend(b"rootfilesin: ");
579 dirs_vec.sort();
579 dirs_vec.sort();
580 patterns.extend(dirs_vec.escaped_bytes());
580 patterns.extend(dirs_vec.escaped_bytes());
581 } else {
581 } else {
582 let (new_re, match_func) = build_regex_match(&ignore_patterns)?;
582 let (new_re, match_func) = build_regex_match(&ignore_patterns)?;
583 patterns = new_re;
583 patterns = new_re;
584 match_funcs.push(match_func)
584 match_funcs.push(match_func)
585 }
585 }
586 }
586 }
587
587
588 Ok(if match_funcs.len() == 1 {
588 Ok(if match_funcs.len() == 1 {
589 (patterns, match_funcs.remove(0), all_warnings)
589 (patterns, match_funcs.remove(0), all_warnings)
590 } else {
590 } else {
591 (
591 (
592 patterns,
592 patterns,
593 Box::new(move |f: &HgPath| -> bool {
593 Box::new(move |f: &HgPath| -> bool {
594 match_funcs.iter().any(|match_func| match_func(f))
594 match_funcs.iter().any(|match_func| match_func(f))
595 }),
595 }),
596 all_warnings,
596 all_warnings,
597 )
597 )
598 })
598 })
599 }
599 }
600
600
601 /// Parses all "ignore" files with their recursive includes and returns a
601 /// Parses all "ignore" files with their recursive includes and returns a
602 /// function that checks whether a given file (in the general sense) should be
602 /// function that checks whether a given file (in the general sense) should be
603 /// ignored.
603 /// ignored.
604 pub fn get_ignore_function<'a>(
604 pub fn get_ignore_function<'a>(
605 all_pattern_files: Vec<PathBuf>,
605 all_pattern_files: Vec<PathBuf>,
606 root_dir: impl AsRef<Path>,
606 root_dir: impl AsRef<Path>,
607 ) -> PatternResult<(
607 ) -> PatternResult<(
608 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>,
608 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>,
609 Vec<PatternFileWarning>,
609 Vec<PatternFileWarning>,
610 )> {
610 )> {
611 let mut all_patterns = vec![];
611 let mut all_patterns = vec![];
612 let mut all_warnings = vec![];
612 let mut all_warnings = vec![];
613
613
614 for pattern_file in all_pattern_files.into_iter() {
614 for pattern_file in all_pattern_files.into_iter() {
615 let (patterns, warnings) =
615 let (patterns, warnings) =
616 get_patterns_from_file(pattern_file, &root_dir)?;
616 get_patterns_from_file(pattern_file, &root_dir)?;
617
617
618 all_patterns.extend(patterns.to_owned());
618 all_patterns.extend(patterns.to_owned());
619 all_warnings.extend(warnings);
619 all_warnings.extend(warnings);
620 }
620 }
621 let (matcher, warnings) = IncludeMatcher::new(all_patterns, root_dir)?;
621 let (matcher, warnings) = IncludeMatcher::new(all_patterns, root_dir)?;
622 all_warnings.extend(warnings);
622 all_warnings.extend(warnings);
623 Ok((
623 Ok((
624 Box::new(move |path: &HgPath| matcher.matches(path)),
624 Box::new(move |path: &HgPath| matcher.matches(path)),
625 all_warnings,
625 all_warnings,
626 ))
626 ))
627 }
627 }
628
628
629 impl<'a> IncludeMatcher<'a> {
629 impl<'a> IncludeMatcher<'a> {
630 pub fn new(
630 pub fn new(
631 ignore_patterns: Vec<IgnorePattern>,
631 ignore_patterns: Vec<IgnorePattern>,
632 root_dir: impl AsRef<Path>,
632 root_dir: impl AsRef<Path>,
633 ) -> PatternResult<(Self, Vec<PatternFileWarning>)> {
633 ) -> PatternResult<(Self, Vec<PatternFileWarning>)> {
634 let (patterns, match_fn, warnings) =
634 let (patterns, match_fn, warnings) =
635 build_match(&ignore_patterns, root_dir)?;
635 build_match(&ignore_patterns, root_dir)?;
636 let RootsDirsAndParents {
636 let RootsDirsAndParents {
637 roots,
637 roots,
638 dirs,
638 dirs,
639 parents,
639 parents,
640 } = roots_dirs_and_parents(&ignore_patterns)?;
640 } = roots_dirs_and_parents(&ignore_patterns)?;
641
641
642 let prefix = ignore_patterns.iter().any(|k| match k.syntax {
642 let prefix = ignore_patterns.iter().any(|k| match k.syntax {
643 PatternSyntax::Path | PatternSyntax::RelPath => true,
643 PatternSyntax::Path | PatternSyntax::RelPath => true,
644 _ => false,
644 _ => false,
645 });
645 });
646
646
647 Ok((
647 Ok((
648 Self {
648 Self {
649 patterns,
649 patterns,
650 match_fn,
650 match_fn,
651 prefix,
651 prefix,
652 roots,
652 roots,
653 dirs,
653 dirs,
654 parents,
654 parents,
655 },
655 },
656 warnings,
656 warnings,
657 ))
657 ))
658 }
658 }
659
659
660 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
660 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
661 // TODO cache
661 // TODO cache
662 let thing = self
662 let thing = self
663 .dirs
663 .dirs
664 .iter()
664 .iter()
665 .chain(self.roots.iter())
665 .chain(self.roots.iter())
666 .chain(self.parents.iter());
666 .chain(self.parents.iter());
667 DirsChildrenMultiset::new(thing, Some(&self.parents))
667 DirsChildrenMultiset::new(thing, Some(&self.parents))
668 }
668 }
669 }
669 }
670
670
671 impl<'a> Display for IncludeMatcher<'a> {
671 impl<'a> Display for IncludeMatcher<'a> {
672 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
672 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
673 // XXX What about exact matches?
674 // I'm not sure it's worth it to clone the HashSet and keep it
675 // around just in case someone wants to display the matcher, plus
676 // it's going to be unreadable after a few entries, but we need to
677 // inform in this display that exact matches are being used and are
678 // (on purpose) missing from the `includes`.
673 write!(
679 write!(
674 f,
680 f,
675 "IncludeMatcher(includes='{}')",
681 "IncludeMatcher(includes='{}')",
676 String::from_utf8_lossy(&self.patterns.escaped_bytes())
682 String::from_utf8_lossy(&self.patterns.escaped_bytes())
677 )
683 )
678 }
684 }
679 }
685 }
680
686
681 #[cfg(test)]
687 #[cfg(test)]
682 mod tests {
688 mod tests {
683 use super::*;
689 use super::*;
684 use pretty_assertions::assert_eq;
690 use pretty_assertions::assert_eq;
685 use std::path::Path;
691 use std::path::Path;
686
692
687 #[test]
693 #[test]
688 fn test_roots_and_dirs() {
694 fn test_roots_and_dirs() {
689 let pats = vec![
695 let pats = vec![
690 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
696 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
691 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
697 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
692 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
698 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
693 ];
699 ];
694 let (roots, dirs) = roots_and_dirs(&pats);
700 let (roots, dirs) = roots_and_dirs(&pats);
695
701
696 assert_eq!(
702 assert_eq!(
697 roots,
703 roots,
698 vec!(
704 vec!(
699 HgPathBuf::from_bytes(b"g/h"),
705 HgPathBuf::from_bytes(b"g/h"),
700 HgPathBuf::from_bytes(b"g/h"),
706 HgPathBuf::from_bytes(b"g/h"),
701 HgPathBuf::new()
707 HgPathBuf::new()
702 ),
708 ),
703 );
709 );
704 assert_eq!(dirs, vec!());
710 assert_eq!(dirs, vec!());
705 }
711 }
706
712
707 #[test]
713 #[test]
708 fn test_roots_dirs_and_parents() {
714 fn test_roots_dirs_and_parents() {
709 let pats = vec![
715 let pats = vec![
710 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
716 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
711 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
717 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
712 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
718 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
713 ];
719 ];
714
720
715 let mut roots = HashSet::new();
721 let mut roots = HashSet::new();
716 roots.insert(HgPathBuf::from_bytes(b"g/h"));
722 roots.insert(HgPathBuf::from_bytes(b"g/h"));
717 roots.insert(HgPathBuf::new());
723 roots.insert(HgPathBuf::new());
718
724
719 let dirs = HashSet::new();
725 let dirs = HashSet::new();
720
726
721 let mut parents = HashSet::new();
727 let mut parents = HashSet::new();
722 parents.insert(HgPathBuf::new());
728 parents.insert(HgPathBuf::new());
723 parents.insert(HgPathBuf::from_bytes(b"g"));
729 parents.insert(HgPathBuf::from_bytes(b"g"));
724
730
725 assert_eq!(
731 assert_eq!(
726 roots_dirs_and_parents(&pats).unwrap(),
732 roots_dirs_and_parents(&pats).unwrap(),
727 RootsDirsAndParents {
733 RootsDirsAndParents {
728 roots,
734 roots,
729 dirs,
735 dirs,
730 parents
736 parents
731 }
737 }
732 );
738 );
733 }
739 }
734
740
735 #[test]
741 #[test]
736 fn test_filematcher_visit_children_set() {
742 fn test_filematcher_visit_children_set() {
737 // Visitchildrenset
743 // Visitchildrenset
738 let files = vec![HgPath::new(b"dir/subdir/foo.txt")];
744 let files = vec![HgPath::new(b"dir/subdir/foo.txt")];
739 let matcher = FileMatcher::new(&files).unwrap();
745 let matcher = FileMatcher::new(&files).unwrap();
740
746
741 let mut set = HashSet::new();
747 let mut set = HashSet::new();
742 set.insert(HgPath::new(b"dir"));
748 set.insert(HgPath::new(b"dir"));
743 assert_eq!(
749 assert_eq!(
744 matcher.visit_children_set(HgPath::new(b"")),
750 matcher.visit_children_set(HgPath::new(b"")),
745 VisitChildrenSet::Set(set)
751 VisitChildrenSet::Set(set)
746 );
752 );
747
753
748 let mut set = HashSet::new();
754 let mut set = HashSet::new();
749 set.insert(HgPath::new(b"subdir"));
755 set.insert(HgPath::new(b"subdir"));
750 assert_eq!(
756 assert_eq!(
751 matcher.visit_children_set(HgPath::new(b"dir")),
757 matcher.visit_children_set(HgPath::new(b"dir")),
752 VisitChildrenSet::Set(set)
758 VisitChildrenSet::Set(set)
753 );
759 );
754
760
755 let mut set = HashSet::new();
761 let mut set = HashSet::new();
756 set.insert(HgPath::new(b"foo.txt"));
762 set.insert(HgPath::new(b"foo.txt"));
757 assert_eq!(
763 assert_eq!(
758 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
764 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
759 VisitChildrenSet::Set(set)
765 VisitChildrenSet::Set(set)
760 );
766 );
761
767
762 assert_eq!(
768 assert_eq!(
763 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
769 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
764 VisitChildrenSet::Empty
770 VisitChildrenSet::Empty
765 );
771 );
766 assert_eq!(
772 assert_eq!(
767 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
773 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
768 VisitChildrenSet::Empty
774 VisitChildrenSet::Empty
769 );
775 );
770 assert_eq!(
776 assert_eq!(
771 matcher.visit_children_set(HgPath::new(b"folder")),
777 matcher.visit_children_set(HgPath::new(b"folder")),
772 VisitChildrenSet::Empty
778 VisitChildrenSet::Empty
773 );
779 );
774 }
780 }
775
781
776 #[test]
782 #[test]
777 fn test_filematcher_visit_children_set_files_and_dirs() {
783 fn test_filematcher_visit_children_set_files_and_dirs() {
778 let files = vec![
784 let files = vec![
779 HgPath::new(b"rootfile.txt"),
785 HgPath::new(b"rootfile.txt"),
780 HgPath::new(b"a/file1.txt"),
786 HgPath::new(b"a/file1.txt"),
781 HgPath::new(b"a/b/file2.txt"),
787 HgPath::new(b"a/b/file2.txt"),
782 // No file in a/b/c
788 // No file in a/b/c
783 HgPath::new(b"a/b/c/d/file4.txt"),
789 HgPath::new(b"a/b/c/d/file4.txt"),
784 ];
790 ];
785 let matcher = FileMatcher::new(&files).unwrap();
791 let matcher = FileMatcher::new(&files).unwrap();
786
792
787 let mut set = HashSet::new();
793 let mut set = HashSet::new();
788 set.insert(HgPath::new(b"a"));
794 set.insert(HgPath::new(b"a"));
789 set.insert(HgPath::new(b"rootfile.txt"));
795 set.insert(HgPath::new(b"rootfile.txt"));
790 assert_eq!(
796 assert_eq!(
791 matcher.visit_children_set(HgPath::new(b"")),
797 matcher.visit_children_set(HgPath::new(b"")),
792 VisitChildrenSet::Set(set)
798 VisitChildrenSet::Set(set)
793 );
799 );
794
800
795 let mut set = HashSet::new();
801 let mut set = HashSet::new();
796 set.insert(HgPath::new(b"b"));
802 set.insert(HgPath::new(b"b"));
797 set.insert(HgPath::new(b"file1.txt"));
803 set.insert(HgPath::new(b"file1.txt"));
798 assert_eq!(
804 assert_eq!(
799 matcher.visit_children_set(HgPath::new(b"a")),
805 matcher.visit_children_set(HgPath::new(b"a")),
800 VisitChildrenSet::Set(set)
806 VisitChildrenSet::Set(set)
801 );
807 );
802
808
803 let mut set = HashSet::new();
809 let mut set = HashSet::new();
804 set.insert(HgPath::new(b"c"));
810 set.insert(HgPath::new(b"c"));
805 set.insert(HgPath::new(b"file2.txt"));
811 set.insert(HgPath::new(b"file2.txt"));
806 assert_eq!(
812 assert_eq!(
807 matcher.visit_children_set(HgPath::new(b"a/b")),
813 matcher.visit_children_set(HgPath::new(b"a/b")),
808 VisitChildrenSet::Set(set)
814 VisitChildrenSet::Set(set)
809 );
815 );
810
816
811 let mut set = HashSet::new();
817 let mut set = HashSet::new();
812 set.insert(HgPath::new(b"d"));
818 set.insert(HgPath::new(b"d"));
813 assert_eq!(
819 assert_eq!(
814 matcher.visit_children_set(HgPath::new(b"a/b/c")),
820 matcher.visit_children_set(HgPath::new(b"a/b/c")),
815 VisitChildrenSet::Set(set)
821 VisitChildrenSet::Set(set)
816 );
822 );
817 let mut set = HashSet::new();
823 let mut set = HashSet::new();
818 set.insert(HgPath::new(b"file4.txt"));
824 set.insert(HgPath::new(b"file4.txt"));
819 assert_eq!(
825 assert_eq!(
820 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
826 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
821 VisitChildrenSet::Set(set)
827 VisitChildrenSet::Set(set)
822 );
828 );
823
829
824 assert_eq!(
830 assert_eq!(
825 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
831 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
826 VisitChildrenSet::Empty
832 VisitChildrenSet::Empty
827 );
833 );
828 assert_eq!(
834 assert_eq!(
829 matcher.visit_children_set(HgPath::new(b"folder")),
835 matcher.visit_children_set(HgPath::new(b"folder")),
830 VisitChildrenSet::Empty
836 VisitChildrenSet::Empty
831 );
837 );
832 }
838 }
833
839
834 #[cfg(feature = "with-re2")]
840 #[cfg(feature = "with-re2")]
835 #[test]
841 #[test]
836 fn test_includematcher() {
842 fn test_includematcher() {
837 // VisitchildrensetPrefix
843 // VisitchildrensetPrefix
838 let (matcher, _) = IncludeMatcher::new(
844 let (matcher, _) = IncludeMatcher::new(
839 vec![IgnorePattern::new(
845 vec![IgnorePattern::new(
840 PatternSyntax::RelPath,
846 PatternSyntax::RelPath,
841 b"dir/subdir",
847 b"dir/subdir",
842 Path::new(""),
848 Path::new(""),
843 )],
849 )],
844 "",
850 "",
845 )
851 )
846 .unwrap();
852 .unwrap();
847
853
848 let mut set = HashSet::new();
854 let mut set = HashSet::new();
849 set.insert(HgPath::new(b"dir"));
855 set.insert(HgPath::new(b"dir"));
850 assert_eq!(
856 assert_eq!(
851 matcher.visit_children_set(HgPath::new(b"")),
857 matcher.visit_children_set(HgPath::new(b"")),
852 VisitChildrenSet::Set(set)
858 VisitChildrenSet::Set(set)
853 );
859 );
854
860
855 let mut set = HashSet::new();
861 let mut set = HashSet::new();
856 set.insert(HgPath::new(b"subdir"));
862 set.insert(HgPath::new(b"subdir"));
857 assert_eq!(
863 assert_eq!(
858 matcher.visit_children_set(HgPath::new(b"dir")),
864 matcher.visit_children_set(HgPath::new(b"dir")),
859 VisitChildrenSet::Set(set)
865 VisitChildrenSet::Set(set)
860 );
866 );
861 assert_eq!(
867 assert_eq!(
862 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
868 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
863 VisitChildrenSet::Recursive
869 VisitChildrenSet::Recursive
864 );
870 );
865 // OPT: This should probably be 'all' if its parent is?
871 // OPT: This should probably be 'all' if its parent is?
866 assert_eq!(
872 assert_eq!(
867 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
873 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
868 VisitChildrenSet::This
874 VisitChildrenSet::This
869 );
875 );
870 assert_eq!(
876 assert_eq!(
871 matcher.visit_children_set(HgPath::new(b"folder")),
877 matcher.visit_children_set(HgPath::new(b"folder")),
872 VisitChildrenSet::Empty
878 VisitChildrenSet::Empty
873 );
879 );
874
880
875 // VisitchildrensetRootfilesin
881 // VisitchildrensetRootfilesin
876 let (matcher, _) = IncludeMatcher::new(
882 let (matcher, _) = IncludeMatcher::new(
877 vec![IgnorePattern::new(
883 vec![IgnorePattern::new(
878 PatternSyntax::RootFiles,
884 PatternSyntax::RootFiles,
879 b"dir/subdir",
885 b"dir/subdir",
880 Path::new(""),
886 Path::new(""),
881 )],
887 )],
882 "",
888 "",
883 )
889 )
884 .unwrap();
890 .unwrap();
885
891
886 let mut set = HashSet::new();
892 let mut set = HashSet::new();
887 set.insert(HgPath::new(b"dir"));
893 set.insert(HgPath::new(b"dir"));
888 assert_eq!(
894 assert_eq!(
889 matcher.visit_children_set(HgPath::new(b"")),
895 matcher.visit_children_set(HgPath::new(b"")),
890 VisitChildrenSet::Set(set)
896 VisitChildrenSet::Set(set)
891 );
897 );
892
898
893 let mut set = HashSet::new();
899 let mut set = HashSet::new();
894 set.insert(HgPath::new(b"subdir"));
900 set.insert(HgPath::new(b"subdir"));
895 assert_eq!(
901 assert_eq!(
896 matcher.visit_children_set(HgPath::new(b"dir")),
902 matcher.visit_children_set(HgPath::new(b"dir")),
897 VisitChildrenSet::Set(set)
903 VisitChildrenSet::Set(set)
898 );
904 );
899
905
900 assert_eq!(
906 assert_eq!(
901 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
907 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
902 VisitChildrenSet::This
908 VisitChildrenSet::This
903 );
909 );
904 assert_eq!(
910 assert_eq!(
905 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
911 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
906 VisitChildrenSet::Empty
912 VisitChildrenSet::Empty
907 );
913 );
908 assert_eq!(
914 assert_eq!(
909 matcher.visit_children_set(HgPath::new(b"folder")),
915 matcher.visit_children_set(HgPath::new(b"folder")),
910 VisitChildrenSet::Empty
916 VisitChildrenSet::Empty
911 );
917 );
912
918
913 // VisitchildrensetGlob
919 // VisitchildrensetGlob
914 let (matcher, _) = IncludeMatcher::new(
920 let (matcher, _) = IncludeMatcher::new(
915 vec![IgnorePattern::new(
921 vec![IgnorePattern::new(
916 PatternSyntax::Glob,
922 PatternSyntax::Glob,
917 b"dir/z*",
923 b"dir/z*",
918 Path::new(""),
924 Path::new(""),
919 )],
925 )],
920 "",
926 "",
921 )
927 )
922 .unwrap();
928 .unwrap();
923
929
924 let mut set = HashSet::new();
930 let mut set = HashSet::new();
925 set.insert(HgPath::new(b"dir"));
931 set.insert(HgPath::new(b"dir"));
926 assert_eq!(
932 assert_eq!(
927 matcher.visit_children_set(HgPath::new(b"")),
933 matcher.visit_children_set(HgPath::new(b"")),
928 VisitChildrenSet::Set(set)
934 VisitChildrenSet::Set(set)
929 );
935 );
930 assert_eq!(
936 assert_eq!(
931 matcher.visit_children_set(HgPath::new(b"folder")),
937 matcher.visit_children_set(HgPath::new(b"folder")),
932 VisitChildrenSet::Empty
938 VisitChildrenSet::Empty
933 );
939 );
934 assert_eq!(
940 assert_eq!(
935 matcher.visit_children_set(HgPath::new(b"dir")),
941 matcher.visit_children_set(HgPath::new(b"dir")),
936 VisitChildrenSet::This
942 VisitChildrenSet::This
937 );
943 );
938 // OPT: these should probably be set().
944 // OPT: these should probably be set().
939 assert_eq!(
945 assert_eq!(
940 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
946 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
941 VisitChildrenSet::This
947 VisitChildrenSet::This
942 );
948 );
943 assert_eq!(
949 assert_eq!(
944 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
950 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
945 VisitChildrenSet::This
951 VisitChildrenSet::This
946 );
952 );
947 }
953 }
948 }
954 }
General Comments 0
You need to be logged in to leave comments. Login now