##// END OF EJS Templates
dirstate-v2: complain early on docket name collision...
dirstate-v2: complain early on docket name collision The alternative is that the dirstate gets deleted so the corruption persists and is hard to investigate. This happened to me in tests, where the dirstate names are taken from file, since the file got reverted. I expect this can also happen in prod with non-trivial probability (1/4 billion).

File last commit:

r50831:2cd8352f default
r50992:ca9d65d6 stable
Show More
config.rs
654 lines | 21.4 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: Move config value parsing functions to a new module...
r47344 use super::values;
Simon Sapin
rust: use HgError in ConfigError...
r47176 use crate::config::layer::{
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 ConfigError, ConfigLayer, ConfigOrigin, ConfigValue,
Simon Sapin
rust: use HgError in ConfigError...
r47176 };
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 use crate::config::plain_info::PlainInfo;
Simon Sapin
rust: Introduce a get_bytes_from_os_str utility function...
r47338 use crate::utils::files::get_bytes_from_os_str;
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 use format_bytes::{write_bytes, DisplayBytes};
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467 use std::collections::HashSet;
Simon Sapin
rust: Parse system and user configuration...
r47212 use std::env;
Simon Sapin
rhg: Add more conversions between error types...
r47555 use std::fmt;
Simon Sapin
rust: Parse system and user configuration...
r47212 use std::path::{Path, PathBuf};
Simon Sapin
rust: Add config parsing support for more value types...
r47339 use std::str;
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
Simon Sapin
rhg: Add support for the HGRCSKIPREPO environment variable...
r47475 #[derive(Clone)]
Raphaël Gomès
hg-core: add basic config module...
r46803 pub struct Config {
layers: Vec<layer::ConfigLayer>,
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 plain: PlainInfo,
Raphaël Gomès
hg-core: add basic config module...
r46803 }
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 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...
r47249 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),
}
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 #[derive(Debug)]
pub struct ConfigValueParseError {
pub origin: ConfigOrigin,
pub line: Option<usize>,
pub section: Vec<u8>,
pub item: Vec<u8>,
pub value: Vec<u8>,
pub expected_type: &'static str,
}
Simon Sapin
rhg: Add more conversions between error types...
r47555 impl fmt::Display for ConfigValueParseError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO: add origin and line number information, here and in
// corresponding python code
write!(
f,
"config error: {}.{} is not a {} ('{}')",
String::from_utf8_lossy(&self.section),
String::from_utf8_lossy(&self.item),
self.expected_type,
String::from_utf8_lossy(&self.value)
)
}
}
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 /// Returns true if the config item is disabled by PLAIN or PLAINEXCEPT
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 fn should_ignore(plain: &PlainInfo, section: &[u8], item: &[u8]) -> bool {
// duplication with [_applyconfig] in [ui.py],
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 if !plain.is_plain() {
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 return false;
}
if section == b"alias" {
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 return plain.plainalias();
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 }
if section == b"revsetalias" {
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 return plain.plainrevsetalias();
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 }
if section == b"templatealias" {
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 return plain.plaintemplatealias();
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 }
if section == b"ui" {
let to_delete: &[&[u8]] = &[
b"debug",
b"fallbackencoding",
b"quiet",
b"slash",
b"logtemplate",
b"message-output",
b"statuscopies",
b"style",
b"traceback",
b"verbose",
];
return to_delete.contains(&item);
}
let sections_to_delete: &[&[u8]] =
&[b"defaults", b"commands", b"command-templates"];
return sections_to_delete.contains(&section);
}
Raphaël Gomès
hg-core: add basic config module...
r46803 impl Config {
Simon Sapin
rhg: Pass a &Config to Ui::new...
r49581 /// The configuration to use when printing configuration-loading errors
pub fn empty() -> Self {
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 Self {
layers: Vec::new(),
plain: PlainInfo::empty(),
}
Simon Sapin
rhg: Pass a &Config to Ui::new...
r49581 }
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.
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 pub fn load_non_repo() -> Result<Self, ConfigError> {
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 let mut config = Self::empty();
Simon Sapin
rust: Parse system and user configuration...
r47212 let opt_rc_path = env::var_os("HGRCPATH");
// HGRCPATH replaces system config
if opt_rc_path.is_none() {
config.add_system_config()?
}
Simon Sapin
rhg: Add environment variables for fallback configuration...
r47456
Simon Sapin
rust: Parse system and user configuration...
r47212 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");
Simon Sapin
rhg: Add environment variables for fallback configuration...
r47456
// These are set by `run-tests.py --rhg` to enable fallback for the
// entire test suite. Alternatives would be setting configuration
// through `$HGRCPATH` but some tests override that, or changing the
// `hg` shell alias to include `--config` but that disrupts tests that
// print command lines and check expected output.
config.add_for_environment_variable(
"RHG_ON_UNSUPPORTED",
b"rhg",
b"on-unsupported",
);
config.add_for_environment_variable(
"RHG_FALLBACK_EXECUTABLE",
b"rhg",
b"fallback-executable",
);
Simon Sapin
rust: Parse system and user configuration...
r47212 // 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)?
}
}
}
}
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 Ok(config)
}
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 pub fn load_cli_args(
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 &mut self,
cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 color_arg: Option<Vec<u8>>,
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 ) -> Result<(), ConfigError> {
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 if let Some(layer) = ConfigLayer::parse_cli_args(cli_config_args)? {
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 self.layers.push(layer)
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 }
Simon Sapin
rhg: Add parsing for the --color global CLI argument...
r49583 if let Some(arg) = color_arg {
let mut layer = ConfigLayer::new(ConfigOrigin::CommandLineColor);
layer.add(b"ui"[..].into(), b"color"[..].into(), arg, None);
self.layers.push(layer)
}
Pulkit Goyal
rhg: split non_repo_config and `--config` loading in different functions...
r48198 Ok(())
Simon Sapin
rust: Parse system and user configuration...
r47212 }
fn add_trusted_dir(&mut self, path: &Path) -> Result<(), ConfigError> {
if let Some(entries) = std::fs::read_dir(path)
Simon Sapin
rust: Add a log file rotation utility...
r47341 .when_reading_file(path)
Simon Sapin
rust: Parse system and user configuration...
r47212 .io_not_found_as_none()?
{
Simon Sapin
rhg: Sort config files when adding a directory...
r47466 let mut file_paths = entries
.map(|result| {
result.when_reading_file(path).map(|entry| entry.path())
})
.collect::<Result<Vec<_>, _>>()?;
file_paths.sort();
for file_path in &file_paths {
Simon Sapin
rust: Parse system and user configuration...
r47212 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> {
Simon Sapin
rust: Add a log file rotation utility...
r47341 if let Some(data) = std::fs::read(path)
.when_reading_file(path)
.io_not_found_as_none()?
Simon Sapin
rust: Parse system and user configuration...
r47212 {
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(),
Simon Sapin
rust: Introduce a get_bytes_from_os_str utility function...
r47338 get_bytes_from_os_str(value),
Simon Sapin
rust: Parse system and user configuration...
r47212 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)?)
}
}
}
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 Ok(Config {
layers,
plain: PlainInfo::empty(),
})
Raphaël Gomès
hg-core: add basic config module...
r46803 }
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,
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 plain: PlainInfo::empty(),
Simon Sapin
rhg: Parse per-repository configuration...
r47215 };
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 }
Arseniy Alekseyev
rhg: central treatment of PLAIN and PLAINEXCEPT
r50407 pub fn apply_plain(&mut self, plain: PlainInfo) {
self.plain = plain;
}
Simon Sapin
rust: Add config parsing support for more value types...
r47339 fn get_parse<'config, T: 'config>(
&'config self,
Raphaël Gomès
hg-core: add basic config module...
r46803 section: &[u8],
item: &[u8],
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 expected_type: &'static str,
Simon Sapin
rust: Add config parsing support for more value types...
r47339 parse: impl Fn(&'config [u8]) -> Option<T>,
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 ) -> Result<Option<T>, ConfigValueParseError> {
Raphaël Gomès
hg-core: add basic config module...
r46803 match self.get_inner(&section, &item) {
Simon Sapin
rust: Add config parsing support for more value types...
r47339 Some((layer, v)) => match parse(&v.bytes) {
Raphaël Gomès
hg-core: add basic config module...
r46803 Some(b) => Ok(Some(b)),
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 None => Err(ConfigValueParseError {
Raphaël Gomès
hg-core: add basic config module...
r46803 origin: layer.origin.to_owned(),
line: v.line,
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 value: v.bytes.to_owned(),
section: section.to_owned(),
item: item.to_owned(),
expected_type,
Raphaël Gomès
hg-core: add basic config module...
r46803 }),
},
None => Ok(None),
}
}
Simon Sapin
rust: Add config parsing support for more value types...
r47339 /// Returns an `Err` if the first value found is not a valid UTF-8 string.
/// Otherwise, returns an `Ok(value)` if found, or `None`.
pub fn get_str(
&self,
section: &[u8],
item: &[u8],
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 ) -> Result<Option<&str>, ConfigValueParseError> {
self.get_parse(section, item, "ASCII or UTF-8 string", |value| {
str::from_utf8(value).ok()
})
Simon Sapin
rust: Add config parsing support for more value types...
r47339 }
/// Returns an `Err` if the first value found is not a valid unsigned
/// integer. Otherwise, returns an `Ok(value)` if found, or `None`.
pub fn get_u32(
&self,
section: &[u8],
item: &[u8],
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 ) -> Result<Option<u32>, ConfigValueParseError> {
self.get_parse(section, item, "valid integer", |value| {
Simon Sapin
rust: Add config parsing support for more value types...
r47339 str::from_utf8(value).ok()?.parse().ok()
})
}
/// Returns an `Err` if the first value found is not a valid file size
/// value such as `30` (default unit is bytes), `7 MB`, or `42.5 kb`.
/// Otherwise, returns an `Ok(value_in_bytes)` if found, or `None`.
pub fn get_byte_size(
&self,
section: &[u8],
item: &[u8],
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 ) -> Result<Option<u64>, ConfigValueParseError> {
Simon Sapin
rust: Move config value parsing functions to a new module...
r47344 self.get_parse(section, item, "byte quantity", values::parse_byte_size)
Simon Sapin
rust: Add config parsing support for more value types...
r47339 }
/// 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: Add a `ConfigValueParseError` variant to common errors...
r47340 ) -> Result<Option<bool>, ConfigValueParseError> {
Simon Sapin
rust: Move config value parsing functions to a new module...
r47344 self.get_parse(section, item, "boolean", values::parse_bool)
Simon Sapin
rust: Add config parsing support for more value types...
r47339 }
Raphaël Gomès
hg-core: add basic config module...
r46803 /// 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],
Simon Sapin
rust: Add a `ConfigValueParseError` variant to common errors...
r47340 ) -> Result<bool, ConfigValueParseError> {
Raphaël Gomès
hg-core: add basic config module...
r46803 Ok(self.get_option(section, item)?.unwrap_or(false))
}
Raphaël Gomès
rust: add function to check if an extension is enabled...
r49242 /// Returns `true` if the extension is enabled, `false` otherwise
pub fn is_extension_enabled(&self, extension: &[u8]) -> bool {
let value = self.get(b"extensions", extension);
match value {
Some(c) => !c.starts_with(b"!"),
None => false,
}
}
Simon Sapin
rhg: Port Python’s `ui.configlist` as `Config::get_list`...
r48762 /// If there is an `item` value in `section`, parse and return a list of
/// byte strings.
pub fn get_list(
&self,
section: &[u8],
item: &[u8],
) -> Option<Vec<Vec<u8>>> {
self.get(section, item).map(values::parse_list)
}
Raphaël Gomès
hg-core: add basic config module...
r46803 /// 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())
}
Simon Sapin
rhg: Add support for colored output...
r49584 /// Returns the raw value bytes of the first one found, or `None`.
pub fn get_with_origin(
&self,
section: &[u8],
item: &[u8],
) -> Option<(&[u8], &ConfigOrigin)> {
self.get_inner(section, item)
.map(|(layer, value)| (value.bytes.as_ref(), &layer.origin))
}
Raphaël Gomès
hg-core: add basic config module...
r46803 /// Returns the layer and the value of the first one found, or `None`.
fn get_inner(
&self,
section: &[u8],
item: &[u8],
) -> Option<(&ConfigLayer, &ConfigValue)> {
Arseniy Alekseyev
rhg: centralize PlainInfo
r50408 // Filter out the config items that are hidden by [PLAIN].
// This differs from python hg where we delete them from the config.
Arseniy Alekseyev
rhg: support tweakdefaults
r50409 let should_ignore = should_ignore(&self.plain, &section, &item);
Raphaël Gomès
hg-core: add basic config module...
r46803 for layer in self.layers.iter().rev() {
if !layer.trusted {
continue;
}
Arseniy Alekseyev
rhg: support tweakdefaults
r50409 //The [PLAIN] config should not affect the defaults.
//
// However, PLAIN should also affect the "tweaked" defaults (unless
// "tweakdefault" is part of "HGPLAINEXCEPT").
//
// In practice the tweak-default layer is only added when it is
// relevant, so we can safely always take it into
// account here.
if should_ignore && !(layer.origin == ConfigOrigin::Tweakdefaults)
{
continue;
}
Raphaël Gomès
hg-core: add basic config module...
r46803 if let Some(v) = layer.get(&section, &item) {
return Some((&layer, v));
}
}
None
}
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467 /// Return all keys defined for the given section
pub fn get_section_keys(&self, section: &[u8]) -> HashSet<&[u8]> {
self.layers
.iter()
.flat_map(|layer| layer.iter_keys(section))
.collect()
}
Simon Sapin
rhg: [encode] and [decode] config sections are not supported...
r49162 /// Returns whether any key is defined in the given section
pub fn has_non_empty_section(&self, section: &[u8]) -> bool {
self.layers
.iter()
.any(|layer| layer.has_non_empty_section(section))
}
Simon Sapin
rhg: Add support for ui.ignore and ui.ignore.* config...
r49282 /// Yields (key, value) pairs for everything in the given section
pub fn iter_section<'a>(
&'a self,
section: &'a [u8],
) -> impl Iterator<Item = (&[u8], &[u8])> + 'a {
// TODO: Use `Iterator`’s `.peekable()` when its `peek_mut` is
// available:
// https://doc.rust-lang.org/nightly/std/iter/struct.Peekable.html#method.peek_mut
struct Peekable<I: Iterator> {
iter: I,
/// Remember a peeked value, even if it was None.
peeked: Option<Option<I::Item>>,
}
impl<I: Iterator> Peekable<I> {
fn new(iter: I) -> Self {
Self { iter, peeked: None }
}
fn next(&mut self) {
self.peeked = None
}
fn peek_mut(&mut self) -> Option<&mut I::Item> {
let iter = &mut self.iter;
self.peeked.get_or_insert_with(|| iter.next()).as_mut()
}
}
// Deduplicate keys redefined in multiple layers
let mut keys_already_seen = HashSet::new();
let mut key_is_new =
move |&(key, _value): &(&'a [u8], &'a [u8])| -> bool {
keys_already_seen.insert(key)
};
// This is similar to `flat_map` + `filter_map`, except with a single
// closure that owns `key_is_new` (and therefore the
// `keys_already_seen` set):
let mut layer_iters = Peekable::new(
self.layers
.iter()
.rev()
.map(move |layer| layer.iter_section(section)),
);
std::iter::from_fn(move || loop {
if let Some(pair) = layer_iters.peek_mut()?.find(&mut key_is_new) {
return Some(pair);
} else {
layer_iters.next();
}
})
}
Raphaël Gomès
hg-core: add basic config module...
r46803 /// 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
}
Arseniy Alekseyev
rhg: support tweakdefaults
r50409
// a config layer that's introduced by ui.tweakdefaults
fn tweakdefaults_layer() -> ConfigLayer {
let mut layer = ConfigLayer::new(ConfigOrigin::Tweakdefaults);
let mut add = |section: &[u8], item: &[u8], value: &[u8]| {
layer.add(
section[..].into(),
item[..].into(),
value[..].into(),
None,
);
};
// duplication of [tweakrc] from [ui.py]
add(b"ui", b"rollback", b"False");
add(b"ui", b"statuscopies", b"yes");
add(b"ui", b"interface", b"curses");
add(b"ui", b"relative-paths", b"yes");
add(b"commands", b"grep.all-files", b"True");
add(b"commands", b"update.check", b"noconflict");
add(b"commands", b"status.verbose", b"True");
add(b"commands", b"resolve.explicit-re-merge", b"True");
add(b"git", b"git", b"1");
add(b"git", b"showfunc", b"1");
add(b"git", b"word-diff", b"1");
return layer;
}
// introduce the tweaked defaults as implied by ui.tweakdefaults
pub fn tweakdefaults<'a>(&mut self) -> () {
self.layers.insert(0, Config::tweakdefaults_layer());
}
Raphaël Gomès
hg-core: add basic config module...
r46803 }
#[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 =
Simon Sapin
rust: Add config parsing support for more value types...
r47339 b"[section]\nitem=value0\n%include included.rc\nitem=value2\n\
[section2]\ncount = 4\nsize = 1.5 KB\nnot-count = 1.5\nnot-size = 1 ub";
Raphaël Gomès
hg-core: add basic config module...
r46803 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"]
);
Simon Sapin
rust: Add config parsing support for more value types...
r47339
assert_eq!(config.get_u32(b"section2", b"count").unwrap(), Some(4));
assert_eq!(
config.get_byte_size(b"section2", b"size").unwrap(),
Some(1024 + 512)
);
assert!(config.get_u32(b"section2", b"not-count").is_err());
assert!(config.get_byte_size(b"section2", b"not-size").is_err());
Raphaël Gomès
hg-core: add basic config module...
r46803 }
}