# HG changeset patch # User Raphaël Gomès # Date 1658756344 -7200 # Node ID 7c93e38a0bbde950ab7bccb1b014f6fbeaa58154 # Parent 9f14126cfc4cae50e74056731bc84eab4a913925 rhg-status: add support for narrow clones diff -r 9f14126cfc4c -r 7c93e38a0bbd rust/hg-core/src/exit_codes.rs --- a/rust/hg-core/src/exit_codes.rs Tue Jul 19 17:07:09 2022 +0200 +++ b/rust/hg-core/src/exit_codes.rs Mon Jul 25 15:39:04 2022 +0200 @@ -9,6 +9,10 @@ // Abort when there is a config related error pub const CONFIG_ERROR_ABORT: ExitCode = 30; +/// Indicates that the operation might work if retried in a different state. +/// Examples: Unresolved merge conflicts, unfinished operations +pub const STATE_ERROR: ExitCode = 20; + // Abort when there is an error while parsing config pub const CONFIG_PARSE_ERROR_ABORT: ExitCode = 10; diff -r 9f14126cfc4c -r 7c93e38a0bbd rust/hg-core/src/filepatterns.rs --- a/rust/hg-core/src/filepatterns.rs Tue Jul 19 17:07:09 2022 +0200 +++ b/rust/hg-core/src/filepatterns.rs Mon Jul 25 15:39:04 2022 +0200 @@ -314,6 +314,8 @@ m.insert(b"rootglob".as_ref(), b"rootglob:".as_ref()); m.insert(b"include".as_ref(), b"include:".as_ref()); m.insert(b"subinclude".as_ref(), b"subinclude:".as_ref()); + m.insert(b"path".as_ref(), b"path:".as_ref()); + m.insert(b"rootfilesin".as_ref(), b"rootfilesin:".as_ref()); m }; } diff -r 9f14126cfc4c -r 7c93e38a0bbd rust/hg-core/src/lib.rs --- a/rust/hg-core/src/lib.rs Tue Jul 19 17:07:09 2022 +0200 +++ b/rust/hg-core/src/lib.rs Mon Jul 25 15:39:04 2022 +0200 @@ -7,6 +7,7 @@ mod ancestors; pub mod dagops; pub mod errors; +pub mod narrow; pub mod sparse; pub use ancestors::{AncestorsIterator, MissingAncestors}; pub mod dirstate; diff -r 9f14126cfc4c -r 7c93e38a0bbd rust/hg-core/src/narrow.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rust/hg-core/src/narrow.rs Mon Jul 25 15:39:04 2022 +0200 @@ -0,0 +1,111 @@ +use std::path::Path; + +use crate::{ + errors::HgError, + exit_codes, + filepatterns::parse_pattern_file_contents, + matchers::{ + AlwaysMatcher, DifferenceMatcher, IncludeMatcher, Matcher, + NeverMatcher, + }, + repo::Repo, + requirements::NARROW_REQUIREMENT, + sparse::{self, SparseConfigError, SparseWarning}, +}; + +/// The file in .hg/store/ that indicates which paths exit in the store +const FILENAME: &str = "narrowspec"; +/// The file in .hg/ that indicates which paths exit in the dirstate +const DIRSTATE_FILENAME: &str = "narrowspec.dirstate"; + +/// Pattern prefixes that are allowed in narrow patterns. This list MUST +/// only contain patterns that are fast and safe to evaluate. Keep in mind +/// that patterns are supplied by clients and executed on remote servers +/// as part of wire protocol commands. That means that changes to this +/// data structure influence the wire protocol and should not be taken +/// lightly - especially removals. +const VALID_PREFIXES: [&str; 2] = ["path:", "rootfilesin:"]; + +/// Return the matcher for the current narrow spec, and all configuration +/// warnings to display. +pub fn matcher( + repo: &Repo, +) -> Result<(Box, Vec), SparseConfigError> { + let mut warnings = vec![]; + if !repo.requirements().contains(NARROW_REQUIREMENT) { + return Ok((Box::new(AlwaysMatcher), warnings)); + } + // Treat "narrowspec does not exist" the same as "narrowspec file exists + // and is empty". + let store_spec = repo.store_vfs().try_read(FILENAME)?.unwrap_or(vec![]); + let working_copy_spec = + repo.hg_vfs().try_read(DIRSTATE_FILENAME)?.unwrap_or(vec![]); + if store_spec != working_copy_spec { + return Err(HgError::abort( + "working copy's narrowspec is stale", + exit_codes::STATE_ERROR, + Some("run 'hg tracked --update-working-copy'".into()), + ) + .into()); + } + + let config = sparse::parse_config( + &store_spec, + sparse::SparseConfigContext::Narrow, + )?; + + warnings.extend(config.warnings); + + if !config.profiles.is_empty() { + // TODO (from Python impl) maybe do something with profiles? + return Err(SparseConfigError::IncludesInNarrow); + } + validate_patterns(&config.includes)?; + validate_patterns(&config.excludes)?; + + if config.includes.is_empty() { + return Ok((Box::new(NeverMatcher), warnings)); + } + + let (patterns, subwarnings) = parse_pattern_file_contents( + &config.includes, + Path::new(""), + None, + false, + )?; + warnings.extend(subwarnings.into_iter().map(From::from)); + + let mut m: Box = + Box::new(IncludeMatcher::new(patterns)?); + + let (patterns, subwarnings) = parse_pattern_file_contents( + &config.excludes, + Path::new(""), + None, + false, + )?; + if !patterns.is_empty() { + warnings.extend(subwarnings.into_iter().map(From::from)); + let exclude_matcher = Box::new(IncludeMatcher::new(patterns)?); + m = Box::new(DifferenceMatcher::new(m, exclude_matcher)); + } + + Ok((m, warnings)) +} + +fn validate_patterns(patterns: &[u8]) -> Result<(), SparseConfigError> { + for pattern in patterns.split(|c| *c == b'\n') { + if pattern.is_empty() { + continue; + } + for prefix in VALID_PREFIXES.iter() { + if pattern.starts_with(prefix.as_bytes()) { + break; + } + return Err(SparseConfigError::InvalidNarrowPrefix( + pattern.to_owned(), + )); + } + } + Ok(()) +} diff -r 9f14126cfc4c -r 7c93e38a0bbd rust/hg-core/src/sparse.rs --- a/rust/hg-core/src/sparse.rs Tue Jul 19 17:07:09 2022 +0200 +++ b/rust/hg-core/src/sparse.rs Mon Jul 25 15:39:04 2022 +0200 @@ -54,14 +54,14 @@ #[derive(Debug, Default)] pub struct SparseConfig { // Line-separated - includes: Vec, + pub(crate) includes: Vec, // Line-separated - excludes: Vec, - profiles: HashSet>, - warnings: Vec, + pub(crate) excludes: Vec, + pub(crate) profiles: HashSet>, + pub(crate) warnings: Vec, } -/// All possible errors when reading sparse config +/// All possible errors when reading sparse/narrow config #[derive(Debug, derive_more::From)] pub enum SparseConfigError { IncludesAfterExcludes { @@ -71,6 +71,11 @@ context: SparseConfigContext, line: Vec, }, + /// Narrow config does not support '%include' directives + IncludesInNarrow, + /// An invalid pattern prefix was given to the narrow spec. Includes the + /// entire pattern for context. + InvalidNarrowPrefix(Vec), #[from] HgError(HgError), #[from] @@ -78,7 +83,7 @@ } /// Parse sparse config file content. -fn parse_config( +pub(crate) fn parse_config( raw: &[u8], context: SparseConfigContext, ) -> Result { diff -r 9f14126cfc4c -r 7c93e38a0bbd rust/rhg/src/commands/status.rs --- a/rust/rhg/src/commands/status.rs Tue Jul 19 17:07:09 2022 +0200 +++ b/rust/rhg/src/commands/status.rs Mon Jul 25 15:39:04 2022 +0200 @@ -10,7 +10,6 @@ use crate::utils::path_utils::RelativizePaths; use clap::{Arg, SubCommand}; use format_bytes::format_bytes; -use hg; use hg::config::Config; use hg::dirstate::has_exec_bit; use hg::dirstate::status::StatusPath; @@ -18,8 +17,8 @@ use hg::errors::{HgError, IoResultExt}; use hg::lock::LockError; use hg::manifest::Manifest; +use hg::matchers::{AlwaysMatcher, IntersectionMatcher}; use hg::repo::Repo; -use hg::sparse::{matcher, SparseWarning}; use hg::utils::files::get_bytes_from_os_string; use hg::utils::files::get_bytes_from_path; use hg::utils::files::get_path_from_bytes; @@ -28,6 +27,7 @@ use hg::PatternFileWarning; use hg::StatusError; use hg::StatusOptions; +use hg::{self, narrow, sparse}; use log::info; use std::io; use std::path::PathBuf; @@ -251,12 +251,6 @@ }; } - if repo.has_narrow() { - return Err(CommandError::unsupported( - "rhg status is not supported for narrow clones yet", - )); - } - let mut dmap = repo.dirstate_map_mut()?; let options = StatusOptions { @@ -366,11 +360,20 @@ filesystem_time_at_status_start, )) }; - let (matcher, sparse_warnings) = matcher(repo)?; + let (narrow_matcher, narrow_warnings) = narrow::matcher(repo)?; + let (sparse_matcher, sparse_warnings) = sparse::matcher(repo)?; + let matcher = match (repo.has_narrow(), repo.has_sparse()) { + (true, true) => { + Box::new(IntersectionMatcher::new(narrow_matcher, sparse_matcher)) + } + (true, false) => narrow_matcher, + (false, true) => sparse_matcher, + (false, false) => Box::new(AlwaysMatcher), + }; - for warning in sparse_warnings { + for warning in narrow_warnings.into_iter().chain(sparse_warnings) { match &warning { - SparseWarning::RootWarning { context, line } => { + sparse::SparseWarning::RootWarning { context, line } => { let msg = format_bytes!( b"warning: {} profile cannot use paths \" starting with /, ignoring {}\n", @@ -379,7 +382,7 @@ ); ui.write_stderr(&msg)?; } - SparseWarning::ProfileNotFound { profile, rev } => { + sparse::SparseWarning::ProfileNotFound { profile, rev } => { let msg = format_bytes!( b"warning: sparse profile '{}' not found \" in rev {} - ignoring it\n", @@ -388,7 +391,7 @@ ); ui.write_stderr(&msg)?; } - SparseWarning::Pattern(e) => { + sparse::SparseWarning::Pattern(e) => { ui.write_stderr(&print_pattern_file_warning(e, &repo))?; } } diff -r 9f14126cfc4c -r 7c93e38a0bbd rust/rhg/src/error.rs --- a/rust/rhg/src/error.rs Tue Jul 19 17:07:09 2022 +0200 +++ b/rust/rhg/src/error.rs Mon Jul 25 15:39:04 2022 +0200 @@ -268,6 +268,19 @@ exit_codes::CONFIG_PARSE_ERROR_ABORT, ) } + SparseConfigError::InvalidNarrowPrefix(prefix) => { + Self::abort_with_exit_code_bytes( + format_bytes!( + b"invalid prefix on narrow pattern: {}", + &prefix + ), + exit_codes::ABORT, + ) + } + SparseConfigError::IncludesInNarrow => Self::abort( + "including other spec files using '%include' \ + is not supported in narrowspec", + ), SparseConfigError::HgError(e) => Self::from(e), SparseConfigError::PatternError(e) => { Self::unsupported(format!("{}", e)) diff -r 9f14126cfc4c -r 7c93e38a0bbd tests/test-rhg-sparse-narrow.t --- a/tests/test-rhg-sparse-narrow.t Tue Jul 19 17:07:09 2022 +0200 +++ b/tests/test-rhg-sparse-narrow.t Mon Jul 25 15:39:04 2022 +0200 @@ -85,15 +85,12 @@ dir1/x dir1/y -Hg status needs to do some filtering based on narrow spec, so we don't -support it in rhg for narrow clones yet. +Hg status needs to do some filtering based on narrow spec $ mkdir dir2 $ touch dir2/q $ "$real_hg" status $ $NO_FALLBACK rhg --config rhg.status=true status - unsupported feature: rhg status is not supported for narrow clones yet - [252] Adding "orphaned" index files: