rhg: Parse per-repository configuration
Differential Revision: https://phab.mercurial-scm.org/D9964
--- a/rust/hg-core/src/config/config.rs Thu Feb 04 14:29:47 2021 +0100
+++ b/rust/hg-core/src/config/config.rs Thu Feb 04 15:04:53 2021 +0100
@@ -16,7 +16,6 @@
use std::path::{Path, PathBuf};
use crate::errors::{HgResultExt, IoResultExt};
-use crate::repo::Repo;
/// Holds the config values for the current repository
/// TODO update this docstring once we support more sources
@@ -196,12 +195,28 @@
Ok(Config { layers })
}
- /// Loads the local config. In a future version, this will also load the
- /// `$HOME/.hgrc` and more to mirror the Python implementation.
- pub fn load_for_repo(repo: &Repo) -> Result<Self, ConfigError> {
- Ok(Self::load_from_explicit_sources(vec![
- ConfigSource::AbsPath(repo.hg_vfs().join("hgrc")),
- ])?)
+ /// Loads the per-repository config into a new `Config` which is combined
+ /// with `self`.
+ pub(crate) fn combine_with_repo(
+ &self,
+ repo_config_files: &[PathBuf],
+ ) -> Result<Self, ConfigError> {
+ let (cli_layers, other_layers) = self
+ .layers
+ .iter()
+ .cloned()
+ .partition(ConfigLayer::is_from_command_line);
+
+ let mut repo_config = Self {
+ layers: other_layers,
+ };
+ for path in repo_config_files {
+ // TODO: check if this file should be trusted:
+ // `mercurial/ui.py:427`
+ repo_config.add_trusted_file(path)?;
+ }
+ repo_config.layers.extend(cli_layers);
+ Ok(repo_config)
}
/// Returns an `Err` if the first value found is not a valid boolean.
@@ -297,8 +312,6 @@
let config = Config::load_from_explicit_sources(sources)
.expect("expected valid config");
- dbg!(&config);
-
let (_, value) = config.get_inner(b"section", b"item").unwrap();
assert_eq!(
value,
--- a/rust/hg-core/src/config/layer.rs Thu Feb 04 14:29:47 2021 +0100
+++ b/rust/hg-core/src/config/layer.rs Thu Feb 04 15:04:53 2021 +0100
@@ -51,6 +51,15 @@
}
}
+ /// Returns whether this layer comes from `--config` CLI arguments
+ pub(crate) fn is_from_command_line(&self) -> bool {
+ if let ConfigOrigin::CommandLine = self.origin {
+ true
+ } else {
+ false
+ }
+ }
+
/// Add an entry to the config, overwriting the old one if already present.
pub fn add(
&mut self,
@@ -97,11 +106,13 @@
if let Some(m) = INCLUDE_RE.captures(&bytes) {
let filename_bytes = &m[1];
// `Path::parent` only fails for the root directory,
- // which `src` can’t be since we’ve managed to open it as a file.
+ // which `src` can’t be since we’ve managed to open it as a
+ // file.
let dir = src
.parent()
.expect("Path::parent fail on a file we’ve read");
- // `Path::join` with an absolute argument correctly ignores the base path
+ // `Path::join` with an absolute argument correctly ignores the
+ // base path
let filename = dir.join(&get_path_from_bytes(&filename_bytes));
let data = std::fs::read(&filename).for_file(&filename)?;
layers.push(current_layer);
@@ -200,9 +211,11 @@
#[derive(Clone, Debug)]
pub enum ConfigOrigin {
- /// The value comes from a configuration file
+ /// From a configuration file
File(PathBuf),
- /// The value comes from the environment like `$PAGER` or `$EDITOR`
+ /// From a `--config` CLI argument
+ CommandLine,
+ /// From environment variables like `$PAGER` or `$EDITOR`
Environment(Vec<u8>),
/* TODO cli
* TODO defaults (configitems.py)
@@ -216,6 +229,7 @@
pub fn to_bytes(&self) -> Vec<u8> {
match self {
ConfigOrigin::File(p) => get_bytes_from_path(p),
+ ConfigOrigin::CommandLine => b"--config".to_vec(),
ConfigOrigin::Environment(e) => format_bytes!(b"${}", e),
}
}
--- a/rust/hg-core/src/repo.rs Thu Feb 04 14:29:47 2021 +0100
+++ b/rust/hg-core/src/repo.rs Thu Feb 04 15:04:53 2021 +0100
@@ -1,4 +1,4 @@
-use crate::config::Config;
+use crate::config::{Config, ConfigError, ConfigParseError};
use crate::errors::{HgError, IoResultExt};
use crate::requirements;
use crate::utils::files::get_path_from_bytes;
@@ -12,17 +12,29 @@
dot_hg: PathBuf,
store: PathBuf,
requirements: HashSet<String>,
+ config: Config,
}
#[derive(Debug, derive_more::From)]
-pub enum RepoFindError {
- NotFoundInCurrentDirectoryOrAncestors {
+pub enum RepoError {
+ NotFound {
current_directory: PathBuf,
},
#[from]
+ ConfigParseError(ConfigParseError),
+ #[from]
Other(HgError),
}
+impl From<ConfigError> for RepoError {
+ fn from(error: ConfigError) -> Self {
+ match error {
+ ConfigError::Parse(error) => error.into(),
+ ConfigError::Other(error) => error.into(),
+ }
+ }
+}
+
/// Filesystem access abstraction for the contents of a given "base" diretory
#[derive(Clone, Copy)]
pub(crate) struct Vfs<'a> {
@@ -32,7 +44,7 @@
impl Repo {
/// Search the current directory and its ancestores for a repository:
/// a working directory that contains a `.hg` sub-directory.
- pub fn find(config: &Config) -> Result<Self, RepoFindError> {
+ pub fn find(config: &Config) -> Result<Self, RepoError> {
let current_directory = crate::utils::current_dir()?;
// ancestors() is inclusive: it first yields `current_directory` as-is.
for ancestor in current_directory.ancestors() {
@@ -40,18 +52,20 @@
return Ok(Self::new_at_path(ancestor.to_owned(), config)?);
}
}
- Err(RepoFindError::NotFoundInCurrentDirectoryOrAncestors {
- current_directory,
- })
+ Err(RepoError::NotFound { current_directory })
}
/// To be called after checking that `.hg` is a sub-directory
fn new_at_path(
working_directory: PathBuf,
config: &Config,
- ) -> Result<Self, HgError> {
+ ) -> Result<Self, RepoError> {
let dot_hg = working_directory.join(".hg");
+ let mut repo_config_files = Vec::new();
+ repo_config_files.push(dot_hg.join("hgrc"));
+ repo_config_files.push(dot_hg.join("hgrc-not-shared"));
+
let hg_vfs = Vfs { base: &dot_hg };
let mut reqs = requirements::load_if_exists(hg_vfs)?;
let relative =
@@ -89,7 +103,8 @@
return Err(HgError::corrupted(format!(
".hg/sharedpath points to nonexistent directory {}",
shared_path.display()
- )));
+ ))
+ .into());
}
store_path = shared_path.join("store");
@@ -99,12 +114,15 @@
.contains(requirements::SHARESAFE_REQUIREMENT);
if share_safe && !source_is_share_safe {
- return Err(match config.get(b"safe-mismatch", b"source-not-safe") {
+ return Err(match config
+ .get(b"safe-mismatch", b"source-not-safe")
+ {
Some(b"abort") | None => HgError::abort(
- "share source does not support share-safe requirement"
+ "share source does not support share-safe requirement",
),
- _ => HgError::unsupported("share-safe downgrade")
- });
+ _ => HgError::unsupported("share-safe downgrade"),
+ }
+ .into());
} else if source_is_share_safe && !share_safe {
return Err(
match config.get(b"safe-mismatch", b"source-safe") {
@@ -113,16 +131,24 @@
functionality while the current share does not",
),
_ => HgError::unsupported("share-safe upgrade"),
- },
+ }
+ .into(),
);
}
+
+ if share_safe {
+ repo_config_files.insert(0, shared_path.join("hgrc"))
+ }
}
+ let repo_config = config.combine_with_repo(&repo_config_files)?;
+
let repo = Self {
requirements: reqs,
working_directory,
store: store_path,
dot_hg,
+ config: repo_config,
};
requirements::check(&repo)?;
@@ -138,6 +164,10 @@
&self.requirements
}
+ pub fn config(&self) -> &Config {
+ &self.config
+ }
+
/// For accessing repository files (in `.hg`), except for the store
/// (`.hg/store`).
pub(crate) fn hg_vfs(&self) -> Vfs<'_> {
--- a/rust/rhg/src/error.rs Thu Feb 04 14:29:47 2021 +0100
+++ b/rust/rhg/src/error.rs Thu Feb 04 15:04:53 2021 +0100
@@ -3,7 +3,7 @@
use format_bytes::format_bytes;
use hg::config::{ConfigError, ConfigParseError};
use hg::errors::HgError;
-use hg::repo::RepoFindError;
+use hg::repo::RepoError;
use hg::revlog::revlog::RevlogError;
use hg::utils::files::get_bytes_from_path;
use std::convert::From;
@@ -51,18 +51,17 @@
}
}
-impl From<RepoFindError> for CommandError {
- fn from(error: RepoFindError) -> Self {
+impl From<RepoError> for CommandError {
+ fn from(error: RepoError) -> Self {
match error {
- RepoFindError::NotFoundInCurrentDirectoryOrAncestors {
- current_directory,
- } => CommandError::Abort {
+ RepoError::NotFound { current_directory } => CommandError::Abort {
message: format_bytes!(
b"no repository found in '{}' (.hg not found)!",
get_bytes_from_path(current_directory)
),
},
- RepoFindError::Other(error) => error.into(),
+ RepoError::ConfigParseError(error) => error.into(),
+ RepoError::Other(error) => error.into(),
}
}
}
@@ -70,33 +69,35 @@
impl From<ConfigError> for CommandError {
fn from(error: ConfigError) -> Self {
match error {
- ConfigError::Parse(ConfigParseError {
- origin,
- line,
- bytes,
- }) => {
- let line_message = if let Some(line_number) = line {
- format_bytes!(
- b" at line {}",
- line_number.to_string().into_bytes()
- )
- } else {
- Vec::new()
- };
- CommandError::Abort {
- message: format_bytes!(
- b"config parse error in {}{}: '{}'",
- origin.to_bytes(),
- line_message,
- bytes
- ),
- }
- }
+ ConfigError::Parse(error) => error.into(),
ConfigError::Other(error) => error.into(),
}
}
}
+impl From<ConfigParseError> for CommandError {
+ fn from(error: ConfigParseError) -> Self {
+ let ConfigParseError {
+ origin,
+ line,
+ bytes,
+ } = error;
+ let line_message = if let Some(line_number) = line {
+ format_bytes!(b" at line {}", line_number.to_string().into_bytes())
+ } else {
+ Vec::new()
+ };
+ CommandError::Abort {
+ message: format_bytes!(
+ b"config parse error in {}{}: '{}'",
+ origin.to_bytes(),
+ line_message,
+ bytes
+ ),
+ }
+ }
+}
+
impl From<(RevlogError, &str)> for CommandError {
fn from((err, rev): (RevlogError, &str)) -> CommandError {
match err {