Show More
@@ -0,0 +1,111 b'' | |||
|
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 | } |
@@ -9,6 +9,10 b' pub const ABORT: ExitCode = 255;' | |||
|
9 | 9 | // Abort when there is a config related error |
|
10 | 10 | pub const CONFIG_ERROR_ABORT: ExitCode = 30; |
|
11 | 11 | |
|
12 | /// Indicates that the operation might work if retried in a different state. | |
|
13 | /// Examples: Unresolved merge conflicts, unfinished operations | |
|
14 | pub const STATE_ERROR: ExitCode = 20; | |
|
15 | ||
|
12 | 16 | // Abort when there is an error while parsing config |
|
13 | 17 | pub const CONFIG_PARSE_ERROR_ABORT: ExitCode = 10; |
|
14 | 18 |
@@ -314,6 +314,8 b' lazy_static! {' | |||
|
314 | 314 | m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref()); |
|
315 | 315 | m.insert(b"include".as_ref(), b"include:".as_ref()); |
|
316 | 316 | m.insert(b"subinclude".as_ref(), b"subinclude:".as_ref()); |
|
317 | m.insert(b"path".as_ref(), b"path:".as_ref()); | |
|
318 | m.insert(b"rootfilesin".as_ref(), b"rootfilesin:".as_ref()); | |
|
317 | 319 | m |
|
318 | 320 | }; |
|
319 | 321 | } |
@@ -7,6 +7,7 b'' | |||
|
7 | 7 | mod ancestors; |
|
8 | 8 | pub mod dagops; |
|
9 | 9 | pub mod errors; |
|
10 | pub mod narrow; | |
|
10 | 11 | pub mod sparse; |
|
11 | 12 | pub use ancestors::{AncestorsIterator, MissingAncestors}; |
|
12 | 13 | pub mod dirstate; |
@@ -54,14 +54,14 b' pub enum SparseWarning {' | |||
|
54 | 54 | #[derive(Debug, Default)] |
|
55 | 55 | pub struct SparseConfig { |
|
56 | 56 | // Line-separated |
|
57 | includes: Vec<u8>, | |
|
57 | pub(crate) includes: Vec<u8>, | |
|
58 | 58 | // Line-separated |
|
59 | excludes: Vec<u8>, | |
|
60 | profiles: HashSet<Vec<u8>>, | |
|
61 | warnings: Vec<SparseWarning>, | |
|
59 | pub(crate) excludes: Vec<u8>, | |
|
60 | pub(crate) profiles: HashSet<Vec<u8>>, | |
|
61 | pub(crate) warnings: Vec<SparseWarning>, | |
|
62 | 62 | } |
|
63 | 63 | |
|
64 | /// All possible errors when reading sparse config | |
|
64 | /// All possible errors when reading sparse/narrow config | |
|
65 | 65 | #[derive(Debug, derive_more::From)] |
|
66 | 66 | pub enum SparseConfigError { |
|
67 | 67 | IncludesAfterExcludes { |
@@ -71,6 +71,11 b' pub enum SparseConfigError {' | |||
|
71 | 71 | context: SparseConfigContext, |
|
72 | 72 | line: Vec<u8>, |
|
73 | 73 | }, |
|
74 | /// Narrow config does not support '%include' directives | |
|
75 | IncludesInNarrow, | |
|
76 | /// An invalid pattern prefix was given to the narrow spec. Includes the | |
|
77 | /// entire pattern for context. | |
|
78 | InvalidNarrowPrefix(Vec<u8>), | |
|
74 | 79 | #[from] |
|
75 | 80 | HgError(HgError), |
|
76 | 81 | #[from] |
@@ -78,7 +83,7 b' pub enum SparseConfigError {' | |||
|
78 | 83 | } |
|
79 | 84 | |
|
80 | 85 | /// Parse sparse config file content. |
|
81 | fn parse_config( | |
|
86 | pub(crate) fn parse_config( | |
|
82 | 87 | raw: &[u8], |
|
83 | 88 | context: SparseConfigContext, |
|
84 | 89 | ) -> Result<SparseConfig, SparseConfigError> { |
@@ -10,7 +10,6 b' use crate::ui::Ui;' | |||
|
10 | 10 | use crate::utils::path_utils::RelativizePaths; |
|
11 | 11 | use clap::{Arg, SubCommand}; |
|
12 | 12 | use format_bytes::format_bytes; |
|
13 | use hg; | |
|
14 | 13 | use hg::config::Config; |
|
15 | 14 | use hg::dirstate::has_exec_bit; |
|
16 | 15 | use hg::dirstate::status::StatusPath; |
@@ -18,8 +17,8 b' use hg::dirstate::TruncatedTimestamp;' | |||
|
18 | 17 | use hg::errors::{HgError, IoResultExt}; |
|
19 | 18 | use hg::lock::LockError; |
|
20 | 19 | use hg::manifest::Manifest; |
|
20 | use hg::matchers::{AlwaysMatcher, IntersectionMatcher}; | |
|
21 | 21 | use hg::repo::Repo; |
|
22 | use hg::sparse::{matcher, SparseWarning}; | |
|
23 | 22 | use hg::utils::files::get_bytes_from_os_string; |
|
24 | 23 | use hg::utils::files::get_bytes_from_path; |
|
25 | 24 | use hg::utils::files::get_path_from_bytes; |
@@ -28,6 +27,7 b' use hg::DirstateStatus;' | |||
|
28 | 27 | use hg::PatternFileWarning; |
|
29 | 28 | use hg::StatusError; |
|
30 | 29 | use hg::StatusOptions; |
|
30 | use hg::{self, narrow, sparse}; | |
|
31 | 31 | use log::info; |
|
32 | 32 | use std::io; |
|
33 | 33 | use std::path::PathBuf; |
@@ -251,12 +251,6 b' pub fn run(invocation: &crate::CliInvoca' | |||
|
251 | 251 | }; |
|
252 | 252 | } |
|
253 | 253 | |
|
254 | if repo.has_narrow() { | |
|
255 | return Err(CommandError::unsupported( | |
|
256 | "rhg status is not supported for narrow clones yet", | |
|
257 | )); | |
|
258 | } | |
|
259 | ||
|
260 | 254 | let mut dmap = repo.dirstate_map_mut()?; |
|
261 | 255 | |
|
262 | 256 | let options = StatusOptions { |
@@ -366,11 +360,20 b' pub fn run(invocation: &crate::CliInvoca' | |||
|
366 | 360 | filesystem_time_at_status_start, |
|
367 | 361 | )) |
|
368 | 362 | }; |
|
369 |
let (matcher, |
|
|
363 | let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?; | |
|
364 | let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?; | |
|
365 | let matcher = match (repo.has_narrow(), repo.has_sparse()) { | |
|
366 | (true, true) => { | |
|
367 | Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher)) | |
|
368 | } | |
|
369 | (true, false) => narrow_matcher, | |
|
370 | (false, true) => sparse_matcher, | |
|
371 | (false, false) => Box::new(AlwaysMatcher), | |
|
372 | }; | |
|
370 | 373 | |
|
371 | for warning in sparse_warnings { | |
|
374 | for warning in narrow_warnings.into_iter().chain(sparse_warnings) { | |
|
372 | 375 | match &warning { |
|
373 | SparseWarning::RootWarning { context, line } => { | |
|
376 | sparse::SparseWarning::RootWarning { context, line } => { | |
|
374 | 377 | let msg = format_bytes!( |
|
375 | 378 | b"warning: {} profile cannot use paths \" |
|
376 | 379 | starting with /, ignoring {}\n", |
@@ -379,7 +382,7 b' pub fn run(invocation: &crate::CliInvoca' | |||
|
379 | 382 | ); |
|
380 | 383 | ui.write_stderr(&msg)?; |
|
381 | 384 | } |
|
382 | SparseWarning::ProfileNotFound { profile, rev } => { | |
|
385 | sparse::SparseWarning::ProfileNotFound { profile, rev } => { | |
|
383 | 386 | let msg = format_bytes!( |
|
384 | 387 | b"warning: sparse profile '{}' not found \" |
|
385 | 388 | in rev {} - ignoring it\n", |
@@ -388,7 +391,7 b' pub fn run(invocation: &crate::CliInvoca' | |||
|
388 | 391 | ); |
|
389 | 392 | ui.write_stderr(&msg)?; |
|
390 | 393 | } |
|
391 | SparseWarning::Pattern(e) => { | |
|
394 | sparse::SparseWarning::Pattern(e) => { | |
|
392 | 395 | ui.write_stderr(&print_pattern_file_warning(e, &repo))?; |
|
393 | 396 | } |
|
394 | 397 | } |
@@ -268,6 +268,19 b' impl From<SparseConfigError> for Command' | |||
|
268 | 268 | exit_codes::CONFIG_PARSE_ERROR_ABORT, |
|
269 | 269 | ) |
|
270 | 270 | } |
|
271 | SparseConfigError::InvalidNarrowPrefix(prefix) => { | |
|
272 | Self::abort_with_exit_code_bytes( | |
|
273 | format_bytes!( | |
|
274 | b"invalid prefix on narrow pattern: {}", | |
|
275 | &prefix | |
|
276 | ), | |
|
277 | exit_codes::ABORT, | |
|
278 | ) | |
|
279 | } | |
|
280 | SparseConfigError::IncludesInNarrow => Self::abort( | |
|
281 | "including other spec files using '%include' \ | |
|
282 | is not supported in narrowspec", | |
|
283 | ), | |
|
271 | 284 | SparseConfigError::HgError(e) => Self::from(e), |
|
272 | 285 | SparseConfigError::PatternError(e) => { |
|
273 | 286 | Self::unsupported(format!("{}", e)) |
@@ -85,15 +85,12 b' hidden by narrow, so we just fall back t' | |||
|
85 | 85 | dir1/x |
|
86 | 86 | dir1/y |
|
87 | 87 | |
|
88 |
Hg status needs to do some filtering based on narrow spec |
|
|
89 | support it in rhg for narrow clones yet. | |
|
88 | Hg status needs to do some filtering based on narrow spec | |
|
90 | 89 | |
|
91 | 90 | $ mkdir dir2 |
|
92 | 91 | $ touch dir2/q |
|
93 | 92 | $ "$real_hg" status |
|
94 | 93 | $ $NO_FALLBACK rhg --config rhg.status=true status |
|
95 | unsupported feature: rhg status is not supported for narrow clones yet | |
|
96 | [252] | |
|
97 | 94 | |
|
98 | 95 | Adding "orphaned" index files: |
|
99 | 96 |
General Comments 0
You need to be logged in to leave comments.
Login now