diff --git a/rust/hg-core/src/config/config.rs b/rust/hg-core/src/config/config.rs --- a/rust/hg-core/src/config/config.rs +++ b/rust/hg-core/src/config/config.rs @@ -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 { - 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 { + 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, diff --git a/rust/hg-core/src/config/layer.rs b/rust/hg-core/src/config/layer.rs --- a/rust/hg-core/src/config/layer.rs +++ b/rust/hg-core/src/config/layer.rs @@ -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), /* TODO cli * TODO defaults (configitems.py) @@ -216,6 +229,7 @@ pub fn to_bytes(&self) -> Vec { match self { ConfigOrigin::File(p) => get_bytes_from_path(p), + ConfigOrigin::CommandLine => b"--config".to_vec(), ConfigOrigin::Environment(e) => format_bytes!(b"${}", e), } } diff --git a/rust/hg-core/src/repo.rs b/rust/hg-core/src/repo.rs --- a/rust/hg-core/src/repo.rs +++ b/rust/hg-core/src/repo.rs @@ -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, + 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 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 { + pub fn find(config: &Config) -> Result { 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 { + ) -> Result { 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<'_> { diff --git a/rust/rhg/src/error.rs b/rust/rhg/src/error.rs --- a/rust/rhg/src/error.rs +++ b/rust/rhg/src/error.rs @@ -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 for CommandError { - fn from(error: RepoFindError) -> Self { +impl From 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 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 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 {