##// END OF EJS Templates
rhg-status: add support for narrow clones
Raphaël Gomès -
r50383:7c93e38a default
parent child Browse files
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, sparse_warnings) = matcher(repo)?;
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, so we don't
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