##// END OF EJS Templates
hgignore: add a GlobSuffix type, instead of passing byte arrays...
Arseniy Alekseyev -
r53249:2ff004fb default
parent child Browse files
Show More
@@ -1,913 +1,940
1 // filepatterns.rs
1 // filepatterns.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 //! Handling of Mercurial-specific patterns.
8 //! Handling of Mercurial-specific patterns.
9
9
10 use crate::{
10 use crate::{
11 utils::{
11 utils::{
12 files::{canonical_path, get_bytes_from_path, get_path_from_bytes},
12 files::{canonical_path, get_bytes_from_path, get_path_from_bytes},
13 hg_path::{path_to_hg_path_buf, HgPathBuf, HgPathError},
13 hg_path::{path_to_hg_path_buf, HgPathBuf, HgPathError},
14 SliceExt,
14 SliceExt,
15 },
15 },
16 FastHashMap,
16 FastHashMap,
17 };
17 };
18 use lazy_static::lazy_static;
18 use lazy_static::lazy_static;
19 use regex::bytes::{NoExpand, Regex};
19 use regex::bytes::{NoExpand, Regex};
20 use std::path::{Path, PathBuf};
20 use std::path::{Path, PathBuf};
21 use std::vec::Vec;
21 use std::vec::Vec;
22 use std::{fmt, ops::Deref};
22 use std::{fmt, ops::Deref};
23
23
24 #[derive(Debug, derive_more::From)]
24 #[derive(Debug, derive_more::From)]
25 pub enum PatternError {
25 pub enum PatternError {
26 #[from]
26 #[from]
27 Path(HgPathError),
27 Path(HgPathError),
28 UnsupportedSyntax(String),
28 UnsupportedSyntax(String),
29 UnsupportedSyntaxInFile(String, String, usize),
29 UnsupportedSyntaxInFile(String, String, usize),
30 TooLong(usize),
30 TooLong(usize),
31 #[from]
31 #[from]
32 IO(std::io::Error),
32 IO(std::io::Error),
33 /// Needed a pattern that can be turned into a regex but got one that
33 /// Needed a pattern that can be turned into a regex but got one that
34 /// can't. This should only happen through programmer error.
34 /// can't. This should only happen through programmer error.
35 NonRegexPattern(IgnorePattern),
35 NonRegexPattern(IgnorePattern),
36 }
36 }
37
37
38 impl fmt::Display for PatternError {
38 impl fmt::Display for PatternError {
39 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
40 match self {
40 match self {
41 PatternError::UnsupportedSyntax(syntax) => {
41 PatternError::UnsupportedSyntax(syntax) => {
42 write!(f, "Unsupported syntax {}", syntax)
42 write!(f, "Unsupported syntax {}", syntax)
43 }
43 }
44 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
44 PatternError::UnsupportedSyntaxInFile(syntax, file_path, line) => {
45 write!(
45 write!(
46 f,
46 f,
47 "{}:{}: unsupported syntax {}",
47 "{}:{}: unsupported syntax {}",
48 file_path, line, syntax
48 file_path, line, syntax
49 )
49 )
50 }
50 }
51 PatternError::TooLong(size) => {
51 PatternError::TooLong(size) => {
52 write!(f, "matcher pattern is too long ({} bytes)", size)
52 write!(f, "matcher pattern is too long ({} bytes)", size)
53 }
53 }
54 PatternError::IO(error) => error.fmt(f),
54 PatternError::IO(error) => error.fmt(f),
55 PatternError::Path(error) => error.fmt(f),
55 PatternError::Path(error) => error.fmt(f),
56 PatternError::NonRegexPattern(pattern) => {
56 PatternError::NonRegexPattern(pattern) => {
57 write!(f, "'{:?}' cannot be turned into a regex", pattern)
57 write!(f, "'{:?}' cannot be turned into a regex", pattern)
58 }
58 }
59 }
59 }
60 }
60 }
61 }
61 }
62
62
63 lazy_static! {
63 lazy_static! {
64 static ref RE_ESCAPE: Vec<Vec<u8>> = {
64 static ref RE_ESCAPE: Vec<Vec<u8>> = {
65 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
65 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
66 let to_escape = b"()[]{}?*+-|^$\\.&~#\t\n\r\x0b\x0c";
66 let to_escape = b"()[]{}?*+-|^$\\.&~#\t\n\r\x0b\x0c";
67 for byte in to_escape {
67 for byte in to_escape {
68 v[*byte as usize].insert(0, b'\\');
68 v[*byte as usize].insert(0, b'\\');
69 }
69 }
70 v
70 v
71 };
71 };
72 }
72 }
73
73
74 /// These are matched in order
74 /// These are matched in order
75 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
75 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
76 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
76 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
77
77
78 #[derive(Debug, Clone, PartialEq, Eq)]
78 #[derive(Debug, Clone, PartialEq, Eq)]
79 pub enum PatternSyntax {
79 pub enum PatternSyntax {
80 /// A regular expression
80 /// A regular expression
81 Regexp,
81 Regexp,
82 /// Glob that matches at the front of the path
82 /// Glob that matches at the front of the path
83 RootGlob,
83 RootGlob,
84 /// Glob that matches at any suffix of the path (still anchored at
84 /// Glob that matches at any suffix of the path (still anchored at
85 /// slashes)
85 /// slashes)
86 Glob,
86 Glob,
87 /// a path relative to repository root, which is matched recursively
87 /// a path relative to repository root, which is matched recursively
88 Path,
88 Path,
89 /// a single exact path relative to repository root
89 /// a single exact path relative to repository root
90 FilePath,
90 FilePath,
91 /// A path relative to cwd
91 /// A path relative to cwd
92 RelPath,
92 RelPath,
93 /// an unrooted glob (*.rs matches Rust files in all dirs)
93 /// an unrooted glob (*.rs matches Rust files in all dirs)
94 RelGlob,
94 RelGlob,
95 /// A regexp that needn't match the start of a name
95 /// A regexp that needn't match the start of a name
96 RelRegexp,
96 RelRegexp,
97 /// A path relative to repository root, which is matched non-recursively
97 /// A path relative to repository root, which is matched non-recursively
98 /// (will not match subdirectories)
98 /// (will not match subdirectories)
99 RootFilesIn,
99 RootFilesIn,
100 /// A file of patterns to read and include
100 /// A file of patterns to read and include
101 Include,
101 Include,
102 /// A file of patterns to match against files under the same directory
102 /// A file of patterns to match against files under the same directory
103 SubInclude,
103 SubInclude,
104 /// SubInclude with the result of parsing the included file
104 /// SubInclude with the result of parsing the included file
105 ///
105 ///
106 /// Note: there is no ExpandedInclude because that expansion can be done
106 /// Note: there is no ExpandedInclude because that expansion can be done
107 /// in place by replacing the Include pattern by the included patterns.
107 /// in place by replacing the Include pattern by the included patterns.
108 /// SubInclude requires more handling.
108 /// SubInclude requires more handling.
109 ///
109 ///
110 /// Note: `Box` is used to minimize size impact on other enum variants
110 /// Note: `Box` is used to minimize size impact on other enum variants
111 ExpandedSubInclude(Box<SubInclude>),
111 ExpandedSubInclude(Box<SubInclude>),
112 }
112 }
113
113
114 /// Transforms a glob pattern into a regex
114 /// Transforms a glob pattern into a regex
115 pub fn glob_to_re(pat: &[u8]) -> Vec<u8> {
115 pub fn glob_to_re(pat: &[u8]) -> Vec<u8> {
116 let mut input = pat;
116 let mut input = pat;
117 let mut res: Vec<u8> = vec![];
117 let mut res: Vec<u8> = vec![];
118 let mut group_depth = 0;
118 let mut group_depth = 0;
119
119
120 while let Some((c, rest)) = input.split_first() {
120 while let Some((c, rest)) = input.split_first() {
121 input = rest;
121 input = rest;
122
122
123 match c {
123 match c {
124 b'*' => {
124 b'*' => {
125 for (source, repl) in GLOB_REPLACEMENTS {
125 for (source, repl) in GLOB_REPLACEMENTS {
126 if let Some(rest) = input.drop_prefix(source) {
126 if let Some(rest) = input.drop_prefix(source) {
127 input = rest;
127 input = rest;
128 res.extend(*repl);
128 res.extend(*repl);
129 break;
129 break;
130 }
130 }
131 }
131 }
132 }
132 }
133 b'?' => res.extend(b"."),
133 b'?' => res.extend(b"."),
134 b'[' => {
134 b'[' => {
135 match input.iter().skip(1).position(|b| *b == b']') {
135 match input.iter().skip(1).position(|b| *b == b']') {
136 None => res.extend(b"\\["),
136 None => res.extend(b"\\["),
137 Some(end) => {
137 Some(end) => {
138 // Account for the one we skipped
138 // Account for the one we skipped
139 let end = end + 1;
139 let end = end + 1;
140
140
141 res.extend(b"[");
141 res.extend(b"[");
142
142
143 for (i, b) in input[..end].iter().enumerate() {
143 for (i, b) in input[..end].iter().enumerate() {
144 if *b == b'!' && i == 0 {
144 if *b == b'!' && i == 0 {
145 res.extend(b"^")
145 res.extend(b"^")
146 } else if *b == b'^' && i == 0 {
146 } else if *b == b'^' && i == 0 {
147 res.extend(b"\\^")
147 res.extend(b"\\^")
148 } else if *b == b'\\' {
148 } else if *b == b'\\' {
149 res.extend(b"\\\\")
149 res.extend(b"\\\\")
150 } else {
150 } else {
151 res.push(*b)
151 res.push(*b)
152 }
152 }
153 }
153 }
154 res.extend(b"]");
154 res.extend(b"]");
155 input = &input[end + 1..];
155 input = &input[end + 1..];
156 }
156 }
157 }
157 }
158 }
158 }
159 b'{' => {
159 b'{' => {
160 group_depth += 1;
160 group_depth += 1;
161 res.extend(b"(?:")
161 res.extend(b"(?:")
162 }
162 }
163 b'}' if group_depth > 0 => {
163 b'}' if group_depth > 0 => {
164 group_depth -= 1;
164 group_depth -= 1;
165 res.extend(b")");
165 res.extend(b")");
166 }
166 }
167 b',' if group_depth > 0 => res.extend(b"|"),
167 b',' if group_depth > 0 => res.extend(b"|"),
168 b'\\' => {
168 b'\\' => {
169 let c = {
169 let c = {
170 if let Some((c, rest)) = input.split_first() {
170 if let Some((c, rest)) = input.split_first() {
171 input = rest;
171 input = rest;
172 c
172 c
173 } else {
173 } else {
174 c
174 c
175 }
175 }
176 };
176 };
177 res.extend(&RE_ESCAPE[*c as usize])
177 res.extend(&RE_ESCAPE[*c as usize])
178 }
178 }
179 _ => res.extend(&RE_ESCAPE[*c as usize]),
179 _ => res.extend(&RE_ESCAPE[*c as usize]),
180 }
180 }
181 }
181 }
182 res
182 res
183 }
183 }
184
184
185 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
185 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
186 pattern
186 pattern
187 .iter()
187 .iter()
188 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
188 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
189 .collect()
189 .collect()
190 }
190 }
191
191
192 pub fn parse_pattern_syntax_kind(
192 pub fn parse_pattern_syntax_kind(
193 kind: &[u8],
193 kind: &[u8],
194 ) -> Result<PatternSyntax, PatternError> {
194 ) -> Result<PatternSyntax, PatternError> {
195 match kind {
195 match kind {
196 b"re" => Ok(PatternSyntax::Regexp),
196 b"re" => Ok(PatternSyntax::Regexp),
197 b"path" => Ok(PatternSyntax::Path),
197 b"path" => Ok(PatternSyntax::Path),
198 b"filepath" => Ok(PatternSyntax::FilePath),
198 b"filepath" => Ok(PatternSyntax::FilePath),
199 b"relpath" => Ok(PatternSyntax::RelPath),
199 b"relpath" => Ok(PatternSyntax::RelPath),
200 b"rootfilesin" => Ok(PatternSyntax::RootFilesIn),
200 b"rootfilesin" => Ok(PatternSyntax::RootFilesIn),
201 b"relglob" => Ok(PatternSyntax::RelGlob),
201 b"relglob" => Ok(PatternSyntax::RelGlob),
202 b"relre" => Ok(PatternSyntax::RelRegexp),
202 b"relre" => Ok(PatternSyntax::RelRegexp),
203 b"glob" => Ok(PatternSyntax::Glob),
203 b"glob" => Ok(PatternSyntax::Glob),
204 b"rootglob" => Ok(PatternSyntax::RootGlob),
204 b"rootglob" => Ok(PatternSyntax::RootGlob),
205 b"include" => Ok(PatternSyntax::Include),
205 b"include" => Ok(PatternSyntax::Include),
206 b"subinclude" => Ok(PatternSyntax::SubInclude),
206 b"subinclude" => Ok(PatternSyntax::SubInclude),
207 _ => Err(PatternError::UnsupportedSyntax(
207 _ => Err(PatternError::UnsupportedSyntax(
208 String::from_utf8_lossy(kind).to_string(),
208 String::from_utf8_lossy(kind).to_string(),
209 )),
209 )),
210 }
210 }
211 }
211 }
212
212
213 lazy_static! {
213 lazy_static! {
214 static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap();
214 static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap();
215 }
215 }
216
216
217 /// Extra path components to match at the end of the pattern
218 #[derive(Clone, Copy)]
219 pub enum GlobSuffix {
220 /// `Empty` means the pattern only matches files, not directories,
221 /// so the path needs to match exactly.
222 Empty,
223 /// `MoreComponents` means the pattern matches directories as well,
224 /// so any path that has the pattern as a prefix, should match.
225 MoreComponents,
226 }
227
228 impl GlobSuffix {
229 fn to_re(self) -> &'static [u8] {
230 match self {
231 Self::Empty => b"$",
232 Self::MoreComponents => b"(?:/|$)",
233 }
234 }
235 }
236
217 /// Builds the regex that corresponds to the given pattern.
237 /// Builds the regex that corresponds to the given pattern.
218 /// If within a `syntax: regexp` context, returns the pattern,
238 /// If within a `syntax: regexp` context, returns the pattern,
219 /// otherwise, returns the corresponding regex.
239 /// otherwise, returns the corresponding regex.
220 fn _build_single_regex(entry: &IgnorePattern, glob_suffix: &[u8]) -> Vec<u8> {
240 fn _build_single_regex(
241 entry: &IgnorePattern,
242 glob_suffix: GlobSuffix,
243 ) -> Vec<u8> {
221 let IgnorePattern {
244 let IgnorePattern {
222 syntax, pattern, ..
245 syntax, pattern, ..
223 } = entry;
246 } = entry;
224 if pattern.is_empty() {
247 if pattern.is_empty() {
225 return vec![];
248 return vec![];
226 }
249 }
227 match syntax {
250 match syntax {
228 PatternSyntax::Regexp => pattern.to_owned(),
251 PatternSyntax::Regexp => pattern.to_owned(),
229 PatternSyntax::RelRegexp => {
252 PatternSyntax::RelRegexp => {
230 // The `regex` crate accepts `**` while `re2` and Python's `re`
253 // The `regex` crate accepts `**` while `re2` and Python's `re`
231 // do not. Checking for `*` correctly triggers the same error all
254 // do not. Checking for `*` correctly triggers the same error all
232 // engines.
255 // engines.
233 if pattern[0] == b'^'
256 if pattern[0] == b'^'
234 || pattern[0] == b'*'
257 || pattern[0] == b'*'
235 || pattern.starts_with(b".*")
258 || pattern.starts_with(b".*")
236 {
259 {
237 return pattern.to_owned();
260 return pattern.to_owned();
238 }
261 }
239 match FLAG_RE.find(pattern) {
262 match FLAG_RE.find(pattern) {
240 Some(mat) => {
263 Some(mat) => {
241 let s = mat.start();
264 let s = mat.start();
242 let e = mat.end();
265 let e = mat.end();
243 [
266 [
244 &b"(?"[..],
267 &b"(?"[..],
245 &pattern[s + 2..e - 1],
268 &pattern[s + 2..e - 1],
246 &b":"[..],
269 &b":"[..],
247 if pattern[e] == b'^'
270 if pattern[e] == b'^'
248 || pattern[e] == b'*'
271 || pattern[e] == b'*'
249 || pattern[e..].starts_with(b".*")
272 || pattern[e..].starts_with(b".*")
250 {
273 {
251 &b""[..]
274 &b""[..]
252 } else {
275 } else {
253 &b".*"[..]
276 &b".*"[..]
254 },
277 },
255 &pattern[e..],
278 &pattern[e..],
256 &b")"[..],
279 &b")"[..],
257 ]
280 ]
258 .concat()
281 .concat()
259 }
282 }
260 None => [&b".*"[..], pattern].concat(),
283 None => [&b".*"[..], pattern].concat(),
261 }
284 }
262 }
285 }
263 PatternSyntax::Path | PatternSyntax::RelPath => {
286 PatternSyntax::Path | PatternSyntax::RelPath => {
264 if pattern == b"." {
287 if pattern == b"." {
265 return vec![];
288 return vec![];
266 }
289 }
267 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
290 [
291 escape_pattern(pattern).as_slice(),
292 GlobSuffix::MoreComponents.to_re(),
293 ]
294 .concat()
268 }
295 }
269 PatternSyntax::RootFilesIn => {
296 PatternSyntax::RootFilesIn => {
270 let mut res = if pattern == b"." {
297 let mut res = if pattern == b"." {
271 vec![]
298 vec![]
272 } else {
299 } else {
273 // Pattern is a directory name.
300 // Pattern is a directory name.
274 [escape_pattern(pattern).as_slice(), b"/"].concat()
301 [escape_pattern(pattern).as_slice(), b"/"].concat()
275 };
302 };
276
303
277 // Anything after the pattern must be a non-directory.
304 // Anything after the pattern must be a non-directory.
278 res.extend(b"[^/]+$");
305 res.extend(b"[^/]+$");
279 res
306 res
280 }
307 }
281 PatternSyntax::RelGlob => {
308 PatternSyntax::RelGlob => {
282 let glob_re = glob_to_re(pattern);
309 let glob_re = glob_to_re(pattern);
283 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
310 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
284 [b".*", rest, glob_suffix].concat()
311 [b".*", rest, glob_suffix.to_re()].concat()
285 } else {
312 } else {
286 [b"(?:.*/)?", glob_re.as_slice(), glob_suffix].concat()
313 [b"(?:.*/)?", glob_re.as_slice(), glob_suffix.to_re()].concat()
287 }
314 }
288 }
315 }
289 PatternSyntax::Glob | PatternSyntax::RootGlob => {
316 PatternSyntax::Glob | PatternSyntax::RootGlob => {
290 [glob_to_re(pattern).as_slice(), glob_suffix].concat()
317 [glob_to_re(pattern).as_slice(), glob_suffix.to_re()].concat()
291 }
318 }
292 PatternSyntax::Include
319 PatternSyntax::Include
293 | PatternSyntax::SubInclude
320 | PatternSyntax::SubInclude
294 | PatternSyntax::ExpandedSubInclude(_)
321 | PatternSyntax::ExpandedSubInclude(_)
295 | PatternSyntax::FilePath => unreachable!(),
322 | PatternSyntax::FilePath => unreachable!(),
296 }
323 }
297 }
324 }
298
325
299 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
326 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
300 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
327 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
301
328
302 /// TODO support other platforms
329 /// TODO support other platforms
303 #[cfg(unix)]
330 #[cfg(unix)]
304 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
331 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
305 if bytes.is_empty() {
332 if bytes.is_empty() {
306 return b".".to_vec();
333 return b".".to_vec();
307 }
334 }
308 let sep = b'/';
335 let sep = b'/';
309
336
310 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
337 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
311 if initial_slashes > 2 {
338 if initial_slashes > 2 {
312 // POSIX allows one or two initial slashes, but treats three or more
339 // POSIX allows one or two initial slashes, but treats three or more
313 // as single slash.
340 // as single slash.
314 initial_slashes = 1;
341 initial_slashes = 1;
315 }
342 }
316 let components = bytes
343 let components = bytes
317 .split(|b| *b == sep)
344 .split(|b| *b == sep)
318 .filter(|c| !(c.is_empty() || c == b"."))
345 .filter(|c| !(c.is_empty() || c == b"."))
319 .fold(vec![], |mut acc, component| {
346 .fold(vec![], |mut acc, component| {
320 if component != b".."
347 if component != b".."
321 || (initial_slashes == 0 && acc.is_empty())
348 || (initial_slashes == 0 && acc.is_empty())
322 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
349 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
323 {
350 {
324 acc.push(component)
351 acc.push(component)
325 } else if !acc.is_empty() {
352 } else if !acc.is_empty() {
326 acc.pop();
353 acc.pop();
327 }
354 }
328 acc
355 acc
329 });
356 });
330 let mut new_bytes = components.join(&sep);
357 let mut new_bytes = components.join(&sep);
331
358
332 if initial_slashes > 0 {
359 if initial_slashes > 0 {
333 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
360 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
334 buf.extend(new_bytes);
361 buf.extend(new_bytes);
335 new_bytes = buf;
362 new_bytes = buf;
336 }
363 }
337 if new_bytes.is_empty() {
364 if new_bytes.is_empty() {
338 b".".to_vec()
365 b".".to_vec()
339 } else {
366 } else {
340 new_bytes
367 new_bytes
341 }
368 }
342 }
369 }
343
370
344 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
371 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
345 /// that don't need to be transformed into a regex.
372 /// that don't need to be transformed into a regex.
346 pub fn build_single_regex(
373 pub fn build_single_regex(
347 entry: &IgnorePattern,
374 entry: &IgnorePattern,
348 glob_suffix: &[u8],
375 glob_suffix: GlobSuffix,
349 ) -> Result<Option<Vec<u8>>, PatternError> {
376 ) -> Result<Option<Vec<u8>>, PatternError> {
350 let IgnorePattern {
377 let IgnorePattern {
351 pattern, syntax, ..
378 pattern, syntax, ..
352 } = entry;
379 } = entry;
353 let pattern = match syntax {
380 let pattern = match syntax {
354 PatternSyntax::RootGlob
381 PatternSyntax::RootGlob
355 | PatternSyntax::Path
382 | PatternSyntax::Path
356 | PatternSyntax::RelGlob
383 | PatternSyntax::RelGlob
357 | PatternSyntax::RelPath
384 | PatternSyntax::RelPath
358 | PatternSyntax::RootFilesIn => normalize_path_bytes(pattern),
385 | PatternSyntax::RootFilesIn => normalize_path_bytes(pattern),
359 PatternSyntax::Include | PatternSyntax::SubInclude => {
386 PatternSyntax::Include | PatternSyntax::SubInclude => {
360 return Err(PatternError::NonRegexPattern(entry.clone()))
387 return Err(PatternError::NonRegexPattern(entry.clone()))
361 }
388 }
362 _ => pattern.to_owned(),
389 _ => pattern.to_owned(),
363 };
390 };
364 let is_simple_rootglob = *syntax == PatternSyntax::RootGlob
391 let is_simple_rootglob = *syntax == PatternSyntax::RootGlob
365 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b));
392 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b));
366 if is_simple_rootglob || syntax == &PatternSyntax::FilePath {
393 if is_simple_rootglob || syntax == &PatternSyntax::FilePath {
367 Ok(None)
394 Ok(None)
368 } else {
395 } else {
369 let mut entry = entry.clone();
396 let mut entry = entry.clone();
370 entry.pattern = pattern;
397 entry.pattern = pattern;
371 Ok(Some(_build_single_regex(&entry, glob_suffix)))
398 Ok(Some(_build_single_regex(&entry, glob_suffix)))
372 }
399 }
373 }
400 }
374
401
375 lazy_static! {
402 lazy_static! {
376 static ref SYNTAXES: FastHashMap<&'static [u8], PatternSyntax> = {
403 static ref SYNTAXES: FastHashMap<&'static [u8], PatternSyntax> = {
377 let mut m = FastHashMap::default();
404 let mut m = FastHashMap::default();
378
405
379 m.insert(b"re:".as_ref(), PatternSyntax::Regexp);
406 m.insert(b"re:".as_ref(), PatternSyntax::Regexp);
380 m.insert(b"regexp:".as_ref(), PatternSyntax::Regexp);
407 m.insert(b"regexp:".as_ref(), PatternSyntax::Regexp);
381 m.insert(b"path:".as_ref(), PatternSyntax::Path);
408 m.insert(b"path:".as_ref(), PatternSyntax::Path);
382 m.insert(b"filepath:".as_ref(), PatternSyntax::FilePath);
409 m.insert(b"filepath:".as_ref(), PatternSyntax::FilePath);
383 m.insert(b"relpath:".as_ref(), PatternSyntax::RelPath);
410 m.insert(b"relpath:".as_ref(), PatternSyntax::RelPath);
384 m.insert(b"rootfilesin:".as_ref(), PatternSyntax::RootFilesIn);
411 m.insert(b"rootfilesin:".as_ref(), PatternSyntax::RootFilesIn);
385 m.insert(b"relglob:".as_ref(), PatternSyntax::RelGlob);
412 m.insert(b"relglob:".as_ref(), PatternSyntax::RelGlob);
386 m.insert(b"relre:".as_ref(), PatternSyntax::RelRegexp);
413 m.insert(b"relre:".as_ref(), PatternSyntax::RelRegexp);
387 m.insert(b"glob:".as_ref(), PatternSyntax::Glob);
414 m.insert(b"glob:".as_ref(), PatternSyntax::Glob);
388 m.insert(b"rootglob:".as_ref(), PatternSyntax::RootGlob);
415 m.insert(b"rootglob:".as_ref(), PatternSyntax::RootGlob);
389 m.insert(b"include:".as_ref(), PatternSyntax::Include);
416 m.insert(b"include:".as_ref(), PatternSyntax::Include);
390 m.insert(b"subinclude:".as_ref(), PatternSyntax::SubInclude);
417 m.insert(b"subinclude:".as_ref(), PatternSyntax::SubInclude);
391
418
392 m
419 m
393 };
420 };
394 }
421 }
395
422
396 #[derive(Debug)]
423 #[derive(Debug)]
397 pub enum PatternFileWarning {
424 pub enum PatternFileWarning {
398 /// (file path, syntax bytes)
425 /// (file path, syntax bytes)
399 InvalidSyntax(PathBuf, Vec<u8>),
426 InvalidSyntax(PathBuf, Vec<u8>),
400 /// File path
427 /// File path
401 NoSuchFile(PathBuf),
428 NoSuchFile(PathBuf),
402 }
429 }
403
430
404 pub fn parse_one_pattern(
431 pub fn parse_one_pattern(
405 pattern: &[u8],
432 pattern: &[u8],
406 source: &Path,
433 source: &Path,
407 default: PatternSyntax,
434 default: PatternSyntax,
408 normalize: bool,
435 normalize: bool,
409 ) -> IgnorePattern {
436 ) -> IgnorePattern {
410 let mut pattern_bytes: &[u8] = pattern;
437 let mut pattern_bytes: &[u8] = pattern;
411 let mut syntax = default;
438 let mut syntax = default;
412
439
413 for (s, val) in SYNTAXES.iter() {
440 for (s, val) in SYNTAXES.iter() {
414 if let Some(rest) = pattern_bytes.drop_prefix(s) {
441 if let Some(rest) = pattern_bytes.drop_prefix(s) {
415 syntax = val.clone();
442 syntax = val.clone();
416 pattern_bytes = rest;
443 pattern_bytes = rest;
417 break;
444 break;
418 }
445 }
419 }
446 }
420
447
421 let pattern = match syntax {
448 let pattern = match syntax {
422 PatternSyntax::RootGlob
449 PatternSyntax::RootGlob
423 | PatternSyntax::Path
450 | PatternSyntax::Path
424 | PatternSyntax::Glob
451 | PatternSyntax::Glob
425 | PatternSyntax::RelGlob
452 | PatternSyntax::RelGlob
426 | PatternSyntax::RelPath
453 | PatternSyntax::RelPath
427 | PatternSyntax::RootFilesIn
454 | PatternSyntax::RootFilesIn
428 if normalize =>
455 if normalize =>
429 {
456 {
430 normalize_path_bytes(pattern_bytes)
457 normalize_path_bytes(pattern_bytes)
431 }
458 }
432 _ => pattern_bytes.to_vec(),
459 _ => pattern_bytes.to_vec(),
433 };
460 };
434
461
435 IgnorePattern {
462 IgnorePattern {
436 syntax,
463 syntax,
437 pattern,
464 pattern,
438 source: source.to_owned(),
465 source: source.to_owned(),
439 }
466 }
440 }
467 }
441
468
442 pub fn parse_pattern_file_contents(
469 pub fn parse_pattern_file_contents(
443 lines: &[u8],
470 lines: &[u8],
444 file_path: &Path,
471 file_path: &Path,
445 default_syntax_override: Option<PatternSyntax>,
472 default_syntax_override: Option<PatternSyntax>,
446 warn: bool,
473 warn: bool,
447 relativize: bool,
474 relativize: bool,
448 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
475 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
449 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
476 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
450
477
451 #[allow(clippy::trivial_regex)]
478 #[allow(clippy::trivial_regex)]
452 let comment_escape_regex = Regex::new(r"\\#").unwrap();
479 let comment_escape_regex = Regex::new(r"\\#").unwrap();
453 let mut inputs: Vec<IgnorePattern> = vec![];
480 let mut inputs: Vec<IgnorePattern> = vec![];
454 let mut warnings: Vec<PatternFileWarning> = vec![];
481 let mut warnings: Vec<PatternFileWarning> = vec![];
455
482
456 let mut current_syntax =
483 let mut current_syntax =
457 default_syntax_override.unwrap_or(PatternSyntax::RelRegexp);
484 default_syntax_override.unwrap_or(PatternSyntax::RelRegexp);
458
485
459 for mut line in lines.split(|c| *c == b'\n') {
486 for mut line in lines.split(|c| *c == b'\n') {
460 let line_buf;
487 let line_buf;
461 if line.contains(&b'#') {
488 if line.contains(&b'#') {
462 if let Some(cap) = comment_regex.captures(line) {
489 if let Some(cap) = comment_regex.captures(line) {
463 line = &line[..cap.get(1).unwrap().end()]
490 line = &line[..cap.get(1).unwrap().end()]
464 }
491 }
465 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
492 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
466 line = &line_buf;
493 line = &line_buf;
467 }
494 }
468
495
469 let line = line.trim_end();
496 let line = line.trim_end();
470
497
471 if line.is_empty() {
498 if line.is_empty() {
472 continue;
499 continue;
473 }
500 }
474
501
475 if let Some(syntax) = line.drop_prefix(b"syntax:") {
502 if let Some(syntax) = line.drop_prefix(b"syntax:") {
476 let syntax = syntax.trim();
503 let syntax = syntax.trim();
477
504
478 if let Some(parsed) =
505 if let Some(parsed) =
479 SYNTAXES.get([syntax, &b":"[..]].concat().as_slice())
506 SYNTAXES.get([syntax, &b":"[..]].concat().as_slice())
480 {
507 {
481 current_syntax = parsed.clone();
508 current_syntax = parsed.clone();
482 } else if warn {
509 } else if warn {
483 warnings.push(PatternFileWarning::InvalidSyntax(
510 warnings.push(PatternFileWarning::InvalidSyntax(
484 file_path.to_owned(),
511 file_path.to_owned(),
485 syntax.to_owned(),
512 syntax.to_owned(),
486 ));
513 ));
487 }
514 }
488 } else {
515 } else {
489 let pattern = parse_one_pattern(
516 let pattern = parse_one_pattern(
490 line,
517 line,
491 file_path,
518 file_path,
492 current_syntax.clone(),
519 current_syntax.clone(),
493 false,
520 false,
494 );
521 );
495 inputs.push(if relativize {
522 inputs.push(if relativize {
496 pattern.to_relative()
523 pattern.to_relative()
497 } else {
524 } else {
498 pattern
525 pattern
499 })
526 })
500 }
527 }
501 }
528 }
502 Ok((inputs, warnings))
529 Ok((inputs, warnings))
503 }
530 }
504
531
505 pub fn parse_pattern_args(
532 pub fn parse_pattern_args(
506 patterns: Vec<Vec<u8>>,
533 patterns: Vec<Vec<u8>>,
507 cwd: &Path,
534 cwd: &Path,
508 root: &Path,
535 root: &Path,
509 ) -> Result<Vec<IgnorePattern>, HgPathError> {
536 ) -> Result<Vec<IgnorePattern>, HgPathError> {
510 let mut ignore_patterns: Vec<IgnorePattern> = Vec::new();
537 let mut ignore_patterns: Vec<IgnorePattern> = Vec::new();
511 for pattern in patterns {
538 for pattern in patterns {
512 let pattern = parse_one_pattern(
539 let pattern = parse_one_pattern(
513 &pattern,
540 &pattern,
514 Path::new("<args>"),
541 Path::new("<args>"),
515 PatternSyntax::RelPath,
542 PatternSyntax::RelPath,
516 true,
543 true,
517 );
544 );
518 match pattern.syntax {
545 match pattern.syntax {
519 PatternSyntax::RelGlob | PatternSyntax::RelPath => {
546 PatternSyntax::RelGlob | PatternSyntax::RelPath => {
520 let name = get_path_from_bytes(&pattern.pattern);
547 let name = get_path_from_bytes(&pattern.pattern);
521 let canon = canonical_path(root, cwd, name)?;
548 let canon = canonical_path(root, cwd, name)?;
522 ignore_patterns.push(IgnorePattern {
549 ignore_patterns.push(IgnorePattern {
523 syntax: pattern.syntax,
550 syntax: pattern.syntax,
524 pattern: get_bytes_from_path(canon),
551 pattern: get_bytes_from_path(canon),
525 source: pattern.source,
552 source: pattern.source,
526 })
553 })
527 }
554 }
528 _ => ignore_patterns.push(pattern.to_owned()),
555 _ => ignore_patterns.push(pattern.to_owned()),
529 };
556 };
530 }
557 }
531 Ok(ignore_patterns)
558 Ok(ignore_patterns)
532 }
559 }
533
560
534 pub fn read_pattern_file(
561 pub fn read_pattern_file(
535 file_path: &Path,
562 file_path: &Path,
536 warn: bool,
563 warn: bool,
537 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
564 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
538 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
565 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
539 match std::fs::read(file_path) {
566 match std::fs::read(file_path) {
540 Ok(contents) => {
567 Ok(contents) => {
541 inspect_pattern_bytes(file_path, &contents);
568 inspect_pattern_bytes(file_path, &contents);
542 parse_pattern_file_contents(&contents, file_path, None, warn, true)
569 parse_pattern_file_contents(&contents, file_path, None, warn, true)
543 }
570 }
544 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
571 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
545 vec![],
572 vec![],
546 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
573 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
547 )),
574 )),
548 Err(e) => Err(e.into()),
575 Err(e) => Err(e.into()),
549 }
576 }
550 }
577 }
551
578
552 /// Represents an entry in an "ignore" file.
579 /// Represents an entry in an "ignore" file.
553 #[derive(Debug, Eq, PartialEq, Clone)]
580 #[derive(Debug, Eq, PartialEq, Clone)]
554 pub struct IgnorePattern {
581 pub struct IgnorePattern {
555 pub syntax: PatternSyntax,
582 pub syntax: PatternSyntax,
556 pub pattern: Vec<u8>,
583 pub pattern: Vec<u8>,
557 pub source: PathBuf,
584 pub source: PathBuf,
558 }
585 }
559
586
560 impl IgnorePattern {
587 impl IgnorePattern {
561 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
588 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
562 Self {
589 Self {
563 syntax,
590 syntax,
564 pattern: pattern.to_owned(),
591 pattern: pattern.to_owned(),
565 source: source.to_owned(),
592 source: source.to_owned(),
566 }
593 }
567 }
594 }
568
595
569 pub fn to_relative(self) -> Self {
596 pub fn to_relative(self) -> Self {
570 let Self {
597 let Self {
571 syntax,
598 syntax,
572 pattern,
599 pattern,
573 source,
600 source,
574 } = self;
601 } = self;
575 Self {
602 Self {
576 syntax: match syntax {
603 syntax: match syntax {
577 PatternSyntax::Regexp => PatternSyntax::RelRegexp,
604 PatternSyntax::Regexp => PatternSyntax::RelRegexp,
578 PatternSyntax::Glob => PatternSyntax::RelGlob,
605 PatternSyntax::Glob => PatternSyntax::RelGlob,
579 x => x,
606 x => x,
580 },
607 },
581 pattern,
608 pattern,
582 source,
609 source,
583 }
610 }
584 }
611 }
585 }
612 }
586
613
587 pub type PatternResult<T> = Result<T, PatternError>;
614 pub type PatternResult<T> = Result<T, PatternError>;
588
615
589 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
616 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
590 /// and `subinclude:` patterns.
617 /// and `subinclude:` patterns.
591 ///
618 ///
592 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
619 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
593 /// is used for the latter to form a tree of patterns.
620 /// is used for the latter to form a tree of patterns.
594 pub fn get_patterns_from_file(
621 pub fn get_patterns_from_file(
595 pattern_file: &Path,
622 pattern_file: &Path,
596 root_dir: &Path,
623 root_dir: &Path,
597 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
624 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
598 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
625 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
599 let (patterns, mut warnings) =
626 let (patterns, mut warnings) =
600 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
627 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
601 let patterns = patterns
628 let patterns = patterns
602 .into_iter()
629 .into_iter()
603 .flat_map(|entry| -> PatternResult<_> {
630 .flat_map(|entry| -> PatternResult<_> {
604 Ok(match &entry.syntax {
631 Ok(match &entry.syntax {
605 PatternSyntax::Include => {
632 PatternSyntax::Include => {
606 let inner_include =
633 let inner_include =
607 root_dir.join(get_path_from_bytes(&entry.pattern));
634 root_dir.join(get_path_from_bytes(&entry.pattern));
608 let (inner_pats, inner_warnings) = get_patterns_from_file(
635 let (inner_pats, inner_warnings) = get_patterns_from_file(
609 &inner_include,
636 &inner_include,
610 root_dir,
637 root_dir,
611 inspect_pattern_bytes,
638 inspect_pattern_bytes,
612 )?;
639 )?;
613 warnings.extend(inner_warnings);
640 warnings.extend(inner_warnings);
614 inner_pats
641 inner_pats
615 }
642 }
616 PatternSyntax::SubInclude => {
643 PatternSyntax::SubInclude => {
617 let mut sub_include = SubInclude::new(
644 let mut sub_include = SubInclude::new(
618 root_dir,
645 root_dir,
619 &entry.pattern,
646 &entry.pattern,
620 &entry.source,
647 &entry.source,
621 )?;
648 )?;
622 let (inner_patterns, inner_warnings) =
649 let (inner_patterns, inner_warnings) =
623 get_patterns_from_file(
650 get_patterns_from_file(
624 &sub_include.path,
651 &sub_include.path,
625 &sub_include.root,
652 &sub_include.root,
626 inspect_pattern_bytes,
653 inspect_pattern_bytes,
627 )?;
654 )?;
628 sub_include.included_patterns = inner_patterns;
655 sub_include.included_patterns = inner_patterns;
629 warnings.extend(inner_warnings);
656 warnings.extend(inner_warnings);
630 vec![IgnorePattern {
657 vec![IgnorePattern {
631 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
658 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
632 sub_include,
659 sub_include,
633 )),
660 )),
634 ..entry
661 ..entry
635 }]
662 }]
636 }
663 }
637 _ => vec![entry],
664 _ => vec![entry],
638 })
665 })
639 })
666 })
640 .flatten()
667 .flatten()
641 .collect();
668 .collect();
642
669
643 Ok((patterns, warnings))
670 Ok((patterns, warnings))
644 }
671 }
645
672
646 /// Holds all the information needed to handle a `subinclude:` pattern.
673 /// Holds all the information needed to handle a `subinclude:` pattern.
647 #[derive(Debug, PartialEq, Eq, Clone)]
674 #[derive(Debug, PartialEq, Eq, Clone)]
648 pub struct SubInclude {
675 pub struct SubInclude {
649 /// Will be used for repository (hg) paths that start with this prefix.
676 /// Will be used for repository (hg) paths that start with this prefix.
650 /// It is relative to the current working directory, so comparing against
677 /// It is relative to the current working directory, so comparing against
651 /// repository paths is painless.
678 /// repository paths is painless.
652 pub prefix: HgPathBuf,
679 pub prefix: HgPathBuf,
653 /// The file itself, containing the patterns
680 /// The file itself, containing the patterns
654 pub path: PathBuf,
681 pub path: PathBuf,
655 /// Folder in the filesystem where this it applies
682 /// Folder in the filesystem where this it applies
656 pub root: PathBuf,
683 pub root: PathBuf,
657
684
658 pub included_patterns: Vec<IgnorePattern>,
685 pub included_patterns: Vec<IgnorePattern>,
659 }
686 }
660
687
661 impl SubInclude {
688 impl SubInclude {
662 pub fn new(
689 pub fn new(
663 root_dir: &Path,
690 root_dir: &Path,
664 pattern: &[u8],
691 pattern: &[u8],
665 source: &Path,
692 source: &Path,
666 ) -> Result<SubInclude, HgPathError> {
693 ) -> Result<SubInclude, HgPathError> {
667 let normalized_source =
694 let normalized_source =
668 normalize_path_bytes(&get_bytes_from_path(source));
695 normalize_path_bytes(&get_bytes_from_path(source));
669
696
670 let source_root = get_path_from_bytes(&normalized_source);
697 let source_root = get_path_from_bytes(&normalized_source);
671 let source_root = source_root.parent().unwrap_or(source_root);
698 let source_root = source_root.parent().unwrap_or(source_root);
672
699
673 let path = source_root.join(get_path_from_bytes(pattern));
700 let path = source_root.join(get_path_from_bytes(pattern));
674 let new_root = path.parent().unwrap_or_else(|| path.deref());
701 let new_root = path.parent().unwrap_or_else(|| path.deref());
675
702
676 let prefix = canonical_path(root_dir, root_dir, new_root)?;
703 let prefix = canonical_path(root_dir, root_dir, new_root)?;
677
704
678 Ok(Self {
705 Ok(Self {
679 prefix: path_to_hg_path_buf(prefix).map(|mut p| {
706 prefix: path_to_hg_path_buf(prefix).map(|mut p| {
680 if !p.is_empty() {
707 if !p.is_empty() {
681 p.push_byte(b'/');
708 p.push_byte(b'/');
682 }
709 }
683 p
710 p
684 })?,
711 })?,
685 path: path.to_owned(),
712 path: path.to_owned(),
686 root: new_root.to_owned(),
713 root: new_root.to_owned(),
687 included_patterns: Vec::new(),
714 included_patterns: Vec::new(),
688 })
715 })
689 }
716 }
690 }
717 }
691
718
692 /// Separate and pre-process subincludes from other patterns for the "ignore"
719 /// Separate and pre-process subincludes from other patterns for the "ignore"
693 /// phase.
720 /// phase.
694 pub fn filter_subincludes(
721 pub fn filter_subincludes(
695 ignore_patterns: Vec<IgnorePattern>,
722 ignore_patterns: Vec<IgnorePattern>,
696 ) -> Result<(Vec<SubInclude>, Vec<IgnorePattern>), HgPathError> {
723 ) -> Result<(Vec<SubInclude>, Vec<IgnorePattern>), HgPathError> {
697 let mut subincludes = vec![];
724 let mut subincludes = vec![];
698 let mut others = vec![];
725 let mut others = vec![];
699
726
700 for pattern in ignore_patterns {
727 for pattern in ignore_patterns {
701 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
728 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
702 {
729 {
703 subincludes.push(*sub_include);
730 subincludes.push(*sub_include);
704 } else {
731 } else {
705 others.push(pattern)
732 others.push(pattern)
706 }
733 }
707 }
734 }
708 Ok((subincludes, others))
735 Ok((subincludes, others))
709 }
736 }
710
737
711 #[cfg(test)]
738 #[cfg(test)]
712 mod tests {
739 mod tests {
713 use super::*;
740 use super::*;
714 use pretty_assertions::assert_eq;
741 use pretty_assertions::assert_eq;
715
742
716 #[test]
743 #[test]
717 fn escape_pattern_test() {
744 fn escape_pattern_test() {
718 let untouched =
745 let untouched =
719 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
746 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
720 assert_eq!(escape_pattern(untouched), untouched.to_vec());
747 assert_eq!(escape_pattern(untouched), untouched.to_vec());
721 // All escape codes
748 // All escape codes
722 assert_eq!(
749 assert_eq!(
723 escape_pattern(br"()[]{}?*+-|^$\\.&~#\t\n\r\v\f"),
750 escape_pattern(br"()[]{}?*+-|^$\\.&~#\t\n\r\v\f"),
724 br"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\\t\\n\\r\\v\\f".to_vec()
751 br"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\\t\\n\\r\\v\\f".to_vec()
725 );
752 );
726 }
753 }
727
754
728 #[test]
755 #[test]
729 fn glob_test() {
756 fn glob_test() {
730 assert_eq!(glob_to_re(br"?"), br".");
757 assert_eq!(glob_to_re(br"?"), br".");
731 assert_eq!(glob_to_re(br"*"), br"[^/]*");
758 assert_eq!(glob_to_re(br"*"), br"[^/]*");
732 assert_eq!(glob_to_re(br"**"), br".*");
759 assert_eq!(glob_to_re(br"**"), br".*");
733 assert_eq!(glob_to_re(br"**/a"), br"(?:.*/)?a");
760 assert_eq!(glob_to_re(br"**/a"), br"(?:.*/)?a");
734 assert_eq!(glob_to_re(br"a/**/b"), br"a/(?:.*/)?b");
761 assert_eq!(glob_to_re(br"a/**/b"), br"a/(?:.*/)?b");
735 assert_eq!(glob_to_re(br"[a*?!^][^b][!c]"), br"[a*?!^][\^b][^c]");
762 assert_eq!(glob_to_re(br"[a*?!^][^b][!c]"), br"[a*?!^][\^b][^c]");
736 assert_eq!(glob_to_re(br"{a,b}"), br"(?:a|b)");
763 assert_eq!(glob_to_re(br"{a,b}"), br"(?:a|b)");
737 assert_eq!(glob_to_re(br".\*\?"), br"\.\*\?");
764 assert_eq!(glob_to_re(br".\*\?"), br"\.\*\?");
738 }
765 }
739
766
740 #[test]
767 #[test]
741 fn test_parse_pattern_file_contents() {
768 fn test_parse_pattern_file_contents() {
742 let lines = b"syntax: glob\n*.elc";
769 let lines = b"syntax: glob\n*.elc";
743
770
744 assert_eq!(
771 assert_eq!(
745 parse_pattern_file_contents(
772 parse_pattern_file_contents(
746 lines,
773 lines,
747 Path::new("file_path"),
774 Path::new("file_path"),
748 None,
775 None,
749 false,
776 false,
750 true,
777 true,
751 )
778 )
752 .unwrap()
779 .unwrap()
753 .0,
780 .0,
754 vec![IgnorePattern::new(
781 vec![IgnorePattern::new(
755 PatternSyntax::RelGlob,
782 PatternSyntax::RelGlob,
756 b"*.elc",
783 b"*.elc",
757 Path::new("file_path")
784 Path::new("file_path")
758 )],
785 )],
759 );
786 );
760
787
761 let lines = b"syntax: include\nsyntax: glob";
788 let lines = b"syntax: include\nsyntax: glob";
762
789
763 assert_eq!(
790 assert_eq!(
764 parse_pattern_file_contents(
791 parse_pattern_file_contents(
765 lines,
792 lines,
766 Path::new("file_path"),
793 Path::new("file_path"),
767 None,
794 None,
768 false,
795 false,
769 true,
796 true,
770 )
797 )
771 .unwrap()
798 .unwrap()
772 .0,
799 .0,
773 vec![]
800 vec![]
774 );
801 );
775 let lines = b"glob:**.o";
802 let lines = b"glob:**.o";
776 assert_eq!(
803 assert_eq!(
777 parse_pattern_file_contents(
804 parse_pattern_file_contents(
778 lines,
805 lines,
779 Path::new("file_path"),
806 Path::new("file_path"),
780 None,
807 None,
781 false,
808 false,
782 true,
809 true,
783 )
810 )
784 .unwrap()
811 .unwrap()
785 .0,
812 .0,
786 vec![IgnorePattern::new(
813 vec![IgnorePattern::new(
787 PatternSyntax::RelGlob,
814 PatternSyntax::RelGlob,
788 b"**.o",
815 b"**.o",
789 Path::new("file_path")
816 Path::new("file_path")
790 )]
817 )]
791 );
818 );
792 }
819 }
793
820
794 #[test]
821 #[test]
795 fn test_build_single_regex() {
822 fn test_build_single_regex() {
796 assert_eq!(
823 assert_eq!(
797 build_single_regex(
824 build_single_regex(
798 &IgnorePattern::new(
825 &IgnorePattern::new(
799 PatternSyntax::RelGlob,
826 PatternSyntax::RelGlob,
800 b"rust/target/",
827 b"rust/target/",
801 Path::new("")
828 Path::new("")
802 ),
829 ),
803 b"(?:/|$)"
830 GlobSuffix::MoreComponents
804 )
831 )
805 .unwrap(),
832 .unwrap(),
806 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
833 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
807 );
834 );
808 assert_eq!(
835 assert_eq!(
809 build_single_regex(
836 build_single_regex(
810 &IgnorePattern::new(
837 &IgnorePattern::new(
811 PatternSyntax::Regexp,
838 PatternSyntax::Regexp,
812 br"rust/target/\d+",
839 br"rust/target/\d+",
813 Path::new("")
840 Path::new("")
814 ),
841 ),
815 b"(?:/|$)"
842 GlobSuffix::MoreComponents
816 )
843 )
817 .unwrap(),
844 .unwrap(),
818 Some(br"rust/target/\d+".to_vec()),
845 Some(br"rust/target/\d+".to_vec()),
819 );
846 );
820 }
847 }
821
848
822 #[test]
849 #[test]
823 fn test_build_single_regex_shortcut() {
850 fn test_build_single_regex_shortcut() {
824 assert_eq!(
851 assert_eq!(
825 build_single_regex(
852 build_single_regex(
826 &IgnorePattern::new(
853 &IgnorePattern::new(
827 PatternSyntax::RootGlob,
854 PatternSyntax::RootGlob,
828 b"",
855 b"",
829 Path::new("")
856 Path::new("")
830 ),
857 ),
831 b"(?:/|$)"
858 GlobSuffix::MoreComponents
832 )
859 )
833 .unwrap(),
860 .unwrap(),
834 None,
861 None,
835 );
862 );
836 assert_eq!(
863 assert_eq!(
837 build_single_regex(
864 build_single_regex(
838 &IgnorePattern::new(
865 &IgnorePattern::new(
839 PatternSyntax::RootGlob,
866 PatternSyntax::RootGlob,
840 b"whatever",
867 b"whatever",
841 Path::new("")
868 Path::new("")
842 ),
869 ),
843 b"(?:/|$)"
870 GlobSuffix::MoreComponents
844 )
871 )
845 .unwrap(),
872 .unwrap(),
846 None,
873 None,
847 );
874 );
848 assert_eq!(
875 assert_eq!(
849 build_single_regex(
876 build_single_regex(
850 &IgnorePattern::new(
877 &IgnorePattern::new(
851 PatternSyntax::RootGlob,
878 PatternSyntax::RootGlob,
852 b"*.o",
879 b"*.o",
853 Path::new("")
880 Path::new("")
854 ),
881 ),
855 b"(?:/|$)"
882 GlobSuffix::MoreComponents
856 )
883 )
857 .unwrap(),
884 .unwrap(),
858 Some(br"[^/]*\.o(?:/|$)".to_vec()),
885 Some(br"[^/]*\.o(?:/|$)".to_vec()),
859 );
886 );
860 }
887 }
861
888
862 #[test]
889 #[test]
863 fn test_build_single_relregex() {
890 fn test_build_single_relregex() {
864 assert_eq!(
891 assert_eq!(
865 build_single_regex(
892 build_single_regex(
866 &IgnorePattern::new(
893 &IgnorePattern::new(
867 PatternSyntax::RelRegexp,
894 PatternSyntax::RelRegexp,
868 b"^ba{2}r",
895 b"^ba{2}r",
869 Path::new("")
896 Path::new("")
870 ),
897 ),
871 b"(?:/|$)"
898 GlobSuffix::MoreComponents
872 )
899 )
873 .unwrap(),
900 .unwrap(),
874 Some(b"^ba{2}r".to_vec()),
901 Some(b"^ba{2}r".to_vec()),
875 );
902 );
876 assert_eq!(
903 assert_eq!(
877 build_single_regex(
904 build_single_regex(
878 &IgnorePattern::new(
905 &IgnorePattern::new(
879 PatternSyntax::RelRegexp,
906 PatternSyntax::RelRegexp,
880 b"ba{2}r",
907 b"ba{2}r",
881 Path::new("")
908 Path::new("")
882 ),
909 ),
883 b"(?:/|$)"
910 GlobSuffix::MoreComponents
884 )
911 )
885 .unwrap(),
912 .unwrap(),
886 Some(b".*ba{2}r".to_vec()),
913 Some(b".*ba{2}r".to_vec()),
887 );
914 );
888 assert_eq!(
915 assert_eq!(
889 build_single_regex(
916 build_single_regex(
890 &IgnorePattern::new(
917 &IgnorePattern::new(
891 PatternSyntax::RelRegexp,
918 PatternSyntax::RelRegexp,
892 b"(?ia)ba{2}r",
919 b"(?ia)ba{2}r",
893 Path::new("")
920 Path::new("")
894 ),
921 ),
895 b"(?:/|$)"
922 GlobSuffix::MoreComponents
896 )
923 )
897 .unwrap(),
924 .unwrap(),
898 Some(b"(?ia:.*ba{2}r)".to_vec()),
925 Some(b"(?ia:.*ba{2}r)".to_vec()),
899 );
926 );
900 assert_eq!(
927 assert_eq!(
901 build_single_regex(
928 build_single_regex(
902 &IgnorePattern::new(
929 &IgnorePattern::new(
903 PatternSyntax::RelRegexp,
930 PatternSyntax::RelRegexp,
904 b"(?ia)^ba{2}r",
931 b"(?ia)^ba{2}r",
905 Path::new("")
932 Path::new("")
906 ),
933 ),
907 b"(?:/|$)"
934 GlobSuffix::MoreComponents
908 )
935 )
909 .unwrap(),
936 .unwrap(),
910 Some(b"(?ia:^ba{2}r)".to_vec()),
937 Some(b"(?ia:^ba{2}r)".to_vec()),
911 );
938 );
912 }
939 }
913 }
940 }
@@ -1,2453 +1,2455
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, DirsMultiset},
14 dirstate::dirs_multiset::{DirsChildrenMultiset, DirsMultiset},
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 IgnorePattern, PatternError, PatternFileWarning, PatternResult,
17 GlobSuffix, IgnorePattern, PatternError, PatternFileWarning,
18 PatternSyntax,
18 PatternResult, PatternSyntax,
19 },
19 },
20 utils::{
20 utils::{
21 files::{dir_ancestors, find_dirs},
21 files::{dir_ancestors, find_dirs},
22 hg_path::{HgPath, HgPathBuf, HgPathError},
22 hg_path::{HgPath, HgPathBuf, HgPathError},
23 Escaped,
23 Escaped,
24 },
24 },
25 FastHashMap,
25 FastHashMap,
26 };
26 };
27
27
28 use crate::dirstate::status::IgnoreFnType;
28 use crate::dirstate::status::IgnoreFnType;
29 use crate::filepatterns::normalize_path_bytes;
29 use crate::filepatterns::normalize_path_bytes;
30 use std::collections::HashSet;
30 use std::collections::HashSet;
31 use std::fmt::{Display, Error, Formatter};
31 use std::fmt::{Display, Error, Formatter};
32 use std::path::{Path, PathBuf};
32 use std::path::{Path, PathBuf};
33 use std::{borrow::ToOwned, collections::BTreeSet};
33 use std::{borrow::ToOwned, collections::BTreeSet};
34
34
35 #[derive(Debug, PartialEq)]
35 #[derive(Debug, PartialEq)]
36 pub enum VisitChildrenSet {
36 pub enum VisitChildrenSet {
37 /// Don't visit anything
37 /// Don't visit anything
38 Empty,
38 Empty,
39 /// Visit this directory and probably its children
39 /// Visit this directory and probably its children
40 This,
40 This,
41 /// Only visit the children (both files and directories) if they
41 /// Only visit the children (both files and directories) if they
42 /// are mentioned in this set. (empty set corresponds to [`Self::Empty`])
42 /// are mentioned in this set. (empty set corresponds to [`Self::Empty`])
43 /// TODO Should we implement a `NonEmptyHashSet`?
43 /// TODO Should we implement a `NonEmptyHashSet`?
44 Set(HashSet<HgPathBuf>),
44 Set(HashSet<HgPathBuf>),
45 /// Visit this directory and all subdirectories
45 /// Visit this directory and all subdirectories
46 /// (you can stop asking about the children set)
46 /// (you can stop asking about the children set)
47 Recursive,
47 Recursive,
48 }
48 }
49
49
50 pub trait Matcher: core::fmt::Debug {
50 pub trait Matcher: core::fmt::Debug {
51 /// Explicitly listed files
51 /// Explicitly listed files
52 fn file_set(&self) -> Option<&HashSet<HgPathBuf>>;
52 fn file_set(&self) -> Option<&HashSet<HgPathBuf>>;
53 /// Returns whether `filename` is in `file_set`
53 /// Returns whether `filename` is in `file_set`
54 fn exact_match(&self, filename: &HgPath) -> bool;
54 fn exact_match(&self, filename: &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: &HgPath) -> bool;
56 fn matches(&self, filename: &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(&self, directory: &HgPath) -> VisitChildrenSet;
94 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet;
95 /// Matcher will match everything and `files_set()` will be empty:
95 /// Matcher will match everything and `files_set()` will be empty:
96 /// optimization might be possible.
96 /// optimization might be possible.
97 fn matches_everything(&self) -> bool;
97 fn matches_everything(&self) -> bool;
98 /// Matcher will match exactly the files in `files_set()`: optimization
98 /// Matcher will match exactly the files in `files_set()`: optimization
99 /// might be possible.
99 /// might be possible.
100 fn is_exact(&self) -> bool;
100 fn is_exact(&self) -> bool;
101 }
101 }
102
102
103 /// Matches everything.
103 /// Matches everything.
104 ///```
104 ///```
105 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
105 /// use hg::{ matchers::{Matcher, AlwaysMatcher}, utils::hg_path::HgPath };
106 ///
106 ///
107 /// let matcher = AlwaysMatcher;
107 /// let matcher = AlwaysMatcher;
108 ///
108 ///
109 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
109 /// assert_eq!(matcher.matches(HgPath::new(b"whatever")), true);
110 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
110 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), true);
111 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
111 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true);
112 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
112 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
113 /// ```
113 /// ```
114 #[derive(Debug)]
114 #[derive(Debug)]
115 pub struct AlwaysMatcher;
115 pub struct AlwaysMatcher;
116
116
117 impl Matcher for AlwaysMatcher {
117 impl Matcher for AlwaysMatcher {
118 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
118 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
119 None
119 None
120 }
120 }
121 fn exact_match(&self, _filename: &HgPath) -> bool {
121 fn exact_match(&self, _filename: &HgPath) -> bool {
122 false
122 false
123 }
123 }
124 fn matches(&self, _filename: &HgPath) -> bool {
124 fn matches(&self, _filename: &HgPath) -> bool {
125 true
125 true
126 }
126 }
127 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
127 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
128 VisitChildrenSet::Recursive
128 VisitChildrenSet::Recursive
129 }
129 }
130 fn matches_everything(&self) -> bool {
130 fn matches_everything(&self) -> bool {
131 true
131 true
132 }
132 }
133 fn is_exact(&self) -> bool {
133 fn is_exact(&self) -> bool {
134 false
134 false
135 }
135 }
136 }
136 }
137
137
138 /// Matches nothing.
138 /// Matches nothing.
139 #[derive(Debug)]
139 #[derive(Debug)]
140 pub struct NeverMatcher;
140 pub struct NeverMatcher;
141
141
142 impl Matcher for NeverMatcher {
142 impl Matcher for NeverMatcher {
143 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
143 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
144 None
144 None
145 }
145 }
146 fn exact_match(&self, _filename: &HgPath) -> bool {
146 fn exact_match(&self, _filename: &HgPath) -> bool {
147 false
147 false
148 }
148 }
149 fn matches(&self, _filename: &HgPath) -> bool {
149 fn matches(&self, _filename: &HgPath) -> bool {
150 false
150 false
151 }
151 }
152 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
152 fn visit_children_set(&self, _directory: &HgPath) -> VisitChildrenSet {
153 VisitChildrenSet::Empty
153 VisitChildrenSet::Empty
154 }
154 }
155 fn matches_everything(&self) -> bool {
155 fn matches_everything(&self) -> bool {
156 false
156 false
157 }
157 }
158 fn is_exact(&self) -> bool {
158 fn is_exact(&self) -> bool {
159 true
159 true
160 }
160 }
161 }
161 }
162
162
163 /// Matches the input files exactly. They are interpreted as paths, not
163 /// Matches the input files exactly. They are interpreted as paths, not
164 /// patterns.
164 /// patterns.
165 ///
165 ///
166 ///```
166 ///```
167 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::{HgPath, HgPathBuf} };
167 /// use hg::{ matchers::{Matcher, FileMatcher}, utils::hg_path::{HgPath, HgPathBuf} };
168 ///
168 ///
169 /// let files = vec![HgPathBuf::from_bytes(b"a.txt"), HgPathBuf::from_bytes(br"re:.*\.c$")];
169 /// let files = vec![HgPathBuf::from_bytes(b"a.txt"), HgPathBuf::from_bytes(br"re:.*\.c$")];
170 /// let matcher = FileMatcher::new(files).unwrap();
170 /// let matcher = FileMatcher::new(files).unwrap();
171 ///
171 ///
172 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
172 /// assert_eq!(matcher.matches(HgPath::new(b"a.txt")), true);
173 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
173 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
174 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
174 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), false);
175 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
175 /// assert_eq!(matcher.matches(HgPath::new(br"re:.*\.c$")), true);
176 /// ```
176 /// ```
177 #[derive(Debug)]
177 #[derive(Debug)]
178 pub struct FileMatcher {
178 pub struct FileMatcher {
179 files: HashSet<HgPathBuf>,
179 files: HashSet<HgPathBuf>,
180 dirs: DirsMultiset,
180 dirs: DirsMultiset,
181 sorted_visitchildrenset_candidates: OnceCell<BTreeSet<HgPathBuf>>,
181 sorted_visitchildrenset_candidates: OnceCell<BTreeSet<HgPathBuf>>,
182 }
182 }
183
183
184 impl FileMatcher {
184 impl FileMatcher {
185 pub fn new(files: Vec<HgPathBuf>) -> Result<Self, HgPathError> {
185 pub fn new(files: Vec<HgPathBuf>) -> Result<Self, HgPathError> {
186 let dirs = DirsMultiset::from_manifest(&files)?;
186 let dirs = DirsMultiset::from_manifest(&files)?;
187 Ok(Self {
187 Ok(Self {
188 files: HashSet::from_iter(files),
188 files: HashSet::from_iter(files),
189 dirs,
189 dirs,
190 sorted_visitchildrenset_candidates: OnceCell::new(),
190 sorted_visitchildrenset_candidates: OnceCell::new(),
191 })
191 })
192 }
192 }
193 fn inner_matches(&self, filename: &HgPath) -> bool {
193 fn inner_matches(&self, filename: &HgPath) -> bool {
194 self.files.contains(filename.as_ref())
194 self.files.contains(filename.as_ref())
195 }
195 }
196 }
196 }
197
197
198 impl Matcher for FileMatcher {
198 impl Matcher for FileMatcher {
199 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
199 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
200 Some(&self.files)
200 Some(&self.files)
201 }
201 }
202 fn exact_match(&self, filename: &HgPath) -> bool {
202 fn exact_match(&self, filename: &HgPath) -> bool {
203 self.inner_matches(filename)
203 self.inner_matches(filename)
204 }
204 }
205 fn matches(&self, filename: &HgPath) -> bool {
205 fn matches(&self, filename: &HgPath) -> bool {
206 self.inner_matches(filename)
206 self.inner_matches(filename)
207 }
207 }
208 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
208 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
209 if self.files.is_empty() || !self.dirs.contains(directory) {
209 if self.files.is_empty() || !self.dirs.contains(directory) {
210 return VisitChildrenSet::Empty;
210 return VisitChildrenSet::Empty;
211 }
211 }
212
212
213 let compute_candidates = || -> BTreeSet<HgPathBuf> {
213 let compute_candidates = || -> BTreeSet<HgPathBuf> {
214 let mut candidates: BTreeSet<HgPathBuf> =
214 let mut candidates: BTreeSet<HgPathBuf> =
215 self.dirs.iter().cloned().collect();
215 self.dirs.iter().cloned().collect();
216 candidates.extend(self.files.iter().cloned());
216 candidates.extend(self.files.iter().cloned());
217 candidates.remove(HgPath::new(b""));
217 candidates.remove(HgPath::new(b""));
218 candidates
218 candidates
219 };
219 };
220 let candidates =
220 let candidates =
221 if directory.as_ref().is_empty() {
221 if directory.as_ref().is_empty() {
222 compute_candidates()
222 compute_candidates()
223 } else {
223 } else {
224 let sorted_candidates = self
224 let sorted_candidates = self
225 .sorted_visitchildrenset_candidates
225 .sorted_visitchildrenset_candidates
226 .get_or_init(compute_candidates);
226 .get_or_init(compute_candidates);
227 let directory_bytes = directory.as_ref().as_bytes();
227 let directory_bytes = directory.as_ref().as_bytes();
228 let start: HgPathBuf =
228 let start: HgPathBuf =
229 format_bytes!(b"{}/", directory_bytes).into();
229 format_bytes!(b"{}/", directory_bytes).into();
230 let start_len = start.len();
230 let start_len = start.len();
231 // `0` sorts after `/`
231 // `0` sorts after `/`
232 let end = format_bytes!(b"{}0", directory_bytes).into();
232 let end = format_bytes!(b"{}0", directory_bytes).into();
233 BTreeSet::from_iter(sorted_candidates.range(start..end).map(
233 BTreeSet::from_iter(sorted_candidates.range(start..end).map(
234 |c| HgPathBuf::from_bytes(&c.as_bytes()[start_len..]),
234 |c| HgPathBuf::from_bytes(&c.as_bytes()[start_len..]),
235 ))
235 ))
236 };
236 };
237
237
238 // `self.dirs` includes all of the directories, recursively, so if
238 // `self.dirs` includes all of the directories, recursively, so if
239 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
239 // we're attempting to match 'foo/bar/baz.txt', it'll have '', 'foo',
240 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
240 // 'foo/bar' in it. Thus we can safely ignore a candidate that has a
241 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
241 // '/' in it, indicating it's for a subdir-of-a-subdir; the immediate
242 // subdir will be in there without a slash.
242 // subdir will be in there without a slash.
243 VisitChildrenSet::Set(
243 VisitChildrenSet::Set(
244 candidates
244 candidates
245 .into_iter()
245 .into_iter()
246 .filter_map(|c| {
246 .filter_map(|c| {
247 if c.bytes().all(|b| *b != b'/') {
247 if c.bytes().all(|b| *b != b'/') {
248 Some(c)
248 Some(c)
249 } else {
249 } else {
250 None
250 None
251 }
251 }
252 })
252 })
253 .collect(),
253 .collect(),
254 )
254 )
255 }
255 }
256 fn matches_everything(&self) -> bool {
256 fn matches_everything(&self) -> bool {
257 false
257 false
258 }
258 }
259 fn is_exact(&self) -> bool {
259 fn is_exact(&self) -> bool {
260 true
260 true
261 }
261 }
262 }
262 }
263
263
264 /// Matches a set of (kind, pat, source) against a 'root' directory.
264 /// Matches a set of (kind, pat, source) against a 'root' directory.
265 /// (Currently the 'root' directory is effectively always empty)
265 /// (Currently the 'root' directory is effectively always empty)
266 /// ```
266 /// ```
267 /// use hg::{
267 /// use hg::{
268 /// matchers::{PatternMatcher, Matcher},
268 /// matchers::{PatternMatcher, Matcher},
269 /// filepatterns::{IgnorePattern, PatternSyntax},
269 /// filepatterns::{IgnorePattern, PatternSyntax},
270 /// utils::hg_path::{HgPath, HgPathBuf}
270 /// utils::hg_path::{HgPath, HgPathBuf}
271 /// };
271 /// };
272 /// use std::collections::HashSet;
272 /// use std::collections::HashSet;
273 /// use std::path::Path;
273 /// use std::path::Path;
274 /// ///
274 /// ///
275 /// let ignore_patterns : Vec<IgnorePattern> =
275 /// let ignore_patterns : Vec<IgnorePattern> =
276 /// vec![IgnorePattern::new(PatternSyntax::Regexp, br".*\.c$", Path::new("")),
276 /// vec![IgnorePattern::new(PatternSyntax::Regexp, br".*\.c$", Path::new("")),
277 /// IgnorePattern::new(PatternSyntax::Path, b"foo/a", Path::new("")),
277 /// IgnorePattern::new(PatternSyntax::Path, b"foo/a", Path::new("")),
278 /// IgnorePattern::new(PatternSyntax::RelPath, b"b", Path::new("")),
278 /// IgnorePattern::new(PatternSyntax::RelPath, b"b", Path::new("")),
279 /// IgnorePattern::new(PatternSyntax::Glob, b"*.h", Path::new("")),
279 /// IgnorePattern::new(PatternSyntax::Glob, b"*.h", Path::new("")),
280 /// ];
280 /// ];
281 /// let matcher = PatternMatcher::new(ignore_patterns).unwrap();
281 /// let matcher = PatternMatcher::new(ignore_patterns).unwrap();
282 /// ///
282 /// ///
283 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true); // matches re:.*\.c$
283 /// assert_eq!(matcher.matches(HgPath::new(b"main.c")), true); // matches re:.*\.c$
284 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
284 /// assert_eq!(matcher.matches(HgPath::new(b"b.txt")), false);
285 /// assert_eq!(matcher.matches(HgPath::new(b"foo/a")), true); // matches path:foo/a
285 /// assert_eq!(matcher.matches(HgPath::new(b"foo/a")), true); // matches path:foo/a
286 /// assert_eq!(matcher.matches(HgPath::new(b"a")), false); // does not match path:b, since 'root' is 'foo'
286 /// assert_eq!(matcher.matches(HgPath::new(b"a")), false); // does not match path:b, since 'root' is 'foo'
287 /// assert_eq!(matcher.matches(HgPath::new(b"b")), true); // matches relpath:b, since 'root' is 'foo'
287 /// assert_eq!(matcher.matches(HgPath::new(b"b")), true); // matches relpath:b, since 'root' is 'foo'
288 /// assert_eq!(matcher.matches(HgPath::new(b"lib.h")), true); // matches glob:*.h
288 /// assert_eq!(matcher.matches(HgPath::new(b"lib.h")), true); // matches glob:*.h
289 /// assert_eq!(matcher.file_set().unwrap(),
289 /// assert_eq!(matcher.file_set().unwrap(),
290 /// &HashSet::from([HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"foo/a"),
290 /// &HashSet::from([HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"foo/a"),
291 /// HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"b")]));
291 /// HgPathBuf::from_bytes(b""), HgPathBuf::from_bytes(b"b")]));
292 /// assert_eq!(matcher.exact_match(HgPath::new(b"foo/a")), true);
292 /// assert_eq!(matcher.exact_match(HgPath::new(b"foo/a")), true);
293 /// assert_eq!(matcher.exact_match(HgPath::new(b"b")), true);
293 /// assert_eq!(matcher.exact_match(HgPath::new(b"b")), true);
294 /// assert_eq!(matcher.exact_match(HgPath::new(b"lib.h")), false); // exact matches are for (rel)path kinds
294 /// assert_eq!(matcher.exact_match(HgPath::new(b"lib.h")), false); // exact matches are for (rel)path kinds
295 /// ```
295 /// ```
296 pub struct PatternMatcher<'a> {
296 pub struct PatternMatcher<'a> {
297 patterns: Vec<u8>,
297 patterns: Vec<u8>,
298 match_fn: IgnoreFnType<'a>,
298 match_fn: IgnoreFnType<'a>,
299 /// Whether all the patterns match a prefix (i.e. recursively)
299 /// Whether all the patterns match a prefix (i.e. recursively)
300 prefix: bool,
300 prefix: bool,
301 files: HashSet<HgPathBuf>,
301 files: HashSet<HgPathBuf>,
302 dirs_explicit: HashSet<HgPathBuf>,
302 dirs_explicit: HashSet<HgPathBuf>,
303 dirs: DirsMultiset,
303 dirs: DirsMultiset,
304 }
304 }
305
305
306 impl core::fmt::Debug for PatternMatcher<'_> {
306 impl core::fmt::Debug for PatternMatcher<'_> {
307 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
307 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
308 f.debug_struct("PatternMatcher")
308 f.debug_struct("PatternMatcher")
309 .field("patterns", &String::from_utf8_lossy(&self.patterns))
309 .field("patterns", &String::from_utf8_lossy(&self.patterns))
310 .field("prefix", &self.prefix)
310 .field("prefix", &self.prefix)
311 .field("files", &self.files)
311 .field("files", &self.files)
312 .field("dirs", &self.dirs)
312 .field("dirs", &self.dirs)
313 .finish()
313 .finish()
314 }
314 }
315 }
315 }
316
316
317 impl<'a> PatternMatcher<'a> {
317 impl<'a> PatternMatcher<'a> {
318 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
318 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
319 let RootsDirsAndParents {
319 let RootsDirsAndParents {
320 roots,
320 roots,
321 dirs: dirs_explicit,
321 dirs: dirs_explicit,
322 parents,
322 parents,
323 } = roots_dirs_and_parents(&ignore_patterns)?;
323 } = roots_dirs_and_parents(&ignore_patterns)?;
324 let files = roots;
324 let files = roots;
325 let dirs = parents;
325 let dirs = parents;
326 let files: HashSet<HgPathBuf> = HashSet::from_iter(files);
326 let files: HashSet<HgPathBuf> = HashSet::from_iter(files);
327
327
328 let prefix = ignore_patterns.iter().all(|k| {
328 let prefix = ignore_patterns.iter().all(|k| {
329 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
329 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
330 });
330 });
331 let (patterns, match_fn) = build_match(ignore_patterns, b"$")?;
331 let (patterns, match_fn) =
332 build_match(ignore_patterns, GlobSuffix::Empty)?;
332
333
333 Ok(Self {
334 Ok(Self {
334 patterns,
335 patterns,
335 match_fn,
336 match_fn,
336 prefix,
337 prefix,
337 files,
338 files,
338 dirs,
339 dirs,
339 dirs_explicit,
340 dirs_explicit,
340 })
341 })
341 }
342 }
342 }
343 }
343
344
344 impl<'a> Matcher for PatternMatcher<'a> {
345 impl<'a> Matcher for PatternMatcher<'a> {
345 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
346 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
346 Some(&self.files)
347 Some(&self.files)
347 }
348 }
348
349
349 fn exact_match(&self, filename: &HgPath) -> bool {
350 fn exact_match(&self, filename: &HgPath) -> bool {
350 self.files.contains(filename)
351 self.files.contains(filename)
351 }
352 }
352
353
353 fn matches(&self, filename: &HgPath) -> bool {
354 fn matches(&self, filename: &HgPath) -> bool {
354 if self.files.contains(filename) {
355 if self.files.contains(filename) {
355 return true;
356 return true;
356 }
357 }
357 (self.match_fn)(filename)
358 (self.match_fn)(filename)
358 }
359 }
359
360
360 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
361 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
361 if self.prefix && self.files.contains(directory) {
362 if self.prefix && self.files.contains(directory) {
362 return VisitChildrenSet::Recursive;
363 return VisitChildrenSet::Recursive;
363 }
364 }
364 if self.dirs.contains(directory) {
365 if self.dirs.contains(directory) {
365 return VisitChildrenSet::This;
366 return VisitChildrenSet::This;
366 }
367 }
367 if dir_ancestors(directory).any(|parent_dir| {
368 if dir_ancestors(directory).any(|parent_dir| {
368 self.files.contains(parent_dir)
369 self.files.contains(parent_dir)
369 || self.dirs_explicit.contains(parent_dir)
370 || self.dirs_explicit.contains(parent_dir)
370 }) {
371 }) {
371 VisitChildrenSet::This
372 VisitChildrenSet::This
372 } else {
373 } else {
373 VisitChildrenSet::Empty
374 VisitChildrenSet::Empty
374 }
375 }
375 }
376 }
376
377
377 fn matches_everything(&self) -> bool {
378 fn matches_everything(&self) -> bool {
378 false
379 false
379 }
380 }
380
381
381 fn is_exact(&self) -> bool {
382 fn is_exact(&self) -> bool {
382 false
383 false
383 }
384 }
384 }
385 }
385
386
386 /// Matches files that are included in the ignore rules.
387 /// Matches files that are included in the ignore rules.
387 /// ```
388 /// ```
388 /// use hg::{
389 /// use hg::{
389 /// matchers::{IncludeMatcher, Matcher},
390 /// matchers::{IncludeMatcher, Matcher},
390 /// filepatterns::{IgnorePattern, PatternSyntax},
391 /// filepatterns::{IgnorePattern, PatternSyntax},
391 /// utils::hg_path::HgPath
392 /// utils::hg_path::HgPath
392 /// };
393 /// };
393 /// use std::path::Path;
394 /// use std::path::Path;
394 /// ///
395 /// ///
395 /// let ignore_patterns =
396 /// let ignore_patterns =
396 /// vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
397 /// vec![IgnorePattern::new(PatternSyntax::RootGlob, b"this*", Path::new(""))];
397 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
398 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
398 /// ///
399 /// ///
399 /// assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
400 /// assert_eq!(matcher.matches(HgPath::new(b"testing")), false);
400 /// assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
401 /// assert_eq!(matcher.matches(HgPath::new(b"this should work")), true);
401 /// assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
402 /// assert_eq!(matcher.matches(HgPath::new(b"this also")), true);
402 /// assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
403 /// assert_eq!(matcher.matches(HgPath::new(b"but not this")), false);
403 /// ///
404 /// ///
404 /// let ignore_patterns =
405 /// let ignore_patterns =
405 /// vec![IgnorePattern::new(PatternSyntax::RootFilesIn, b"dir/subdir", Path::new(""))];
406 /// vec![IgnorePattern::new(PatternSyntax::RootFilesIn, b"dir/subdir", Path::new(""))];
406 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
407 /// let matcher = IncludeMatcher::new(ignore_patterns).unwrap();
407 /// ///
408 /// ///
408 /// assert!(!matcher.matches(HgPath::new(b"file")));
409 /// assert!(!matcher.matches(HgPath::new(b"file")));
409 /// assert!(!matcher.matches(HgPath::new(b"dir/file")));
410 /// assert!(!matcher.matches(HgPath::new(b"dir/file")));
410 /// assert!(matcher.matches(HgPath::new(b"dir/subdir/file")));
411 /// assert!(matcher.matches(HgPath::new(b"dir/subdir/file")));
411 /// assert!(!matcher.matches(HgPath::new(b"dir/subdir/subsubdir/file")));
412 /// assert!(!matcher.matches(HgPath::new(b"dir/subdir/subsubdir/file")));
412 /// ```
413 /// ```
413 pub struct IncludeMatcher<'a> {
414 pub struct IncludeMatcher<'a> {
414 patterns: Vec<u8>,
415 patterns: Vec<u8>,
415 match_fn: IgnoreFnType<'a>,
416 match_fn: IgnoreFnType<'a>,
416 /// Whether all the patterns match a prefix (i.e. recursively)
417 /// Whether all the patterns match a prefix (i.e. recursively)
417 prefix: bool,
418 prefix: bool,
418 roots: HashSet<HgPathBuf>,
419 roots: HashSet<HgPathBuf>,
419 dirs: HashSet<HgPathBuf>,
420 dirs: HashSet<HgPathBuf>,
420 parents: DirsMultiset,
421 parents: DirsMultiset,
421 }
422 }
422
423
423 impl core::fmt::Debug for IncludeMatcher<'_> {
424 impl core::fmt::Debug for IncludeMatcher<'_> {
424 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
425 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
425 f.debug_struct("IncludeMatcher")
426 f.debug_struct("IncludeMatcher")
426 .field("patterns", &String::from_utf8_lossy(&self.patterns))
427 .field("patterns", &String::from_utf8_lossy(&self.patterns))
427 .field("prefix", &self.prefix)
428 .field("prefix", &self.prefix)
428 .field("roots", &self.roots)
429 .field("roots", &self.roots)
429 .field("dirs", &self.dirs)
430 .field("dirs", &self.dirs)
430 .field("parents", &self.parents)
431 .field("parents", &self.parents)
431 .finish()
432 .finish()
432 }
433 }
433 }
434 }
434
435
435 impl<'a> Matcher for IncludeMatcher<'a> {
436 impl<'a> Matcher for IncludeMatcher<'a> {
436 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
437 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
437 None
438 None
438 }
439 }
439
440
440 fn exact_match(&self, _filename: &HgPath) -> bool {
441 fn exact_match(&self, _filename: &HgPath) -> bool {
441 false
442 false
442 }
443 }
443
444
444 fn matches(&self, filename: &HgPath) -> bool {
445 fn matches(&self, filename: &HgPath) -> bool {
445 (self.match_fn)(filename)
446 (self.match_fn)(filename)
446 }
447 }
447
448
448 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
449 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
449 let dir = directory;
450 let dir = directory;
450 if self.prefix && self.roots.contains(dir) {
451 if self.prefix && self.roots.contains(dir) {
451 return VisitChildrenSet::Recursive;
452 return VisitChildrenSet::Recursive;
452 }
453 }
453 if self.roots.contains(HgPath::new(b""))
454 if self.roots.contains(HgPath::new(b""))
454 || self.roots.contains(dir)
455 || self.roots.contains(dir)
455 || self.dirs.contains(dir)
456 || self.dirs.contains(dir)
456 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
457 || find_dirs(dir).any(|parent_dir| self.roots.contains(parent_dir))
457 {
458 {
458 return VisitChildrenSet::This;
459 return VisitChildrenSet::This;
459 }
460 }
460
461
461 if self.parents.contains(dir.as_ref()) {
462 if self.parents.contains(dir.as_ref()) {
462 let multiset = self.get_all_parents_children();
463 let multiset = self.get_all_parents_children();
463 if let Some(children) = multiset.get(dir) {
464 if let Some(children) = multiset.get(dir) {
464 return VisitChildrenSet::Set(
465 return VisitChildrenSet::Set(
465 children.iter().map(HgPathBuf::from).collect(),
466 children.iter().map(HgPathBuf::from).collect(),
466 );
467 );
467 }
468 }
468 }
469 }
469 VisitChildrenSet::Empty
470 VisitChildrenSet::Empty
470 }
471 }
471
472
472 fn matches_everything(&self) -> bool {
473 fn matches_everything(&self) -> bool {
473 false
474 false
474 }
475 }
475
476
476 fn is_exact(&self) -> bool {
477 fn is_exact(&self) -> bool {
477 false
478 false
478 }
479 }
479 }
480 }
480
481
481 /// The union of multiple matchers. Will match if any of the matchers match.
482 /// The union of multiple matchers. Will match if any of the matchers match.
482 #[derive(Debug)]
483 #[derive(Debug)]
483 pub struct UnionMatcher {
484 pub struct UnionMatcher {
484 matchers: Vec<Box<dyn Matcher + Sync>>,
485 matchers: Vec<Box<dyn Matcher + Sync>>,
485 }
486 }
486
487
487 impl Matcher for UnionMatcher {
488 impl Matcher for UnionMatcher {
488 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
489 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
489 None
490 None
490 }
491 }
491
492
492 fn exact_match(&self, _filename: &HgPath) -> bool {
493 fn exact_match(&self, _filename: &HgPath) -> bool {
493 false
494 false
494 }
495 }
495
496
496 fn matches(&self, filename: &HgPath) -> bool {
497 fn matches(&self, filename: &HgPath) -> bool {
497 self.matchers.iter().any(|m| m.matches(filename))
498 self.matchers.iter().any(|m| m.matches(filename))
498 }
499 }
499
500
500 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
501 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
501 let mut result = HashSet::new();
502 let mut result = HashSet::new();
502 let mut this = false;
503 let mut this = false;
503 for matcher in self.matchers.iter() {
504 for matcher in self.matchers.iter() {
504 let visit = matcher.visit_children_set(directory);
505 let visit = matcher.visit_children_set(directory);
505 match visit {
506 match visit {
506 VisitChildrenSet::Empty => continue,
507 VisitChildrenSet::Empty => continue,
507 VisitChildrenSet::This => {
508 VisitChildrenSet::This => {
508 this = true;
509 this = true;
509 // Don't break, we might have an 'all' in here.
510 // Don't break, we might have an 'all' in here.
510 continue;
511 continue;
511 }
512 }
512 VisitChildrenSet::Set(set) => {
513 VisitChildrenSet::Set(set) => {
513 result.extend(set);
514 result.extend(set);
514 }
515 }
515 VisitChildrenSet::Recursive => {
516 VisitChildrenSet::Recursive => {
516 return visit;
517 return visit;
517 }
518 }
518 }
519 }
519 }
520 }
520 if this {
521 if this {
521 return VisitChildrenSet::This;
522 return VisitChildrenSet::This;
522 }
523 }
523 if result.is_empty() {
524 if result.is_empty() {
524 VisitChildrenSet::Empty
525 VisitChildrenSet::Empty
525 } else {
526 } else {
526 VisitChildrenSet::Set(result)
527 VisitChildrenSet::Set(result)
527 }
528 }
528 }
529 }
529
530
530 fn matches_everything(&self) -> bool {
531 fn matches_everything(&self) -> bool {
531 // TODO Maybe if all are AlwaysMatcher?
532 // TODO Maybe if all are AlwaysMatcher?
532 false
533 false
533 }
534 }
534
535
535 fn is_exact(&self) -> bool {
536 fn is_exact(&self) -> bool {
536 false
537 false
537 }
538 }
538 }
539 }
539
540
540 impl UnionMatcher {
541 impl UnionMatcher {
541 pub fn new(matchers: Vec<Box<dyn Matcher + Sync>>) -> Self {
542 pub fn new(matchers: Vec<Box<dyn Matcher + Sync>>) -> Self {
542 Self { matchers }
543 Self { matchers }
543 }
544 }
544 }
545 }
545
546
546 #[derive(Debug)]
547 #[derive(Debug)]
547 pub struct IntersectionMatcher {
548 pub struct IntersectionMatcher {
548 m1: Box<dyn Matcher + Sync>,
549 m1: Box<dyn Matcher + Sync>,
549 m2: Box<dyn Matcher + Sync>,
550 m2: Box<dyn Matcher + Sync>,
550 files: Option<HashSet<HgPathBuf>>,
551 files: Option<HashSet<HgPathBuf>>,
551 }
552 }
552
553
553 impl Matcher for IntersectionMatcher {
554 impl Matcher for IntersectionMatcher {
554 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
555 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
555 self.files.as_ref()
556 self.files.as_ref()
556 }
557 }
557
558
558 fn exact_match(&self, filename: &HgPath) -> bool {
559 fn exact_match(&self, filename: &HgPath) -> bool {
559 self.files.as_ref().map_or(false, |f| f.contains(filename))
560 self.files.as_ref().map_or(false, |f| f.contains(filename))
560 }
561 }
561
562
562 fn matches(&self, filename: &HgPath) -> bool {
563 fn matches(&self, filename: &HgPath) -> bool {
563 self.m1.matches(filename) && self.m2.matches(filename)
564 self.m1.matches(filename) && self.m2.matches(filename)
564 }
565 }
565
566
566 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
567 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
567 let m1_set = self.m1.visit_children_set(directory);
568 let m1_set = self.m1.visit_children_set(directory);
568 if m1_set == VisitChildrenSet::Empty {
569 if m1_set == VisitChildrenSet::Empty {
569 return VisitChildrenSet::Empty;
570 return VisitChildrenSet::Empty;
570 }
571 }
571 let m2_set = self.m2.visit_children_set(directory);
572 let m2_set = self.m2.visit_children_set(directory);
572 if m2_set == VisitChildrenSet::Empty {
573 if m2_set == VisitChildrenSet::Empty {
573 return VisitChildrenSet::Empty;
574 return VisitChildrenSet::Empty;
574 }
575 }
575
576
576 if m1_set == VisitChildrenSet::Recursive {
577 if m1_set == VisitChildrenSet::Recursive {
577 return m2_set;
578 return m2_set;
578 } else if m2_set == VisitChildrenSet::Recursive {
579 } else if m2_set == VisitChildrenSet::Recursive {
579 return m1_set;
580 return m1_set;
580 }
581 }
581
582
582 match (&m1_set, &m2_set) {
583 match (&m1_set, &m2_set) {
583 (VisitChildrenSet::Recursive, _) => m2_set,
584 (VisitChildrenSet::Recursive, _) => m2_set,
584 (_, VisitChildrenSet::Recursive) => m1_set,
585 (_, VisitChildrenSet::Recursive) => m1_set,
585 (VisitChildrenSet::This, _) | (_, VisitChildrenSet::This) => {
586 (VisitChildrenSet::This, _) | (_, VisitChildrenSet::This) => {
586 VisitChildrenSet::This
587 VisitChildrenSet::This
587 }
588 }
588 (VisitChildrenSet::Set(m1), VisitChildrenSet::Set(m2)) => {
589 (VisitChildrenSet::Set(m1), VisitChildrenSet::Set(m2)) => {
589 let set: HashSet<_> = m1.intersection(m2).cloned().collect();
590 let set: HashSet<_> = m1.intersection(m2).cloned().collect();
590 if set.is_empty() {
591 if set.is_empty() {
591 VisitChildrenSet::Empty
592 VisitChildrenSet::Empty
592 } else {
593 } else {
593 VisitChildrenSet::Set(set)
594 VisitChildrenSet::Set(set)
594 }
595 }
595 }
596 }
596 _ => unreachable!(),
597 _ => unreachable!(),
597 }
598 }
598 }
599 }
599
600
600 fn matches_everything(&self) -> bool {
601 fn matches_everything(&self) -> bool {
601 self.m1.matches_everything() && self.m2.matches_everything()
602 self.m1.matches_everything() && self.m2.matches_everything()
602 }
603 }
603
604
604 fn is_exact(&self) -> bool {
605 fn is_exact(&self) -> bool {
605 self.m1.is_exact() || self.m2.is_exact()
606 self.m1.is_exact() || self.m2.is_exact()
606 }
607 }
607 }
608 }
608
609
609 impl IntersectionMatcher {
610 impl IntersectionMatcher {
610 pub fn new(
611 pub fn new(
611 mut m1: Box<dyn Matcher + Sync>,
612 mut m1: Box<dyn Matcher + Sync>,
612 mut m2: Box<dyn Matcher + Sync>,
613 mut m2: Box<dyn Matcher + Sync>,
613 ) -> Self {
614 ) -> Self {
614 let files = if m1.is_exact() || m2.is_exact() {
615 let files = if m1.is_exact() || m2.is_exact() {
615 if !m1.is_exact() {
616 if !m1.is_exact() {
616 std::mem::swap(&mut m1, &mut m2);
617 std::mem::swap(&mut m1, &mut m2);
617 }
618 }
618 m1.file_set().map(|m1_files| {
619 m1.file_set().map(|m1_files| {
619 m1_files
620 m1_files
620 .iter()
621 .iter()
621 .filter(|&f| m2.matches(f))
622 .filter(|&f| m2.matches(f))
622 .cloned()
623 .cloned()
623 .collect()
624 .collect()
624 })
625 })
625 } else {
626 } else {
626 // without exact input file sets, we can't do an exact
627 // without exact input file sets, we can't do an exact
627 // intersection, so we must over-approximate by
628 // intersection, so we must over-approximate by
628 // unioning instead
629 // unioning instead
629 m1.file_set().map(|m1_files| match m2.file_set() {
630 m1.file_set().map(|m1_files| match m2.file_set() {
630 Some(m2_files) => m1_files.union(m2_files).cloned().collect(),
631 Some(m2_files) => m1_files.union(m2_files).cloned().collect(),
631 None => m1_files.iter().cloned().collect(),
632 None => m1_files.iter().cloned().collect(),
632 })
633 })
633 };
634 };
634 Self { m1, m2, files }
635 Self { m1, m2, files }
635 }
636 }
636 }
637 }
637
638
638 #[derive(Debug)]
639 #[derive(Debug)]
639 pub struct DifferenceMatcher {
640 pub struct DifferenceMatcher {
640 base: Box<dyn Matcher + Sync>,
641 base: Box<dyn Matcher + Sync>,
641 excluded: Box<dyn Matcher + Sync>,
642 excluded: Box<dyn Matcher + Sync>,
642 files: Option<HashSet<HgPathBuf>>,
643 files: Option<HashSet<HgPathBuf>>,
643 }
644 }
644
645
645 impl Matcher for DifferenceMatcher {
646 impl Matcher for DifferenceMatcher {
646 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
647 fn file_set(&self) -> Option<&HashSet<HgPathBuf>> {
647 self.files.as_ref()
648 self.files.as_ref()
648 }
649 }
649
650
650 fn exact_match(&self, filename: &HgPath) -> bool {
651 fn exact_match(&self, filename: &HgPath) -> bool {
651 self.files.as_ref().map_or(false, |f| f.contains(filename))
652 self.files.as_ref().map_or(false, |f| f.contains(filename))
652 }
653 }
653
654
654 fn matches(&self, filename: &HgPath) -> bool {
655 fn matches(&self, filename: &HgPath) -> bool {
655 self.base.matches(filename) && !self.excluded.matches(filename)
656 self.base.matches(filename) && !self.excluded.matches(filename)
656 }
657 }
657
658
658 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
659 fn visit_children_set(&self, directory: &HgPath) -> VisitChildrenSet {
659 let excluded_set = self.excluded.visit_children_set(directory);
660 let excluded_set = self.excluded.visit_children_set(directory);
660 if excluded_set == VisitChildrenSet::Recursive {
661 if excluded_set == VisitChildrenSet::Recursive {
661 return VisitChildrenSet::Empty;
662 return VisitChildrenSet::Empty;
662 }
663 }
663 let base_set = self.base.visit_children_set(directory);
664 let base_set = self.base.visit_children_set(directory);
664 // Possible values for base: 'recursive', 'this', set(...), set()
665 // Possible values for base: 'recursive', 'this', set(...), set()
665 // Possible values for excluded: 'this', set(...), set()
666 // Possible values for excluded: 'this', set(...), set()
666 // If excluded has nothing under here that we care about, return base,
667 // If excluded has nothing under here that we care about, return base,
667 // even if it's 'recursive'.
668 // even if it's 'recursive'.
668 if excluded_set == VisitChildrenSet::Empty {
669 if excluded_set == VisitChildrenSet::Empty {
669 return base_set;
670 return base_set;
670 }
671 }
671 match base_set {
672 match base_set {
672 VisitChildrenSet::This | VisitChildrenSet::Recursive => {
673 VisitChildrenSet::This | VisitChildrenSet::Recursive => {
673 // Never return 'recursive' here if excluded_set is any kind of
674 // Never return 'recursive' here if excluded_set is any kind of
674 // non-empty (either 'this' or set(foo)), since excluded might
675 // non-empty (either 'this' or set(foo)), since excluded might
675 // return set() for a subdirectory.
676 // return set() for a subdirectory.
676 VisitChildrenSet::This
677 VisitChildrenSet::This
677 }
678 }
678 set => {
679 set => {
679 // Possible values for base: set(...), set()
680 // Possible values for base: set(...), set()
680 // Possible values for excluded: 'this', set(...)
681 // Possible values for excluded: 'this', set(...)
681 // We ignore excluded set results. They're possibly incorrect:
682 // We ignore excluded set results. They're possibly incorrect:
682 // base = path:dir/subdir
683 // base = path:dir/subdir
683 // excluded=rootfilesin:dir,
684 // excluded=rootfilesin:dir,
684 // visit_children_set(''):
685 // visit_children_set(''):
685 // base returns {'dir'}, excluded returns {'dir'}, if we
686 // base returns {'dir'}, excluded returns {'dir'}, if we
686 // subtracted we'd return set(), which is *not* correct, we
687 // subtracted we'd return set(), which is *not* correct, we
687 // still need to visit 'dir'!
688 // still need to visit 'dir'!
688 set
689 set
689 }
690 }
690 }
691 }
691 }
692 }
692
693
693 fn matches_everything(&self) -> bool {
694 fn matches_everything(&self) -> bool {
694 false
695 false
695 }
696 }
696
697
697 fn is_exact(&self) -> bool {
698 fn is_exact(&self) -> bool {
698 self.base.is_exact()
699 self.base.is_exact()
699 }
700 }
700 }
701 }
701
702
702 impl DifferenceMatcher {
703 impl DifferenceMatcher {
703 pub fn new(
704 pub fn new(
704 base: Box<dyn Matcher + Sync>,
705 base: Box<dyn Matcher + Sync>,
705 excluded: Box<dyn Matcher + Sync>,
706 excluded: Box<dyn Matcher + Sync>,
706 ) -> Self {
707 ) -> Self {
707 let base_is_exact = base.is_exact();
708 let base_is_exact = base.is_exact();
708 let base_files = base.file_set().map(ToOwned::to_owned);
709 let base_files = base.file_set().map(ToOwned::to_owned);
709 let mut new = Self {
710 let mut new = Self {
710 base,
711 base,
711 excluded,
712 excluded,
712 files: None,
713 files: None,
713 };
714 };
714 if base_is_exact {
715 if base_is_exact {
715 new.files = base_files.map(|files| {
716 new.files = base_files.map(|files| {
716 files.iter().filter(|&f| new.matches(f)).cloned().collect()
717 files.iter().filter(|&f| new.matches(f)).cloned().collect()
717 });
718 });
718 }
719 }
719 new
720 new
720 }
721 }
721 }
722 }
722
723
723 /// Wraps [`regex::bytes::Regex`] to improve performance in multithreaded
724 /// Wraps [`regex::bytes::Regex`] to improve performance in multithreaded
724 /// contexts.
725 /// contexts.
725 ///
726 ///
726 /// The `status` algorithm makes heavy use of threads, and calling `is_match`
727 /// The `status` algorithm makes heavy use of threads, and calling `is_match`
727 /// from many threads at once is prone to contention, probably within the
728 /// from many threads at once is prone to contention, probably within the
728 /// scratch space needed as the regex DFA is built lazily.
729 /// scratch space needed as the regex DFA is built lazily.
729 ///
730 ///
730 /// We are in the process of raising the issue upstream, but for now
731 /// We are in the process of raising the issue upstream, but for now
731 /// the workaround used here is to store the `Regex` in a lazily populated
732 /// the workaround used here is to store the `Regex` in a lazily populated
732 /// thread-local variable, sharing the initial read-only compilation, but
733 /// thread-local variable, sharing the initial read-only compilation, but
733 /// not the lazy dfa scratch space mentioned above.
734 /// not the lazy dfa scratch space mentioned above.
734 ///
735 ///
735 /// This reduces the contention observed with 16+ threads, but does not
736 /// This reduces the contention observed with 16+ threads, but does not
736 /// completely remove it. Hopefully this can be addressed upstream.
737 /// completely remove it. Hopefully this can be addressed upstream.
737 struct RegexMatcher {
738 struct RegexMatcher {
738 /// Compiled at the start of the status algorithm, used as a base for
739 /// Compiled at the start of the status algorithm, used as a base for
739 /// cloning in each thread-local `self.local`, thus sharing the expensive
740 /// cloning in each thread-local `self.local`, thus sharing the expensive
740 /// first compilation.
741 /// first compilation.
741 base: regex::bytes::Regex,
742 base: regex::bytes::Regex,
742 /// Thread-local variable that holds the `Regex` that is actually queried
743 /// Thread-local variable that holds the `Regex` that is actually queried
743 /// from each thread.
744 /// from each thread.
744 local: thread_local::ThreadLocal<regex::bytes::Regex>,
745 local: thread_local::ThreadLocal<regex::bytes::Regex>,
745 }
746 }
746
747
747 impl RegexMatcher {
748 impl RegexMatcher {
748 /// Returns whether the path matches the stored `Regex`.
749 /// Returns whether the path matches the stored `Regex`.
749 pub fn is_match(&self, path: &HgPath) -> bool {
750 pub fn is_match(&self, path: &HgPath) -> bool {
750 self.local
751 self.local
751 .get_or(|| self.base.clone())
752 .get_or(|| self.base.clone())
752 .is_match(path.as_bytes())
753 .is_match(path.as_bytes())
753 }
754 }
754 }
755 }
755
756
756 /// Return a `RegexBuilder` from a bytes pattern
757 /// Return a `RegexBuilder` from a bytes pattern
757 ///
758 ///
758 /// This works around the fact that even if it works on byte haysacks,
759 /// This works around the fact that even if it works on byte haysacks,
759 /// [`regex::bytes::Regex`] still uses UTF-8 patterns.
760 /// [`regex::bytes::Regex`] still uses UTF-8 patterns.
760 pub fn re_bytes_builder(pattern: &[u8]) -> regex::bytes::RegexBuilder {
761 pub fn re_bytes_builder(pattern: &[u8]) -> regex::bytes::RegexBuilder {
761 use std::io::Write;
762 use std::io::Write;
762
763
763 // The `regex` crate adds `.*` to the start and end of expressions if there
764 // The `regex` crate adds `.*` to the start and end of expressions if there
764 // are no anchors, so add the start anchor.
765 // are no anchors, so add the start anchor.
765 let mut escaped_bytes = vec![b'^', b'(', b'?', b':'];
766 let mut escaped_bytes = vec![b'^', b'(', b'?', b':'];
766 for byte in pattern {
767 for byte in pattern {
767 if *byte > 127 {
768 if *byte > 127 {
768 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
769 write!(escaped_bytes, "\\x{:x}", *byte).unwrap();
769 } else {
770 } else {
770 escaped_bytes.push(*byte);
771 escaped_bytes.push(*byte);
771 }
772 }
772 }
773 }
773 escaped_bytes.push(b')');
774 escaped_bytes.push(b')');
774
775
775 // Avoid the cost of UTF8 checking
776 // Avoid the cost of UTF8 checking
776 //
777 //
777 // # Safety
778 // # Safety
778 // This is safe because we escaped all non-ASCII bytes.
779 // This is safe because we escaped all non-ASCII bytes.
779 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
780 let pattern_string = unsafe { String::from_utf8_unchecked(escaped_bytes) };
780 regex::bytes::RegexBuilder::new(&pattern_string)
781 regex::bytes::RegexBuilder::new(&pattern_string)
781 }
782 }
782
783
783 /// Returns a function that matches an `HgPath` against the given regex
784 /// Returns a function that matches an `HgPath` against the given regex
784 /// pattern.
785 /// pattern.
785 ///
786 ///
786 /// This can fail when the pattern is invalid or not supported by the
787 /// This can fail when the pattern is invalid or not supported by the
787 /// underlying engine (the `regex` crate), for instance anything with
788 /// underlying engine (the `regex` crate), for instance anything with
788 /// back-references.
789 /// back-references.
789 #[logging_timer::time("trace")]
790 #[logging_timer::time("trace")]
790 fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
791 fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
791 let re = re_bytes_builder(pattern)
792 let re = re_bytes_builder(pattern)
792 .unicode(false)
793 .unicode(false)
793 // Big repos with big `.hgignore` will hit the default limit and
794 // Big repos with big `.hgignore` will hit the default limit and
794 // incur a significant performance hit. One repo's `hg status` hit
795 // incur a significant performance hit. One repo's `hg status` hit
795 // multiple *minutes*.
796 // multiple *minutes*.
796 .dfa_size_limit(50 * (1 << 20))
797 .dfa_size_limit(50 * (1 << 20))
797 .build()
798 .build()
798 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
799 .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
799
800
800 Ok(RegexMatcher {
801 Ok(RegexMatcher {
801 base: re,
802 base: re,
802 local: Default::default(),
803 local: Default::default(),
803 })
804 })
804 }
805 }
805
806
806 /// Returns the regex pattern and a function that matches an `HgPath` against
807 /// Returns the regex pattern and a function that matches an `HgPath` against
807 /// said regex formed by the given ignore patterns.
808 /// said regex formed by the given ignore patterns.
808 fn build_regex_match<'a>(
809 fn build_regex_match<'a>(
809 ignore_patterns: &[IgnorePattern],
810 ignore_patterns: &[IgnorePattern],
810 glob_suffix: &[u8],
811 glob_suffix: GlobSuffix,
811 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
812 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
812 let mut regexps = vec![];
813 let mut regexps = vec![];
813 let mut exact_set = HashSet::new();
814 let mut exact_set = HashSet::new();
814
815
815 for pattern in ignore_patterns {
816 for pattern in ignore_patterns {
816 if let Some(re) = build_single_regex(pattern, glob_suffix)? {
817 if let Some(re) = build_single_regex(pattern, glob_suffix)? {
817 regexps.push(re);
818 regexps.push(re);
818 } else {
819 } else {
819 let exact = normalize_path_bytes(&pattern.pattern);
820 let exact = normalize_path_bytes(&pattern.pattern);
820 exact_set.insert(HgPathBuf::from_bytes(&exact));
821 exact_set.insert(HgPathBuf::from_bytes(&exact));
821 }
822 }
822 }
823 }
823
824
824 let full_regex = regexps.join(&b'|');
825 let full_regex = regexps.join(&b'|');
825
826
826 // An empty pattern would cause the regex engine to incorrectly match the
827 // An empty pattern would cause the regex engine to incorrectly match the
827 // (empty) root directory
828 // (empty) root directory
828 let func = if !(regexps.is_empty()) {
829 let func = if !(regexps.is_empty()) {
829 let matcher = re_matcher(&full_regex)?;
830 let matcher = re_matcher(&full_regex)?;
830 let func = move |filename: &HgPath| {
831 let func = move |filename: &HgPath| {
831 exact_set.contains(filename) || matcher.is_match(filename)
832 exact_set.contains(filename) || matcher.is_match(filename)
832 };
833 };
833 Box::new(func) as IgnoreFnType
834 Box::new(func) as IgnoreFnType
834 } else {
835 } else {
835 let func = move |filename: &HgPath| exact_set.contains(filename);
836 let func = move |filename: &HgPath| exact_set.contains(filename);
836 Box::new(func) as IgnoreFnType
837 Box::new(func) as IgnoreFnType
837 };
838 };
838
839
839 Ok((full_regex, func))
840 Ok((full_regex, func))
840 }
841 }
841
842
842 /// Returns roots and directories corresponding to each pattern.
843 /// Returns roots and directories corresponding to each pattern.
843 ///
844 ///
844 /// This calculates the roots and directories exactly matching the patterns and
845 /// This calculates the roots and directories exactly matching the patterns and
845 /// returns a tuple of (roots, dirs). It does not return other directories
846 /// returns a tuple of (roots, dirs). It does not return other directories
846 /// which may also need to be considered, like the parent directories.
847 /// which may also need to be considered, like the parent directories.
847 fn roots_and_dirs(
848 fn roots_and_dirs(
848 ignore_patterns: &[IgnorePattern],
849 ignore_patterns: &[IgnorePattern],
849 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
850 ) -> (Vec<HgPathBuf>, Vec<HgPathBuf>) {
850 let mut roots = Vec::new();
851 let mut roots = Vec::new();
851 let mut dirs = Vec::new();
852 let mut dirs = Vec::new();
852
853
853 for ignore_pattern in ignore_patterns {
854 for ignore_pattern in ignore_patterns {
854 let IgnorePattern {
855 let IgnorePattern {
855 syntax, pattern, ..
856 syntax, pattern, ..
856 } = ignore_pattern;
857 } = ignore_pattern;
857 match syntax {
858 match syntax {
858 PatternSyntax::RootGlob | PatternSyntax::Glob => {
859 PatternSyntax::RootGlob | PatternSyntax::Glob => {
859 let mut root = HgPathBuf::new();
860 let mut root = HgPathBuf::new();
860 for p in pattern.split(|c| *c == b'/') {
861 for p in pattern.split(|c| *c == b'/') {
861 if p.iter()
862 if p.iter()
862 .any(|c| matches!(*c, b'[' | b'{' | b'*' | b'?'))
863 .any(|c| matches!(*c, b'[' | b'{' | b'*' | b'?'))
863 {
864 {
864 break;
865 break;
865 }
866 }
866 root.push(HgPathBuf::from_bytes(p).as_ref());
867 root.push(HgPathBuf::from_bytes(p).as_ref());
867 }
868 }
868 roots.push(root);
869 roots.push(root);
869 }
870 }
870 PatternSyntax::Path
871 PatternSyntax::Path
871 | PatternSyntax::RelPath
872 | PatternSyntax::RelPath
872 | PatternSyntax::FilePath => {
873 | PatternSyntax::FilePath => {
873 let pat = HgPath::new(if pattern == b"." {
874 let pat = HgPath::new(if pattern == b"." {
874 &[] as &[u8]
875 &[] as &[u8]
875 } else {
876 } else {
876 pattern
877 pattern
877 });
878 });
878 roots.push(pat.to_owned());
879 roots.push(pat.to_owned());
879 }
880 }
880 PatternSyntax::RootFilesIn => {
881 PatternSyntax::RootFilesIn => {
881 let pat = if pattern == b"." {
882 let pat = if pattern == b"." {
882 &[] as &[u8]
883 &[] as &[u8]
883 } else {
884 } else {
884 pattern
885 pattern
885 };
886 };
886 dirs.push(HgPathBuf::from_bytes(pat));
887 dirs.push(HgPathBuf::from_bytes(pat));
887 }
888 }
888 _ => {
889 _ => {
889 roots.push(HgPathBuf::new());
890 roots.push(HgPathBuf::new());
890 }
891 }
891 }
892 }
892 }
893 }
893 (roots, dirs)
894 (roots, dirs)
894 }
895 }
895
896
896 /// Paths extracted from patterns
897 /// Paths extracted from patterns
897 #[derive(Debug, PartialEq)]
898 #[derive(Debug, PartialEq)]
898 struct RootsDirsAndParents {
899 struct RootsDirsAndParents {
899 /// Directories to match recursively
900 /// Directories to match recursively
900 pub roots: HashSet<HgPathBuf>,
901 pub roots: HashSet<HgPathBuf>,
901 /// Directories to match non-recursively
902 /// Directories to match non-recursively
902 pub dirs: HashSet<HgPathBuf>,
903 pub dirs: HashSet<HgPathBuf>,
903 /// Implicitly required directories to go to items in either roots or dirs
904 /// Implicitly required directories to go to items in either roots or dirs
904 pub parents: DirsMultiset,
905 pub parents: DirsMultiset,
905 }
906 }
906
907
907 /// Extract roots, dirs and parents from patterns.
908 /// Extract roots, dirs and parents from patterns.
908 fn roots_dirs_and_parents(
909 fn roots_dirs_and_parents(
909 ignore_patterns: &[IgnorePattern],
910 ignore_patterns: &[IgnorePattern],
910 ) -> PatternResult<RootsDirsAndParents> {
911 ) -> PatternResult<RootsDirsAndParents> {
911 let (roots, dirs) = roots_and_dirs(ignore_patterns);
912 let (roots, dirs) = roots_and_dirs(ignore_patterns);
912
913
913 let mut parents = DirsMultiset::from_manifest(&dirs)?;
914 let mut parents = DirsMultiset::from_manifest(&dirs)?;
914
915
915 for path in &roots {
916 for path in &roots {
916 parents.add_path(path)?
917 parents.add_path(path)?
917 }
918 }
918
919
919 Ok(RootsDirsAndParents {
920 Ok(RootsDirsAndParents {
920 roots: HashSet::from_iter(roots),
921 roots: HashSet::from_iter(roots),
921 dirs: HashSet::from_iter(dirs),
922 dirs: HashSet::from_iter(dirs),
922 parents,
923 parents,
923 })
924 })
924 }
925 }
925
926
926 /// Returns a function that checks whether a given file (in the general sense)
927 /// Returns a function that checks whether a given file (in the general sense)
927 /// should be matched.
928 /// should be matched.
928 fn build_match<'a>(
929 fn build_match<'a>(
929 ignore_patterns: Vec<IgnorePattern>,
930 ignore_patterns: Vec<IgnorePattern>,
930 glob_suffix: &[u8],
931 glob_suffix: GlobSuffix,
931 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
932 ) -> PatternResult<(Vec<u8>, IgnoreFnType<'a>)> {
932 let mut match_funcs: Vec<IgnoreFnType<'a>> = vec![];
933 let mut match_funcs: Vec<IgnoreFnType<'a>> = vec![];
933 // For debugging and printing
934 // For debugging and printing
934 let mut patterns = vec![];
935 let mut patterns = vec![];
935
936
936 let (subincludes, ignore_patterns) = filter_subincludes(ignore_patterns)?;
937 let (subincludes, ignore_patterns) = filter_subincludes(ignore_patterns)?;
937
938
938 if !subincludes.is_empty() {
939 if !subincludes.is_empty() {
939 // Build prefix-based matcher functions for subincludes
940 // Build prefix-based matcher functions for subincludes
940 let mut submatchers = FastHashMap::default();
941 let mut submatchers = FastHashMap::default();
941 let mut prefixes = vec![];
942 let mut prefixes = vec![];
942
943
943 for sub_include in subincludes {
944 for sub_include in subincludes {
944 let matcher = IncludeMatcher::new(sub_include.included_patterns)?;
945 let matcher = IncludeMatcher::new(sub_include.included_patterns)?;
945 let match_fn =
946 let match_fn =
946 Box::new(move |path: &HgPath| matcher.matches(path));
947 Box::new(move |path: &HgPath| matcher.matches(path));
947 prefixes.push(sub_include.prefix.clone());
948 prefixes.push(sub_include.prefix.clone());
948 submatchers.insert(sub_include.prefix.clone(), match_fn);
949 submatchers.insert(sub_include.prefix.clone(), match_fn);
949 }
950 }
950
951
951 let match_subinclude = move |filename: &HgPath| {
952 let match_subinclude = move |filename: &HgPath| {
952 for prefix in prefixes.iter() {
953 for prefix in prefixes.iter() {
953 if let Some(rel) = filename.relative_to(prefix) {
954 if let Some(rel) = filename.relative_to(prefix) {
954 if (submatchers[prefix])(rel) {
955 if (submatchers[prefix])(rel) {
955 return true;
956 return true;
956 }
957 }
957 }
958 }
958 }
959 }
959 false
960 false
960 };
961 };
961
962
962 match_funcs.push(Box::new(match_subinclude));
963 match_funcs.push(Box::new(match_subinclude));
963 }
964 }
964
965
965 if !ignore_patterns.is_empty() {
966 if !ignore_patterns.is_empty() {
966 // Either do dumb matching if all patterns are rootfiles, or match
967 // Either do dumb matching if all patterns are rootfiles, or match
967 // with a regex.
968 // with a regex.
968 if ignore_patterns
969 if ignore_patterns
969 .iter()
970 .iter()
970 .all(|k| k.syntax == PatternSyntax::RootFilesIn)
971 .all(|k| k.syntax == PatternSyntax::RootFilesIn)
971 {
972 {
972 let dirs: HashSet<_> = ignore_patterns
973 let dirs: HashSet<_> = ignore_patterns
973 .iter()
974 .iter()
974 .map(|k| k.pattern.to_owned())
975 .map(|k| k.pattern.to_owned())
975 .collect();
976 .collect();
976 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
977 let mut dirs_vec: Vec<_> = dirs.iter().cloned().collect();
977
978
978 let match_func = move |path: &HgPath| -> bool {
979 let match_func = move |path: &HgPath| -> bool {
979 let path = path.as_bytes();
980 let path = path.as_bytes();
980 let i = path.iter().rposition(|a| *a == b'/');
981 let i = path.iter().rposition(|a| *a == b'/');
981 let dir = if let Some(i) = i { &path[..i] } else { b"." };
982 let dir = if let Some(i) = i { &path[..i] } else { b"." };
982 dirs.contains(dir)
983 dirs.contains(dir)
983 };
984 };
984 match_funcs.push(Box::new(match_func));
985 match_funcs.push(Box::new(match_func));
985
986
986 patterns.extend(b"rootfilesin: ");
987 patterns.extend(b"rootfilesin: ");
987 dirs_vec.sort();
988 dirs_vec.sort();
988 patterns.extend(dirs_vec.escaped_bytes());
989 patterns.extend(dirs_vec.escaped_bytes());
989 } else {
990 } else {
990 let (new_re, match_func) =
991 let (new_re, match_func) =
991 build_regex_match(&ignore_patterns, glob_suffix)?;
992 build_regex_match(&ignore_patterns, glob_suffix)?;
992 patterns = new_re;
993 patterns = new_re;
993 match_funcs.push(match_func)
994 match_funcs.push(match_func)
994 }
995 }
995 }
996 }
996
997
997 Ok(if match_funcs.len() == 1 {
998 Ok(if match_funcs.len() == 1 {
998 (patterns, match_funcs.remove(0))
999 (patterns, match_funcs.remove(0))
999 } else {
1000 } else {
1000 (
1001 (
1001 patterns,
1002 patterns,
1002 Box::new(move |f: &HgPath| -> bool {
1003 Box::new(move |f: &HgPath| -> bool {
1003 match_funcs.iter().any(|match_func| match_func(f))
1004 match_funcs.iter().any(|match_func| match_func(f))
1004 }),
1005 }),
1005 )
1006 )
1006 })
1007 })
1007 }
1008 }
1008
1009
1009 /// Parses all "ignore" files with their recursive includes and returns a
1010 /// Parses all "ignore" files with their recursive includes and returns a
1010 /// function that checks whether a given file (in the general sense) should be
1011 /// function that checks whether a given file (in the general sense) should be
1011 /// ignored.
1012 /// ignored.
1012 pub fn get_ignore_matcher<'a>(
1013 pub fn get_ignore_matcher<'a>(
1013 mut all_pattern_files: Vec<PathBuf>,
1014 mut all_pattern_files: Vec<PathBuf>,
1014 root_dir: &Path,
1015 root_dir: &Path,
1015 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
1016 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
1016 ) -> PatternResult<(IncludeMatcher<'a>, Vec<PatternFileWarning>)> {
1017 ) -> PatternResult<(IncludeMatcher<'a>, Vec<PatternFileWarning>)> {
1017 let mut all_patterns = vec![];
1018 let mut all_patterns = vec![];
1018 let mut all_warnings = vec![];
1019 let mut all_warnings = vec![];
1019
1020
1020 // Sort to make the ordering of calls to `inspect_pattern_bytes`
1021 // Sort to make the ordering of calls to `inspect_pattern_bytes`
1021 // deterministic even if the ordering of `all_pattern_files` is not (such
1022 // deterministic even if the ordering of `all_pattern_files` is not (such
1022 // as when a iteration order of a Python dict or Rust HashMap is involved).
1023 // as when a iteration order of a Python dict or Rust HashMap is involved).
1023 // Sort by "string" representation instead of the default by component
1024 // Sort by "string" representation instead of the default by component
1024 // (with a Rust-specific definition of a component)
1025 // (with a Rust-specific definition of a component)
1025 all_pattern_files
1026 all_pattern_files
1026 .sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
1027 .sort_unstable_by(|a, b| a.as_os_str().cmp(b.as_os_str()));
1027
1028
1028 for pattern_file in &all_pattern_files {
1029 for pattern_file in &all_pattern_files {
1029 let (patterns, warnings) = get_patterns_from_file(
1030 let (patterns, warnings) = get_patterns_from_file(
1030 pattern_file,
1031 pattern_file,
1031 root_dir,
1032 root_dir,
1032 inspect_pattern_bytes,
1033 inspect_pattern_bytes,
1033 )?;
1034 )?;
1034
1035
1035 all_patterns.extend(patterns.to_owned());
1036 all_patterns.extend(patterns.to_owned());
1036 all_warnings.extend(warnings);
1037 all_warnings.extend(warnings);
1037 }
1038 }
1038 let matcher = IncludeMatcher::new(all_patterns)?;
1039 let matcher = IncludeMatcher::new(all_patterns)?;
1039 Ok((matcher, all_warnings))
1040 Ok((matcher, all_warnings))
1040 }
1041 }
1041
1042
1042 /// Parses all "ignore" files with their recursive includes and returns a
1043 /// Parses all "ignore" files with their recursive includes and returns a
1043 /// function that checks whether a given file (in the general sense) should be
1044 /// function that checks whether a given file (in the general sense) should be
1044 /// ignored.
1045 /// ignored.
1045 pub fn get_ignore_function<'a>(
1046 pub fn get_ignore_function<'a>(
1046 all_pattern_files: Vec<PathBuf>,
1047 all_pattern_files: Vec<PathBuf>,
1047 root_dir: &Path,
1048 root_dir: &Path,
1048 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
1049 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
1049 ) -> PatternResult<(IgnoreFnType<'a>, Vec<PatternFileWarning>)> {
1050 ) -> PatternResult<(IgnoreFnType<'a>, Vec<PatternFileWarning>)> {
1050 let res =
1051 let res =
1051 get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes);
1052 get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes);
1052 res.map(|(matcher, all_warnings)| {
1053 res.map(|(matcher, all_warnings)| {
1053 let res: IgnoreFnType<'a> =
1054 let res: IgnoreFnType<'a> =
1054 Box::new(move |path: &HgPath| matcher.matches(path));
1055 Box::new(move |path: &HgPath| matcher.matches(path));
1055
1056
1056 (res, all_warnings)
1057 (res, all_warnings)
1057 })
1058 })
1058 }
1059 }
1059
1060
1060 impl<'a> IncludeMatcher<'a> {
1061 impl<'a> IncludeMatcher<'a> {
1061 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
1062 pub fn new(ignore_patterns: Vec<IgnorePattern>) -> PatternResult<Self> {
1062 let RootsDirsAndParents {
1063 let RootsDirsAndParents {
1063 roots,
1064 roots,
1064 dirs,
1065 dirs,
1065 parents,
1066 parents,
1066 } = roots_dirs_and_parents(&ignore_patterns)?;
1067 } = roots_dirs_and_parents(&ignore_patterns)?;
1067 let prefix = ignore_patterns.iter().all(|k| {
1068 let prefix = ignore_patterns.iter().all(|k| {
1068 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
1069 matches!(k.syntax, PatternSyntax::Path | PatternSyntax::RelPath)
1069 });
1070 });
1070 let (patterns, match_fn) = build_match(ignore_patterns, b"(?:/|$)")?;
1071 let (patterns, match_fn) =
1072 build_match(ignore_patterns, GlobSuffix::MoreComponents)?;
1071
1073
1072 Ok(Self {
1074 Ok(Self {
1073 patterns,
1075 patterns,
1074 match_fn,
1076 match_fn,
1075 prefix,
1077 prefix,
1076 roots,
1078 roots,
1077 dirs,
1079 dirs,
1078 parents,
1080 parents,
1079 })
1081 })
1080 }
1082 }
1081
1083
1082 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
1084 fn get_all_parents_children(&self) -> DirsChildrenMultiset {
1083 // TODO cache
1085 // TODO cache
1084 let thing = self
1086 let thing = self
1085 .dirs
1087 .dirs
1086 .iter()
1088 .iter()
1087 .chain(self.roots.iter())
1089 .chain(self.roots.iter())
1088 .chain(self.parents.iter());
1090 .chain(self.parents.iter());
1089 DirsChildrenMultiset::new(thing, Some(self.parents.iter()))
1091 DirsChildrenMultiset::new(thing, Some(self.parents.iter()))
1090 }
1092 }
1091
1093
1092 pub fn debug_get_patterns(&self) -> &[u8] {
1094 pub fn debug_get_patterns(&self) -> &[u8] {
1093 self.patterns.as_ref()
1095 self.patterns.as_ref()
1094 }
1096 }
1095 }
1097 }
1096
1098
1097 impl<'a> Display for IncludeMatcher<'a> {
1099 impl<'a> Display for IncludeMatcher<'a> {
1098 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
1100 fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
1099 // XXX What about exact matches?
1101 // XXX What about exact matches?
1100 // I'm not sure it's worth it to clone the HashSet and keep it
1102 // I'm not sure it's worth it to clone the HashSet and keep it
1101 // around just in case someone wants to display the matcher, plus
1103 // around just in case someone wants to display the matcher, plus
1102 // it's going to be unreadable after a few entries, but we need to
1104 // it's going to be unreadable after a few entries, but we need to
1103 // inform in this display that exact matches are being used and are
1105 // inform in this display that exact matches are being used and are
1104 // (on purpose) missing from the `includes`.
1106 // (on purpose) missing from the `includes`.
1105 write!(
1107 write!(
1106 f,
1108 f,
1107 "IncludeMatcher(includes='{}')",
1109 "IncludeMatcher(includes='{}')",
1108 String::from_utf8_lossy(&self.patterns.escaped_bytes())
1110 String::from_utf8_lossy(&self.patterns.escaped_bytes())
1109 )
1111 )
1110 }
1112 }
1111 }
1113 }
1112
1114
1113 #[cfg(test)]
1115 #[cfg(test)]
1114 mod tests {
1116 mod tests {
1115 use super::*;
1117 use super::*;
1116 use pretty_assertions::assert_eq;
1118 use pretty_assertions::assert_eq;
1117 use std::collections::BTreeMap;
1119 use std::collections::BTreeMap;
1118 use std::collections::BTreeSet;
1120 use std::collections::BTreeSet;
1119 use std::fmt::Debug;
1121 use std::fmt::Debug;
1120 use std::path::Path;
1122 use std::path::Path;
1121
1123
1122 #[test]
1124 #[test]
1123 fn test_roots_and_dirs() {
1125 fn test_roots_and_dirs() {
1124 let pats = vec![
1126 let pats = vec![
1125 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1127 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1126 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1128 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1127 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1129 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1128 ];
1130 ];
1129 let (roots, dirs) = roots_and_dirs(&pats);
1131 let (roots, dirs) = roots_and_dirs(&pats);
1130
1132
1131 assert_eq!(
1133 assert_eq!(
1132 roots,
1134 roots,
1133 vec!(
1135 vec!(
1134 HgPathBuf::from_bytes(b"g/h"),
1136 HgPathBuf::from_bytes(b"g/h"),
1135 HgPathBuf::from_bytes(b"g/h"),
1137 HgPathBuf::from_bytes(b"g/h"),
1136 HgPathBuf::new()
1138 HgPathBuf::new()
1137 ),
1139 ),
1138 );
1140 );
1139 assert_eq!(dirs, vec!());
1141 assert_eq!(dirs, vec!());
1140 }
1142 }
1141
1143
1142 #[test]
1144 #[test]
1143 fn test_roots_dirs_and_parents() {
1145 fn test_roots_dirs_and_parents() {
1144 let pats = vec![
1146 let pats = vec![
1145 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1147 IgnorePattern::new(PatternSyntax::Glob, b"g/h/*", Path::new("")),
1146 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1148 IgnorePattern::new(PatternSyntax::Glob, b"g/h", Path::new("")),
1147 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1149 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1148 ];
1150 ];
1149
1151
1150 let mut roots = HashSet::new();
1152 let mut roots = HashSet::new();
1151 roots.insert(HgPathBuf::from_bytes(b"g/h"));
1153 roots.insert(HgPathBuf::from_bytes(b"g/h"));
1152 roots.insert(HgPathBuf::new());
1154 roots.insert(HgPathBuf::new());
1153
1155
1154 let dirs = HashSet::new();
1156 let dirs = HashSet::new();
1155
1157
1156 let parents = DirsMultiset::from_manifest(&[
1158 let parents = DirsMultiset::from_manifest(&[
1157 HgPathBuf::from_bytes(b"x"),
1159 HgPathBuf::from_bytes(b"x"),
1158 HgPathBuf::from_bytes(b"g/x"),
1160 HgPathBuf::from_bytes(b"g/x"),
1159 HgPathBuf::from_bytes(b"g/y"),
1161 HgPathBuf::from_bytes(b"g/y"),
1160 ])
1162 ])
1161 .unwrap();
1163 .unwrap();
1162
1164
1163 assert_eq!(
1165 assert_eq!(
1164 roots_dirs_and_parents(&pats).unwrap(),
1166 roots_dirs_and_parents(&pats).unwrap(),
1165 RootsDirsAndParents {
1167 RootsDirsAndParents {
1166 roots,
1168 roots,
1167 dirs,
1169 dirs,
1168 parents
1170 parents
1169 }
1171 }
1170 );
1172 );
1171 }
1173 }
1172
1174
1173 #[test]
1175 #[test]
1174 fn test_filematcher_visit_children_set() {
1176 fn test_filematcher_visit_children_set() {
1175 // Visitchildrenset
1177 // Visitchildrenset
1176 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/foo.txt")];
1178 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/foo.txt")];
1177 let matcher = FileMatcher::new(files).unwrap();
1179 let matcher = FileMatcher::new(files).unwrap();
1178
1180
1179 let mut set = HashSet::new();
1181 let mut set = HashSet::new();
1180 set.insert(HgPathBuf::from_bytes(b"dir"));
1182 set.insert(HgPathBuf::from_bytes(b"dir"));
1181 assert_eq!(
1183 assert_eq!(
1182 matcher.visit_children_set(HgPath::new(b"")),
1184 matcher.visit_children_set(HgPath::new(b"")),
1183 VisitChildrenSet::Set(set)
1185 VisitChildrenSet::Set(set)
1184 );
1186 );
1185
1187
1186 let mut set = HashSet::new();
1188 let mut set = HashSet::new();
1187 set.insert(HgPathBuf::from_bytes(b"subdir"));
1189 set.insert(HgPathBuf::from_bytes(b"subdir"));
1188 assert_eq!(
1190 assert_eq!(
1189 matcher.visit_children_set(HgPath::new(b"dir")),
1191 matcher.visit_children_set(HgPath::new(b"dir")),
1190 VisitChildrenSet::Set(set)
1192 VisitChildrenSet::Set(set)
1191 );
1193 );
1192
1194
1193 let mut set = HashSet::new();
1195 let mut set = HashSet::new();
1194 set.insert(HgPathBuf::from_bytes(b"foo.txt"));
1196 set.insert(HgPathBuf::from_bytes(b"foo.txt"));
1195 assert_eq!(
1197 assert_eq!(
1196 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1198 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1197 VisitChildrenSet::Set(set)
1199 VisitChildrenSet::Set(set)
1198 );
1200 );
1199
1201
1200 assert_eq!(
1202 assert_eq!(
1201 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1203 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1202 VisitChildrenSet::Empty
1204 VisitChildrenSet::Empty
1203 );
1205 );
1204 assert_eq!(
1206 assert_eq!(
1205 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
1207 matcher.visit_children_set(HgPath::new(b"dir/subdir/foo.txt")),
1206 VisitChildrenSet::Empty
1208 VisitChildrenSet::Empty
1207 );
1209 );
1208 assert_eq!(
1210 assert_eq!(
1209 matcher.visit_children_set(HgPath::new(b"folder")),
1211 matcher.visit_children_set(HgPath::new(b"folder")),
1210 VisitChildrenSet::Empty
1212 VisitChildrenSet::Empty
1211 );
1213 );
1212 }
1214 }
1213
1215
1214 #[test]
1216 #[test]
1215 fn test_filematcher_visit_children_set_files_and_dirs() {
1217 fn test_filematcher_visit_children_set_files_and_dirs() {
1216 let files = vec![
1218 let files = vec![
1217 HgPathBuf::from_bytes(b"rootfile.txt"),
1219 HgPathBuf::from_bytes(b"rootfile.txt"),
1218 HgPathBuf::from_bytes(b"a/file1.txt"),
1220 HgPathBuf::from_bytes(b"a/file1.txt"),
1219 HgPathBuf::from_bytes(b"a/b/file2.txt"),
1221 HgPathBuf::from_bytes(b"a/b/file2.txt"),
1220 // No file in a/b/c
1222 // No file in a/b/c
1221 HgPathBuf::from_bytes(b"a/b/c/d/file4.txt"),
1223 HgPathBuf::from_bytes(b"a/b/c/d/file4.txt"),
1222 ];
1224 ];
1223 let matcher = FileMatcher::new(files).unwrap();
1225 let matcher = FileMatcher::new(files).unwrap();
1224
1226
1225 let mut set = HashSet::new();
1227 let mut set = HashSet::new();
1226 set.insert(HgPathBuf::from_bytes(b"a"));
1228 set.insert(HgPathBuf::from_bytes(b"a"));
1227 set.insert(HgPathBuf::from_bytes(b"rootfile.txt"));
1229 set.insert(HgPathBuf::from_bytes(b"rootfile.txt"));
1228 assert_eq!(
1230 assert_eq!(
1229 matcher.visit_children_set(HgPath::new(b"")),
1231 matcher.visit_children_set(HgPath::new(b"")),
1230 VisitChildrenSet::Set(set)
1232 VisitChildrenSet::Set(set)
1231 );
1233 );
1232
1234
1233 let mut set = HashSet::new();
1235 let mut set = HashSet::new();
1234 set.insert(HgPathBuf::from_bytes(b"b"));
1236 set.insert(HgPathBuf::from_bytes(b"b"));
1235 set.insert(HgPathBuf::from_bytes(b"file1.txt"));
1237 set.insert(HgPathBuf::from_bytes(b"file1.txt"));
1236 assert_eq!(
1238 assert_eq!(
1237 matcher.visit_children_set(HgPath::new(b"a")),
1239 matcher.visit_children_set(HgPath::new(b"a")),
1238 VisitChildrenSet::Set(set)
1240 VisitChildrenSet::Set(set)
1239 );
1241 );
1240
1242
1241 let mut set = HashSet::new();
1243 let mut set = HashSet::new();
1242 set.insert(HgPathBuf::from_bytes(b"c"));
1244 set.insert(HgPathBuf::from_bytes(b"c"));
1243 set.insert(HgPathBuf::from_bytes(b"file2.txt"));
1245 set.insert(HgPathBuf::from_bytes(b"file2.txt"));
1244 assert_eq!(
1246 assert_eq!(
1245 matcher.visit_children_set(HgPath::new(b"a/b")),
1247 matcher.visit_children_set(HgPath::new(b"a/b")),
1246 VisitChildrenSet::Set(set)
1248 VisitChildrenSet::Set(set)
1247 );
1249 );
1248
1250
1249 let mut set = HashSet::new();
1251 let mut set = HashSet::new();
1250 set.insert(HgPathBuf::from_bytes(b"d"));
1252 set.insert(HgPathBuf::from_bytes(b"d"));
1251 assert_eq!(
1253 assert_eq!(
1252 matcher.visit_children_set(HgPath::new(b"a/b/c")),
1254 matcher.visit_children_set(HgPath::new(b"a/b/c")),
1253 VisitChildrenSet::Set(set)
1255 VisitChildrenSet::Set(set)
1254 );
1256 );
1255 let mut set = HashSet::new();
1257 let mut set = HashSet::new();
1256 set.insert(HgPathBuf::from_bytes(b"file4.txt"));
1258 set.insert(HgPathBuf::from_bytes(b"file4.txt"));
1257 assert_eq!(
1259 assert_eq!(
1258 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
1260 matcher.visit_children_set(HgPath::new(b"a/b/c/d")),
1259 VisitChildrenSet::Set(set)
1261 VisitChildrenSet::Set(set)
1260 );
1262 );
1261
1263
1262 assert_eq!(
1264 assert_eq!(
1263 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
1265 matcher.visit_children_set(HgPath::new(b"a/b/c/d/e")),
1264 VisitChildrenSet::Empty
1266 VisitChildrenSet::Empty
1265 );
1267 );
1266 assert_eq!(
1268 assert_eq!(
1267 matcher.visit_children_set(HgPath::new(b"folder")),
1269 matcher.visit_children_set(HgPath::new(b"folder")),
1268 VisitChildrenSet::Empty
1270 VisitChildrenSet::Empty
1269 );
1271 );
1270 }
1272 }
1271
1273
1272 #[test]
1274 #[test]
1273 fn test_patternmatcher() {
1275 fn test_patternmatcher() {
1274 // VisitdirPrefix
1276 // VisitdirPrefix
1275 let m = PatternMatcher::new(vec![IgnorePattern::new(
1277 let m = PatternMatcher::new(vec![IgnorePattern::new(
1276 PatternSyntax::Path,
1278 PatternSyntax::Path,
1277 b"dir/subdir",
1279 b"dir/subdir",
1278 Path::new(""),
1280 Path::new(""),
1279 )])
1281 )])
1280 .unwrap();
1282 .unwrap();
1281 assert_eq!(
1283 assert_eq!(
1282 m.visit_children_set(HgPath::new(b"")),
1284 m.visit_children_set(HgPath::new(b"")),
1283 VisitChildrenSet::This
1285 VisitChildrenSet::This
1284 );
1286 );
1285 assert_eq!(
1287 assert_eq!(
1286 m.visit_children_set(HgPath::new(b"dir")),
1288 m.visit_children_set(HgPath::new(b"dir")),
1287 VisitChildrenSet::This
1289 VisitChildrenSet::This
1288 );
1290 );
1289 assert_eq!(
1291 assert_eq!(
1290 m.visit_children_set(HgPath::new(b"dir/subdir")),
1292 m.visit_children_set(HgPath::new(b"dir/subdir")),
1291 VisitChildrenSet::Recursive
1293 VisitChildrenSet::Recursive
1292 );
1294 );
1293 // OPT: This should probably be Recursive if its parent is?
1295 // OPT: This should probably be Recursive if its parent is?
1294 assert_eq!(
1296 assert_eq!(
1295 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1297 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1296 VisitChildrenSet::This
1298 VisitChildrenSet::This
1297 );
1299 );
1298 assert_eq!(
1300 assert_eq!(
1299 m.visit_children_set(HgPath::new(b"folder")),
1301 m.visit_children_set(HgPath::new(b"folder")),
1300 VisitChildrenSet::Empty
1302 VisitChildrenSet::Empty
1301 );
1303 );
1302
1304
1303 // VisitchildrensetPrefix
1305 // VisitchildrensetPrefix
1304 let m = PatternMatcher::new(vec![IgnorePattern::new(
1306 let m = PatternMatcher::new(vec![IgnorePattern::new(
1305 PatternSyntax::Path,
1307 PatternSyntax::Path,
1306 b"dir/subdir",
1308 b"dir/subdir",
1307 Path::new(""),
1309 Path::new(""),
1308 )])
1310 )])
1309 .unwrap();
1311 .unwrap();
1310 assert_eq!(
1312 assert_eq!(
1311 m.visit_children_set(HgPath::new(b"")),
1313 m.visit_children_set(HgPath::new(b"")),
1312 VisitChildrenSet::This
1314 VisitChildrenSet::This
1313 );
1315 );
1314 assert_eq!(
1316 assert_eq!(
1315 m.visit_children_set(HgPath::new(b"dir")),
1317 m.visit_children_set(HgPath::new(b"dir")),
1316 VisitChildrenSet::This
1318 VisitChildrenSet::This
1317 );
1319 );
1318 assert_eq!(
1320 assert_eq!(
1319 m.visit_children_set(HgPath::new(b"dir/subdir")),
1321 m.visit_children_set(HgPath::new(b"dir/subdir")),
1320 VisitChildrenSet::Recursive
1322 VisitChildrenSet::Recursive
1321 );
1323 );
1322 // OPT: This should probably be Recursive if its parent is?
1324 // OPT: This should probably be Recursive if its parent is?
1323 assert_eq!(
1325 assert_eq!(
1324 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1326 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1325 VisitChildrenSet::This
1327 VisitChildrenSet::This
1326 );
1328 );
1327 assert_eq!(
1329 assert_eq!(
1328 m.visit_children_set(HgPath::new(b"folder")),
1330 m.visit_children_set(HgPath::new(b"folder")),
1329 VisitChildrenSet::Empty
1331 VisitChildrenSet::Empty
1330 );
1332 );
1331
1333
1332 // VisitdirRootfilesin
1334 // VisitdirRootfilesin
1333 let m = PatternMatcher::new(vec![IgnorePattern::new(
1335 let m = PatternMatcher::new(vec![IgnorePattern::new(
1334 PatternSyntax::RootFilesIn,
1336 PatternSyntax::RootFilesIn,
1335 b"dir/subdir",
1337 b"dir/subdir",
1336 Path::new(""),
1338 Path::new(""),
1337 )])
1339 )])
1338 .unwrap();
1340 .unwrap();
1339 assert_eq!(
1341 assert_eq!(
1340 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1342 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1341 VisitChildrenSet::This
1343 VisitChildrenSet::This
1342 );
1344 );
1343 assert_eq!(
1345 assert_eq!(
1344 m.visit_children_set(HgPath::new(b"folder")),
1346 m.visit_children_set(HgPath::new(b"folder")),
1345 VisitChildrenSet::Empty
1347 VisitChildrenSet::Empty
1346 );
1348 );
1347 assert_eq!(
1349 assert_eq!(
1348 m.visit_children_set(HgPath::new(b"")),
1350 m.visit_children_set(HgPath::new(b"")),
1349 VisitChildrenSet::This
1351 VisitChildrenSet::This
1350 );
1352 );
1351 assert_eq!(
1353 assert_eq!(
1352 m.visit_children_set(HgPath::new(b"dir")),
1354 m.visit_children_set(HgPath::new(b"dir")),
1353 VisitChildrenSet::This
1355 VisitChildrenSet::This
1354 );
1356 );
1355 assert_eq!(
1357 assert_eq!(
1356 m.visit_children_set(HgPath::new(b"dir/subdir")),
1358 m.visit_children_set(HgPath::new(b"dir/subdir")),
1357 VisitChildrenSet::This
1359 VisitChildrenSet::This
1358 );
1360 );
1359
1361
1360 // VisitchildrensetRootfilesin
1362 // VisitchildrensetRootfilesin
1361 let m = PatternMatcher::new(vec![IgnorePattern::new(
1363 let m = PatternMatcher::new(vec![IgnorePattern::new(
1362 PatternSyntax::RootFilesIn,
1364 PatternSyntax::RootFilesIn,
1363 b"dir/subdir",
1365 b"dir/subdir",
1364 Path::new(""),
1366 Path::new(""),
1365 )])
1367 )])
1366 .unwrap();
1368 .unwrap();
1367 assert_eq!(
1369 assert_eq!(
1368 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1370 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1369 VisitChildrenSet::This
1371 VisitChildrenSet::This
1370 );
1372 );
1371 assert_eq!(
1373 assert_eq!(
1372 m.visit_children_set(HgPath::new(b"folder")),
1374 m.visit_children_set(HgPath::new(b"folder")),
1373 VisitChildrenSet::Empty
1375 VisitChildrenSet::Empty
1374 );
1376 );
1375 // FIXME: These should probably be {'dir'}, {'subdir'} and This,
1377 // FIXME: These should probably be {'dir'}, {'subdir'} and This,
1376 // respectively
1378 // respectively
1377 assert_eq!(
1379 assert_eq!(
1378 m.visit_children_set(HgPath::new(b"")),
1380 m.visit_children_set(HgPath::new(b"")),
1379 VisitChildrenSet::This
1381 VisitChildrenSet::This
1380 );
1382 );
1381 assert_eq!(
1383 assert_eq!(
1382 m.visit_children_set(HgPath::new(b"dir")),
1384 m.visit_children_set(HgPath::new(b"dir")),
1383 VisitChildrenSet::This
1385 VisitChildrenSet::This
1384 );
1386 );
1385 assert_eq!(
1387 assert_eq!(
1386 m.visit_children_set(HgPath::new(b"dir/subdir")),
1388 m.visit_children_set(HgPath::new(b"dir/subdir")),
1387 VisitChildrenSet::This
1389 VisitChildrenSet::This
1388 );
1390 );
1389
1391
1390 // VisitdirGlob
1392 // VisitdirGlob
1391 let m = PatternMatcher::new(vec![IgnorePattern::new(
1393 let m = PatternMatcher::new(vec![IgnorePattern::new(
1392 PatternSyntax::Glob,
1394 PatternSyntax::Glob,
1393 b"dir/z*",
1395 b"dir/z*",
1394 Path::new(""),
1396 Path::new(""),
1395 )])
1397 )])
1396 .unwrap();
1398 .unwrap();
1397 assert_eq!(
1399 assert_eq!(
1398 m.visit_children_set(HgPath::new(b"")),
1400 m.visit_children_set(HgPath::new(b"")),
1399 VisitChildrenSet::This
1401 VisitChildrenSet::This
1400 );
1402 );
1401 assert_eq!(
1403 assert_eq!(
1402 m.visit_children_set(HgPath::new(b"dir")),
1404 m.visit_children_set(HgPath::new(b"dir")),
1403 VisitChildrenSet::This
1405 VisitChildrenSet::This
1404 );
1406 );
1405 assert_eq!(
1407 assert_eq!(
1406 m.visit_children_set(HgPath::new(b"folder")),
1408 m.visit_children_set(HgPath::new(b"folder")),
1407 VisitChildrenSet::Empty
1409 VisitChildrenSet::Empty
1408 );
1410 );
1409 // OPT: these should probably be False.
1411 // OPT: these should probably be False.
1410 assert_eq!(
1412 assert_eq!(
1411 m.visit_children_set(HgPath::new(b"dir/subdir")),
1413 m.visit_children_set(HgPath::new(b"dir/subdir")),
1412 VisitChildrenSet::This
1414 VisitChildrenSet::This
1413 );
1415 );
1414 assert_eq!(
1416 assert_eq!(
1415 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1417 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1416 VisitChildrenSet::This
1418 VisitChildrenSet::This
1417 );
1419 );
1418
1420
1419 // VisitchildrensetGlob
1421 // VisitchildrensetGlob
1420 let m = PatternMatcher::new(vec![IgnorePattern::new(
1422 let m = PatternMatcher::new(vec![IgnorePattern::new(
1421 PatternSyntax::Glob,
1423 PatternSyntax::Glob,
1422 b"dir/z*",
1424 b"dir/z*",
1423 Path::new(""),
1425 Path::new(""),
1424 )])
1426 )])
1425 .unwrap();
1427 .unwrap();
1426 assert_eq!(
1428 assert_eq!(
1427 m.visit_children_set(HgPath::new(b"")),
1429 m.visit_children_set(HgPath::new(b"")),
1428 VisitChildrenSet::This
1430 VisitChildrenSet::This
1429 );
1431 );
1430 assert_eq!(
1432 assert_eq!(
1431 m.visit_children_set(HgPath::new(b"folder")),
1433 m.visit_children_set(HgPath::new(b"folder")),
1432 VisitChildrenSet::Empty
1434 VisitChildrenSet::Empty
1433 );
1435 );
1434 assert_eq!(
1436 assert_eq!(
1435 m.visit_children_set(HgPath::new(b"dir")),
1437 m.visit_children_set(HgPath::new(b"dir")),
1436 VisitChildrenSet::This
1438 VisitChildrenSet::This
1437 );
1439 );
1438 // OPT: these should probably be Empty
1440 // OPT: these should probably be Empty
1439 assert_eq!(
1441 assert_eq!(
1440 m.visit_children_set(HgPath::new(b"dir/subdir")),
1442 m.visit_children_set(HgPath::new(b"dir/subdir")),
1441 VisitChildrenSet::This
1443 VisitChildrenSet::This
1442 );
1444 );
1443 assert_eq!(
1445 assert_eq!(
1444 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1446 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1445 VisitChildrenSet::This
1447 VisitChildrenSet::This
1446 );
1448 );
1447
1449
1448 // VisitdirFilepath
1450 // VisitdirFilepath
1449 let m = PatternMatcher::new(vec![IgnorePattern::new(
1451 let m = PatternMatcher::new(vec![IgnorePattern::new(
1450 PatternSyntax::FilePath,
1452 PatternSyntax::FilePath,
1451 b"dir/z",
1453 b"dir/z",
1452 Path::new(""),
1454 Path::new(""),
1453 )])
1455 )])
1454 .unwrap();
1456 .unwrap();
1455 assert_eq!(
1457 assert_eq!(
1456 m.visit_children_set(HgPath::new(b"")),
1458 m.visit_children_set(HgPath::new(b"")),
1457 VisitChildrenSet::This
1459 VisitChildrenSet::This
1458 );
1460 );
1459 assert_eq!(
1461 assert_eq!(
1460 m.visit_children_set(HgPath::new(b"dir")),
1462 m.visit_children_set(HgPath::new(b"dir")),
1461 VisitChildrenSet::This
1463 VisitChildrenSet::This
1462 );
1464 );
1463 assert_eq!(
1465 assert_eq!(
1464 m.visit_children_set(HgPath::new(b"folder")),
1466 m.visit_children_set(HgPath::new(b"folder")),
1465 VisitChildrenSet::Empty
1467 VisitChildrenSet::Empty
1466 );
1468 );
1467 assert_eq!(
1469 assert_eq!(
1468 m.visit_children_set(HgPath::new(b"dir/subdir")),
1470 m.visit_children_set(HgPath::new(b"dir/subdir")),
1469 VisitChildrenSet::Empty
1471 VisitChildrenSet::Empty
1470 );
1472 );
1471 assert_eq!(
1473 assert_eq!(
1472 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1474 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1473 VisitChildrenSet::Empty
1475 VisitChildrenSet::Empty
1474 );
1476 );
1475
1477
1476 // VisitchildrensetFilepath
1478 // VisitchildrensetFilepath
1477 let m = PatternMatcher::new(vec![IgnorePattern::new(
1479 let m = PatternMatcher::new(vec![IgnorePattern::new(
1478 PatternSyntax::FilePath,
1480 PatternSyntax::FilePath,
1479 b"dir/z",
1481 b"dir/z",
1480 Path::new(""),
1482 Path::new(""),
1481 )])
1483 )])
1482 .unwrap();
1484 .unwrap();
1483 assert_eq!(
1485 assert_eq!(
1484 m.visit_children_set(HgPath::new(b"")),
1486 m.visit_children_set(HgPath::new(b"")),
1485 VisitChildrenSet::This
1487 VisitChildrenSet::This
1486 );
1488 );
1487 assert_eq!(
1489 assert_eq!(
1488 m.visit_children_set(HgPath::new(b"folder")),
1490 m.visit_children_set(HgPath::new(b"folder")),
1489 VisitChildrenSet::Empty
1491 VisitChildrenSet::Empty
1490 );
1492 );
1491 assert_eq!(
1493 assert_eq!(
1492 m.visit_children_set(HgPath::new(b"dir")),
1494 m.visit_children_set(HgPath::new(b"dir")),
1493 VisitChildrenSet::This
1495 VisitChildrenSet::This
1494 );
1496 );
1495 assert_eq!(
1497 assert_eq!(
1496 m.visit_children_set(HgPath::new(b"dir/subdir")),
1498 m.visit_children_set(HgPath::new(b"dir/subdir")),
1497 VisitChildrenSet::Empty
1499 VisitChildrenSet::Empty
1498 );
1500 );
1499 assert_eq!(
1501 assert_eq!(
1500 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1502 m.visit_children_set(HgPath::new(b"dir/subdir/x")),
1501 VisitChildrenSet::Empty
1503 VisitChildrenSet::Empty
1502 );
1504 );
1503 }
1505 }
1504
1506
1505 #[test]
1507 #[test]
1506 fn test_includematcher() {
1508 fn test_includematcher() {
1507 // VisitchildrensetPrefix
1509 // VisitchildrensetPrefix
1508 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1510 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1509 PatternSyntax::RelPath,
1511 PatternSyntax::RelPath,
1510 b"dir/subdir",
1512 b"dir/subdir",
1511 Path::new(""),
1513 Path::new(""),
1512 )])
1514 )])
1513 .unwrap();
1515 .unwrap();
1514
1516
1515 let mut set = HashSet::new();
1517 let mut set = HashSet::new();
1516 set.insert(HgPathBuf::from_bytes(b"dir"));
1518 set.insert(HgPathBuf::from_bytes(b"dir"));
1517 assert_eq!(
1519 assert_eq!(
1518 matcher.visit_children_set(HgPath::new(b"")),
1520 matcher.visit_children_set(HgPath::new(b"")),
1519 VisitChildrenSet::Set(set)
1521 VisitChildrenSet::Set(set)
1520 );
1522 );
1521
1523
1522 let mut set = HashSet::new();
1524 let mut set = HashSet::new();
1523 set.insert(HgPathBuf::from_bytes(b"subdir"));
1525 set.insert(HgPathBuf::from_bytes(b"subdir"));
1524 assert_eq!(
1526 assert_eq!(
1525 matcher.visit_children_set(HgPath::new(b"dir")),
1527 matcher.visit_children_set(HgPath::new(b"dir")),
1526 VisitChildrenSet::Set(set)
1528 VisitChildrenSet::Set(set)
1527 );
1529 );
1528 assert_eq!(
1530 assert_eq!(
1529 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1531 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1530 VisitChildrenSet::Recursive
1532 VisitChildrenSet::Recursive
1531 );
1533 );
1532 // OPT: This should probably be 'all' if its parent is?
1534 // OPT: This should probably be 'all' if its parent is?
1533 assert_eq!(
1535 assert_eq!(
1534 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1536 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1535 VisitChildrenSet::This
1537 VisitChildrenSet::This
1536 );
1538 );
1537 assert_eq!(
1539 assert_eq!(
1538 matcher.visit_children_set(HgPath::new(b"folder")),
1540 matcher.visit_children_set(HgPath::new(b"folder")),
1539 VisitChildrenSet::Empty
1541 VisitChildrenSet::Empty
1540 );
1542 );
1541
1543
1542 // VisitchildrensetRootfilesin
1544 // VisitchildrensetRootfilesin
1543 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1545 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1544 PatternSyntax::RootFilesIn,
1546 PatternSyntax::RootFilesIn,
1545 b"dir/subdir",
1547 b"dir/subdir",
1546 Path::new(""),
1548 Path::new(""),
1547 )])
1549 )])
1548 .unwrap();
1550 .unwrap();
1549
1551
1550 let mut set = HashSet::new();
1552 let mut set = HashSet::new();
1551 set.insert(HgPathBuf::from_bytes(b"dir"));
1553 set.insert(HgPathBuf::from_bytes(b"dir"));
1552 assert_eq!(
1554 assert_eq!(
1553 matcher.visit_children_set(HgPath::new(b"")),
1555 matcher.visit_children_set(HgPath::new(b"")),
1554 VisitChildrenSet::Set(set)
1556 VisitChildrenSet::Set(set)
1555 );
1557 );
1556
1558
1557 let mut set = HashSet::new();
1559 let mut set = HashSet::new();
1558 set.insert(HgPathBuf::from_bytes(b"subdir"));
1560 set.insert(HgPathBuf::from_bytes(b"subdir"));
1559 assert_eq!(
1561 assert_eq!(
1560 matcher.visit_children_set(HgPath::new(b"dir")),
1562 matcher.visit_children_set(HgPath::new(b"dir")),
1561 VisitChildrenSet::Set(set)
1563 VisitChildrenSet::Set(set)
1562 );
1564 );
1563
1565
1564 assert_eq!(
1566 assert_eq!(
1565 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1567 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1566 VisitChildrenSet::This
1568 VisitChildrenSet::This
1567 );
1569 );
1568 assert_eq!(
1570 assert_eq!(
1569 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1571 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1570 VisitChildrenSet::Empty
1572 VisitChildrenSet::Empty
1571 );
1573 );
1572 assert_eq!(
1574 assert_eq!(
1573 matcher.visit_children_set(HgPath::new(b"folder")),
1575 matcher.visit_children_set(HgPath::new(b"folder")),
1574 VisitChildrenSet::Empty
1576 VisitChildrenSet::Empty
1575 );
1577 );
1576
1578
1577 // VisitchildrensetGlob
1579 // VisitchildrensetGlob
1578 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1580 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1579 PatternSyntax::Glob,
1581 PatternSyntax::Glob,
1580 b"dir/z*",
1582 b"dir/z*",
1581 Path::new(""),
1583 Path::new(""),
1582 )])
1584 )])
1583 .unwrap();
1585 .unwrap();
1584
1586
1585 let mut set = HashSet::new();
1587 let mut set = HashSet::new();
1586 set.insert(HgPathBuf::from_bytes(b"dir"));
1588 set.insert(HgPathBuf::from_bytes(b"dir"));
1587 assert_eq!(
1589 assert_eq!(
1588 matcher.visit_children_set(HgPath::new(b"")),
1590 matcher.visit_children_set(HgPath::new(b"")),
1589 VisitChildrenSet::Set(set)
1591 VisitChildrenSet::Set(set)
1590 );
1592 );
1591 assert_eq!(
1593 assert_eq!(
1592 matcher.visit_children_set(HgPath::new(b"folder")),
1594 matcher.visit_children_set(HgPath::new(b"folder")),
1593 VisitChildrenSet::Empty
1595 VisitChildrenSet::Empty
1594 );
1596 );
1595 assert_eq!(
1597 assert_eq!(
1596 matcher.visit_children_set(HgPath::new(b"dir")),
1598 matcher.visit_children_set(HgPath::new(b"dir")),
1597 VisitChildrenSet::This
1599 VisitChildrenSet::This
1598 );
1600 );
1599 // OPT: these should probably be set().
1601 // OPT: these should probably be set().
1600 assert_eq!(
1602 assert_eq!(
1601 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1603 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1602 VisitChildrenSet::This
1604 VisitChildrenSet::This
1603 );
1605 );
1604 assert_eq!(
1606 assert_eq!(
1605 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1607 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1606 VisitChildrenSet::This
1608 VisitChildrenSet::This
1607 );
1609 );
1608
1610
1609 // VisitchildrensetFilePath
1611 // VisitchildrensetFilePath
1610 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1612 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1611 PatternSyntax::FilePath,
1613 PatternSyntax::FilePath,
1612 b"dir/z",
1614 b"dir/z",
1613 Path::new(""),
1615 Path::new(""),
1614 )])
1616 )])
1615 .unwrap();
1617 .unwrap();
1616
1618
1617 let mut set = HashSet::new();
1619 let mut set = HashSet::new();
1618 set.insert(HgPathBuf::from_bytes(b"dir"));
1620 set.insert(HgPathBuf::from_bytes(b"dir"));
1619 assert_eq!(
1621 assert_eq!(
1620 matcher.visit_children_set(HgPath::new(b"")),
1622 matcher.visit_children_set(HgPath::new(b"")),
1621 VisitChildrenSet::Set(set)
1623 VisitChildrenSet::Set(set)
1622 );
1624 );
1623 assert_eq!(
1625 assert_eq!(
1624 matcher.visit_children_set(HgPath::new(b"folder")),
1626 matcher.visit_children_set(HgPath::new(b"folder")),
1625 VisitChildrenSet::Empty
1627 VisitChildrenSet::Empty
1626 );
1628 );
1627 let mut set = HashSet::new();
1629 let mut set = HashSet::new();
1628 set.insert(HgPathBuf::from_bytes(b"z"));
1630 set.insert(HgPathBuf::from_bytes(b"z"));
1629 assert_eq!(
1631 assert_eq!(
1630 matcher.visit_children_set(HgPath::new(b"dir")),
1632 matcher.visit_children_set(HgPath::new(b"dir")),
1631 VisitChildrenSet::Set(set)
1633 VisitChildrenSet::Set(set)
1632 );
1634 );
1633 // OPT: these should probably be set().
1635 // OPT: these should probably be set().
1634 assert_eq!(
1636 assert_eq!(
1635 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1637 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1636 VisitChildrenSet::Empty
1638 VisitChildrenSet::Empty
1637 );
1639 );
1638 assert_eq!(
1640 assert_eq!(
1639 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1641 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1640 VisitChildrenSet::Empty
1642 VisitChildrenSet::Empty
1641 );
1643 );
1642
1644
1643 // Test multiple patterns
1645 // Test multiple patterns
1644 let matcher = IncludeMatcher::new(vec![
1646 let matcher = IncludeMatcher::new(vec![
1645 IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")),
1647 IgnorePattern::new(PatternSyntax::RelPath, b"foo", Path::new("")),
1646 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1648 IgnorePattern::new(PatternSyntax::Glob, b"g*", Path::new("")),
1647 ])
1649 ])
1648 .unwrap();
1650 .unwrap();
1649
1651
1650 assert_eq!(
1652 assert_eq!(
1651 matcher.visit_children_set(HgPath::new(b"")),
1653 matcher.visit_children_set(HgPath::new(b"")),
1652 VisitChildrenSet::This
1654 VisitChildrenSet::This
1653 );
1655 );
1654
1656
1655 // Test multiple patterns
1657 // Test multiple patterns
1656 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1658 let matcher = IncludeMatcher::new(vec![IgnorePattern::new(
1657 PatternSyntax::Glob,
1659 PatternSyntax::Glob,
1658 b"**/*.exe",
1660 b"**/*.exe",
1659 Path::new(""),
1661 Path::new(""),
1660 )])
1662 )])
1661 .unwrap();
1663 .unwrap();
1662
1664
1663 assert_eq!(
1665 assert_eq!(
1664 matcher.visit_children_set(HgPath::new(b"")),
1666 matcher.visit_children_set(HgPath::new(b"")),
1665 VisitChildrenSet::This
1667 VisitChildrenSet::This
1666 );
1668 );
1667 }
1669 }
1668
1670
1669 #[test]
1671 #[test]
1670 fn test_unionmatcher() {
1672 fn test_unionmatcher() {
1671 // Path + Rootfiles
1673 // Path + Rootfiles
1672 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1674 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1673 PatternSyntax::RelPath,
1675 PatternSyntax::RelPath,
1674 b"dir/subdir",
1676 b"dir/subdir",
1675 Path::new(""),
1677 Path::new(""),
1676 )])
1678 )])
1677 .unwrap();
1679 .unwrap();
1678 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1680 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1679 PatternSyntax::RootFilesIn,
1681 PatternSyntax::RootFilesIn,
1680 b"dir",
1682 b"dir",
1681 Path::new(""),
1683 Path::new(""),
1682 )])
1684 )])
1683 .unwrap();
1685 .unwrap();
1684 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1686 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1685
1687
1686 let mut set = HashSet::new();
1688 let mut set = HashSet::new();
1687 set.insert(HgPathBuf::from_bytes(b"dir"));
1689 set.insert(HgPathBuf::from_bytes(b"dir"));
1688 assert_eq!(
1690 assert_eq!(
1689 matcher.visit_children_set(HgPath::new(b"")),
1691 matcher.visit_children_set(HgPath::new(b"")),
1690 VisitChildrenSet::Set(set)
1692 VisitChildrenSet::Set(set)
1691 );
1693 );
1692 assert_eq!(
1694 assert_eq!(
1693 matcher.visit_children_set(HgPath::new(b"dir")),
1695 matcher.visit_children_set(HgPath::new(b"dir")),
1694 VisitChildrenSet::This
1696 VisitChildrenSet::This
1695 );
1697 );
1696 assert_eq!(
1698 assert_eq!(
1697 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1699 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1698 VisitChildrenSet::Recursive
1700 VisitChildrenSet::Recursive
1699 );
1701 );
1700 assert_eq!(
1702 assert_eq!(
1701 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1703 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1702 VisitChildrenSet::Empty
1704 VisitChildrenSet::Empty
1703 );
1705 );
1704 assert_eq!(
1706 assert_eq!(
1705 matcher.visit_children_set(HgPath::new(b"folder")),
1707 matcher.visit_children_set(HgPath::new(b"folder")),
1706 VisitChildrenSet::Empty
1708 VisitChildrenSet::Empty
1707 );
1709 );
1708 assert_eq!(
1710 assert_eq!(
1709 matcher.visit_children_set(HgPath::new(b"folder")),
1711 matcher.visit_children_set(HgPath::new(b"folder")),
1710 VisitChildrenSet::Empty
1712 VisitChildrenSet::Empty
1711 );
1713 );
1712
1714
1713 // OPT: These next two could be 'all' instead of 'this'.
1715 // OPT: These next two could be 'all' instead of 'this'.
1714 assert_eq!(
1716 assert_eq!(
1715 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1717 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1716 VisitChildrenSet::This
1718 VisitChildrenSet::This
1717 );
1719 );
1718 assert_eq!(
1720 assert_eq!(
1719 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1721 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1720 VisitChildrenSet::This
1722 VisitChildrenSet::This
1721 );
1723 );
1722
1724
1723 // Path + unrelated Path
1725 // Path + unrelated Path
1724 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1726 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1725 PatternSyntax::RelPath,
1727 PatternSyntax::RelPath,
1726 b"dir/subdir",
1728 b"dir/subdir",
1727 Path::new(""),
1729 Path::new(""),
1728 )])
1730 )])
1729 .unwrap();
1731 .unwrap();
1730 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1732 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1731 PatternSyntax::RelPath,
1733 PatternSyntax::RelPath,
1732 b"folder",
1734 b"folder",
1733 Path::new(""),
1735 Path::new(""),
1734 )])
1736 )])
1735 .unwrap();
1737 .unwrap();
1736 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1738 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1737
1739
1738 let mut set = HashSet::new();
1740 let mut set = HashSet::new();
1739 set.insert(HgPathBuf::from_bytes(b"folder"));
1741 set.insert(HgPathBuf::from_bytes(b"folder"));
1740 set.insert(HgPathBuf::from_bytes(b"dir"));
1742 set.insert(HgPathBuf::from_bytes(b"dir"));
1741 assert_eq!(
1743 assert_eq!(
1742 matcher.visit_children_set(HgPath::new(b"")),
1744 matcher.visit_children_set(HgPath::new(b"")),
1743 VisitChildrenSet::Set(set)
1745 VisitChildrenSet::Set(set)
1744 );
1746 );
1745 let mut set = HashSet::new();
1747 let mut set = HashSet::new();
1746 set.insert(HgPathBuf::from_bytes(b"subdir"));
1748 set.insert(HgPathBuf::from_bytes(b"subdir"));
1747 assert_eq!(
1749 assert_eq!(
1748 matcher.visit_children_set(HgPath::new(b"dir")),
1750 matcher.visit_children_set(HgPath::new(b"dir")),
1749 VisitChildrenSet::Set(set)
1751 VisitChildrenSet::Set(set)
1750 );
1752 );
1751
1753
1752 assert_eq!(
1754 assert_eq!(
1753 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1755 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1754 VisitChildrenSet::Recursive
1756 VisitChildrenSet::Recursive
1755 );
1757 );
1756 assert_eq!(
1758 assert_eq!(
1757 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1759 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1758 VisitChildrenSet::Empty
1760 VisitChildrenSet::Empty
1759 );
1761 );
1760
1762
1761 assert_eq!(
1763 assert_eq!(
1762 matcher.visit_children_set(HgPath::new(b"folder")),
1764 matcher.visit_children_set(HgPath::new(b"folder")),
1763 VisitChildrenSet::Recursive
1765 VisitChildrenSet::Recursive
1764 );
1766 );
1765 // OPT: These next two could be 'all' instead of 'this'.
1767 // OPT: These next two could be 'all' instead of 'this'.
1766 assert_eq!(
1768 assert_eq!(
1767 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1769 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1768 VisitChildrenSet::This
1770 VisitChildrenSet::This
1769 );
1771 );
1770 assert_eq!(
1772 assert_eq!(
1771 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1773 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1772 VisitChildrenSet::This
1774 VisitChildrenSet::This
1773 );
1775 );
1774
1776
1775 // Path + subpath
1777 // Path + subpath
1776 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1778 let m1 = IncludeMatcher::new(vec![IgnorePattern::new(
1777 PatternSyntax::RelPath,
1779 PatternSyntax::RelPath,
1778 b"dir/subdir/x",
1780 b"dir/subdir/x",
1779 Path::new(""),
1781 Path::new(""),
1780 )])
1782 )])
1781 .unwrap();
1783 .unwrap();
1782 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1784 let m2 = IncludeMatcher::new(vec![IgnorePattern::new(
1783 PatternSyntax::RelPath,
1785 PatternSyntax::RelPath,
1784 b"dir/subdir",
1786 b"dir/subdir",
1785 Path::new(""),
1787 Path::new(""),
1786 )])
1788 )])
1787 .unwrap();
1789 .unwrap();
1788 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1790 let matcher = UnionMatcher::new(vec![Box::new(m1), Box::new(m2)]);
1789
1791
1790 let mut set = HashSet::new();
1792 let mut set = HashSet::new();
1791 set.insert(HgPathBuf::from_bytes(b"dir"));
1793 set.insert(HgPathBuf::from_bytes(b"dir"));
1792 assert_eq!(
1794 assert_eq!(
1793 matcher.visit_children_set(HgPath::new(b"")),
1795 matcher.visit_children_set(HgPath::new(b"")),
1794 VisitChildrenSet::Set(set)
1796 VisitChildrenSet::Set(set)
1795 );
1797 );
1796 let mut set = HashSet::new();
1798 let mut set = HashSet::new();
1797 set.insert(HgPathBuf::from_bytes(b"subdir"));
1799 set.insert(HgPathBuf::from_bytes(b"subdir"));
1798 assert_eq!(
1800 assert_eq!(
1799 matcher.visit_children_set(HgPath::new(b"dir")),
1801 matcher.visit_children_set(HgPath::new(b"dir")),
1800 VisitChildrenSet::Set(set)
1802 VisitChildrenSet::Set(set)
1801 );
1803 );
1802
1804
1803 assert_eq!(
1805 assert_eq!(
1804 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1806 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1805 VisitChildrenSet::Recursive
1807 VisitChildrenSet::Recursive
1806 );
1808 );
1807 assert_eq!(
1809 assert_eq!(
1808 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1810 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1809 VisitChildrenSet::Empty
1811 VisitChildrenSet::Empty
1810 );
1812 );
1811
1813
1812 assert_eq!(
1814 assert_eq!(
1813 matcher.visit_children_set(HgPath::new(b"folder")),
1815 matcher.visit_children_set(HgPath::new(b"folder")),
1814 VisitChildrenSet::Empty
1816 VisitChildrenSet::Empty
1815 );
1817 );
1816 assert_eq!(
1818 assert_eq!(
1817 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1819 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1818 VisitChildrenSet::Recursive
1820 VisitChildrenSet::Recursive
1819 );
1821 );
1820 // OPT: this should probably be 'all' not 'this'.
1822 // OPT: this should probably be 'all' not 'this'.
1821 assert_eq!(
1823 assert_eq!(
1822 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1824 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1823 VisitChildrenSet::This
1825 VisitChildrenSet::This
1824 );
1826 );
1825 }
1827 }
1826
1828
1827 #[test]
1829 #[test]
1828 fn test_intersectionmatcher() {
1830 fn test_intersectionmatcher() {
1829 // Include path + Include rootfiles
1831 // Include path + Include rootfiles
1830 let m1 = Box::new(
1832 let m1 = Box::new(
1831 IncludeMatcher::new(vec![IgnorePattern::new(
1833 IncludeMatcher::new(vec![IgnorePattern::new(
1832 PatternSyntax::RelPath,
1834 PatternSyntax::RelPath,
1833 b"dir/subdir",
1835 b"dir/subdir",
1834 Path::new(""),
1836 Path::new(""),
1835 )])
1837 )])
1836 .unwrap(),
1838 .unwrap(),
1837 );
1839 );
1838 let m2 = Box::new(
1840 let m2 = Box::new(
1839 IncludeMatcher::new(vec![IgnorePattern::new(
1841 IncludeMatcher::new(vec![IgnorePattern::new(
1840 PatternSyntax::RootFilesIn,
1842 PatternSyntax::RootFilesIn,
1841 b"dir",
1843 b"dir",
1842 Path::new(""),
1844 Path::new(""),
1843 )])
1845 )])
1844 .unwrap(),
1846 .unwrap(),
1845 );
1847 );
1846 let matcher = IntersectionMatcher::new(m1, m2);
1848 let matcher = IntersectionMatcher::new(m1, m2);
1847
1849
1848 let mut set = HashSet::new();
1850 let mut set = HashSet::new();
1849 set.insert(HgPathBuf::from_bytes(b"dir"));
1851 set.insert(HgPathBuf::from_bytes(b"dir"));
1850 assert_eq!(
1852 assert_eq!(
1851 matcher.visit_children_set(HgPath::new(b"")),
1853 matcher.visit_children_set(HgPath::new(b"")),
1852 VisitChildrenSet::Set(set)
1854 VisitChildrenSet::Set(set)
1853 );
1855 );
1854 assert_eq!(
1856 assert_eq!(
1855 matcher.visit_children_set(HgPath::new(b"dir")),
1857 matcher.visit_children_set(HgPath::new(b"dir")),
1856 VisitChildrenSet::This
1858 VisitChildrenSet::This
1857 );
1859 );
1858 assert_eq!(
1860 assert_eq!(
1859 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1861 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1860 VisitChildrenSet::Empty
1862 VisitChildrenSet::Empty
1861 );
1863 );
1862 assert_eq!(
1864 assert_eq!(
1863 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1865 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1864 VisitChildrenSet::Empty
1866 VisitChildrenSet::Empty
1865 );
1867 );
1866 assert_eq!(
1868 assert_eq!(
1867 matcher.visit_children_set(HgPath::new(b"folder")),
1869 matcher.visit_children_set(HgPath::new(b"folder")),
1868 VisitChildrenSet::Empty
1870 VisitChildrenSet::Empty
1869 );
1871 );
1870 assert_eq!(
1872 assert_eq!(
1871 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1873 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1872 VisitChildrenSet::Empty
1874 VisitChildrenSet::Empty
1873 );
1875 );
1874 assert_eq!(
1876 assert_eq!(
1875 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1877 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1876 VisitChildrenSet::Empty
1878 VisitChildrenSet::Empty
1877 );
1879 );
1878
1880
1879 // Non intersecting paths
1881 // Non intersecting paths
1880 let m1 = Box::new(
1882 let m1 = Box::new(
1881 IncludeMatcher::new(vec![IgnorePattern::new(
1883 IncludeMatcher::new(vec![IgnorePattern::new(
1882 PatternSyntax::RelPath,
1884 PatternSyntax::RelPath,
1883 b"dir/subdir",
1885 b"dir/subdir",
1884 Path::new(""),
1886 Path::new(""),
1885 )])
1887 )])
1886 .unwrap(),
1888 .unwrap(),
1887 );
1889 );
1888 let m2 = Box::new(
1890 let m2 = Box::new(
1889 IncludeMatcher::new(vec![IgnorePattern::new(
1891 IncludeMatcher::new(vec![IgnorePattern::new(
1890 PatternSyntax::RelPath,
1892 PatternSyntax::RelPath,
1891 b"folder",
1893 b"folder",
1892 Path::new(""),
1894 Path::new(""),
1893 )])
1895 )])
1894 .unwrap(),
1896 .unwrap(),
1895 );
1897 );
1896 let matcher = IntersectionMatcher::new(m1, m2);
1898 let matcher = IntersectionMatcher::new(m1, m2);
1897
1899
1898 assert_eq!(
1900 assert_eq!(
1899 matcher.visit_children_set(HgPath::new(b"")),
1901 matcher.visit_children_set(HgPath::new(b"")),
1900 VisitChildrenSet::Empty
1902 VisitChildrenSet::Empty
1901 );
1903 );
1902 assert_eq!(
1904 assert_eq!(
1903 matcher.visit_children_set(HgPath::new(b"dir")),
1905 matcher.visit_children_set(HgPath::new(b"dir")),
1904 VisitChildrenSet::Empty
1906 VisitChildrenSet::Empty
1905 );
1907 );
1906 assert_eq!(
1908 assert_eq!(
1907 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1909 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1908 VisitChildrenSet::Empty
1910 VisitChildrenSet::Empty
1909 );
1911 );
1910 assert_eq!(
1912 assert_eq!(
1911 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1913 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1912 VisitChildrenSet::Empty
1914 VisitChildrenSet::Empty
1913 );
1915 );
1914 assert_eq!(
1916 assert_eq!(
1915 matcher.visit_children_set(HgPath::new(b"folder")),
1917 matcher.visit_children_set(HgPath::new(b"folder")),
1916 VisitChildrenSet::Empty
1918 VisitChildrenSet::Empty
1917 );
1919 );
1918 assert_eq!(
1920 assert_eq!(
1919 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1921 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1920 VisitChildrenSet::Empty
1922 VisitChildrenSet::Empty
1921 );
1923 );
1922 assert_eq!(
1924 assert_eq!(
1923 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1925 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1924 VisitChildrenSet::Empty
1926 VisitChildrenSet::Empty
1925 );
1927 );
1926
1928
1927 // Nested paths
1929 // Nested paths
1928 let m1 = Box::new(
1930 let m1 = Box::new(
1929 IncludeMatcher::new(vec![IgnorePattern::new(
1931 IncludeMatcher::new(vec![IgnorePattern::new(
1930 PatternSyntax::RelPath,
1932 PatternSyntax::RelPath,
1931 b"dir/subdir/x",
1933 b"dir/subdir/x",
1932 Path::new(""),
1934 Path::new(""),
1933 )])
1935 )])
1934 .unwrap(),
1936 .unwrap(),
1935 );
1937 );
1936 let m2 = Box::new(
1938 let m2 = Box::new(
1937 IncludeMatcher::new(vec![IgnorePattern::new(
1939 IncludeMatcher::new(vec![IgnorePattern::new(
1938 PatternSyntax::RelPath,
1940 PatternSyntax::RelPath,
1939 b"dir/subdir",
1941 b"dir/subdir",
1940 Path::new(""),
1942 Path::new(""),
1941 )])
1943 )])
1942 .unwrap(),
1944 .unwrap(),
1943 );
1945 );
1944 let matcher = IntersectionMatcher::new(m1, m2);
1946 let matcher = IntersectionMatcher::new(m1, m2);
1945
1947
1946 let mut set = HashSet::new();
1948 let mut set = HashSet::new();
1947 set.insert(HgPathBuf::from_bytes(b"dir"));
1949 set.insert(HgPathBuf::from_bytes(b"dir"));
1948 assert_eq!(
1950 assert_eq!(
1949 matcher.visit_children_set(HgPath::new(b"")),
1951 matcher.visit_children_set(HgPath::new(b"")),
1950 VisitChildrenSet::Set(set)
1952 VisitChildrenSet::Set(set)
1951 );
1953 );
1952
1954
1953 let mut set = HashSet::new();
1955 let mut set = HashSet::new();
1954 set.insert(HgPathBuf::from_bytes(b"subdir"));
1956 set.insert(HgPathBuf::from_bytes(b"subdir"));
1955 assert_eq!(
1957 assert_eq!(
1956 matcher.visit_children_set(HgPath::new(b"dir")),
1958 matcher.visit_children_set(HgPath::new(b"dir")),
1957 VisitChildrenSet::Set(set)
1959 VisitChildrenSet::Set(set)
1958 );
1960 );
1959 let mut set = HashSet::new();
1961 let mut set = HashSet::new();
1960 set.insert(HgPathBuf::from_bytes(b"x"));
1962 set.insert(HgPathBuf::from_bytes(b"x"));
1961 assert_eq!(
1963 assert_eq!(
1962 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1964 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
1963 VisitChildrenSet::Set(set)
1965 VisitChildrenSet::Set(set)
1964 );
1966 );
1965 assert_eq!(
1967 assert_eq!(
1966 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1968 matcher.visit_children_set(HgPath::new(b"dir/foo")),
1967 VisitChildrenSet::Empty
1969 VisitChildrenSet::Empty
1968 );
1970 );
1969 assert_eq!(
1971 assert_eq!(
1970 matcher.visit_children_set(HgPath::new(b"folder")),
1972 matcher.visit_children_set(HgPath::new(b"folder")),
1971 VisitChildrenSet::Empty
1973 VisitChildrenSet::Empty
1972 );
1974 );
1973 assert_eq!(
1975 assert_eq!(
1974 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1976 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
1975 VisitChildrenSet::Empty
1977 VisitChildrenSet::Empty
1976 );
1978 );
1977 // OPT: this should probably be 'all' not 'this'.
1979 // OPT: this should probably be 'all' not 'this'.
1978 assert_eq!(
1980 assert_eq!(
1979 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1981 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
1980 VisitChildrenSet::This
1982 VisitChildrenSet::This
1981 );
1983 );
1982
1984
1983 // Diverging paths
1985 // Diverging paths
1984 let m1 = Box::new(
1986 let m1 = Box::new(
1985 IncludeMatcher::new(vec![IgnorePattern::new(
1987 IncludeMatcher::new(vec![IgnorePattern::new(
1986 PatternSyntax::RelPath,
1988 PatternSyntax::RelPath,
1987 b"dir/subdir/x",
1989 b"dir/subdir/x",
1988 Path::new(""),
1990 Path::new(""),
1989 )])
1991 )])
1990 .unwrap(),
1992 .unwrap(),
1991 );
1993 );
1992 let m2 = Box::new(
1994 let m2 = Box::new(
1993 IncludeMatcher::new(vec![IgnorePattern::new(
1995 IncludeMatcher::new(vec![IgnorePattern::new(
1994 PatternSyntax::RelPath,
1996 PatternSyntax::RelPath,
1995 b"dir/subdir/z",
1997 b"dir/subdir/z",
1996 Path::new(""),
1998 Path::new(""),
1997 )])
1999 )])
1998 .unwrap(),
2000 .unwrap(),
1999 );
2001 );
2000 let matcher = IntersectionMatcher::new(m1, m2);
2002 let matcher = IntersectionMatcher::new(m1, m2);
2001
2003
2002 // OPT: these next two could probably be Empty as well.
2004 // OPT: these next two could probably be Empty as well.
2003 let mut set = HashSet::new();
2005 let mut set = HashSet::new();
2004 set.insert(HgPathBuf::from_bytes(b"dir"));
2006 set.insert(HgPathBuf::from_bytes(b"dir"));
2005 assert_eq!(
2007 assert_eq!(
2006 matcher.visit_children_set(HgPath::new(b"")),
2008 matcher.visit_children_set(HgPath::new(b"")),
2007 VisitChildrenSet::Set(set)
2009 VisitChildrenSet::Set(set)
2008 );
2010 );
2009 // OPT: these next two could probably be Empty as well.
2011 // OPT: these next two could probably be Empty as well.
2010 let mut set = HashSet::new();
2012 let mut set = HashSet::new();
2011 set.insert(HgPathBuf::from_bytes(b"subdir"));
2013 set.insert(HgPathBuf::from_bytes(b"subdir"));
2012 assert_eq!(
2014 assert_eq!(
2013 matcher.visit_children_set(HgPath::new(b"dir")),
2015 matcher.visit_children_set(HgPath::new(b"dir")),
2014 VisitChildrenSet::Set(set)
2016 VisitChildrenSet::Set(set)
2015 );
2017 );
2016 assert_eq!(
2018 assert_eq!(
2017 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
2019 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
2018 VisitChildrenSet::Empty
2020 VisitChildrenSet::Empty
2019 );
2021 );
2020 assert_eq!(
2022 assert_eq!(
2021 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2023 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2022 VisitChildrenSet::Empty
2024 VisitChildrenSet::Empty
2023 );
2025 );
2024 assert_eq!(
2026 assert_eq!(
2025 matcher.visit_children_set(HgPath::new(b"folder")),
2027 matcher.visit_children_set(HgPath::new(b"folder")),
2026 VisitChildrenSet::Empty
2028 VisitChildrenSet::Empty
2027 );
2029 );
2028 assert_eq!(
2030 assert_eq!(
2029 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2031 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2030 VisitChildrenSet::Empty
2032 VisitChildrenSet::Empty
2031 );
2033 );
2032 assert_eq!(
2034 assert_eq!(
2033 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2035 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2034 VisitChildrenSet::Empty
2036 VisitChildrenSet::Empty
2035 );
2037 );
2036 }
2038 }
2037
2039
2038 #[test]
2040 #[test]
2039 fn test_differencematcher() {
2041 fn test_differencematcher() {
2040 // Two alwaysmatchers should function like a nevermatcher
2042 // Two alwaysmatchers should function like a nevermatcher
2041 let m1 = AlwaysMatcher;
2043 let m1 = AlwaysMatcher;
2042 let m2 = AlwaysMatcher;
2044 let m2 = AlwaysMatcher;
2043 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2045 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2044
2046
2045 for case in &[
2047 for case in &[
2046 &b""[..],
2048 &b""[..],
2047 b"dir",
2049 b"dir",
2048 b"dir/subdir",
2050 b"dir/subdir",
2049 b"dir/subdir/z",
2051 b"dir/subdir/z",
2050 b"dir/foo",
2052 b"dir/foo",
2051 b"dir/subdir/x",
2053 b"dir/subdir/x",
2052 b"folder",
2054 b"folder",
2053 ] {
2055 ] {
2054 assert_eq!(
2056 assert_eq!(
2055 matcher.visit_children_set(HgPath::new(case)),
2057 matcher.visit_children_set(HgPath::new(case)),
2056 VisitChildrenSet::Empty
2058 VisitChildrenSet::Empty
2057 );
2059 );
2058 }
2060 }
2059
2061
2060 // One always and one never should behave the same as an always
2062 // One always and one never should behave the same as an always
2061 let m1 = AlwaysMatcher;
2063 let m1 = AlwaysMatcher;
2062 let m2 = NeverMatcher;
2064 let m2 = NeverMatcher;
2063 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2065 let matcher = DifferenceMatcher::new(Box::new(m1), Box::new(m2));
2064
2066
2065 for case in &[
2067 for case in &[
2066 &b""[..],
2068 &b""[..],
2067 b"dir",
2069 b"dir",
2068 b"dir/subdir",
2070 b"dir/subdir",
2069 b"dir/subdir/z",
2071 b"dir/subdir/z",
2070 b"dir/foo",
2072 b"dir/foo",
2071 b"dir/subdir/x",
2073 b"dir/subdir/x",
2072 b"folder",
2074 b"folder",
2073 ] {
2075 ] {
2074 assert_eq!(
2076 assert_eq!(
2075 matcher.visit_children_set(HgPath::new(case)),
2077 matcher.visit_children_set(HgPath::new(case)),
2076 VisitChildrenSet::Recursive
2078 VisitChildrenSet::Recursive
2077 );
2079 );
2078 }
2080 }
2079
2081
2080 // Two include matchers
2082 // Two include matchers
2081 let m1 = Box::new(
2083 let m1 = Box::new(
2082 IncludeMatcher::new(vec![IgnorePattern::new(
2084 IncludeMatcher::new(vec![IgnorePattern::new(
2083 PatternSyntax::RelPath,
2085 PatternSyntax::RelPath,
2084 b"dir/subdir",
2086 b"dir/subdir",
2085 Path::new("/repo"),
2087 Path::new("/repo"),
2086 )])
2088 )])
2087 .unwrap(),
2089 .unwrap(),
2088 );
2090 );
2089 let m2 = Box::new(
2091 let m2 = Box::new(
2090 IncludeMatcher::new(vec![IgnorePattern::new(
2092 IncludeMatcher::new(vec![IgnorePattern::new(
2091 PatternSyntax::RootFilesIn,
2093 PatternSyntax::RootFilesIn,
2092 b"dir",
2094 b"dir",
2093 Path::new("/repo"),
2095 Path::new("/repo"),
2094 )])
2096 )])
2095 .unwrap(),
2097 .unwrap(),
2096 );
2098 );
2097
2099
2098 let matcher = DifferenceMatcher::new(m1, m2);
2100 let matcher = DifferenceMatcher::new(m1, m2);
2099
2101
2100 let mut set = HashSet::new();
2102 let mut set = HashSet::new();
2101 set.insert(HgPathBuf::from_bytes(b"dir"));
2103 set.insert(HgPathBuf::from_bytes(b"dir"));
2102 assert_eq!(
2104 assert_eq!(
2103 matcher.visit_children_set(HgPath::new(b"")),
2105 matcher.visit_children_set(HgPath::new(b"")),
2104 VisitChildrenSet::Set(set)
2106 VisitChildrenSet::Set(set)
2105 );
2107 );
2106
2108
2107 let mut set = HashSet::new();
2109 let mut set = HashSet::new();
2108 set.insert(HgPathBuf::from_bytes(b"subdir"));
2110 set.insert(HgPathBuf::from_bytes(b"subdir"));
2109 assert_eq!(
2111 assert_eq!(
2110 matcher.visit_children_set(HgPath::new(b"dir")),
2112 matcher.visit_children_set(HgPath::new(b"dir")),
2111 VisitChildrenSet::Set(set)
2113 VisitChildrenSet::Set(set)
2112 );
2114 );
2113 assert_eq!(
2115 assert_eq!(
2114 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
2116 matcher.visit_children_set(HgPath::new(b"dir/subdir")),
2115 VisitChildrenSet::Recursive
2117 VisitChildrenSet::Recursive
2116 );
2118 );
2117 assert_eq!(
2119 assert_eq!(
2118 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2120 matcher.visit_children_set(HgPath::new(b"dir/foo")),
2119 VisitChildrenSet::Empty
2121 VisitChildrenSet::Empty
2120 );
2122 );
2121 assert_eq!(
2123 assert_eq!(
2122 matcher.visit_children_set(HgPath::new(b"folder")),
2124 matcher.visit_children_set(HgPath::new(b"folder")),
2123 VisitChildrenSet::Empty
2125 VisitChildrenSet::Empty
2124 );
2126 );
2125 assert_eq!(
2127 assert_eq!(
2126 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2128 matcher.visit_children_set(HgPath::new(b"dir/subdir/z")),
2127 VisitChildrenSet::This
2129 VisitChildrenSet::This
2128 );
2130 );
2129 assert_eq!(
2131 assert_eq!(
2130 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2132 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")),
2131 VisitChildrenSet::This
2133 VisitChildrenSet::This
2132 );
2134 );
2133 }
2135 }
2134
2136
2135 mod invariants {
2137 mod invariants {
2136 pub mod visit_children_set {
2138 pub mod visit_children_set {
2137
2139
2138 use crate::{
2140 use crate::{
2139 matchers::{tests::Tree, Matcher, VisitChildrenSet},
2141 matchers::{tests::Tree, Matcher, VisitChildrenSet},
2140 utils::hg_path::HgPath,
2142 utils::hg_path::HgPath,
2141 };
2143 };
2142
2144
2143 #[allow(dead_code)]
2145 #[allow(dead_code)]
2144 #[derive(Debug)]
2146 #[derive(Debug)]
2145 struct Error<'a, M> {
2147 struct Error<'a, M> {
2146 matcher: &'a M,
2148 matcher: &'a M,
2147 path: &'a HgPath,
2149 path: &'a HgPath,
2148 matching: &'a Tree,
2150 matching: &'a Tree,
2149 visit_children_set: &'a VisitChildrenSet,
2151 visit_children_set: &'a VisitChildrenSet,
2150 }
2152 }
2151
2153
2152 fn holds(
2154 fn holds(
2153 matching: &Tree,
2155 matching: &Tree,
2154 not_matching: &Tree,
2156 not_matching: &Tree,
2155 vcs: &VisitChildrenSet,
2157 vcs: &VisitChildrenSet,
2156 ) -> bool {
2158 ) -> bool {
2157 match vcs {
2159 match vcs {
2158 VisitChildrenSet::Empty => matching.is_empty(),
2160 VisitChildrenSet::Empty => matching.is_empty(),
2159 VisitChildrenSet::This => {
2161 VisitChildrenSet::This => {
2160 // `This` does not come with any obligations.
2162 // `This` does not come with any obligations.
2161 true
2163 true
2162 }
2164 }
2163 VisitChildrenSet::Recursive => {
2165 VisitChildrenSet::Recursive => {
2164 // `Recursive` requires that *everything* in the
2166 // `Recursive` requires that *everything* in the
2165 // subtree matches. This
2167 // subtree matches. This
2166 // requirement is relied on for example in
2168 // requirement is relied on for example in
2167 // DifferenceMatcher implementation.
2169 // DifferenceMatcher implementation.
2168 not_matching.is_empty()
2170 not_matching.is_empty()
2169 }
2171 }
2170 VisitChildrenSet::Set(allowed_children) => {
2172 VisitChildrenSet::Set(allowed_children) => {
2171 // `allowed_children` does not distinguish between
2173 // `allowed_children` does not distinguish between
2172 // files and directories: if it's not included, it
2174 // files and directories: if it's not included, it
2173 // must not be matched.
2175 // must not be matched.
2174 for k in matching.dirs.keys() {
2176 for k in matching.dirs.keys() {
2175 if !(allowed_children.contains(k)) {
2177 if !(allowed_children.contains(k)) {
2176 return false;
2178 return false;
2177 }
2179 }
2178 }
2180 }
2179 for k in matching.files.iter() {
2181 for k in matching.files.iter() {
2180 if !(allowed_children.contains(k)) {
2182 if !(allowed_children.contains(k)) {
2181 return false;
2183 return false;
2182 }
2184 }
2183 }
2185 }
2184 true
2186 true
2185 }
2187 }
2186 }
2188 }
2187 }
2189 }
2188
2190
2189 pub fn check<M: Matcher + std::fmt::Debug>(
2191 pub fn check<M: Matcher + std::fmt::Debug>(
2190 matcher: &M,
2192 matcher: &M,
2191 path: &HgPath,
2193 path: &HgPath,
2192 matching: &Tree,
2194 matching: &Tree,
2193 not_matching: &Tree,
2195 not_matching: &Tree,
2194 visit_children_set: &VisitChildrenSet,
2196 visit_children_set: &VisitChildrenSet,
2195 ) {
2197 ) {
2196 if !holds(matching, not_matching, visit_children_set) {
2198 if !holds(matching, not_matching, visit_children_set) {
2197 panic!(
2199 panic!(
2198 "{:#?}",
2200 "{:#?}",
2199 Error {
2201 Error {
2200 matcher,
2202 matcher,
2201 path,
2203 path,
2202 visit_children_set,
2204 visit_children_set,
2203 matching
2205 matching
2204 }
2206 }
2205 )
2207 )
2206 }
2208 }
2207 }
2209 }
2208 }
2210 }
2209 }
2211 }
2210
2212
2211 #[derive(Debug, Clone)]
2213 #[derive(Debug, Clone)]
2212 pub struct Tree {
2214 pub struct Tree {
2213 files: BTreeSet<HgPathBuf>,
2215 files: BTreeSet<HgPathBuf>,
2214 dirs: BTreeMap<HgPathBuf, Tree>,
2216 dirs: BTreeMap<HgPathBuf, Tree>,
2215 }
2217 }
2216
2218
2217 impl Tree {
2219 impl Tree {
2218 fn len(&self) -> usize {
2220 fn len(&self) -> usize {
2219 let mut n = 0;
2221 let mut n = 0;
2220 n += self.files.len();
2222 n += self.files.len();
2221 for d in self.dirs.values() {
2223 for d in self.dirs.values() {
2222 n += d.len();
2224 n += d.len();
2223 }
2225 }
2224 n
2226 n
2225 }
2227 }
2226
2228
2227 fn is_empty(&self) -> bool {
2229 fn is_empty(&self) -> bool {
2228 self.files.is_empty() && self.dirs.is_empty()
2230 self.files.is_empty() && self.dirs.is_empty()
2229 }
2231 }
2230
2232
2231 fn make(
2233 fn make(
2232 files: BTreeSet<HgPathBuf>,
2234 files: BTreeSet<HgPathBuf>,
2233 dirs: BTreeMap<HgPathBuf, Tree>,
2235 dirs: BTreeMap<HgPathBuf, Tree>,
2234 ) -> Self {
2236 ) -> Self {
2235 Self {
2237 Self {
2236 files,
2238 files,
2237 dirs: dirs
2239 dirs: dirs
2238 .into_iter()
2240 .into_iter()
2239 .filter(|(_k, v)| (!(v.is_empty())))
2241 .filter(|(_k, v)| (!(v.is_empty())))
2240 .collect(),
2242 .collect(),
2241 }
2243 }
2242 }
2244 }
2243
2245
2244 fn filter_and_check<M: Matcher + Debug>(
2246 fn filter_and_check<M: Matcher + Debug>(
2245 &self,
2247 &self,
2246 m: &M,
2248 m: &M,
2247 path: &HgPath,
2249 path: &HgPath,
2248 ) -> (Self, Self) {
2250 ) -> (Self, Self) {
2249 let (files1, files2): (BTreeSet<HgPathBuf>, BTreeSet<HgPathBuf>) =
2251 let (files1, files2): (BTreeSet<HgPathBuf>, BTreeSet<HgPathBuf>) =
2250 self.files
2252 self.files
2251 .iter()
2253 .iter()
2252 .map(|v| v.to_owned())
2254 .map(|v| v.to_owned())
2253 .partition(|v| m.matches(&path.join(v)));
2255 .partition(|v| m.matches(&path.join(v)));
2254 let (dirs1, dirs2): (
2256 let (dirs1, dirs2): (
2255 BTreeMap<HgPathBuf, Tree>,
2257 BTreeMap<HgPathBuf, Tree>,
2256 BTreeMap<HgPathBuf, Tree>,
2258 BTreeMap<HgPathBuf, Tree>,
2257 ) = self
2259 ) = self
2258 .dirs
2260 .dirs
2259 .iter()
2261 .iter()
2260 .map(|(k, v)| {
2262 .map(|(k, v)| {
2261 let path = path.join(k);
2263 let path = path.join(k);
2262 let (t1, t2) = v.filter_and_check(m, &path);
2264 let (t1, t2) = v.filter_and_check(m, &path);
2263 ((k.clone(), t1), (k.clone(), t2))
2265 ((k.clone(), t1), (k.clone(), t2))
2264 })
2266 })
2265 .unzip();
2267 .unzip();
2266 let matching = Self::make(files1, dirs1);
2268 let matching = Self::make(files1, dirs1);
2267 let not_matching = Self::make(files2, dirs2);
2269 let not_matching = Self::make(files2, dirs2);
2268 let vcs = m.visit_children_set(path);
2270 let vcs = m.visit_children_set(path);
2269 invariants::visit_children_set::check(
2271 invariants::visit_children_set::check(
2270 m,
2272 m,
2271 path,
2273 path,
2272 &matching,
2274 &matching,
2273 &not_matching,
2275 &not_matching,
2274 &vcs,
2276 &vcs,
2275 );
2277 );
2276 (matching, not_matching)
2278 (matching, not_matching)
2277 }
2279 }
2278
2280
2279 fn check_matcher<M: Matcher + Debug>(
2281 fn check_matcher<M: Matcher + Debug>(
2280 &self,
2282 &self,
2281 m: &M,
2283 m: &M,
2282 expect_count: usize,
2284 expect_count: usize,
2283 ) {
2285 ) {
2284 let res = self.filter_and_check(m, &HgPathBuf::new());
2286 let res = self.filter_and_check(m, &HgPathBuf::new());
2285 if expect_count != res.0.len() {
2287 if expect_count != res.0.len() {
2286 eprintln!(
2288 eprintln!(
2287 "warning: expected {} matches, got {} for {:#?}",
2289 "warning: expected {} matches, got {} for {:#?}",
2288 expect_count,
2290 expect_count,
2289 res.0.len(),
2291 res.0.len(),
2290 m
2292 m
2291 );
2293 );
2292 }
2294 }
2293 }
2295 }
2294 }
2296 }
2295
2297
2296 fn mkdir(children: &[(&[u8], &Tree)]) -> Tree {
2298 fn mkdir(children: &[(&[u8], &Tree)]) -> Tree {
2297 let p = HgPathBuf::from_bytes;
2299 let p = HgPathBuf::from_bytes;
2298 let names = [
2300 let names = [
2299 p(b"a"),
2301 p(b"a"),
2300 p(b"b.txt"),
2302 p(b"b.txt"),
2301 p(b"file.txt"),
2303 p(b"file.txt"),
2302 p(b"c.c"),
2304 p(b"c.c"),
2303 p(b"c.h"),
2305 p(b"c.h"),
2304 p(b"dir1"),
2306 p(b"dir1"),
2305 p(b"dir2"),
2307 p(b"dir2"),
2306 p(b"subdir"),
2308 p(b"subdir"),
2307 ];
2309 ];
2308 let files: BTreeSet<HgPathBuf> = BTreeSet::from(names);
2310 let files: BTreeSet<HgPathBuf> = BTreeSet::from(names);
2309 let dirs = children
2311 let dirs = children
2310 .iter()
2312 .iter()
2311 .map(|(name, t)| (p(name), (*t).clone()))
2313 .map(|(name, t)| (p(name), (*t).clone()))
2312 .collect();
2314 .collect();
2313 Tree { files, dirs }
2315 Tree { files, dirs }
2314 }
2316 }
2315
2317
2316 fn make_example_tree() -> Tree {
2318 fn make_example_tree() -> Tree {
2317 let leaf = mkdir(&[]);
2319 let leaf = mkdir(&[]);
2318 let abc = mkdir(&[(b"d", &leaf)]);
2320 let abc = mkdir(&[(b"d", &leaf)]);
2319 let ab = mkdir(&[(b"c", &abc)]);
2321 let ab = mkdir(&[(b"c", &abc)]);
2320 let a = mkdir(&[(b"b", &ab)]);
2322 let a = mkdir(&[(b"b", &ab)]);
2321 let dir = mkdir(&[(b"subdir", &leaf), (b"subdir.c", &leaf)]);
2323 let dir = mkdir(&[(b"subdir", &leaf), (b"subdir.c", &leaf)]);
2322 mkdir(&[(b"dir", &dir), (b"dir1", &dir), (b"dir2", &dir), (b"a", &a)])
2324 mkdir(&[(b"dir", &dir), (b"dir1", &dir), (b"dir2", &dir), (b"a", &a)])
2323 }
2325 }
2324
2326
2325 #[test]
2327 #[test]
2326 fn test_pattern_matcher_visit_children_set() {
2328 fn test_pattern_matcher_visit_children_set() {
2327 let tree = make_example_tree();
2329 let tree = make_example_tree();
2328 let pattern_dir1_glob_c =
2330 let pattern_dir1_glob_c =
2329 PatternMatcher::new(vec![IgnorePattern::new(
2331 PatternMatcher::new(vec![IgnorePattern::new(
2330 PatternSyntax::Glob,
2332 PatternSyntax::Glob,
2331 b"dir1/*.c",
2333 b"dir1/*.c",
2332 Path::new(""),
2334 Path::new(""),
2333 )])
2335 )])
2334 .unwrap();
2336 .unwrap();
2335 let pattern_dir1 = || {
2337 let pattern_dir1 = || {
2336 PatternMatcher::new(vec![IgnorePattern::new(
2338 PatternMatcher::new(vec![IgnorePattern::new(
2337 PatternSyntax::Path,
2339 PatternSyntax::Path,
2338 b"dir1",
2340 b"dir1",
2339 Path::new(""),
2341 Path::new(""),
2340 )])
2342 )])
2341 .unwrap()
2343 .unwrap()
2342 };
2344 };
2343 let pattern_dir1_a = PatternMatcher::new(vec![IgnorePattern::new(
2345 let pattern_dir1_a = PatternMatcher::new(vec![IgnorePattern::new(
2344 PatternSyntax::Glob,
2346 PatternSyntax::Glob,
2345 b"dir1/a",
2347 b"dir1/a",
2346 Path::new(""),
2348 Path::new(""),
2347 )])
2349 )])
2348 .unwrap();
2350 .unwrap();
2349 let pattern_relglob_c = || {
2351 let pattern_relglob_c = || {
2350 PatternMatcher::new(vec![IgnorePattern::new(
2352 PatternMatcher::new(vec![IgnorePattern::new(
2351 PatternSyntax::RelGlob,
2353 PatternSyntax::RelGlob,
2352 b"*.c",
2354 b"*.c",
2353 Path::new(""),
2355 Path::new(""),
2354 )])
2356 )])
2355 .unwrap()
2357 .unwrap()
2356 };
2358 };
2357 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/b.txt")];
2359 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/b.txt")];
2358 let file_dir_subdir_b = FileMatcher::new(files).unwrap();
2360 let file_dir_subdir_b = FileMatcher::new(files).unwrap();
2359
2361
2360 let files = vec![
2362 let files = vec![
2361 HgPathBuf::from_bytes(b"file.txt"),
2363 HgPathBuf::from_bytes(b"file.txt"),
2362 HgPathBuf::from_bytes(b"a/file.txt"),
2364 HgPathBuf::from_bytes(b"a/file.txt"),
2363 HgPathBuf::from_bytes(b"a/b/file.txt"),
2365 HgPathBuf::from_bytes(b"a/b/file.txt"),
2364 // No file in a/b/c
2366 // No file in a/b/c
2365 HgPathBuf::from_bytes(b"a/b/c/d/file.txt"),
2367 HgPathBuf::from_bytes(b"a/b/c/d/file.txt"),
2366 ];
2368 ];
2367 let file_abcdfile = FileMatcher::new(files).unwrap();
2369 let file_abcdfile = FileMatcher::new(files).unwrap();
2368 let rootfilesin_dir = PatternMatcher::new(vec![IgnorePattern::new(
2370 let rootfilesin_dir = PatternMatcher::new(vec![IgnorePattern::new(
2369 PatternSyntax::RootFilesIn,
2371 PatternSyntax::RootFilesIn,
2370 b"dir",
2372 b"dir",
2371 Path::new(""),
2373 Path::new(""),
2372 )])
2374 )])
2373 .unwrap();
2375 .unwrap();
2374
2376
2375 let pattern_filepath_dir_subdir =
2377 let pattern_filepath_dir_subdir =
2376 PatternMatcher::new(vec![IgnorePattern::new(
2378 PatternMatcher::new(vec![IgnorePattern::new(
2377 PatternSyntax::FilePath,
2379 PatternSyntax::FilePath,
2378 b"dir/subdir",
2380 b"dir/subdir",
2379 Path::new(""),
2381 Path::new(""),
2380 )])
2382 )])
2381 .unwrap();
2383 .unwrap();
2382
2384
2383 let include_dir_subdir =
2385 let include_dir_subdir =
2384 IncludeMatcher::new(vec![IgnorePattern::new(
2386 IncludeMatcher::new(vec![IgnorePattern::new(
2385 PatternSyntax::RelPath,
2387 PatternSyntax::RelPath,
2386 b"dir/subdir",
2388 b"dir/subdir",
2387 Path::new(""),
2389 Path::new(""),
2388 )])
2390 )])
2389 .unwrap();
2391 .unwrap();
2390
2392
2391 let more_includematchers = [
2393 let more_includematchers = [
2392 IncludeMatcher::new(vec![IgnorePattern::new(
2394 IncludeMatcher::new(vec![IgnorePattern::new(
2393 PatternSyntax::Glob,
2395 PatternSyntax::Glob,
2394 b"dir/s*",
2396 b"dir/s*",
2395 Path::new(""),
2397 Path::new(""),
2396 )])
2398 )])
2397 .unwrap(),
2399 .unwrap(),
2398 // Test multiple patterns
2400 // Test multiple patterns
2399 IncludeMatcher::new(vec![
2401 IncludeMatcher::new(vec![
2400 IgnorePattern::new(
2402 IgnorePattern::new(
2401 PatternSyntax::RelPath,
2403 PatternSyntax::RelPath,
2402 b"dir",
2404 b"dir",
2403 Path::new(""),
2405 Path::new(""),
2404 ),
2406 ),
2405 IgnorePattern::new(PatternSyntax::Glob, b"s*", Path::new("")),
2407 IgnorePattern::new(PatternSyntax::Glob, b"s*", Path::new("")),
2406 ])
2408 ])
2407 .unwrap(),
2409 .unwrap(),
2408 // Test multiple patterns
2410 // Test multiple patterns
2409 IncludeMatcher::new(vec![IgnorePattern::new(
2411 IncludeMatcher::new(vec![IgnorePattern::new(
2410 PatternSyntax::Glob,
2412 PatternSyntax::Glob,
2411 b"**/*.c",
2413 b"**/*.c",
2412 Path::new(""),
2414 Path::new(""),
2413 )])
2415 )])
2414 .unwrap(),
2416 .unwrap(),
2415 ];
2417 ];
2416
2418
2417 tree.check_matcher(&pattern_dir1(), 25);
2419 tree.check_matcher(&pattern_dir1(), 25);
2418 tree.check_matcher(&pattern_dir1_a, 1);
2420 tree.check_matcher(&pattern_dir1_a, 1);
2419 tree.check_matcher(&pattern_dir1_glob_c, 2);
2421 tree.check_matcher(&pattern_dir1_glob_c, 2);
2420 tree.check_matcher(&pattern_relglob_c(), 14);
2422 tree.check_matcher(&pattern_relglob_c(), 14);
2421 tree.check_matcher(&AlwaysMatcher, 112);
2423 tree.check_matcher(&AlwaysMatcher, 112);
2422 tree.check_matcher(&NeverMatcher, 0);
2424 tree.check_matcher(&NeverMatcher, 0);
2423 tree.check_matcher(
2425 tree.check_matcher(
2424 &IntersectionMatcher::new(
2426 &IntersectionMatcher::new(
2425 Box::new(pattern_relglob_c()),
2427 Box::new(pattern_relglob_c()),
2426 Box::new(pattern_dir1()),
2428 Box::new(pattern_dir1()),
2427 ),
2429 ),
2428 3,
2430 3,
2429 );
2431 );
2430 tree.check_matcher(
2432 tree.check_matcher(
2431 &UnionMatcher::new(vec![
2433 &UnionMatcher::new(vec![
2432 Box::new(pattern_relglob_c()),
2434 Box::new(pattern_relglob_c()),
2433 Box::new(pattern_dir1()),
2435 Box::new(pattern_dir1()),
2434 ]),
2436 ]),
2435 36,
2437 36,
2436 );
2438 );
2437 tree.check_matcher(
2439 tree.check_matcher(
2438 &DifferenceMatcher::new(
2440 &DifferenceMatcher::new(
2439 Box::new(pattern_relglob_c()),
2441 Box::new(pattern_relglob_c()),
2440 Box::new(pattern_dir1()),
2442 Box::new(pattern_dir1()),
2441 ),
2443 ),
2442 11,
2444 11,
2443 );
2445 );
2444 tree.check_matcher(&file_dir_subdir_b, 1);
2446 tree.check_matcher(&file_dir_subdir_b, 1);
2445 tree.check_matcher(&file_abcdfile, 4);
2447 tree.check_matcher(&file_abcdfile, 4);
2446 tree.check_matcher(&rootfilesin_dir, 8);
2448 tree.check_matcher(&rootfilesin_dir, 8);
2447 tree.check_matcher(&pattern_filepath_dir_subdir, 1);
2449 tree.check_matcher(&pattern_filepath_dir_subdir, 1);
2448 tree.check_matcher(&include_dir_subdir, 9);
2450 tree.check_matcher(&include_dir_subdir, 9);
2449 tree.check_matcher(&more_includematchers[0], 17);
2451 tree.check_matcher(&more_includematchers[0], 17);
2450 tree.check_matcher(&more_includematchers[1], 25);
2452 tree.check_matcher(&more_includematchers[1], 25);
2451 tree.check_matcher(&more_includematchers[2], 35);
2453 tree.check_matcher(&more_includematchers[2], 35);
2452 }
2454 }
2453 }
2455 }
General Comments 0
You need to be logged in to leave comments. Login now