Show More
@@ -7,11 +7,19 b'' | |||
|
7 | 7 | |
|
8 | 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 | 18 | use lazy_static::lazy_static; |
|
12 | 19 | use regex::bytes::{NoExpand, Regex}; |
|
13 | 20 | use std::fs::File; |
|
14 | 21 | use std::io::Read; |
|
22 | use std::ops::Deref; | |
|
15 | 23 | use std::path::{Path, PathBuf}; |
|
16 | 24 | use std::vec::Vec; |
|
17 | 25 | |
@@ -53,6 +61,10 b' pub enum PatternSyntax {' | |||
|
53 | 61 | /// A path relative to repository root, which is matched non-recursively |
|
54 | 62 | /// (will not match subdirectories) |
|
55 | 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 | 70 | /// Transforms a glob pattern into a regex |
@@ -145,6 +157,8 b' pub fn parse_pattern_syntax(' | |||
|
145 | 157 | b"relre:" => Ok(PatternSyntax::RelRegexp), |
|
146 | 158 | b"glob:" => Ok(PatternSyntax::Glob), |
|
147 | 159 | b"rootglob:" => Ok(PatternSyntax::RootGlob), |
|
160 | b"include:" => Ok(PatternSyntax::Include), | |
|
161 | b"subinclude:" => Ok(PatternSyntax::SubInclude), | |
|
148 | 162 | _ => Err(PatternError::UnsupportedSyntax( |
|
149 | 163 | String::from_utf8_lossy(kind).to_string(), |
|
150 | 164 | )), |
@@ -198,6 +212,7 b' fn _build_single_regex(entry: &IgnorePat' | |||
|
198 | 212 | PatternSyntax::Glob | PatternSyntax::RootGlob => { |
|
199 | 213 | [glob_to_re(pattern).as_slice(), GLOB_SUFFIX].concat() |
|
200 | 214 | } |
|
215 | PatternSyntax::Include | PatternSyntax::SubInclude => unreachable!(), | |
|
201 | 216 | } |
|
202 | 217 | } |
|
203 | 218 | |
@@ -259,6 +274,9 b' pub fn build_single_regex(' | |||
|
259 | 274 | | PatternSyntax::Path |
|
260 | 275 | | PatternSyntax::RelGlob |
|
261 | 276 | | PatternSyntax::RootFiles => normalize_path_bytes(&pattern), |
|
277 | PatternSyntax::Include | PatternSyntax::SubInclude => { | |
|
278 | return Err(PatternError::NonRegexPattern(entry.clone())) | |
|
279 | } | |
|
262 | 280 | _ => pattern.to_owned(), |
|
263 | 281 | }; |
|
264 | 282 | if *syntax == PatternSyntax::RootGlob |
@@ -422,6 +440,110 b' impl IgnorePattern {' | |||
|
422 | 440 | |
|
423 | 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 | 547 | #[cfg(test)] |
|
426 | 548 | mod tests { |
|
427 | 549 | use super::*; |
@@ -121,6 +121,9 b' pub enum PatternError {' | |||
|
121 | 121 | UnsupportedSyntaxInFile(String, String, usize), |
|
122 | 122 | TooLong(usize), |
|
123 | 123 | IO(std::io::Error), |
|
124 | /// Needed a pattern that can be turned into a regex but got one that | |
|
125 | /// can't. This should only happen through programmer error. | |
|
126 | NonRegexPattern(IgnorePattern), | |
|
124 | 127 | } |
|
125 | 128 | |
|
126 | 129 | impl ToString for PatternError { |
@@ -140,6 +143,9 b' impl ToString for PatternError {' | |||
|
140 | 143 | } |
|
141 | 144 | PatternError::IO(e) => e.to_string(), |
|
142 | 145 | PatternError::Path(e) => e.to_string(), |
|
146 | PatternError::NonRegexPattern(pattern) => { | |
|
147 | format!("'{:?}' cannot be turned into a regex", pattern) | |
|
148 | } | |
|
143 | 149 | } |
|
144 | 150 | } |
|
145 | 151 | } |
General Comments 0
You need to be logged in to leave comments.
Login now