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::{utils::SliceExt, FastHashMap, PatternError}; |
10 use crate::{ |
|
11 utils::{ |
|
12 files::{canonical_path, get_bytes_from_path, get_path_from_bytes}, |
|
13 hg_path::{path_to_hg_path_buf, HgPathBuf, HgPathError}, |
|
14 SliceExt, |
|
15 }, |
|
16 FastHashMap, PatternError, |
|
17 }; |
11 use lazy_static::lazy_static; |
18 use lazy_static::lazy_static; |
12 use regex::bytes::{NoExpand, Regex}; |
19 use regex::bytes::{NoExpand, Regex}; |
13 use std::fs::File; |
20 use std::fs::File; |
14 use std::io::Read; |
21 use std::io::Read; |
|
22 use std::ops::Deref; |
15 use std::path::{Path, PathBuf}; |
23 use std::path::{Path, PathBuf}; |
16 use std::vec::Vec; |
24 use std::vec::Vec; |
17 |
25 |
18 lazy_static! { |
26 lazy_static! { |
19 static ref RE_ESCAPE: Vec<Vec<u8>> = { |
27 static ref RE_ESCAPE: Vec<Vec<u8>> = { |
51 /// A regexp that needn't match the start of a name |
59 /// A regexp that needn't match the start of a name |
52 RelRegexp, |
60 RelRegexp, |
53 /// A path relative to repository root, which is matched non-recursively |
61 /// A path relative to repository root, which is matched non-recursively |
54 /// (will not match subdirectories) |
62 /// (will not match subdirectories) |
55 RootFiles, |
63 RootFiles, |
|
64 /// A file of patterns to read and include |
|
65 Include, |
|
66 /// A file of patterns to match against files under the same directory |
|
67 SubInclude, |
56 } |
68 } |
57 |
69 |
58 /// Transforms a glob pattern into a regex |
70 /// Transforms a glob pattern into a regex |
59 fn glob_to_re(pat: &[u8]) -> Vec<u8> { |
71 fn glob_to_re(pat: &[u8]) -> Vec<u8> { |
60 let mut input = pat; |
72 let mut input = pat; |
143 b"rootfilesin:" => Ok(PatternSyntax::RootFiles), |
155 b"rootfilesin:" => Ok(PatternSyntax::RootFiles), |
144 b"relglob:" => Ok(PatternSyntax::RelGlob), |
156 b"relglob:" => Ok(PatternSyntax::RelGlob), |
145 b"relre:" => Ok(PatternSyntax::RelRegexp), |
157 b"relre:" => Ok(PatternSyntax::RelRegexp), |
146 b"glob:" => Ok(PatternSyntax::Glob), |
158 b"glob:" => Ok(PatternSyntax::Glob), |
147 b"rootglob:" => Ok(PatternSyntax::RootGlob), |
159 b"rootglob:" => Ok(PatternSyntax::RootGlob), |
|
160 b"include:" => Ok(PatternSyntax::Include), |
|
161 b"subinclude:" => Ok(PatternSyntax::SubInclude), |
148 _ => Err(PatternError::UnsupportedSyntax( |
162 _ => Err(PatternError::UnsupportedSyntax( |
149 String::from_utf8_lossy(kind).to_string(), |
163 String::from_utf8_lossy(kind).to_string(), |
150 )), |
164 )), |
151 } |
165 } |
152 } |
166 } |
196 } |
210 } |
197 } |
211 } |
198 PatternSyntax::Glob | PatternSyntax::RootGlob => { |
212 PatternSyntax::Glob | PatternSyntax::RootGlob => { |
199 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat() |
213 [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat() |
200 } |
214 } |
|
215 PatternSyntax::Include | PatternSyntax::SubInclude => unreachable!(), |
201 } |
216 } |
202 } |
217 } |
203 |
218 |
204 const GLOB_SPECIAL_CHARACTERS: [u8; 7] = |
219 const GLOB_SPECIAL_CHARACTERS: [u8; 7] = |
205 [b'*', b'?', b'[', b']', b'{', b'}', b'\\']; |
220 [b'*', b'?', b'[', b']', b'{', b'}', b'\\']; |
257 let pattern = match syntax { |
272 let pattern = match syntax { |
258 PatternSyntax::RootGlob |
273 PatternSyntax::RootGlob |
259 | PatternSyntax::Path |
274 | PatternSyntax::Path |
260 | PatternSyntax::RelGlob |
275 | PatternSyntax::RelGlob |
261 | PatternSyntax::RootFiles => normalize_path_bytes(&pattern), |
276 | PatternSyntax::RootFiles => normalize_path_bytes(&pattern), |
|
277 PatternSyntax::Include | PatternSyntax::SubInclude => { |
|
278 return Err(PatternError::NonRegexPattern(entry.clone())) |
|
279 } |
262 _ => pattern.to_owned(), |
280 _ => pattern.to_owned(), |
263 }; |
281 }; |
264 if *syntax == PatternSyntax::RootGlob |
282 if *syntax == PatternSyntax::RootGlob |
265 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b)) |
283 && !pattern.iter().any(|b| GLOB_SPECIAL_CHARACTERS.contains(b)) |
266 { |
284 { |
420 } |
438 } |
421 } |
439 } |
422 |
440 |
423 pub type PatternResult<T> = Result<T, PatternError>; |
441 pub type PatternResult<T> = Result<T, PatternError>; |
424 |
442 |
|
443 /// Wrapper for `read_pattern_file` that also recursively expands `include:` |
|
444 /// patterns. |
|
445 /// |
|
446 /// `subinclude:` is not treated as a special pattern here: unraveling them |
|
447 /// needs to occur in the "ignore" phase. |
|
448 pub fn get_patterns_from_file( |
|
449 pattern_file: impl AsRef<Path>, |
|
450 root_dir: impl AsRef<Path>, |
|
451 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> { |
|
452 let (patterns, mut warnings) = read_pattern_file(&pattern_file, true)?; |
|
453 let patterns = patterns |
|
454 .into_iter() |
|
455 .flat_map(|entry| -> PatternResult<_> { |
|
456 let IgnorePattern { |
|
457 syntax, |
|
458 pattern, |
|
459 source: _, |
|
460 } = &entry; |
|
461 Ok(match syntax { |
|
462 PatternSyntax::Include => { |
|
463 let inner_include = |
|
464 root_dir.as_ref().join(get_path_from_bytes(&pattern)); |
|
465 let (inner_pats, inner_warnings) = get_patterns_from_file( |
|
466 &inner_include, |
|
467 root_dir.as_ref(), |
|
468 )?; |
|
469 warnings.extend(inner_warnings); |
|
470 inner_pats |
|
471 } |
|
472 _ => vec![entry], |
|
473 }) |
|
474 }) |
|
475 .flatten() |
|
476 .collect(); |
|
477 |
|
478 Ok((patterns, warnings)) |
|
479 } |
|
480 |
|
481 /// Holds all the information needed to handle a `subinclude:` pattern. |
|
482 pub struct SubInclude { |
|
483 /// Will be used for repository (hg) paths that start with this prefix. |
|
484 /// It is relative to the current working directory, so comparing against |
|
485 /// repository paths is painless. |
|
486 pub prefix: HgPathBuf, |
|
487 /// The file itself, containing the patterns |
|
488 pub path: PathBuf, |
|
489 /// Folder in the filesystem where this it applies |
|
490 pub root: PathBuf, |
|
491 } |
|
492 |
|
493 impl SubInclude { |
|
494 pub fn new( |
|
495 root_dir: impl AsRef<Path>, |
|
496 pattern: &[u8], |
|
497 source: impl AsRef<Path>, |
|
498 ) -> Result<SubInclude, HgPathError> { |
|
499 let normalized_source = |
|
500 normalize_path_bytes(&get_bytes_from_path(source)); |
|
501 |
|
502 let source_root = get_path_from_bytes(&normalized_source); |
|
503 let source_root = source_root.parent().unwrap_or(source_root.deref()); |
|
504 |
|
505 let path = source_root.join(get_path_from_bytes(pattern)); |
|
506 let new_root = path.parent().unwrap_or(path.deref()); |
|
507 |
|
508 let prefix = canonical_path(&root_dir, &root_dir, new_root)?; |
|
509 |
|
510 Ok(Self { |
|
511 prefix: path_to_hg_path_buf(prefix).and_then(|mut p| { |
|
512 if !p.is_empty() { |
|
513 p.push(b'/'); |
|
514 } |
|
515 Ok(p) |
|
516 })?, |
|
517 path: path.to_owned(), |
|
518 root: new_root.to_owned(), |
|
519 }) |
|
520 } |
|
521 } |
|
522 |
|
523 /// Separate and pre-process subincludes from other patterns for the "ignore" |
|
524 /// phase. |
|
525 pub fn filter_subincludes( |
|
526 ignore_patterns: &[IgnorePattern], |
|
527 root_dir: impl AsRef<Path>, |
|
528 ) -> Result<(Vec<SubInclude>, Vec<&IgnorePattern>), HgPathError> { |
|
529 let mut subincludes = vec![]; |
|
530 let mut others = vec![]; |
|
531 |
|
532 for ignore_pattern in ignore_patterns.iter() { |
|
533 let IgnorePattern { |
|
534 syntax, |
|
535 pattern, |
|
536 source, |
|
537 } = ignore_pattern; |
|
538 if *syntax == PatternSyntax::SubInclude { |
|
539 subincludes.push(SubInclude::new(&root_dir, pattern, &source)?); |
|
540 } else { |
|
541 others.push(ignore_pattern) |
|
542 } |
|
543 } |
|
544 Ok((subincludes, others)) |
|
545 } |
|
546 |
425 #[cfg(test)] |
547 #[cfg(test)] |
426 mod tests { |
548 mod tests { |
427 use super::*; |
549 use super::*; |
428 use pretty_assertions::assert_eq; |
550 use pretty_assertions::assert_eq; |
429 |
551 |