rust/hg-core/src/narrow.rs
changeset 49488 7c93e38a0bbd
child 49915 c8ef85ace216
equal deleted inserted replaced
49487:9f14126cfc4c 49488:7c93e38a0bbd
       
     1 use std::path::Path;
       
     2 
       
     3 use crate::{
       
     4     errors::HgError,
       
     5     exit_codes,
       
     6     filepatterns::parse_pattern_file_contents,
       
     7     matchers::{
       
     8         AlwaysMatcher, DifferenceMatcher, IncludeMatcher, Matcher,
       
     9         NeverMatcher,
       
    10     },
       
    11     repo::Repo,
       
    12     requirements::NARROW_REQUIREMENT,
       
    13     sparse::{self, SparseConfigError, SparseWarning},
       
    14 };
       
    15 
       
    16 /// The file in .hg/store/ that indicates which paths exit in the store
       
    17 const FILENAME: &str = "narrowspec";
       
    18 /// The file in .hg/ that indicates which paths exit in the dirstate
       
    19 const DIRSTATE_FILENAME: &str = "narrowspec.dirstate";
       
    20 
       
    21 /// Pattern prefixes that are allowed in narrow patterns. This list MUST
       
    22 /// only contain patterns that are fast and safe to evaluate. Keep in mind
       
    23 /// that patterns are supplied by clients and executed on remote servers
       
    24 /// as part of wire protocol commands. That means that changes to this
       
    25 /// data structure influence the wire protocol and should not be taken
       
    26 /// lightly - especially removals.
       
    27 const VALID_PREFIXES: [&str; 2] = ["path:", "rootfilesin:"];
       
    28 
       
    29 /// Return the matcher for the current narrow spec, and all configuration
       
    30 /// warnings to display.
       
    31 pub fn matcher(
       
    32     repo: &Repo,
       
    33 ) -> Result<(Box<dyn Matcher + Sync>, Vec<SparseWarning>), SparseConfigError> {
       
    34     let mut warnings = vec![];
       
    35     if !repo.requirements().contains(NARROW_REQUIREMENT) {
       
    36         return Ok((Box::new(AlwaysMatcher), warnings));
       
    37     }
       
    38     // Treat "narrowspec does not exist" the same as "narrowspec file exists
       
    39     // and is empty".
       
    40     let store_spec = repo.store_vfs().try_read(FILENAME)?.unwrap_or(vec![]);
       
    41     let working_copy_spec =
       
    42         repo.hg_vfs().try_read(DIRSTATE_FILENAME)?.unwrap_or(vec![]);
       
    43     if store_spec != working_copy_spec {
       
    44         return Err(HgError::abort(
       
    45             "working copy's narrowspec is stale",
       
    46             exit_codes::STATE_ERROR,
       
    47             Some("run 'hg tracked --update-working-copy'".into()),
       
    48         )
       
    49         .into());
       
    50     }
       
    51 
       
    52     let config = sparse::parse_config(
       
    53         &store_spec,
       
    54         sparse::SparseConfigContext::Narrow,
       
    55     )?;
       
    56 
       
    57     warnings.extend(config.warnings);
       
    58 
       
    59     if !config.profiles.is_empty() {
       
    60         // TODO (from Python impl) maybe do something with profiles?
       
    61         return Err(SparseConfigError::IncludesInNarrow);
       
    62     }
       
    63     validate_patterns(&config.includes)?;
       
    64     validate_patterns(&config.excludes)?;
       
    65 
       
    66     if config.includes.is_empty() {
       
    67         return Ok((Box::new(NeverMatcher), warnings));
       
    68     }
       
    69 
       
    70     let (patterns, subwarnings) = parse_pattern_file_contents(
       
    71         &config.includes,
       
    72         Path::new(""),
       
    73         None,
       
    74         false,
       
    75     )?;
       
    76     warnings.extend(subwarnings.into_iter().map(From::from));
       
    77 
       
    78     let mut m: Box<dyn Matcher + Sync> =
       
    79         Box::new(IncludeMatcher::new(patterns)?);
       
    80 
       
    81     let (patterns, subwarnings) = parse_pattern_file_contents(
       
    82         &config.excludes,
       
    83         Path::new(""),
       
    84         None,
       
    85         false,
       
    86     )?;
       
    87     if !patterns.is_empty() {
       
    88         warnings.extend(subwarnings.into_iter().map(From::from));
       
    89         let exclude_matcher = Box::new(IncludeMatcher::new(patterns)?);
       
    90         m = Box::new(DifferenceMatcher::new(m, exclude_matcher));
       
    91     }
       
    92 
       
    93     Ok((m, warnings))
       
    94 }
       
    95 
       
    96 fn validate_patterns(patterns: &[u8]) -> Result<(), SparseConfigError> {
       
    97     for pattern in patterns.split(|c| *c == b'\n') {
       
    98         if pattern.is_empty() {
       
    99             continue;
       
   100         }
       
   101         for prefix in VALID_PREFIXES.iter() {
       
   102             if pattern.starts_with(prefix.as_bytes()) {
       
   103                 break;
       
   104             }
       
   105             return Err(SparseConfigError::InvalidNarrowPrefix(
       
   106                 pattern.to_owned(),
       
   107             ));
       
   108         }
       
   109     }
       
   110     Ok(())
       
   111 }