--- 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;
--- 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
};
}
--- 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;
--- /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<dyn Matcher + Sync>, Vec<SparseWarning>), 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<dyn Matcher + Sync> =
+ 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(())
+}
--- 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<u8>,
+ pub(crate) includes: Vec<u8>,
// Line-separated
- excludes: Vec<u8>,
- profiles: HashSet<Vec<u8>>,
- warnings: Vec<SparseWarning>,
+ pub(crate) excludes: Vec<u8>,
+ pub(crate) profiles: HashSet<Vec<u8>>,
+ pub(crate) warnings: Vec<SparseWarning>,
}
-/// 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<u8>,
},
+ /// 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<u8>),
#[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<SparseConfig, SparseConfigError> {
--- 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))?;
}
}
--- 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))
--- 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: