rust/hg-core/src/filepatterns.rs
changeset 44347 2fe89bec8011
parent 44346 d42eea9a0494
child 44601 496868f1030c
equal deleted inserted replaced
44346:d42eea9a0494 44347:2fe89bec8011
     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