##// END OF EJS Templates
revlog: change _addrevision to return the new revision...
revlog: change _addrevision to return the new revision The node is passed as argument already, so returning it is quite pointless. The revision number on the other is useful as it decouples the caller from the revlog internals. Differential Revision: https://phab.mercurial-scm.org/D9880

File last commit:

r47232:87fd7f47 default
r47234:9f8f0df3 default
Show More
config.rs
344 lines | 11.1 KiB | application/rls-services+xml | RustLexer
Raphaël Gomès
hg-core: add basic config module...
r46803 // config.rs
//
// Copyright 2020
// Valentin Gatien-Baron,
// Raphaël Gomès <rgomes@octobus.net>
//
// This software may be used and distributed according to the terms of the
// GNU General Public License version 2 or any later version.
use super::layer;
Simon Sapin
rust: use HgError in ConfigError...
r47176 use crate::config::layer::{
ConfigError, ConfigLayer, ConfigParseError, ConfigValue,
};
Simon Sapin
rust: Parse system and user configuration...
r47212 use crate::utils::files::get_bytes_from_path;
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47227 use format_bytes::{write_bytes, DisplayBytes};
Simon Sapin
rust: Parse system and user configuration...
r47212 use std::env;
use std::path::{Path, PathBuf};
Raphaël Gomès
hg-core: add basic config module...
r46803
Simon Sapin
rust: Parse system and user configuration...
r47212 use crate::errors::{HgResultExt, IoResultExt};
Raphaël Gomès
hg-core: add basic config module...
r46803
/// Holds the config values for the current repository
/// TODO update this docstring once we support more sources
pub struct Config {
layers: Vec<layer::ConfigLayer>,
}
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47227 impl DisplayBytes for Config {
fn display_bytes(
&self,
out: &mut dyn std::io::Write,
) -> std::io::Result<()> {
Raphaël Gomès
hg-core: add basic config module...
r46803 for (index, layer) in self.layers.iter().rev().enumerate() {
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47227 write_bytes!(
out,
b"==== Layer {} (trusted: {}) ====\n{}",
index,
if layer.trusted {
&b"yes"[..]
} else {
&b"no"[..]
},
layer
Raphaël Gomès
hg-core: add basic config module...
r46803 )?;
}
Ok(())
}
}
pub enum ConfigSource {
/// Absolute path to a config file
AbsPath(PathBuf),
/// Already parsed (from the CLI, env, Python resources, etc.)
Parsed(layer::ConfigLayer),
}
pub fn parse_bool(v: &[u8]) -> Option<bool> {
match v.to_ascii_lowercase().as_slice() {
b"1" | b"yes" | b"true" | b"on" | b"always" => Some(true),
b"0" | b"no" | b"false" | b"off" | b"never" => Some(false),
_ => None,
}
}
impl Config {
Simon Sapin
rust: Parse system and user configuration...
r47212 /// Load system and user configuration from various files.
///
/// This is also affected by some environment variables.
Simon Sapin
rhg: Add support for --config CLI arguments...
r47232 pub fn load(
cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
) -> Result<Self, ConfigError> {
Simon Sapin
rust: Parse system and user configuration...
r47212 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)?
}
}
}
}
Simon Sapin
rhg: Add support for --config CLI arguments...
r47232 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
config.layers.push(layer)
}
Simon Sapin
rust: Parse system and user configuration...
r47212 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(())
}
Raphaël Gomès
hg-core: add basic config module...
r46803 /// Loads in order, which means that the precedence is the same
/// as the order of `sources`.
pub fn load_from_explicit_sources(
sources: Vec<ConfigSource>,
) -> Result<Self, ConfigError> {
let mut layers = vec![];
for source in sources.into_iter() {
match source {
ConfigSource::Parsed(c) => layers.push(c),
ConfigSource::AbsPath(c) => {
// TODO check if it should be trusted
// mercurial/ui.py:427
Simon Sapin
rust: replace read_whole_file with std::fs::read...
r47210 let data = match std::fs::read(&c) {
Raphaël Gomès
hg-core: add basic config module...
r46803 Err(_) => continue, // same as the python code
Ok(data) => data,
};
layers.extend(ConfigLayer::parse(&c, &data)?)
}
}
}
Ok(Config { layers })
}
Simon Sapin
rhg: Parse per-repository configuration...
r47215 /// 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)
Raphaël Gomès
hg-core: add basic config module...
r46803 }
/// Returns an `Err` if the first value found is not a valid boolean.
/// Otherwise, returns an `Ok(option)`, where `option` is the boolean if
/// found, or `None`.
pub fn get_option(
&self,
section: &[u8],
item: &[u8],
Simon Sapin
rust: use HgError in ConfigError...
r47176 ) -> Result<Option<bool>, ConfigParseError> {
Raphaël Gomès
hg-core: add basic config module...
r46803 match self.get_inner(&section, &item) {
Some((layer, v)) => match parse_bool(&v.bytes) {
Some(b) => Ok(Some(b)),
Simon Sapin
rust: use HgError in ConfigError...
r47176 None => Err(ConfigParseError {
Raphaël Gomès
hg-core: add basic config module...
r46803 origin: layer.origin.to_owned(),
line: v.line,
bytes: v.bytes.to_owned(),
}),
},
None => Ok(None),
}
}
/// Returns the corresponding boolean in the config. Returns `Ok(false)`
/// if the value is not found, an `Err` if it's not a valid boolean.
pub fn get_bool(
&self,
section: &[u8],
item: &[u8],
) -> Result<bool, ConfigError> {
Ok(self.get_option(section, item)?.unwrap_or(false))
}
/// Returns the raw value bytes of the first one found, or `None`.
pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&[u8]> {
self.get_inner(section, item)
.map(|(_, value)| value.bytes.as_ref())
}
/// Returns the layer and the value of the first one found, or `None`.
fn get_inner(
&self,
section: &[u8],
item: &[u8],
) -> Option<(&ConfigLayer, &ConfigValue)> {
for layer in self.layers.iter().rev() {
if !layer.trusted {
continue;
}
if let Some(v) = layer.get(&section, &item) {
return Some((&layer, v));
}
}
None
}
/// Get raw values bytes from all layers (even untrusted ones) in order
/// of precedence.
#[cfg(test)]
fn get_all(&self, section: &[u8], item: &[u8]) -> Vec<&[u8]> {
let mut res = vec![];
for layer in self.layers.iter().rev() {
if let Some(v) = layer.get(&section, &item) {
res.push(v.bytes.as_ref());
}
}
res
}
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::fs::File;
use std::io::Write;
#[test]
fn test_include_layer_ordering() {
let tmpdir = tempfile::tempdir().unwrap();
let tmpdir_path = tmpdir.path();
let mut included_file =
File::create(&tmpdir_path.join("included.rc")).unwrap();
included_file.write_all(b"[section]\nitem=value1").unwrap();
let base_config_path = tmpdir_path.join("base.rc");
let mut config_file = File::create(&base_config_path).unwrap();
let data =
b"[section]\nitem=value0\n%include included.rc\nitem=value2";
config_file.write_all(data).unwrap();
let sources = vec![ConfigSource::AbsPath(base_config_path)];
let config = Config::load_from_explicit_sources(sources)
.expect("expected valid config");
let (_, value) = config.get_inner(b"section", b"item").unwrap();
assert_eq!(
value,
&ConfigValue {
bytes: b"value2".to_vec(),
line: Some(4)
}
);
let value = config.get(b"section", b"item").unwrap();
assert_eq!(value, b"value2",);
assert_eq!(
config.get_all(b"section", b"item"),
[b"value2", b"value1", b"value0"]
);
}
}