diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -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" diff --git a/rust/hg-core/Cargo.toml b/rust/hg-core/Cargo.toml --- a/rust/hg-core/Cargo.toml +++ b/rust/hg-core/Cargo.toml @@ -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" diff --git a/rust/hg-core/src/config.rs b/rust/hg-core/src/config.rs --- a/rust/hg-core/src/config.rs +++ b/rust/hg-core/src/config.rs @@ -12,3 +12,4 @@ mod config; mod layer; pub use config::Config; +pub use layer::{ConfigError, ConfigParseError}; 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 @@ -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 { + 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( 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 @@ -216,7 +216,7 @@ pub fn to_bytes(&self) -> Vec { match self { ConfigOrigin::File(p) => get_bytes_from_path(p), - ConfigOrigin::Environment(e) => e.to_owned(), + ConfigOrigin::Environment(e) => format_bytes!(b"${}", e), } } } diff --git a/rust/hg-core/src/errors.rs b/rust/hg-core/src/errors.rs --- a/rust/hg-core/src/errors.rs +++ b/rust/hg-core/src/errors.rs @@ -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"), } } } diff --git a/rust/hg-core/src/utils.rs b/rust/hg-core/src/utils.rs --- a/rust/hg-core/src/utils.rs +++ b/rust/hg-core/src/utils.rs @@ -184,3 +184,10 @@ context: IoErrorContext::CurrentDir, }) } + +pub fn current_exe() -> Result { + std::env::current_exe().map_err(|error| HgError::IoError { + error, + context: IoErrorContext::CurrentExe, + }) +}