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