##// END OF EJS Templates
rust-filepatterns: also normalize RelPath...
Spencer Baugh -
r51751:df6dfad5 default
parent child Browse files
Show More
@@ -1,807 +1,808 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::ops::Deref;
20 use std::ops::Deref;
21 use std::path::{Path, PathBuf};
21 use std::path::{Path, PathBuf};
22 use std::vec::Vec;
22 use std::vec::Vec;
23
23
24 lazy_static! {
24 lazy_static! {
25 static ref RE_ESCAPE: Vec<Vec<u8>> = {
25 static ref RE_ESCAPE: Vec<Vec<u8>> = {
26 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
26 let mut v: Vec<Vec<u8>> = (0..=255).map(|byte| vec![byte]).collect();
27 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
27 let to_escape = b"()[]{}?*+-|^$\\.&~# \t\n\r\x0b\x0c";
28 for byte in to_escape {
28 for byte in to_escape {
29 v[*byte as usize].insert(0, b'\\');
29 v[*byte as usize].insert(0, b'\\');
30 }
30 }
31 v
31 v
32 };
32 };
33 }
33 }
34
34
35 /// These are matched in order
35 /// These are matched in order
36 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
36 const GLOB_REPLACEMENTS: &[(&[u8], &[u8])] =
37 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
37 &[(b"*/", b"(?:.*/)?"), (b"*", b".*"), (b"", b"[^/]*")];
38
38
39 /// Appended to the regexp of globs
39 /// Appended to the regexp of globs
40 const GLOB_SUFFIX: &[u8; 7] = b"(?:/|$)";
40 const GLOB_SUFFIX: &[u8; 7] = b"(?:/|$)";
41
41
42 #[derive(Debug, Clone, PartialEq, Eq)]
42 #[derive(Debug, Clone, PartialEq, Eq)]
43 pub enum PatternSyntax {
43 pub enum PatternSyntax {
44 /// A regular expression
44 /// A regular expression
45 Regexp,
45 Regexp,
46 /// Glob that matches at the front of the path
46 /// Glob that matches at the front of the path
47 RootGlob,
47 RootGlob,
48 /// Glob that matches at any suffix of the path (still anchored at
48 /// Glob that matches at any suffix of the path (still anchored at
49 /// slashes)
49 /// slashes)
50 Glob,
50 Glob,
51 /// a path relative to repository root, which is matched recursively
51 /// a path relative to repository root, which is matched recursively
52 Path,
52 Path,
53 /// a single exact path relative to repository root
53 /// a single exact path relative to repository root
54 FilePath,
54 FilePath,
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 /// SubInclude with the result of parsing the included file
68 /// SubInclude with the result of parsing the included file
69 ///
69 ///
70 /// Note: there is no ExpandedInclude because that expansion can be done
70 /// Note: there is no ExpandedInclude because that expansion can be done
71 /// in place by replacing the Include pattern by the included patterns.
71 /// in place by replacing the Include pattern by the included patterns.
72 /// SubInclude requires more handling.
72 /// SubInclude requires more handling.
73 ///
73 ///
74 /// Note: `Box` is used to minimize size impact on other enum variants
74 /// Note: `Box` is used to minimize size impact on other enum variants
75 ExpandedSubInclude(Box<SubInclude>),
75 ExpandedSubInclude(Box<SubInclude>),
76 }
76 }
77
77
78 /// Transforms a glob pattern into a regex
78 /// Transforms a glob pattern into a regex
79 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
79 fn glob_to_re(pat: &[u8]) -> Vec<u8> {
80 let mut input = pat;
80 let mut input = pat;
81 let mut res: Vec<u8> = vec![];
81 let mut res: Vec<u8> = vec![];
82 let mut group_depth = 0;
82 let mut group_depth = 0;
83
83
84 while let Some((c, rest)) = input.split_first() {
84 while let Some((c, rest)) = input.split_first() {
85 input = rest;
85 input = rest;
86
86
87 match c {
87 match c {
88 b'*' => {
88 b'*' => {
89 for (source, repl) in GLOB_REPLACEMENTS {
89 for (source, repl) in GLOB_REPLACEMENTS {
90 if let Some(rest) = input.drop_prefix(source) {
90 if let Some(rest) = input.drop_prefix(source) {
91 input = rest;
91 input = rest;
92 res.extend(*repl);
92 res.extend(*repl);
93 break;
93 break;
94 }
94 }
95 }
95 }
96 }
96 }
97 b'?' => res.extend(b"."),
97 b'?' => res.extend(b"."),
98 b'[' => {
98 b'[' => {
99 match input.iter().skip(1).position(|b| *b == b']') {
99 match input.iter().skip(1).position(|b| *b == b']') {
100 None => res.extend(b"\\["),
100 None => res.extend(b"\\["),
101 Some(end) => {
101 Some(end) => {
102 // Account for the one we skipped
102 // Account for the one we skipped
103 let end = end + 1;
103 let end = end + 1;
104
104
105 res.extend(b"[");
105 res.extend(b"[");
106
106
107 for (i, b) in input[..end].iter().enumerate() {
107 for (i, b) in input[..end].iter().enumerate() {
108 if *b == b'!' && i == 0 {
108 if *b == b'!' && i == 0 {
109 res.extend(b"^")
109 res.extend(b"^")
110 } else if *b == b'^' && i == 0 {
110 } else if *b == b'^' && i == 0 {
111 res.extend(b"\\^")
111 res.extend(b"\\^")
112 } else if *b == b'\\' {
112 } else if *b == b'\\' {
113 res.extend(b"\\\\")
113 res.extend(b"\\\\")
114 } else {
114 } else {
115 res.push(*b)
115 res.push(*b)
116 }
116 }
117 }
117 }
118 res.extend(b"]");
118 res.extend(b"]");
119 input = &input[end + 1..];
119 input = &input[end + 1..];
120 }
120 }
121 }
121 }
122 }
122 }
123 b'{' => {
123 b'{' => {
124 group_depth += 1;
124 group_depth += 1;
125 res.extend(b"(?:")
125 res.extend(b"(?:")
126 }
126 }
127 b'}' if group_depth > 0 => {
127 b'}' if group_depth > 0 => {
128 group_depth -= 1;
128 group_depth -= 1;
129 res.extend(b")");
129 res.extend(b")");
130 }
130 }
131 b',' if group_depth > 0 => res.extend(b"|"),
131 b',' if group_depth > 0 => res.extend(b"|"),
132 b'\\' => {
132 b'\\' => {
133 let c = {
133 let c = {
134 if let Some((c, rest)) = input.split_first() {
134 if let Some((c, rest)) = input.split_first() {
135 input = rest;
135 input = rest;
136 c
136 c
137 } else {
137 } else {
138 c
138 c
139 }
139 }
140 };
140 };
141 res.extend(&RE_ESCAPE[*c as usize])
141 res.extend(&RE_ESCAPE[*c as usize])
142 }
142 }
143 _ => res.extend(&RE_ESCAPE[*c as usize]),
143 _ => res.extend(&RE_ESCAPE[*c as usize]),
144 }
144 }
145 }
145 }
146 res
146 res
147 }
147 }
148
148
149 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
149 fn escape_pattern(pattern: &[u8]) -> Vec<u8> {
150 pattern
150 pattern
151 .iter()
151 .iter()
152 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
152 .flat_map(|c| RE_ESCAPE[*c as usize].clone())
153 .collect()
153 .collect()
154 }
154 }
155
155
156 pub fn parse_pattern_syntax(
156 pub fn parse_pattern_syntax(
157 kind: &[u8],
157 kind: &[u8],
158 ) -> Result<PatternSyntax, PatternError> {
158 ) -> Result<PatternSyntax, PatternError> {
159 match kind {
159 match kind {
160 b"re:" => Ok(PatternSyntax::Regexp),
160 b"re:" => Ok(PatternSyntax::Regexp),
161 b"path:" => Ok(PatternSyntax::Path),
161 b"path:" => Ok(PatternSyntax::Path),
162 b"filepath:" => Ok(PatternSyntax::FilePath),
162 b"filepath:" => Ok(PatternSyntax::FilePath),
163 b"relpath:" => Ok(PatternSyntax::RelPath),
163 b"relpath:" => Ok(PatternSyntax::RelPath),
164 b"rootfilesin:" => Ok(PatternSyntax::RootFiles),
164 b"rootfilesin:" => Ok(PatternSyntax::RootFiles),
165 b"relglob:" => Ok(PatternSyntax::RelGlob),
165 b"relglob:" => Ok(PatternSyntax::RelGlob),
166 b"relre:" => Ok(PatternSyntax::RelRegexp),
166 b"relre:" => Ok(PatternSyntax::RelRegexp),
167 b"glob:" => Ok(PatternSyntax::Glob),
167 b"glob:" => Ok(PatternSyntax::Glob),
168 b"rootglob:" => Ok(PatternSyntax::RootGlob),
168 b"rootglob:" => Ok(PatternSyntax::RootGlob),
169 b"include:" => Ok(PatternSyntax::Include),
169 b"include:" => Ok(PatternSyntax::Include),
170 b"subinclude:" => Ok(PatternSyntax::SubInclude),
170 b"subinclude:" => Ok(PatternSyntax::SubInclude),
171 _ => Err(PatternError::UnsupportedSyntax(
171 _ => Err(PatternError::UnsupportedSyntax(
172 String::from_utf8_lossy(kind).to_string(),
172 String::from_utf8_lossy(kind).to_string(),
173 )),
173 )),
174 }
174 }
175 }
175 }
176
176
177 lazy_static! {
177 lazy_static! {
178 static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap();
178 static ref FLAG_RE: Regex = Regex::new(r"^\(\?[aiLmsux]+\)").unwrap();
179 }
179 }
180
180
181 /// Builds the regex that corresponds to the given pattern.
181 /// Builds the regex that corresponds to the given pattern.
182 /// If within a `syntax: regexp` context, returns the pattern,
182 /// If within a `syntax: regexp` context, returns the pattern,
183 /// otherwise, returns the corresponding regex.
183 /// otherwise, returns the corresponding regex.
184 fn _build_single_regex(entry: &IgnorePattern) -> Vec<u8> {
184 fn _build_single_regex(entry: &IgnorePattern) -> Vec<u8> {
185 let IgnorePattern {
185 let IgnorePattern {
186 syntax, pattern, ..
186 syntax, pattern, ..
187 } = entry;
187 } = entry;
188 if pattern.is_empty() {
188 if pattern.is_empty() {
189 return vec![];
189 return vec![];
190 }
190 }
191 match syntax {
191 match syntax {
192 PatternSyntax::Regexp => pattern.to_owned(),
192 PatternSyntax::Regexp => pattern.to_owned(),
193 PatternSyntax::RelRegexp => {
193 PatternSyntax::RelRegexp => {
194 // The `regex` crate accepts `**` while `re2` and Python's `re`
194 // The `regex` crate accepts `**` while `re2` and Python's `re`
195 // do not. Checking for `*` correctly triggers the same error all
195 // do not. Checking for `*` correctly triggers the same error all
196 // engines.
196 // engines.
197 if pattern[0] == b'^'
197 if pattern[0] == b'^'
198 || pattern[0] == b'*'
198 || pattern[0] == b'*'
199 || pattern.starts_with(b".*")
199 || pattern.starts_with(b".*")
200 {
200 {
201 return pattern.to_owned();
201 return pattern.to_owned();
202 }
202 }
203 match FLAG_RE.find(pattern) {
203 match FLAG_RE.find(pattern) {
204 Some(mat) => {
204 Some(mat) => {
205 let s = mat.start();
205 let s = mat.start();
206 let e = mat.end();
206 let e = mat.end();
207 [
207 [
208 &b"(?"[..],
208 &b"(?"[..],
209 &pattern[s + 2..e - 1],
209 &pattern[s + 2..e - 1],
210 &b":"[..],
210 &b":"[..],
211 if pattern[e] == b'^'
211 if pattern[e] == b'^'
212 || pattern[e] == b'*'
212 || pattern[e] == b'*'
213 || pattern[e..].starts_with(b".*")
213 || pattern[e..].starts_with(b".*")
214 {
214 {
215 &b""[..]
215 &b""[..]
216 } else {
216 } else {
217 &b".*"[..]
217 &b".*"[..]
218 },
218 },
219 &pattern[e..],
219 &pattern[e..],
220 &b")"[..],
220 &b")"[..],
221 ]
221 ]
222 .concat()
222 .concat()
223 }
223 }
224 None => [&b".*"[..], pattern].concat(),
224 None => [&b".*"[..], pattern].concat(),
225 }
225 }
226 }
226 }
227 PatternSyntax::Path | PatternSyntax::RelPath => {
227 PatternSyntax::Path | PatternSyntax::RelPath => {
228 if pattern == b"." {
228 if pattern == b"." {
229 return vec![];
229 return vec![];
230 }
230 }
231 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
231 [escape_pattern(pattern).as_slice(), b"(?:/|$)"].concat()
232 }
232 }
233 PatternSyntax::RootFiles => {
233 PatternSyntax::RootFiles => {
234 let mut res = if pattern == b"." {
234 let mut res = if pattern == b"." {
235 vec![]
235 vec![]
236 } else {
236 } else {
237 // Pattern is a directory name.
237 // Pattern is a directory name.
238 [escape_pattern(pattern).as_slice(), b"/"].concat()
238 [escape_pattern(pattern).as_slice(), b"/"].concat()
239 };
239 };
240
240
241 // Anything after the pattern must be a non-directory.
241 // Anything after the pattern must be a non-directory.
242 res.extend(b"[^/]+$");
242 res.extend(b"[^/]+$");
243 res
243 res
244 }
244 }
245 PatternSyntax::RelGlob => {
245 PatternSyntax::RelGlob => {
246 let glob_re = glob_to_re(pattern);
246 let glob_re = glob_to_re(pattern);
247 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
247 if let Some(rest) = glob_re.drop_prefix(b"[^/]*") {
248 [b".*", rest, GLOB_SUFFIX].concat()
248 [b".*", rest, GLOB_SUFFIX].concat()
249 } else {
249 } else {
250 [b"(?:.*/)?", glob_re.as_slice(), GLOB_SUFFIX].concat()
250 [b"(?:.*/)?", glob_re.as_slice(), GLOB_SUFFIX].concat()
251 }
251 }
252 }
252 }
253 PatternSyntax::Glob | PatternSyntax::RootGlob => {
253 PatternSyntax::Glob | PatternSyntax::RootGlob => {
254 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
254 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat()
255 }
255 }
256 PatternSyntax::Include
256 PatternSyntax::Include
257 | PatternSyntax::SubInclude
257 | PatternSyntax::SubInclude
258 | PatternSyntax::ExpandedSubInclude(_)
258 | PatternSyntax::ExpandedSubInclude(_)
259 | PatternSyntax::FilePath => unreachable!(),
259 | PatternSyntax::FilePath => unreachable!(),
260 }
260 }
261 }
261 }
262
262
263 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
263 const GLOB_SPECIAL_CHARACTERS: [u8; 7] =
264 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
264 [b'*', b'?', b'[', b']', b'{', b'}', b'\\'];
265
265
266 /// TODO support other platforms
266 /// TODO support other platforms
267 #[cfg(unix)]
267 #[cfg(unix)]
268 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
268 pub fn normalize_path_bytes(bytes: &[u8]) -> Vec<u8> {
269 if bytes.is_empty() {
269 if bytes.is_empty() {
270 return b".".to_vec();
270 return b".".to_vec();
271 }
271 }
272 let sep = b'/';
272 let sep = b'/';
273
273
274 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
274 let mut initial_slashes = bytes.iter().take_while(|b| **b == sep).count();
275 if initial_slashes > 2 {
275 if initial_slashes > 2 {
276 // POSIX allows one or two initial slashes, but treats three or more
276 // POSIX allows one or two initial slashes, but treats three or more
277 // as single slash.
277 // as single slash.
278 initial_slashes = 1;
278 initial_slashes = 1;
279 }
279 }
280 let components = bytes
280 let components = bytes
281 .split(|b| *b == sep)
281 .split(|b| *b == sep)
282 .filter(|c| !(c.is_empty() || c == b"."))
282 .filter(|c| !(c.is_empty() || c == b"."))
283 .fold(vec![], |mut acc, component| {
283 .fold(vec![], |mut acc, component| {
284 if component != b".."
284 if component != b".."
285 || (initial_slashes == 0 && acc.is_empty())
285 || (initial_slashes == 0 && acc.is_empty())
286 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
286 || (!acc.is_empty() && acc[acc.len() - 1] == b"..")
287 {
287 {
288 acc.push(component)
288 acc.push(component)
289 } else if !acc.is_empty() {
289 } else if !acc.is_empty() {
290 acc.pop();
290 acc.pop();
291 }
291 }
292 acc
292 acc
293 });
293 });
294 let mut new_bytes = components.join(&sep);
294 let mut new_bytes = components.join(&sep);
295
295
296 if initial_slashes > 0 {
296 if initial_slashes > 0 {
297 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
297 let mut buf: Vec<_> = (0..initial_slashes).map(|_| sep).collect();
298 buf.extend(new_bytes);
298 buf.extend(new_bytes);
299 new_bytes = buf;
299 new_bytes = buf;
300 }
300 }
301 if new_bytes.is_empty() {
301 if new_bytes.is_empty() {
302 b".".to_vec()
302 b".".to_vec()
303 } else {
303 } else {
304 new_bytes
304 new_bytes
305 }
305 }
306 }
306 }
307
307
308 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
308 /// Wrapper function to `_build_single_regex` that short-circuits 'exact' globs
309 /// that don't need to be transformed into a regex.
309 /// that don't need to be transformed into a regex.
310 pub fn build_single_regex(
310 pub fn build_single_regex(
311 entry: &IgnorePattern,
311 entry: &IgnorePattern,
312 ) -> Result<Option<Vec<u8>>, PatternError> {
312 ) -> Result<Option<Vec<u8>>, PatternError> {
313 let IgnorePattern {
313 let IgnorePattern {
314 pattern, syntax, ..
314 pattern, syntax, ..
315 } = entry;
315 } = entry;
316 let pattern = match syntax {
316 let pattern = match syntax {
317 PatternSyntax::RootGlob
317 PatternSyntax::RootGlob
318 | PatternSyntax::Path
318 | PatternSyntax::Path
319 | PatternSyntax::RelGlob
319 | PatternSyntax::RelGlob
320 | PatternSyntax::RelPath
320 | PatternSyntax::RootFiles => normalize_path_bytes(pattern),
321 | PatternSyntax::RootFiles => normalize_path_bytes(pattern),
321 PatternSyntax::Include | PatternSyntax::SubInclude => {
322 PatternSyntax::Include | PatternSyntax::SubInclude => {
322 return Err(PatternError::NonRegexPattern(entry.clone()))
323 return Err(PatternError::NonRegexPattern(entry.clone()))
323 }
324 }
324 _ => pattern.to_owned(),
325 _ => pattern.to_owned(),
325 };
326 };
326 let is_simple_rootglob = *syntax == PatternSyntax::RootGlob
327 let is_simple_rootglob = *syntax == PatternSyntax::RootGlob
327 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b));
328 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b));
328 if is_simple_rootglob || syntax == &PatternSyntax::FilePath {
329 if is_simple_rootglob || syntax == &PatternSyntax::FilePath {
329 Ok(None)
330 Ok(None)
330 } else {
331 } else {
331 let mut entry = entry.clone();
332 let mut entry = entry.clone();
332 entry.pattern = pattern;
333 entry.pattern = pattern;
333 Ok(Some(_build_single_regex(&entry)))
334 Ok(Some(_build_single_regex(&entry)))
334 }
335 }
335 }
336 }
336
337
337 lazy_static! {
338 lazy_static! {
338 static ref SYNTAXES: FastHashMap<&'static [u8], PatternSyntax> = {
339 static ref SYNTAXES: FastHashMap<&'static [u8], PatternSyntax> = {
339 let mut m = FastHashMap::default();
340 let mut m = FastHashMap::default();
340
341
341 m.insert(b"re:".as_ref(), PatternSyntax::Regexp);
342 m.insert(b"re:".as_ref(), PatternSyntax::Regexp);
342 m.insert(b"regexp:".as_ref(), PatternSyntax::Regexp);
343 m.insert(b"regexp:".as_ref(), PatternSyntax::Regexp);
343 m.insert(b"path:".as_ref(), PatternSyntax::Path);
344 m.insert(b"path:".as_ref(), PatternSyntax::Path);
344 m.insert(b"filepath:".as_ref(), PatternSyntax::FilePath);
345 m.insert(b"filepath:".as_ref(), PatternSyntax::FilePath);
345 m.insert(b"relpath:".as_ref(), PatternSyntax::RelPath);
346 m.insert(b"relpath:".as_ref(), PatternSyntax::RelPath);
346 m.insert(b"rootfilesin:".as_ref(), PatternSyntax::RootFiles);
347 m.insert(b"rootfilesin:".as_ref(), PatternSyntax::RootFiles);
347 m.insert(b"relglob:".as_ref(), PatternSyntax::RelGlob);
348 m.insert(b"relglob:".as_ref(), PatternSyntax::RelGlob);
348 m.insert(b"relre:".as_ref(), PatternSyntax::RelRegexp);
349 m.insert(b"relre:".as_ref(), PatternSyntax::RelRegexp);
349 m.insert(b"glob:".as_ref(), PatternSyntax::Glob);
350 m.insert(b"glob:".as_ref(), PatternSyntax::Glob);
350 m.insert(b"rootglob:".as_ref(), PatternSyntax::RootGlob);
351 m.insert(b"rootglob:".as_ref(), PatternSyntax::RootGlob);
351 m.insert(b"include:".as_ref(), PatternSyntax::Include);
352 m.insert(b"include:".as_ref(), PatternSyntax::Include);
352 m.insert(b"subinclude:".as_ref(), PatternSyntax::SubInclude);
353 m.insert(b"subinclude:".as_ref(), PatternSyntax::SubInclude);
353
354
354 m
355 m
355 };
356 };
356 }
357 }
357
358
358 #[derive(Debug)]
359 #[derive(Debug)]
359 pub enum PatternFileWarning {
360 pub enum PatternFileWarning {
360 /// (file path, syntax bytes)
361 /// (file path, syntax bytes)
361 InvalidSyntax(PathBuf, Vec<u8>),
362 InvalidSyntax(PathBuf, Vec<u8>),
362 /// File path
363 /// File path
363 NoSuchFile(PathBuf),
364 NoSuchFile(PathBuf),
364 }
365 }
365
366
366 pub fn parse_one_pattern(
367 pub fn parse_one_pattern(
367 pattern: &[u8],
368 pattern: &[u8],
368 source: &Path,
369 source: &Path,
369 default: PatternSyntax,
370 default: PatternSyntax,
370 ) -> IgnorePattern {
371 ) -> IgnorePattern {
371 let mut pattern_bytes: &[u8] = pattern;
372 let mut pattern_bytes: &[u8] = pattern;
372 let mut syntax = default;
373 let mut syntax = default;
373
374
374 for (s, val) in SYNTAXES.iter() {
375 for (s, val) in SYNTAXES.iter() {
375 if let Some(rest) = pattern_bytes.drop_prefix(s) {
376 if let Some(rest) = pattern_bytes.drop_prefix(s) {
376 syntax = val.clone();
377 syntax = val.clone();
377 pattern_bytes = rest;
378 pattern_bytes = rest;
378 break;
379 break;
379 }
380 }
380 }
381 }
381
382
382 let pattern = pattern_bytes.to_vec();
383 let pattern = pattern_bytes.to_vec();
383
384
384 IgnorePattern {
385 IgnorePattern {
385 syntax,
386 syntax,
386 pattern,
387 pattern,
387 source: source.to_owned(),
388 source: source.to_owned(),
388 }
389 }
389 }
390 }
390
391
391 pub fn parse_pattern_file_contents(
392 pub fn parse_pattern_file_contents(
392 lines: &[u8],
393 lines: &[u8],
393 file_path: &Path,
394 file_path: &Path,
394 default_syntax_override: Option<PatternSyntax>,
395 default_syntax_override: Option<PatternSyntax>,
395 warn: bool,
396 warn: bool,
396 relativize: bool,
397 relativize: bool,
397 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
398 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
398 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
399 let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
399
400
400 #[allow(clippy::trivial_regex)]
401 #[allow(clippy::trivial_regex)]
401 let comment_escape_regex = Regex::new(r"\\#").unwrap();
402 let comment_escape_regex = Regex::new(r"\\#").unwrap();
402 let mut inputs: Vec<IgnorePattern> = vec![];
403 let mut inputs: Vec<IgnorePattern> = vec![];
403 let mut warnings: Vec<PatternFileWarning> = vec![];
404 let mut warnings: Vec<PatternFileWarning> = vec![];
404
405
405 let mut current_syntax =
406 let mut current_syntax =
406 default_syntax_override.unwrap_or(PatternSyntax::RelRegexp);
407 default_syntax_override.unwrap_or(PatternSyntax::RelRegexp);
407
408
408 for mut line in lines.split(|c| *c == b'\n') {
409 for mut line in lines.split(|c| *c == b'\n') {
409 let line_buf;
410 let line_buf;
410 if line.contains(&b'#') {
411 if line.contains(&b'#') {
411 if let Some(cap) = comment_regex.captures(line) {
412 if let Some(cap) = comment_regex.captures(line) {
412 line = &line[..cap.get(1).unwrap().end()]
413 line = &line[..cap.get(1).unwrap().end()]
413 }
414 }
414 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
415 line_buf = comment_escape_regex.replace_all(line, NoExpand(b"#"));
415 line = &line_buf;
416 line = &line_buf;
416 }
417 }
417
418
418 let line = line.trim_end();
419 let line = line.trim_end();
419
420
420 if line.is_empty() {
421 if line.is_empty() {
421 continue;
422 continue;
422 }
423 }
423
424
424 if let Some(syntax) = line.drop_prefix(b"syntax:") {
425 if let Some(syntax) = line.drop_prefix(b"syntax:") {
425 let syntax = syntax.trim();
426 let syntax = syntax.trim();
426
427
427 if let Some(parsed) =
428 if let Some(parsed) =
428 SYNTAXES.get([syntax, &b":"[..]].concat().as_slice())
429 SYNTAXES.get([syntax, &b":"[..]].concat().as_slice())
429 {
430 {
430 current_syntax = parsed.clone();
431 current_syntax = parsed.clone();
431 } else if warn {
432 } else if warn {
432 warnings.push(PatternFileWarning::InvalidSyntax(
433 warnings.push(PatternFileWarning::InvalidSyntax(
433 file_path.to_owned(),
434 file_path.to_owned(),
434 syntax.to_owned(),
435 syntax.to_owned(),
435 ));
436 ));
436 }
437 }
437 } else {
438 } else {
438 let pattern = parse_one_pattern(
439 let pattern = parse_one_pattern(
439 line,
440 line,
440 file_path,
441 file_path,
441 current_syntax.clone(),
442 current_syntax.clone(),
442 );
443 );
443 inputs.push(if relativize {
444 inputs.push(if relativize {
444 pattern.to_relative()
445 pattern.to_relative()
445 } else {
446 } else {
446 pattern
447 pattern
447 })
448 })
448 }
449 }
449 }
450 }
450 Ok((inputs, warnings))
451 Ok((inputs, warnings))
451 }
452 }
452
453
453 pub fn read_pattern_file(
454 pub fn read_pattern_file(
454 file_path: &Path,
455 file_path: &Path,
455 warn: bool,
456 warn: bool,
456 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
457 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
457 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
458 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
458 match std::fs::read(file_path) {
459 match std::fs::read(file_path) {
459 Ok(contents) => {
460 Ok(contents) => {
460 inspect_pattern_bytes(file_path, &contents);
461 inspect_pattern_bytes(file_path, &contents);
461 parse_pattern_file_contents(&contents, file_path, None, warn, true)
462 parse_pattern_file_contents(&contents, file_path, None, warn, true)
462 }
463 }
463 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
464 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
464 vec![],
465 vec![],
465 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
466 vec![PatternFileWarning::NoSuchFile(file_path.to_owned())],
466 )),
467 )),
467 Err(e) => Err(e.into()),
468 Err(e) => Err(e.into()),
468 }
469 }
469 }
470 }
470
471
471 /// Represents an entry in an "ignore" file.
472 /// Represents an entry in an "ignore" file.
472 #[derive(Debug, Eq, PartialEq, Clone)]
473 #[derive(Debug, Eq, PartialEq, Clone)]
473 pub struct IgnorePattern {
474 pub struct IgnorePattern {
474 pub syntax: PatternSyntax,
475 pub syntax: PatternSyntax,
475 pub pattern: Vec<u8>,
476 pub pattern: Vec<u8>,
476 pub source: PathBuf,
477 pub source: PathBuf,
477 }
478 }
478
479
479 impl IgnorePattern {
480 impl IgnorePattern {
480 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
481 pub fn new(syntax: PatternSyntax, pattern: &[u8], source: &Path) -> Self {
481 Self {
482 Self {
482 syntax,
483 syntax,
483 pattern: pattern.to_owned(),
484 pattern: pattern.to_owned(),
484 source: source.to_owned(),
485 source: source.to_owned(),
485 }
486 }
486 }
487 }
487
488
488 pub fn to_relative(self) -> Self {
489 pub fn to_relative(self) -> Self {
489 let Self {
490 let Self {
490 syntax,
491 syntax,
491 pattern,
492 pattern,
492 source,
493 source,
493 } = self;
494 } = self;
494 Self {
495 Self {
495 syntax: match syntax {
496 syntax: match syntax {
496 PatternSyntax::Regexp => PatternSyntax::RelRegexp,
497 PatternSyntax::Regexp => PatternSyntax::RelRegexp,
497 PatternSyntax::Glob => PatternSyntax::RelGlob,
498 PatternSyntax::Glob => PatternSyntax::RelGlob,
498 x => x,
499 x => x,
499 },
500 },
500 pattern,
501 pattern,
501 source,
502 source,
502 }
503 }
503 }
504 }
504 }
505 }
505
506
506 pub type PatternResult<T> = Result<T, PatternError>;
507 pub type PatternResult<T> = Result<T, PatternError>;
507
508
508 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
509 /// Wrapper for `read_pattern_file` that also recursively expands `include:`
509 /// and `subinclude:` patterns.
510 /// and `subinclude:` patterns.
510 ///
511 ///
511 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
512 /// The former are expanded in place, while `PatternSyntax::ExpandedSubInclude`
512 /// is used for the latter to form a tree of patterns.
513 /// is used for the latter to form a tree of patterns.
513 pub fn get_patterns_from_file(
514 pub fn get_patterns_from_file(
514 pattern_file: &Path,
515 pattern_file: &Path,
515 root_dir: &Path,
516 root_dir: &Path,
516 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
517 inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
517 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
518 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
518 let (patterns, mut warnings) =
519 let (patterns, mut warnings) =
519 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
520 read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
520 let patterns = patterns
521 let patterns = patterns
521 .into_iter()
522 .into_iter()
522 .flat_map(|entry| -> PatternResult<_> {
523 .flat_map(|entry| -> PatternResult<_> {
523 Ok(match &entry.syntax {
524 Ok(match &entry.syntax {
524 PatternSyntax::Include => {
525 PatternSyntax::Include => {
525 let inner_include =
526 let inner_include =
526 root_dir.join(get_path_from_bytes(&entry.pattern));
527 root_dir.join(get_path_from_bytes(&entry.pattern));
527 let (inner_pats, inner_warnings) = get_patterns_from_file(
528 let (inner_pats, inner_warnings) = get_patterns_from_file(
528 &inner_include,
529 &inner_include,
529 root_dir,
530 root_dir,
530 inspect_pattern_bytes,
531 inspect_pattern_bytes,
531 )?;
532 )?;
532 warnings.extend(inner_warnings);
533 warnings.extend(inner_warnings);
533 inner_pats
534 inner_pats
534 }
535 }
535 PatternSyntax::SubInclude => {
536 PatternSyntax::SubInclude => {
536 let mut sub_include = SubInclude::new(
537 let mut sub_include = SubInclude::new(
537 root_dir,
538 root_dir,
538 &entry.pattern,
539 &entry.pattern,
539 &entry.source,
540 &entry.source,
540 )?;
541 )?;
541 let (inner_patterns, inner_warnings) =
542 let (inner_patterns, inner_warnings) =
542 get_patterns_from_file(
543 get_patterns_from_file(
543 &sub_include.path,
544 &sub_include.path,
544 &sub_include.root,
545 &sub_include.root,
545 inspect_pattern_bytes,
546 inspect_pattern_bytes,
546 )?;
547 )?;
547 sub_include.included_patterns = inner_patterns;
548 sub_include.included_patterns = inner_patterns;
548 warnings.extend(inner_warnings);
549 warnings.extend(inner_warnings);
549 vec![IgnorePattern {
550 vec![IgnorePattern {
550 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
551 syntax: PatternSyntax::ExpandedSubInclude(Box::new(
551 sub_include,
552 sub_include,
552 )),
553 )),
553 ..entry
554 ..entry
554 }]
555 }]
555 }
556 }
556 _ => vec![entry],
557 _ => vec![entry],
557 })
558 })
558 })
559 })
559 .flatten()
560 .flatten()
560 .collect();
561 .collect();
561
562
562 Ok((patterns, warnings))
563 Ok((patterns, warnings))
563 }
564 }
564
565
565 /// Holds all the information needed to handle a `subinclude:` pattern.
566 /// Holds all the information needed to handle a `subinclude:` pattern.
566 #[derive(Debug, PartialEq, Eq, Clone)]
567 #[derive(Debug, PartialEq, Eq, Clone)]
567 pub struct SubInclude {
568 pub struct SubInclude {
568 /// Will be used for repository (hg) paths that start with this prefix.
569 /// Will be used for repository (hg) paths that start with this prefix.
569 /// It is relative to the current working directory, so comparing against
570 /// It is relative to the current working directory, so comparing against
570 /// repository paths is painless.
571 /// repository paths is painless.
571 pub prefix: HgPathBuf,
572 pub prefix: HgPathBuf,
572 /// The file itself, containing the patterns
573 /// The file itself, containing the patterns
573 pub path: PathBuf,
574 pub path: PathBuf,
574 /// Folder in the filesystem where this it applies
575 /// Folder in the filesystem where this it applies
575 pub root: PathBuf,
576 pub root: PathBuf,
576
577
577 pub included_patterns: Vec<IgnorePattern>,
578 pub included_patterns: Vec<IgnorePattern>,
578 }
579 }
579
580
580 impl SubInclude {
581 impl SubInclude {
581 pub fn new(
582 pub fn new(
582 root_dir: &Path,
583 root_dir: &Path,
583 pattern: &[u8],
584 pattern: &[u8],
584 source: &Path,
585 source: &Path,
585 ) -> Result<SubInclude, HgPathError> {
586 ) -> Result<SubInclude, HgPathError> {
586 let normalized_source =
587 let normalized_source =
587 normalize_path_bytes(&get_bytes_from_path(source));
588 normalize_path_bytes(&get_bytes_from_path(source));
588
589
589 let source_root = get_path_from_bytes(&normalized_source);
590 let source_root = get_path_from_bytes(&normalized_source);
590 let source_root =
591 let source_root =
591 source_root.parent().unwrap_or_else(|| source_root.deref());
592 source_root.parent().unwrap_or_else(|| source_root.deref());
592
593
593 let path = source_root.join(get_path_from_bytes(pattern));
594 let path = source_root.join(get_path_from_bytes(pattern));
594 let new_root = path.parent().unwrap_or_else(|| path.deref());
595 let new_root = path.parent().unwrap_or_else(|| path.deref());
595
596
596 let prefix = canonical_path(root_dir, root_dir, new_root)?;
597 let prefix = canonical_path(root_dir, root_dir, new_root)?;
597
598
598 Ok(Self {
599 Ok(Self {
599 prefix: path_to_hg_path_buf(prefix).map(|mut p| {
600 prefix: path_to_hg_path_buf(prefix).map(|mut p| {
600 if !p.is_empty() {
601 if !p.is_empty() {
601 p.push_byte(b'/');
602 p.push_byte(b'/');
602 }
603 }
603 p
604 p
604 })?,
605 })?,
605 path: path.to_owned(),
606 path: path.to_owned(),
606 root: new_root.to_owned(),
607 root: new_root.to_owned(),
607 included_patterns: Vec::new(),
608 included_patterns: Vec::new(),
608 })
609 })
609 }
610 }
610 }
611 }
611
612
612 /// Separate and pre-process subincludes from other patterns for the "ignore"
613 /// Separate and pre-process subincludes from other patterns for the "ignore"
613 /// phase.
614 /// phase.
614 pub fn filter_subincludes(
615 pub fn filter_subincludes(
615 ignore_patterns: Vec<IgnorePattern>,
616 ignore_patterns: Vec<IgnorePattern>,
616 ) -> Result<(Vec<SubInclude>, Vec<IgnorePattern>), HgPathError> {
617 ) -> Result<(Vec<SubInclude>, Vec<IgnorePattern>), HgPathError> {
617 let mut subincludes = vec![];
618 let mut subincludes = vec![];
618 let mut others = vec![];
619 let mut others = vec![];
619
620
620 for pattern in ignore_patterns {
621 for pattern in ignore_patterns {
621 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
622 if let PatternSyntax::ExpandedSubInclude(sub_include) = pattern.syntax
622 {
623 {
623 subincludes.push(*sub_include);
624 subincludes.push(*sub_include);
624 } else {
625 } else {
625 others.push(pattern)
626 others.push(pattern)
626 }
627 }
627 }
628 }
628 Ok((subincludes, others))
629 Ok((subincludes, others))
629 }
630 }
630
631
631 #[cfg(test)]
632 #[cfg(test)]
632 mod tests {
633 mod tests {
633 use super::*;
634 use super::*;
634 use pretty_assertions::assert_eq;
635 use pretty_assertions::assert_eq;
635
636
636 #[test]
637 #[test]
637 fn escape_pattern_test() {
638 fn escape_pattern_test() {
638 let untouched =
639 let untouched =
639 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
640 br#"!"%',/0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz"#;
640 assert_eq!(escape_pattern(untouched), untouched.to_vec());
641 assert_eq!(escape_pattern(untouched), untouched.to_vec());
641 // All escape codes
642 // All escape codes
642 assert_eq!(
643 assert_eq!(
643 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
644 escape_pattern(br#"()[]{}?*+-|^$\\.&~# \t\n\r\v\f"#),
644 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
645 br#"\(\)\[\]\{\}\?\*\+\-\|\^\$\\\\\.\&\~\#\ \\t\\n\\r\\v\\f"#
645 .to_vec()
646 .to_vec()
646 );
647 );
647 }
648 }
648
649
649 #[test]
650 #[test]
650 fn glob_test() {
651 fn glob_test() {
651 assert_eq!(glob_to_re(br#"?"#), br#"."#);
652 assert_eq!(glob_to_re(br#"?"#), br#"."#);
652 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
653 assert_eq!(glob_to_re(br#"*"#), br#"[^/]*"#);
653 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
654 assert_eq!(glob_to_re(br#"**"#), br#".*"#);
654 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
655 assert_eq!(glob_to_re(br#"**/a"#), br#"(?:.*/)?a"#);
655 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
656 assert_eq!(glob_to_re(br#"a/**/b"#), br#"a/(?:.*/)?b"#);
656 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
657 assert_eq!(glob_to_re(br#"[a*?!^][^b][!c]"#), br#"[a*?!^][\^b][^c]"#);
657 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
658 assert_eq!(glob_to_re(br#"{a,b}"#), br#"(?:a|b)"#);
658 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
659 assert_eq!(glob_to_re(br#".\*\?"#), br#"\.\*\?"#);
659 }
660 }
660
661
661 #[test]
662 #[test]
662 fn test_parse_pattern_file_contents() {
663 fn test_parse_pattern_file_contents() {
663 let lines = b"syntax: glob\n*.elc";
664 let lines = b"syntax: glob\n*.elc";
664
665
665 assert_eq!(
666 assert_eq!(
666 parse_pattern_file_contents(
667 parse_pattern_file_contents(
667 lines,
668 lines,
668 Path::new("file_path"),
669 Path::new("file_path"),
669 None,
670 None,
670 false,
671 false,
671 true,
672 true,
672 )
673 )
673 .unwrap()
674 .unwrap()
674 .0,
675 .0,
675 vec![IgnorePattern::new(
676 vec![IgnorePattern::new(
676 PatternSyntax::RelGlob,
677 PatternSyntax::RelGlob,
677 b"*.elc",
678 b"*.elc",
678 Path::new("file_path")
679 Path::new("file_path")
679 )],
680 )],
680 );
681 );
681
682
682 let lines = b"syntax: include\nsyntax: glob";
683 let lines = b"syntax: include\nsyntax: glob";
683
684
684 assert_eq!(
685 assert_eq!(
685 parse_pattern_file_contents(
686 parse_pattern_file_contents(
686 lines,
687 lines,
687 Path::new("file_path"),
688 Path::new("file_path"),
688 None,
689 None,
689 false,
690 false,
690 true,
691 true,
691 )
692 )
692 .unwrap()
693 .unwrap()
693 .0,
694 .0,
694 vec![]
695 vec![]
695 );
696 );
696 let lines = b"glob:**.o";
697 let lines = b"glob:**.o";
697 assert_eq!(
698 assert_eq!(
698 parse_pattern_file_contents(
699 parse_pattern_file_contents(
699 lines,
700 lines,
700 Path::new("file_path"),
701 Path::new("file_path"),
701 None,
702 None,
702 false,
703 false,
703 true,
704 true,
704 )
705 )
705 .unwrap()
706 .unwrap()
706 .0,
707 .0,
707 vec![IgnorePattern::new(
708 vec![IgnorePattern::new(
708 PatternSyntax::RelGlob,
709 PatternSyntax::RelGlob,
709 b"**.o",
710 b"**.o",
710 Path::new("file_path")
711 Path::new("file_path")
711 )]
712 )]
712 );
713 );
713 }
714 }
714
715
715 #[test]
716 #[test]
716 fn test_build_single_regex() {
717 fn test_build_single_regex() {
717 assert_eq!(
718 assert_eq!(
718 build_single_regex(&IgnorePattern::new(
719 build_single_regex(&IgnorePattern::new(
719 PatternSyntax::RelGlob,
720 PatternSyntax::RelGlob,
720 b"rust/target/",
721 b"rust/target/",
721 Path::new("")
722 Path::new("")
722 ))
723 ))
723 .unwrap(),
724 .unwrap(),
724 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
725 Some(br"(?:.*/)?rust/target(?:/|$)".to_vec()),
725 );
726 );
726 assert_eq!(
727 assert_eq!(
727 build_single_regex(&IgnorePattern::new(
728 build_single_regex(&IgnorePattern::new(
728 PatternSyntax::Regexp,
729 PatternSyntax::Regexp,
729 br"rust/target/\d+",
730 br"rust/target/\d+",
730 Path::new("")
731 Path::new("")
731 ))
732 ))
732 .unwrap(),
733 .unwrap(),
733 Some(br"rust/target/\d+".to_vec()),
734 Some(br"rust/target/\d+".to_vec()),
734 );
735 );
735 }
736 }
736
737
737 #[test]
738 #[test]
738 fn test_build_single_regex_shortcut() {
739 fn test_build_single_regex_shortcut() {
739 assert_eq!(
740 assert_eq!(
740 build_single_regex(&IgnorePattern::new(
741 build_single_regex(&IgnorePattern::new(
741 PatternSyntax::RootGlob,
742 PatternSyntax::RootGlob,
742 b"",
743 b"",
743 Path::new("")
744 Path::new("")
744 ))
745 ))
745 .unwrap(),
746 .unwrap(),
746 None,
747 None,
747 );
748 );
748 assert_eq!(
749 assert_eq!(
749 build_single_regex(&IgnorePattern::new(
750 build_single_regex(&IgnorePattern::new(
750 PatternSyntax::RootGlob,
751 PatternSyntax::RootGlob,
751 b"whatever",
752 b"whatever",
752 Path::new("")
753 Path::new("")
753 ))
754 ))
754 .unwrap(),
755 .unwrap(),
755 None,
756 None,
756 );
757 );
757 assert_eq!(
758 assert_eq!(
758 build_single_regex(&IgnorePattern::new(
759 build_single_regex(&IgnorePattern::new(
759 PatternSyntax::RootGlob,
760 PatternSyntax::RootGlob,
760 b"*.o",
761 b"*.o",
761 Path::new("")
762 Path::new("")
762 ))
763 ))
763 .unwrap(),
764 .unwrap(),
764 Some(br"[^/]*\.o(?:/|$)".to_vec()),
765 Some(br"[^/]*\.o(?:/|$)".to_vec()),
765 );
766 );
766 }
767 }
767
768
768 #[test]
769 #[test]
769 fn test_build_single_relregex() {
770 fn test_build_single_relregex() {
770 assert_eq!(
771 assert_eq!(
771 build_single_regex(&IgnorePattern::new(
772 build_single_regex(&IgnorePattern::new(
772 PatternSyntax::RelRegexp,
773 PatternSyntax::RelRegexp,
773 b"^ba{2}r",
774 b"^ba{2}r",
774 Path::new("")
775 Path::new("")
775 ))
776 ))
776 .unwrap(),
777 .unwrap(),
777 Some(b"^ba{2}r".to_vec()),
778 Some(b"^ba{2}r".to_vec()),
778 );
779 );
779 assert_eq!(
780 assert_eq!(
780 build_single_regex(&IgnorePattern::new(
781 build_single_regex(&IgnorePattern::new(
781 PatternSyntax::RelRegexp,
782 PatternSyntax::RelRegexp,
782 b"ba{2}r",
783 b"ba{2}r",
783 Path::new("")
784 Path::new("")
784 ))
785 ))
785 .unwrap(),
786 .unwrap(),
786 Some(b".*ba{2}r".to_vec()),
787 Some(b".*ba{2}r".to_vec()),
787 );
788 );
788 assert_eq!(
789 assert_eq!(
789 build_single_regex(&IgnorePattern::new(
790 build_single_regex(&IgnorePattern::new(
790 PatternSyntax::RelRegexp,
791 PatternSyntax::RelRegexp,
791 b"(?ia)ba{2}r",
792 b"(?ia)ba{2}r",
792 Path::new("")
793 Path::new("")
793 ))
794 ))
794 .unwrap(),
795 .unwrap(),
795 Some(b"(?ia:.*ba{2}r)".to_vec()),
796 Some(b"(?ia:.*ba{2}r)".to_vec()),
796 );
797 );
797 assert_eq!(
798 assert_eq!(
798 build_single_regex(&IgnorePattern::new(
799 build_single_regex(&IgnorePattern::new(
799 PatternSyntax::RelRegexp,
800 PatternSyntax::RelRegexp,
800 b"(?ia)^ba{2}r",
801 b"(?ia)^ba{2}r",
801 Path::new("")
802 Path::new("")
802 ))
803 ))
803 .unwrap(),
804 .unwrap(),
804 Some(b"(?ia:^ba{2}r)".to_vec()),
805 Some(b"(?ia:^ba{2}r)".to_vec()),
805 );
806 );
806 }
807 }
807 }
808 }
General Comments 0
You need to be logged in to leave comments. Login now