|
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 } |