##// END OF EJS Templates
rust-matchers: add functions to get roots, dirs and parents from patterns...
Raphaël Gomès -
r45007:d4e8cfcd default
parent child Browse files
Show More
@@ -1,346 +1,494 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 filepatterns::PatternResult, utils::hg_path::HgPath, DirsMultiset,
13 filepatterns::PatternResult,
14 DirstateMapError, PatternError,
14 utils::hg_path::{HgPath, HgPathBuf},
15 DirsMultiset, DirstateMapError, IgnorePattern, PatternError,
16 PatternSyntax,
15 };
17 };
16 use std::collections::HashSet;
18 use std::collections::HashSet;
17 use std::iter::FromIterator;
19 use std::iter::FromIterator;
18 use std::ops::Deref;
20 use std::ops::Deref;
19
21
20 #[derive(Debug, PartialEq)]
22 #[derive(Debug, PartialEq)]
21 pub enum VisitChildrenSet<'a> {
23 pub enum VisitChildrenSet<'a> {
22 /// Don't visit anything
24 /// Don't visit anything
23 Empty,
25 Empty,
24 /// Only visit this directory
26 /// Only visit this directory
25 This,
27 This,
26 /// Visit this directory and these subdirectories
28 /// Visit this directory and these subdirectories
27 /// TODO Should we implement a `NonEmptyHashSet`?
29 /// TODO Should we implement a `NonEmptyHashSet`?
28 Set(HashSet<&'a HgPath>),
30 Set(HashSet<&'a HgPath>),
29 /// Visit this directory and all subdirectories
31 /// Visit this directory and all subdirectories
30 Recursive,
32 Recursive,
31 }
33 }
32
34
33 pub trait Matcher {
35 pub trait Matcher {
34 /// Explicitly listed files
36 /// Explicitly listed files
35 fn file_set(&self) -> Option<&HashSet<&HgPath>>;
37 fn file_set(&self) -> Option<&HashSet<&HgPath>>;
36 /// Returns whether `filename` is in `file_set`
38 /// Returns whether `filename` is in `file_set`
37 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool;
39 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool;
38 /// Returns whether `filename` is matched by this matcher
40 /// Returns whether `filename` is matched by this matcher
39 fn matches(&self, filename: impl AsRef<HgPath>) -> bool;
41 fn matches(&self, filename: impl AsRef<HgPath>) -> bool;
40 /// Decides whether a directory should be visited based on whether it
42 /// Decides whether a directory should be visited based on whether it
41 /// has potential matches in it or one of its subdirectories, and
43 /// has potential matches in it or one of its subdirectories, and
42 /// potentially lists which subdirectories of that directory should be
44 /// potentially lists which subdirectories of that directory should be
43 /// visited. This is based on the match's primary, included, and excluded
45 /// visited. This is based on the match's primary, included, and excluded
44 /// patterns.
46 /// patterns.
45 ///
47 ///
46 /// # Example
48 /// # Example
47 ///
49 ///
48 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
50 /// Assume matchers `['path:foo/bar', 'rootfilesin:qux']`, we would
49 /// return the following values (assuming the implementation of
51 /// return the following values (assuming the implementation of
50 /// visit_children_set is capable of recognizing this; some implementations
52 /// visit_children_set is capable of recognizing this; some implementations
51 /// are not).
53 /// are not).
52 ///
54 ///
53 /// ```text
55 /// ```text
54 /// ```ignore
56 /// ```ignore
55 /// '' -> {'foo', 'qux'}
57 /// '' -> {'foo', 'qux'}
56 /// 'baz' -> set()
58 /// 'baz' -> set()
57 /// 'foo' -> {'bar'}
59 /// 'foo' -> {'bar'}
58 /// // Ideally this would be `Recursive`, but since the prefix nature of
60 /// // Ideally this would be `Recursive`, but since the prefix nature of
59 /// // matchers is applied to the entire matcher, we have to downgrade this
61 /// // matchers is applied to the entire matcher, we have to downgrade this
60 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
62 /// // to `This` due to the (yet to be implemented in Rust) non-prefix
61 /// // `RootFilesIn'-kind matcher being mixed in.
63 /// // `RootFilesIn'-kind matcher being mixed in.
62 /// 'foo/bar' -> 'this'
64 /// 'foo/bar' -> 'this'
63 /// 'qux' -> 'this'
65 /// 'qux' -> 'this'
64 /// ```
66 /// ```
65 /// # Important
67 /// # Important
66 ///
68 ///
67 /// Most matchers do not know if they're representing files or
69 /// Most matchers do not know if they're representing files or
68 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
70 /// directories. They see `['path:dir/f']` and don't know whether `f` is a
69 /// file or a directory, so `visit_children_set('dir')` for most matchers
71 /// file or a directory, so `visit_children_set('dir')` for most matchers
70 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
72 /// will return `HashSet{ HgPath { "f" } }`, but if the matcher knows it's
71 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
73 /// a file (like the yet to be implemented in Rust `ExactMatcher` does),
72 /// it may return `VisitChildrenSet::This`.
74 /// it may return `VisitChildrenSet::This`.
73 /// Do not rely on the return being a `HashSet` indicating that there are
75 /// Do not rely on the return being a `HashSet` indicating that there are
74 /// no files in this dir to investigate (or equivalently that if there are
76 /// no files in this dir to investigate (or equivalently that if there are
75 /// files to investigate in 'dir' that it will always return
77 /// files to investigate in 'dir' that it will always return
76 /// `VisitChildrenSet::This`).
78 /// `VisitChildrenSet::This`).
77 fn visit_children_set(
79 fn visit_children_set(
78 &self,
80 &self,
79 directory: impl AsRef<HgPath>,
81 directory: impl AsRef<HgPath>,
80 ) -> VisitChildrenSet;
82 ) -> VisitChildrenSet;
81 /// Matcher will match everything and `files_set()` will be empty:
83 /// Matcher will match everything and `files_set()` will be empty:
82 /// optimization might be possible.
84 /// optimization might be possible.
83 fn matches_everything(&self) -> bool;
85 fn matches_everything(&self) -> bool;
84 /// Matcher will match exactly the files in `files_set()`: optimization
86 /// Matcher will match exactly the files in `files_set()`: optimization
85 /// might be possible.
87 /// might be possible.
86 fn is_exact(&self) -> bool;
88 fn is_exact(&self) -> bool;
87 }
89 }
88
90
89 /// Matches everything.
91 /// Matches everything.
90 ///```
92 ///```
91 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
93 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
92 ///
94 ///
93 /// let matcher = AlwaysMatcher;
95 /// let matcher = AlwaysMatcher;
94 ///
96 ///
95 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
97 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
96 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
98 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
97 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
99 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
98 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
100 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
99 /// ```
101 /// ```
100 #[derive(Debug)]
102 #[derive(Debug)]
101 pub struct AlwaysMatcher;
103 pub struct AlwaysMatcher;
102
104
103 impl Matcher for AlwaysMatcher {
105 impl Matcher for AlwaysMatcher {
104 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
106 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
105 None
107 None
106 }
108 }
107 fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool {
109 fn exact_match(&self, _filename: impl AsRef<HgPath>) -> bool {
108 false
110 false
109 }
111 }
110 fn matches(&self, _filename: impl AsRef<HgPath>) -> bool {
112 fn matches(&self, _filename: impl AsRef<HgPath>) -> bool {
111 true
113 true
112 }
114 }
113 fn visit_children_set(
115 fn visit_children_set(
114 &self,
116 &self,
115 _directory: impl AsRef<HgPath>,
117 _directory: impl AsRef<HgPath>,
116 ) -> VisitChildrenSet {
118 ) -> VisitChildrenSet {
117 VisitChildrenSet::Recursive
119 VisitChildrenSet::Recursive
118 }
120 }
119 fn matches_everything(&self) -> bool {
121 fn matches_everything(&self) -> bool {
120 true
122 true
121 }
123 }
122 fn is_exact(&self) -> bool {
124 fn is_exact(&self) -> bool {
123 false
125 false
124 }
126 }
125 }
127 }
126
128
127 /// Matches the input files exactly. They are interpreted as paths, not
129 /// Matches the input files exactly. They are interpreted as paths, not
128 /// patterns.
130 /// patterns.
129 ///
131 ///
130 ///```
132 ///```
131 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath };
133 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::HgPath };
132 ///
134 ///
133 /// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")];
135 /// let files = [HgPath::new(b"a.txt"), HgPath::new(br"re:.*\.c$")];
134 /// let matcher = FileMatcher::new(&files).unwrap();
136 /// let matcher = FileMatcher::new(&files).unwrap();
135 ///
137 ///
136 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
138 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
137 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
139 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
138 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
140 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
139 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
141 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
140 /// ```
142 /// ```
141 #[derive(Debug)]
143 #[derive(Debug)]
142 pub struct FileMatcher<'a> {
144 pub struct FileMatcher<'a> {
143 files: HashSet<&'a HgPath>,
145 files: HashSet<&'a HgPath>,
144 dirs: DirsMultiset,
146 dirs: DirsMultiset,
145 }
147 }
146
148
147 impl<'a> FileMatcher<'a> {
149 impl<'a> FileMatcher<'a> {
148 pub fn new(
150 pub fn new(
149 files: &'a [impl AsRef<HgPath>],
151 files: &'a [impl AsRef<HgPath>],
150 ) -> Result<Self, DirstateMapError> {
152 ) -> Result<Self, DirstateMapError> {
151 Ok(Self {
153 Ok(Self {
152 files: HashSet::from_iter(files.iter().map(|f| f.as_ref())),
154 files: HashSet::from_iter(files.iter().map(|f| f.as_ref())),
153 dirs: DirsMultiset::from_manifest(files)?,
155 dirs: DirsMultiset::from_manifest(files)?,
154 })
156 })
155 }
157 }
156 fn inner_matches(&self, filename: impl AsRef<HgPath>) -> bool {
158 fn inner_matches(&self, filename: impl AsRef<HgPath>) -> bool {
157 self.files.contains(filename.as_ref())
159 self.files.contains(filename.as_ref())
158 }
160 }
159 }
161 }
160
162
161 impl<'a> Matcher for FileMatcher<'a> {
163 impl<'a> Matcher for FileMatcher<'a> {
162 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
164 fn file_set(&self) -> Option<&HashSet<&HgPath>> {
163 Some(&self.files)
165 Some(&self.files)
164 }
166 }
165 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool {
167 fn exact_match(&self, filename: impl AsRef<HgPath>) -> bool {
166 self.inner_matches(filename)
168 self.inner_matches(filename)
167 }
169 }
168 fn matches(&self, filename: impl AsRef<HgPath>) -> bool {
170 fn matches(&self, filename: impl AsRef<HgPath>) -> bool {
169 self.inner_matches(filename)
171 self.inner_matches(filename)
170 }
172 }
171 fn visit_children_set(
173 fn visit_children_set(
172 &self,
174 &self,
173 directory: impl AsRef<HgPath>,
175 directory: impl AsRef<HgPath>,
174 ) -> VisitChildrenSet {
176 ) -> VisitChildrenSet {
175 if self.files.is_empty() || !self.dirs.contains(&directory) {
177 if self.files.is_empty() || !self.dirs.contains(&directory) {
176 return VisitChildrenSet::Empty;
178 return VisitChildrenSet::Empty;
177 }
179 }
178 let dirs_as_set = self.dirs.iter().map(|k| k.deref()).collect();
180 let dirs_as_set = self.dirs.iter().map(|k| k.deref()).collect();
179
181
180 let mut candidates: HashSet<&HgPath> =
182 let mut candidates: HashSet<&HgPath> =
181 self.files.union(&dirs_as_set).map(|k| *k).collect();
183 self.files.union(&dirs_as_set).map(|k| *k).collect();
182 candidates.remove(HgPath::new(b""));
184 candidates.remove(HgPath::new(b""));
183
185
184 if !directory.as_ref().is_empty() {
186 if !directory.as_ref().is_empty() {
185 let directory = [directory.as_ref().as_bytes(), b"/"].concat();
187 let directory = [directory.as_ref().as_bytes(), b"/"].concat();
186 candidates = candidates
188 candidates = candidates
187 .iter()
189 .iter()
188 .filter_map(|c| {
190 .filter_map(|c| {
189 if c.as_bytes().starts_with(&directory) {
191 if c.as_bytes().starts_with(&directory) {
190 Some(HgPath::new(&c.as_bytes()[directory.len()..]))
192 Some(HgPath::new(&c.as_bytes()[directory.len()..]))
191 } else {
193 } else {
192 None
194 None
193 }
195 }
194 })
196 })
195 .collect();
197 .collect();
196 }
198 }
197
199
198 // `self.dirs` includes all of the directories, recursively, so if
200 // `self.dirs` includes all of the directories, recursively, so if
199 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
201 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
200 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
202 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
201 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
203 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
202 // subdir will be in there without a slash.
204 // subdir will be in there without a slash.
203 VisitChildrenSet::Set(
205 VisitChildrenSet::Set(
204 candidates
206 candidates
205 .iter()
207 .iter()
206 .filter_map(|c| {
208 .filter_map(|c| {
207 if c.bytes().all(|b| *b != b'/') {
209 if c.bytes().all(|b| *b != b'/') {
208 Some(*c)
210 Some(*c)
209 } else {
211 } else {
210 None
212 None
211 }
213 }
212 })
214 })
213 .collect(),
215 .collect(),
214 )
216 )
215 }
217 }
216 fn matches_everything(&self) -> bool {
218 fn matches_everything(&self) -> bool {
217 false
219 false
218 }
220 }
219 fn is_exact(&self) -> bool {
221 fn is_exact(&self) -> bool {
220 true
222 true
221 }
223 }
222 }
224 }
223
225
224 #[cfg(feature = "with-re2")]
226 #[cfg(feature = "with-re2")]
225 /// Returns a function that matches an `HgPath` against the given regex
227 /// Returns a function that matches an `HgPath` against the given regex
226 /// pattern.
228 /// pattern.
227 ///
229 ///
228 /// This can fail when the pattern is invalid or not supported by the
230 /// This can fail when the pattern is invalid or not supported by the
229 /// underlying engine `Re2`, for instance anything with back-references.
231 /// underlying engine `Re2`, for instance anything with back-references.
230 fn re_matcher(
232 fn re_matcher(
231 pattern: &[u8],
233 pattern: &[u8],
232 ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
234 ) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
233 let regex = Re2::new(pattern);
235 let regex = Re2::new(pattern);
234 let regex = regex.map_err(|e| PatternError::UnsupportedSyntax(e))?;
236 let regex = regex.map_err(|e| PatternError::UnsupportedSyntax(e))?;
235 Ok(move |path: &HgPath| regex.is_match(path.as_bytes()))
237 Ok(move |path: &HgPath| regex.is_match(path.as_bytes()))
236 }
238 }
237
239
238 #[cfg(not(feature = "with-re2"))]
240 #[cfg(not(feature = "with-re2"))]
239 fn re_matcher(_: &[u8]) -> PatternResult<Box<dyn Fn(&HgPath) -> bool + Sync>> {
241 fn re_matcher(_: &[u8]) -> PatternResult<Box<dyn Fn(&HgPath) -> bool + Sync>> {
240 Err(PatternError::Re2NotInstalled)
242 Err(PatternError::Re2NotInstalled)
241 }
243 }
242
244
245 /// Returns roots and directories corresponding to each pattern.
246 ///
247 /// This calculates the roots and directories exactly matching the patterns and
248 /// returns a tuple of (roots, dirs). It does not return other directories
249 /// which may also need to be considered, like the parent directories.
250 fn roots_and_dirs(
251 ignore_patterns: &[IgnorePattern],
252 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
253 let mut roots = Vec::new();
254 let mut dirs = Vec::new();
255
256 for ignore_pattern in ignore_patterns {
257 let IgnorePattern {
258 syntax, pattern, ..
259 } = ignore_pattern;
260 match syntax {
261 PatternSyntax::RootGlob | PatternSyntax::Glob => {
262 let mut root = vec![];
263
264 for p in pattern.split(|c| *c == b'/') {
265 if p.iter().any(|c| match *c {
266 b'[' | b'{' | b'*' | b'?' => true,
267 _ => false,
268 }) {
269 break;
270 }
271 root.push(HgPathBuf::from_bytes(p));
272 }
273 let buf =
274 root.iter().fold(HgPathBuf::new(), |acc, r| acc.join(r));
275 roots.push(buf);
276 }
277 PatternSyntax::Path | PatternSyntax::RelPath => {
278 let pat = HgPath::new(if pattern == b"." {
279 &[] as &[u8]
280 } else {
281 pattern
282 });
283 roots.push(pat.to_owned());
284 }
285 PatternSyntax::RootFiles => {
286 let pat = if pattern == b"." {
287 &[] as &[u8]
288 } else {
289 pattern
290 };
291 dirs.push(HgPathBuf::from_bytes(pat));
292 }
293 _ => {
294 roots.push(HgPathBuf::new());
295 }
296 }
297 }
298 (roots, dirs)
299 }
300
301 /// Paths extracted from patterns
302 #[derive(Debug, PartialEq)]
303 struct RootsDirsAndParents {
304 /// Directories to match recursively
305 pub roots: HashSet<HgPathBuf>,
306 /// Directories to match non-recursively
307 pub dirs: HashSet<HgPathBuf>,
308 /// Implicitly required directories to go to items in either roots or dirs
309 pub parents: HashSet<HgPathBuf>,
310 }
311
312 /// Extract roots, dirs and parents from patterns.
313 fn roots_dirs_and_parents(
314 ignore_patterns: &[IgnorePattern],
315 ) -> PatternResult<RootsDirsAndParents> {
316 let (roots, dirs) = roots_and_dirs(ignore_patterns);
317
318 let mut parents = HashSet::new();
319
320 parents.extend(
321 DirsMultiset::from_manifest(&dirs)
322 .map_err(|e| match e {
323 DirstateMapError::InvalidPath(e) => e,
324 _ => unreachable!(),
325 })?
326 .iter()
327 .map(|k| k.to_owned()),
328 );
329 parents.extend(
330 DirsMultiset::from_manifest(&roots)
331 .map_err(|e| match e {
332 DirstateMapError::InvalidPath(e) => e,
333 _ => unreachable!(),
334 })?
335 .iter()
336 .map(|k| k.to_owned()),
337 );
338
339 Ok(RootsDirsAndParents {
340 roots: HashSet::from_iter(roots),
341 dirs: HashSet::from_iter(dirs),
342 parents,
343 })
344 }
345
243 #[cfg(test)]
346 #[cfg(test)]
244 mod tests {
347 mod tests {
245 use super::*;
348 use super::*;
246 use pretty_assertions::assert_eq;
349 use pretty_assertions::assert_eq;
350 use std::path::Path;
351
352 #[test]
353 fn test_roots_and_dirs() {
354 let pats = vec![
355 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
356 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
357 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
358 ];
359 let (roots, dirs) = roots_and_dirs(&pats);
360
361 assert_eq!(
362 roots,
363 vec!(
364 HgPathBuf::from_bytes(b"g/h"),
365 HgPathBuf::from_bytes(b"g/h"),
366 HgPathBuf::new()
367 ),
368 );
369 assert_eq!(dirs, vec!());
370 }
371
372 #[test]
373 fn test_roots_dirs_and_parents() {
374 let pats = vec![
375 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
376 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
377 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
378 ];
379
380 let mut roots = HashSet::new();
381 roots.insert(HgPathBuf::from_bytes(b"g/h"));
382 roots.insert(HgPathBuf::new());
383
384 let dirs = HashSet::new();
385
386 let mut parents = HashSet::new();
387 parents.insert(HgPathBuf::new());
388 parents.insert(HgPathBuf::from_bytes(b"g"));
389
390 assert_eq!(
391 roots_dirs_and_parents(&pats).unwrap(),
392 RootsDirsAndParents {roots, dirs, parents}
393 );
394 }
247
395
248 #[test]
396 #[test]
249 fn test_filematcher_visit_children_set() {
397 fn test_filematcher_visit_children_set() {
250 // Visitchildrenset
398 // Visitchildrenset
251 let files = vec![HgPath::new(b"dir/subdir/foo.txt")];
399 let files = vec![HgPath::new(b"dir/subdir/foo.txt")];
252 let matcher = FileMatcher::new(&files).unwrap();
400 let matcher = FileMatcher::new(&files).unwrap();
253
401
254 let mut set = HashSet::new();
402 let mut set = HashSet::new();
255 set.insert(HgPath::new(b"dir"));
403 set.insert(HgPath::new(b"dir"));
256 assert_eq!(
404 assert_eq!(
257 matcher.visit_children_set(HgPath::new(b"")),
405 matcher.visit_children_set(HgPath::new(b"")),
258 VisitChildrenSet::Set(set)
406 VisitChildrenSet::Set(set)
259 );
407 );
260
408
261 let mut set = HashSet::new();
409 let mut set = HashSet::new();
262 set.insert(HgPath::new(b"subdir"));
410 set.insert(HgPath::new(b"subdir"));
263 assert_eq!(
411 assert_eq!(
264 matcher.visit_children_set(HgPath::new(b"dir")),
412 matcher.visit_children_set(HgPath::new(b"dir")),
265 VisitChildrenSet::Set(set)
413 VisitChildrenSet::Set(set)
266 );
414 );
267
415
268 let mut set = HashSet::new();
416 let mut set = HashSet::new();
269 set.insert(HgPath::new(b"foo.txt"));
417 set.insert(HgPath::new(b"foo.txt"));
270 assert_eq!(
418 assert_eq!(
271 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
419 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
272 VisitChildrenSet::Set(set)
420 VisitChildrenSet::Set(set)
273 );
421 );
274
422
275 assert_eq!(
423 assert_eq!(
276 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
424 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
277 VisitChildrenSet::Empty
425 VisitChildrenSet::Empty
278 );
426 );
279 assert_eq!(
427 assert_eq!(
280 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
428 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
281 VisitChildrenSet::Empty
429 VisitChildrenSet::Empty
282 );
430 );
283 assert_eq!(
431 assert_eq!(
284 matcher.visit_children_set(HgPath::new(b"folder")),
432 matcher.visit_children_set(HgPath::new(b"folder")),
285 VisitChildrenSet::Empty
433 VisitChildrenSet::Empty
286 );
434 );
287 }
435 }
288
436
289 #[test]
437 #[test]
290 fn test_filematcher_visit_children_set_files_and_dirs() {
438 fn test_filematcher_visit_children_set_files_and_dirs() {
291 let files = vec![
439 let files = vec![
292 HgPath::new(b"rootfile.txt"),
440 HgPath::new(b"rootfile.txt"),
293 HgPath::new(b"a/file1.txt"),
441 HgPath::new(b"a/file1.txt"),
294 HgPath::new(b"a/b/file2.txt"),
442 HgPath::new(b"a/b/file2.txt"),
295 // No file in a/b/c
443 // No file in a/b/c
296 HgPath::new(b"a/b/c/d/file4.txt"),
444 HgPath::new(b"a/b/c/d/file4.txt"),
297 ];
445 ];
298 let matcher = FileMatcher::new(&files).unwrap();
446 let matcher = FileMatcher::new(&files).unwrap();
299
447
300 let mut set = HashSet::new();
448 let mut set = HashSet::new();
301 set.insert(HgPath::new(b"a"));
449 set.insert(HgPath::new(b"a"));
302 set.insert(HgPath::new(b"rootfile.txt"));
450 set.insert(HgPath::new(b"rootfile.txt"));
303 assert_eq!(
451 assert_eq!(
304 matcher.visit_children_set(HgPath::new(b"")),
452 matcher.visit_children_set(HgPath::new(b"")),
305 VisitChildrenSet::Set(set)
453 VisitChildrenSet::Set(set)
306 );
454 );
307
455
308 let mut set = HashSet::new();
456 let mut set = HashSet::new();
309 set.insert(HgPath::new(b"b"));
457 set.insert(HgPath::new(b"b"));
310 set.insert(HgPath::new(b"file1.txt"));
458 set.insert(HgPath::new(b"file1.txt"));
311 assert_eq!(
459 assert_eq!(
312 matcher.visit_children_set(HgPath::new(b"a")),
460 matcher.visit_children_set(HgPath::new(b"a")),
313 VisitChildrenSet::Set(set)
461 VisitChildrenSet::Set(set)
314 );
462 );
315
463
316 let mut set = HashSet::new();
464 let mut set = HashSet::new();
317 set.insert(HgPath::new(b"c"));
465 set.insert(HgPath::new(b"c"));
318 set.insert(HgPath::new(b"file2.txt"));
466 set.insert(HgPath::new(b"file2.txt"));
319 assert_eq!(
467 assert_eq!(
320 matcher.visit_children_set(HgPath::new(b"a/b")),
468 matcher.visit_children_set(HgPath::new(b"a/b")),
321 VisitChildrenSet::Set(set)
469 VisitChildrenSet::Set(set)
322 );
470 );
323
471
324 let mut set = HashSet::new();
472 let mut set = HashSet::new();
325 set.insert(HgPath::new(b"d"));
473 set.insert(HgPath::new(b"d"));
326 assert_eq!(
474 assert_eq!(
327 matcher.visit_children_set(HgPath::new(b"a/b/c")),
475 matcher.visit_children_set(HgPath::new(b"a/b/c")),
328 VisitChildrenSet::Set(set)
476 VisitChildrenSet::Set(set)
329 );
477 );
330 let mut set = HashSet::new();
478 let mut set = HashSet::new();
331 set.insert(HgPath::new(b"file4.txt"));
479 set.insert(HgPath::new(b"file4.txt"));
332 assert_eq!(
480 assert_eq!(
333 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
481 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
334 VisitChildrenSet::Set(set)
482 VisitChildrenSet::Set(set)
335 );
483 );
336
484
337 assert_eq!(
485 assert_eq!(
338 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
486 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
339 VisitChildrenSet::Empty
487 VisitChildrenSet::Empty
340 );
488 );
341 assert_eq!(
489 assert_eq!(
342 matcher.visit_children_set(HgPath::new(b"folder")),
490 matcher.visit_children_set(HgPath::new(b"folder")),
343 VisitChildrenSet::Empty
491 VisitChildrenSet::Empty
344 );
492 );
345 }
493 }
346 }
494 }
General Comments 0
You need to be logged in to leave comments. Login now