##// 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 // 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, 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 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, so we don't
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