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 | // Abort when there is a config related error |
|
9 | // Abort when there is a config related error | |
10 | pub const CONFIG_ERROR_ABORT: ExitCode = 30; |
|
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 | // Abort when there is an error while parsing config |
|
16 | // Abort when there is an error while parsing config | |
13 | pub const CONFIG_PARSE_ERROR_ABORT: ExitCode = 10; |
|
17 | pub const CONFIG_PARSE_ERROR_ABORT: ExitCode = 10; | |
14 |
|
18 |
@@ -314,6 +314,8 b' lazy_static! {' | |||||
314 | m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref()); |
|
314 | m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref()); | |
315 | m.insert(b"include".as_ref(), b"include:".as_ref()); |
|
315 | m.insert(b"include".as_ref(), b"include:".as_ref()); | |
316 | m.insert(b"subinclude".as_ref(), b"subinclude:".as_ref()); |
|
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 | m |
|
319 | m | |
318 | }; |
|
320 | }; | |
319 | } |
|
321 | } |
@@ -7,6 +7,7 b'' | |||||
7 | mod ancestors; |
|
7 | mod ancestors; | |
8 | pub mod dagops; |
|
8 | pub mod dagops; | |
9 | pub mod errors; |
|
9 | pub mod errors; | |
|
10 | pub mod narrow; | |||
10 | pub mod sparse; |
|
11 | pub mod sparse; | |
11 | pub use ancestors::{AncestorsIterator, MissingAncestors}; |
|
12 | pub use ancestors::{AncestorsIterator, MissingAncestors}; | |
12 | pub mod dirstate; |
|
13 | pub mod dirstate; |
@@ -54,14 +54,14 b' pub enum SparseWarning {' | |||||
54 | #[derive(Debug, Default)] |
|
54 | #[derive(Debug, Default)] | |
55 | pub struct SparseConfig { |
|
55 | pub struct SparseConfig { | |
56 | // Line-separated |
|
56 | // Line-separated | |
57 | includes: Vec<u8>, |
|
57 | pub(crate) includes: Vec<u8>, | |
58 | // Line-separated |
|
58 | // Line-separated | |
59 | excludes: Vec<u8>, |
|
59 | pub(crate) excludes: Vec<u8>, | |
60 | profiles: HashSet<Vec<u8>>, |
|
60 | pub(crate) profiles: HashSet<Vec<u8>>, | |
61 | warnings: Vec<SparseWarning>, |
|
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 | #[derive(Debug, derive_more::From)] |
|
65 | #[derive(Debug, derive_more::From)] | |
66 | pub enum SparseConfigError { |
|
66 | pub enum SparseConfigError { | |
67 | IncludesAfterExcludes { |
|
67 | IncludesAfterExcludes { | |
@@ -71,6 +71,11 b' pub enum SparseConfigError {' | |||||
71 | context: SparseConfigContext, |
|
71 | context: SparseConfigContext, | |
72 | line: Vec<u8>, |
|
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 | #[from] |
|
79 | #[from] | |
75 | HgError(HgError), |
|
80 | HgError(HgError), | |
76 | #[from] |
|
81 | #[from] | |
@@ -78,7 +83,7 b' pub enum SparseConfigError {' | |||||
78 | } |
|
83 | } | |
79 |
|
84 | |||
80 | /// Parse sparse config file content. |
|
85 | /// Parse sparse config file content. | |
81 | fn parse_config( |
|
86 | pub(crate) fn parse_config( | |
82 | raw: &[u8], |
|
87 | raw: &[u8], | |
83 | context: SparseConfigContext, |
|
88 | context: SparseConfigContext, | |
84 | ) -> Result<SparseConfig, SparseConfigError> { |
|
89 | ) -> Result<SparseConfig, SparseConfigError> { |
@@ -10,7 +10,6 b' use crate::ui::Ui;' | |||||
10 | use crate::utils::path_utils::RelativizePaths; |
|
10 | use crate::utils::path_utils::RelativizePaths; | |
11 | use clap::{Arg, SubCommand}; |
|
11 | use clap::{Arg, SubCommand}; | |
12 | use format_bytes::format_bytes; |
|
12 | use format_bytes::format_bytes; | |
13 | use hg; |
|
|||
14 | use hg::config::Config; |
|
13 | use hg::config::Config; | |
15 | use hg::dirstate::has_exec_bit; |
|
14 | use hg::dirstate::has_exec_bit; | |
16 | use hg::dirstate::status::StatusPath; |
|
15 | use hg::dirstate::status::StatusPath; | |
@@ -18,8 +17,8 b' use hg::dirstate::TruncatedTimestamp;' | |||||
18 | use hg::errors::{HgError, IoResultExt}; |
|
17 | use hg::errors::{HgError, IoResultExt}; | |
19 | use hg::lock::LockError; |
|
18 | use hg::lock::LockError; | |
20 | use hg::manifest::Manifest; |
|
19 | use hg::manifest::Manifest; | |
|
20 | use hg::matchers::{AlwaysMatcher, IntersectionMatcher}; | |||
21 | use hg::repo::Repo; |
|
21 | use hg::repo::Repo; | |
22 | use hg::sparse::{matcher, SparseWarning}; |
|
|||
23 | use hg::utils::files::get_bytes_from_os_string; |
|
22 | use hg::utils::files::get_bytes_from_os_string; | |
24 | use hg::utils::files::get_bytes_from_path; |
|
23 | use hg::utils::files::get_bytes_from_path; | |
25 | use hg::utils::files::get_path_from_bytes; |
|
24 | use hg::utils::files::get_path_from_bytes; | |
@@ -28,6 +27,7 b' use hg::DirstateStatus;' | |||||
28 | use hg::PatternFileWarning; |
|
27 | use hg::PatternFileWarning; | |
29 | use hg::StatusError; |
|
28 | use hg::StatusError; | |
30 | use hg::StatusOptions; |
|
29 | use hg::StatusOptions; | |
|
30 | use hg::{self, narrow, sparse}; | |||
31 | use log::info; |
|
31 | use log::info; | |
32 | use std::io; |
|
32 | use std::io; | |
33 | use std::path::PathBuf; |
|
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 | let mut dmap = repo.dirstate_map_mut()?; |
|
254 | let mut dmap = repo.dirstate_map_mut()?; | |
261 |
|
255 | |||
262 | let options = StatusOptions { |
|
256 | let options = StatusOptions { | |
@@ -366,11 +360,20 b' pub fn run(invocation: &crate::CliInvoca' | |||||
366 | filesystem_time_at_status_start, |
|
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 | match &warning { |
|
375 | match &warning { | |
373 | SparseWarning::RootWarning { context, line } => { |
|
376 | sparse::SparseWarning::RootWarning { context, line } => { | |
374 | let msg = format_bytes!( |
|
377 | let msg = format_bytes!( | |
375 | b"warning: {} profile cannot use paths \" |
|
378 | b"warning: {} profile cannot use paths \" | |
376 | starting with /, ignoring {}\n", |
|
379 | starting with /, ignoring {}\n", | |
@@ -379,7 +382,7 b' pub fn run(invocation: &crate::CliInvoca' | |||||
379 | ); |
|
382 | ); | |
380 | ui.write_stderr(&msg)?; |
|
383 | ui.write_stderr(&msg)?; | |
381 | } |
|
384 | } | |
382 | SparseWarning::ProfileNotFound { profile, rev } => { |
|
385 | sparse::SparseWarning::ProfileNotFound { profile, rev } => { | |
383 | let msg = format_bytes!( |
|
386 | let msg = format_bytes!( | |
384 | b"warning: sparse profile '{}' not found \" |
|
387 | b"warning: sparse profile '{}' not found \" | |
385 | in rev {} - ignoring it\n", |
|
388 | in rev {} - ignoring it\n", | |
@@ -388,7 +391,7 b' pub fn run(invocation: &crate::CliInvoca' | |||||
388 | ); |
|
391 | ); | |
389 | ui.write_stderr(&msg)?; |
|
392 | ui.write_stderr(&msg)?; | |
390 | } |
|
393 | } | |
391 | SparseWarning::Pattern(e) => { |
|
394 | sparse::SparseWarning::Pattern(e) => { | |
392 | ui.write_stderr(&print_pattern_file_warning(e, &repo))?; |
|
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 | exit_codes::CONFIG_PARSE_ERROR_ABORT, |
|
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 | SparseConfigError::HgError(e) => Self::from(e), |
|
284 | SparseConfigError::HgError(e) => Self::from(e), | |
272 | SparseConfigError::PatternError(e) => { |
|
285 | SparseConfigError::PatternError(e) => { | |
273 | Self::unsupported(format!("{}", e)) |
|
286 | Self::unsupported(format!("{}", e)) |
@@ -85,15 +85,12 b' hidden by narrow, so we just fall back t' | |||||
85 | dir1/x |
|
85 | dir1/x | |
86 | dir1/y |
|
86 | dir1/y | |
87 |
|
87 | |||
88 |
Hg status needs to do some filtering based on narrow spec |
|
88 | Hg status needs to do some filtering based on narrow spec | |
89 | support it in rhg for narrow clones yet. |
|
|||
90 |
|
89 | |||
91 | $ mkdir dir2 |
|
90 | $ mkdir dir2 | |
92 | $ touch dir2/q |
|
91 | $ touch dir2/q | |
93 | $ "$real_hg" status |
|
92 | $ "$real_hg" status | |
94 | $ $NO_FALLBACK rhg --config rhg.status=true status |
|
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 | Adding "orphaned" index files: |
|
95 | Adding "orphaned" index files: | |
99 |
|
96 |
General Comments 0
You need to be logged in to leave comments.
Login now