rust: Parse system and user configuration
CLI `--config` argument parsing is still missing, as is per-repo config
Differential Revision: https://phab.mercurial-scm.org/D9961
--- a/rust/Cargo.lock Mon Feb 01 13:32:00 2021 +0100
+++ b/rust/Cargo.lock Thu Feb 04 13:16:21 2021 +0100
@@ -306,6 +306,7 @@
"derive_more 0.99.11 (registry+https://github.com/rust-lang/crates.io-index)",
"flate2 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
"format-bytes 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "home 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"im-rc 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -337,6 +338,14 @@
]
[[package]]
+name = "home"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -963,6 +972,7 @@
"checksum getrandom 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
"checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
"checksum hermit-abi 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
+"checksum home 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
"checksum im-rc 15.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f"
"checksum itertools 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
--- a/rust/hg-core/Cargo.toml Mon Feb 01 13:32:00 2021 +0100
+++ b/rust/hg-core/Cargo.toml Thu Feb 04 13:16:21 2021 +0100
@@ -12,6 +12,7 @@
bytes-cast = "0.1"
byteorder = "1.3.4"
derive_more = "0.99"
+home = "0.5"
im-rc = "15.0.*"
lazy_static = "1.4.0"
memchr = "2.3.3"
--- a/rust/hg-core/src/config.rs Mon Feb 01 13:32:00 2021 +0100
+++ b/rust/hg-core/src/config.rs Thu Feb 04 13:16:21 2021 +0100
@@ -12,3 +12,4 @@
mod config;
mod layer;
pub use config::Config;
+pub use layer::{ConfigError, ConfigParseError};
--- a/rust/hg-core/src/config/config.rs Mon Feb 01 13:32:00 2021 +0100
+++ b/rust/hg-core/src/config/config.rs Thu Feb 04 13:16:21 2021 +0100
@@ -11,8 +11,11 @@
use crate::config::layer::{
ConfigError, ConfigLayer, ConfigParseError, ConfigValue,
};
-use std::path::PathBuf;
+use crate::utils::files::get_bytes_from_path;
+use std::env;
+use std::path::{Path, PathBuf};
+use crate::errors::{HgResultExt, IoResultExt};
use crate::repo::Repo;
/// Holds the config values for the current repository
@@ -50,6 +53,124 @@
}
impl Config {
+ /// Load system and user configuration from various files.
+ ///
+ /// This is also affected by some environment variables.
+ ///
+ /// TODO: add a parameter for `--config` CLI arguments
+ pub fn load() -> Result<Self, ConfigError> {
+ let mut config = Self { layers: Vec::new() };
+ let opt_rc_path = env::var_os("HGRCPATH");
+ // HGRCPATH replaces system config
+ if opt_rc_path.is_none() {
+ config.add_system_config()?
+ }
+ config.add_for_environment_variable("EDITOR", b"ui", b"editor");
+ config.add_for_environment_variable("VISUAL", b"ui", b"editor");
+ config.add_for_environment_variable("PAGER", b"pager", b"pager");
+ // HGRCPATH replaces user config
+ if opt_rc_path.is_none() {
+ config.add_user_config()?
+ }
+ if let Some(rc_path) = &opt_rc_path {
+ for path in env::split_paths(rc_path) {
+ if !path.as_os_str().is_empty() {
+ if path.is_dir() {
+ config.add_trusted_dir(&path)?
+ } else {
+ config.add_trusted_file(&path)?
+ }
+ }
+ }
+ }
+ Ok(config)
+ }
+
+ fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
+ if let Some(entries) = std::fs::read_dir(path)
+ .for_file(path)
+ .io_not_found_as_none()?
+ {
+ for entry in entries {
+ let file_path = entry.for_file(path)?.path();
+ if file_path.extension() == Some(std::ffi::OsStr::new("rc")) {
+ self.add_trusted_file(&file_path)?
+ }
+ }
+ }
+ Ok(())
+ }
+
+ fn add_trusted_file(&mut self, path: &Path) -> Result<(), ConfigError> {
+ if let Some(data) =
+ std::fs::read(path).for_file(path).io_not_found_as_none()?
+ {
+ self.layers.extend(ConfigLayer::parse(path, &data)?)
+ }
+ Ok(())
+ }
+
+ fn add_for_environment_variable(
+ &mut self,
+ var: &str,
+ section: &[u8],
+ key: &[u8],
+ ) {
+ if let Some(value) = env::var_os(var) {
+ let origin = layer::ConfigOrigin::Environment(var.into());
+ let mut layer = ConfigLayer::new(origin);
+ layer.add(
+ section.to_owned(),
+ key.to_owned(),
+ // `value` is not a path but this works for any `OsStr`:
+ get_bytes_from_path(value),
+ None,
+ );
+ self.layers.push(layer)
+ }
+ }
+
+ #[cfg(unix)] // TODO: other platforms
+ fn add_system_config(&mut self) -> Result<(), ConfigError> {
+ let mut add_for_prefix = |prefix: &Path| -> Result<(), ConfigError> {
+ let etc = prefix.join("etc").join("mercurial");
+ self.add_trusted_file(&etc.join("hgrc"))?;
+ self.add_trusted_dir(&etc.join("hgrc.d"))
+ };
+ let root = Path::new("/");
+ // TODO: use `std::env::args_os().next().unwrap()` a.k.a. argv[0]
+ // instead? TODO: can this be a relative path?
+ let hg = crate::utils::current_exe()?;
+ // TODO: this order (per-installation then per-system) matches
+ // `systemrcpath()` in `mercurial/scmposix.py`, but
+ // `mercurial/helptext/config.txt` suggests it should be reversed
+ if let Some(installation_prefix) = hg.parent().and_then(Path::parent) {
+ if installation_prefix != root {
+ add_for_prefix(&installation_prefix)?
+ }
+ }
+ add_for_prefix(root)?;
+ Ok(())
+ }
+
+ #[cfg(unix)] // TODO: other plateforms
+ fn add_user_config(&mut self) -> Result<(), ConfigError> {
+ let opt_home = home::home_dir();
+ if let Some(home) = &opt_home {
+ self.add_trusted_file(&home.join(".hgrc"))?
+ }
+ let darwin = cfg!(any(target_os = "macos", target_os = "ios"));
+ if !darwin {
+ if let Some(config_home) = env::var_os("XDG_CONFIG_HOME")
+ .map(PathBuf::from)
+ .or_else(|| opt_home.map(|home| home.join(".config")))
+ {
+ self.add_trusted_file(&config_home.join("hg").join("hgrc"))?
+ }
+ }
+ Ok(())
+ }
+
/// Loads in order, which means that the precedence is the same
/// as the order of `sources`.
pub fn load_from_explicit_sources(
--- a/rust/hg-core/src/config/layer.rs Mon Feb 01 13:32:00 2021 +0100
+++ b/rust/hg-core/src/config/layer.rs Thu Feb 04 13:16:21 2021 +0100
@@ -216,7 +216,7 @@
pub fn to_bytes(&self) -> Vec<u8> {
match self {
ConfigOrigin::File(p) => get_bytes_from_path(p),
- ConfigOrigin::Environment(e) => e.to_owned(),
+ ConfigOrigin::Environment(e) => format_bytes!(b"${}", e),
}
}
}
--- a/rust/hg-core/src/errors.rs Mon Feb 01 13:32:00 2021 +0100
+++ b/rust/hg-core/src/errors.rs Thu Feb 04 13:16:21 2021 +0100
@@ -26,11 +26,13 @@
/// Details about where an I/O error happened
#[derive(Debug, derive_more::From)]
pub enum IoErrorContext {
- /// A filesystem operation returned `std::io::Error`
+ /// A filesystem operation for the given file
#[from]
File(std::path::PathBuf),
- /// `std::env::current_dir` returned `std::io::Error`
+ /// `std::env::current_dir`
CurrentDir,
+ /// `std::env::current_exe`
+ CurrentExe,
}
impl HgError {
@@ -69,6 +71,7 @@
match self {
IoErrorContext::File(path) => path.display().fmt(f),
IoErrorContext::CurrentDir => f.write_str("current directory"),
+ IoErrorContext::CurrentExe => f.write_str("current executable"),
}
}
}
--- a/rust/hg-core/src/utils.rs Mon Feb 01 13:32:00 2021 +0100
+++ b/rust/hg-core/src/utils.rs Thu Feb 04 13:16:21 2021 +0100
@@ -184,3 +184,10 @@
context: IoErrorContext::CurrentDir,
})
}
+
+pub fn current_exe() -> Result<std::path::PathBuf, HgError> {
+ std::env::current_exe().map_err(|error| HgError::IoError {
+ error,
+ context: IoErrorContext::CurrentExe,
+ })
+}