##// END OF EJS Templates
dirstate-v2: Add storage space for nanoseconds precision in file mtimes...
dirstate-v2: Add storage space for nanoseconds precision in file mtimes For now the sub-second component is always set to zero for tracked files and symlinks. (The mtime of directories for the `readdir`-skipping optimization is a different code path and already uses the full precision available.) This extra storage uses the space previously freed by replacing the 32-bit `mode` field by two bits in the existing `flags` field, so the overall size of nodes is unchanged. (This space had been left as padding for this purpose.) Also move things around in the node layout and documentation to have less duplication. Now that they have the same representation, directory mtime and file mtime are kept in the same field. (Only either one can exist for a given node.) Differential Revision: https://phab.mercurial-scm.org/D11655

File last commit:

r48199:6e49769b default
r49033:308d9c24 default
Show More
layer.rs
323 lines | 10.9 KiB | application/rls-services+xml | RustLexer
Raphaël Gomès
hg-core: add basic config module...
r46803 // layer.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.
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 use crate::errors::HgError;
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 use crate::exit_codes::CONFIG_PARSE_ERROR_ABORT;
Simon Sapin
rust: replace read_whole_file with std::fs::read...
r47210 use crate::utils::files::{get_bytes_from_path, get_path_from_bytes};
Simon Sapin
rhg: Align config file parse error formatting with Python...
r47465 use format_bytes::{format_bytes, write_bytes, DisplayBytes};
Raphaël Gomès
hg-core: add basic config module...
r46803 use lazy_static::lazy_static;
use regex::bytes::Regex;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
lazy_static! {
static ref SECTION_RE: Regex = make_regex(r"^\[([^\[]+)\]");
static ref ITEM_RE: Regex = make_regex(r"^([^=\s][^=]*?)\s*=\s*((.*\S)?)");
/// Continuation whitespace
static ref CONT_RE: Regex = make_regex(r"^\s+(\S|\S.*\S)\s*$");
static ref EMPTY_RE: Regex = make_regex(r"^(;|#|\s*$)");
static ref COMMENT_RE: Regex = make_regex(r"^(;|#)");
/// A directive that allows for removing previous entries
static ref UNSET_RE: Regex = make_regex(r"^%unset\s+(\S+)");
/// A directive that allows for including other config files
static ref INCLUDE_RE: Regex = make_regex(r"^%include\s+(\S|\S.*\S)\s*$");
}
/// All config values separated by layers of precedence.
/// Each config source may be split in multiple layers if `%include` directives
/// are used.
/// TODO detail the general precedence
#[derive(Clone)]
pub struct ConfigLayer {
/// Mapping of the sections to their items
sections: HashMap<Vec<u8>, ConfigItem>,
/// All sections (and their items/values) in a layer share the same origin
pub origin: ConfigOrigin,
/// Whether this layer comes from a trusted user or group
pub trusted: bool,
}
impl ConfigLayer {
pub fn new(origin: ConfigOrigin) -> Self {
ConfigLayer {
sections: HashMap::new(),
trusted: true, // TODO check
origin,
}
}
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 /// Parse `--config` CLI arguments and return a layer if there’s any
pub(crate) fn parse_cli_args(
cli_config_args: impl IntoIterator<Item = impl AsRef<[u8]>>,
) -> Result<Option<Self>, ConfigError> {
fn parse_one(arg: &[u8]) -> Option<(Vec<u8>, Vec<u8>, Vec<u8>)> {
use crate::utils::SliceExt;
Simon Sapin
rhg: add limited support for the `config` sub-command...
r47255 let (section_and_item, value) = arg.split_2(b'=')?;
let (section, item) = section_and_item.trim().split_2(b'.')?;
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 Some((
section.to_owned(),
item.to_owned(),
value.trim().to_owned(),
))
}
let mut layer = Self::new(ConfigOrigin::CommandLine);
for arg in cli_config_args {
let arg = arg.as_ref();
if let Some((section, item, value)) = parse_one(arg) {
layer.add(section, item, value, None);
} else {
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 Err(HgError::abort(
format!(
"abort: malformed --config option: '{}' \
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 (use --config section.name=value)",
Pulkit Goyal
rhg: add exit code to HgError::Abort()...
r48199 String::from_utf8_lossy(arg),
),
CONFIG_PARSE_ERROR_ABORT,
))?
Simon Sapin
rhg: Add support for --config CLI arguments...
r47254 }
}
if layer.sections.is_empty() {
Ok(None)
} else {
Ok(Some(layer))
}
}
Simon Sapin
rhg: Parse per-repository configuration...
r47215 /// 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
}
}
Raphaël Gomès
hg-core: add basic config module...
r46803 /// Add an entry to the config, overwriting the old one if already present.
pub fn add(
&mut self,
section: Vec<u8>,
item: Vec<u8>,
value: Vec<u8>,
line: Option<usize>,
) {
self.sections
.entry(section)
.or_insert_with(|| HashMap::new())
.insert(item, ConfigValue { bytes: value, line });
}
/// Returns the config value in `<section>.<item>` if it exists
pub fn get(&self, section: &[u8], item: &[u8]) -> Option<&ConfigValue> {
Some(self.sections.get(section)?.get(item)?)
}
Simon Sapin
rhg: Fall back to Python if unsupported extensions are enabled...
r47467 /// Returns the keys defined in the given section
pub fn iter_keys(&self, section: &[u8]) -> impl Iterator<Item = &[u8]> {
self.sections
.get(section)
.into_iter()
.flat_map(|section| section.keys().map(|vec| &**vec))
}
Raphaël Gomès
hg-core: add basic config module...
r46803 pub fn is_empty(&self) -> bool {
self.sections.is_empty()
}
/// Returns a `Vec` of layers in order of precedence (so, in read order),
/// recursively parsing the `%include` directives if any.
pub fn parse(src: &Path, data: &[u8]) -> Result<Vec<Self>, ConfigError> {
let mut layers = vec![];
// Discard byte order mark if any
let data = if data.starts_with(b"\xef\xbb\xbf") {
&data[3..]
} else {
data
};
// TODO check if it's trusted
let mut current_layer = Self::new(ConfigOrigin::File(src.to_owned()));
let mut lines_iter =
data.split(|b| *b == b'\n').enumerate().peekable();
let mut section = b"".to_vec();
while let Some((index, bytes)) = lines_iter.next() {
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 let line = Some(index + 1);
Raphaël Gomès
hg-core: add basic config module...
r46803 if let Some(m) = INCLUDE_RE.captures(&bytes) {
let filename_bytes = &m[1];
Simon Sapin
rhg: Add support for environment variables in config include paths...
r47476 let filename_bytes = crate::utils::expand_vars(filename_bytes);
Simon Sapin
rust: Remove unnecessary check for absolute path before joining...
r47211 // `Path::parent` only fails for the root directory,
Simon Sapin
rhg: Parse per-repository configuration...
r47215 // which `src` can’t be since we’ve managed to open it as a
// file.
Simon Sapin
rust: Remove unnecessary check for absolute path before joining...
r47211 let dir = src
.parent()
.expect("Path::parent fail on a file we’ve read");
Simon Sapin
rhg: Parse per-repository configuration...
r47215 // `Path::join` with an absolute argument correctly ignores the
// base path
Simon Sapin
rust: Remove unnecessary check for absolute path before joining...
r47211 let filename = dir.join(&get_path_from_bytes(&filename_bytes));
Simon Sapin
rhg: Silently ignore missing files in config %include...
r47477 match std::fs::read(&filename) {
Ok(data) => {
layers.push(current_layer);
layers.extend(Self::parse(&filename, &data)?);
current_layer =
Self::new(ConfigOrigin::File(src.to_owned()));
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 }
Simon Sapin
rhg: Silently ignore missing files in config %include...
r47477 Err(error) => {
if error.kind() != std::io::ErrorKind::NotFound {
return Err(ConfigParseError {
origin: ConfigOrigin::File(src.to_owned()),
line,
message: format_bytes!(
b"cannot include {} ({})",
filename_bytes,
format_bytes::Utf8(error)
),
}
.into());
}
}
}
Raphaël Gomès
hg-core: add basic config module...
r46803 } else if let Some(_) = EMPTY_RE.captures(&bytes) {
} else if let Some(m) = SECTION_RE.captures(&bytes) {
section = m[1].to_vec();
} else if let Some(m) = ITEM_RE.captures(&bytes) {
let item = m[1].to_vec();
let mut value = m[2].to_vec();
loop {
match lines_iter.peek() {
None => break,
Some((_, v)) => {
if let Some(_) = COMMENT_RE.captures(&v) {
} else if let Some(_) = CONT_RE.captures(&v) {
value.extend(b"\n");
value.extend(&m[1]);
} else {
break;
}
}
};
lines_iter.next();
}
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 current_layer.add(section.clone(), item, value, line);
Raphaël Gomès
hg-core: add basic config module...
r46803 } else if let Some(m) = UNSET_RE.captures(&bytes) {
if let Some(map) = current_layer.sections.get_mut(&section) {
map.remove(&m[1]);
}
} else {
Simon Sapin
rhg: Align config file parse error formatting with Python...
r47465 let message = if bytes.starts_with(b" ") {
format_bytes!(b"unexpected leading whitespace: {}", bytes)
} else {
bytes.to_owned()
};
Simon Sapin
rust: use HgError in ConfigError...
r47176 return Err(ConfigParseError {
Raphaël Gomès
hg-core: add basic config module...
r46803 origin: ConfigOrigin::File(src.to_owned()),
Simon Sapin
rhg: Align with Python on some more error messages...
r47469 line,
Simon Sapin
rhg: Align config file parse error formatting with Python...
r47465 message,
Simon Sapin
rust: use HgError in ConfigError...
r47176 }
.into());
Raphaël Gomès
hg-core: add basic config module...
r46803 }
}
if !current_layer.is_empty() {
layers.push(current_layer);
}
Ok(layers)
}
}
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 impl DisplayBytes for ConfigLayer {
fn display_bytes(
&self,
out: &mut dyn std::io::Write,
) -> std::io::Result<()> {
Raphaël Gomès
hg-core: add basic config module...
r46803 let mut sections: Vec<_> = self.sections.iter().collect();
sections.sort_by(|e0, e1| e0.0.cmp(e1.0));
for (section, items) in sections.into_iter() {
let mut items: Vec<_> = items.into_iter().collect();
items.sort_by(|e0, e1| e0.0.cmp(e1.0));
for (item, config_entry) in items {
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 write_bytes!(
out,
b"{}.{}={} # {}\n",
section,
item,
&config_entry.bytes,
&self.origin,
Raphaël Gomès
hg-core: add basic config module...
r46803 )?
}
}
Ok(())
}
}
/// Mapping of section item to value.
/// In the following:
/// ```text
/// [ui]
/// paginate=no
/// ```
/// "paginate" is the section item and "no" the value.
pub type ConfigItem = HashMap<Vec<u8>, ConfigValue>;
#[derive(Clone, Debug, PartialEq)]
pub struct ConfigValue {
/// The raw bytes of the value (be it from the CLI, env or from a file)
pub bytes: Vec<u8>,
/// Only present if the value comes from a file, 1-indexed.
pub line: Option<usize>,
}
#[derive(Clone, Debug)]
pub enum ConfigOrigin {
Simon Sapin
rhg: Parse per-repository configuration...
r47215 /// From a configuration file
Raphaël Gomès
hg-core: add basic config module...
r46803 File(PathBuf),
Simon Sapin
rhg: Parse per-repository configuration...
r47215 /// From a `--config` CLI argument
CommandLine,
/// From environment variables like `$PAGER` or `$EDITOR`
Raphaël Gomès
hg-core: add basic config module...
r46803 Environment(Vec<u8>),
/* TODO cli
* TODO defaults (configitems.py)
* TODO extensions
* TODO Python resources?
* Others? */
}
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 impl DisplayBytes for ConfigOrigin {
fn display_bytes(
&self,
out: &mut dyn std::io::Write,
) -> std::io::Result<()> {
Raphaël Gomès
hg-core: add basic config module...
r46803 match self {
Simon Sapin
rust: Use the DisplayBytes trait in config printing...
r47249 ConfigOrigin::File(p) => out.write_all(&get_bytes_from_path(p)),
ConfigOrigin::CommandLine => out.write_all(b"--config"),
ConfigOrigin::Environment(e) => write_bytes!(out, b"${}", e),
Raphaël Gomès
hg-core: add basic config module...
r46803 }
}
}
Simon Sapin
rust: use HgError in ConfigError...
r47176 #[derive(Debug)]
pub struct ConfigParseError {
pub origin: ConfigOrigin,
pub line: Option<usize>,
Simon Sapin
rhg: Align config file parse error formatting with Python...
r47465 pub message: Vec<u8>,
Simon Sapin
rust: use HgError in ConfigError...
r47176 }
Simon Sapin
rust: replace trivial `impl From …` with `#[derive(derive_more::From)]`...
r47164 #[derive(Debug, derive_more::From)]
Raphaël Gomès
hg-core: add basic config module...
r46803 pub enum ConfigError {
Simon Sapin
rust: use HgError in ConfigError...
r47176 Parse(ConfigParseError),
Other(HgError),
Raphaël Gomès
hg-core: add basic config module...
r46803 }
fn make_regex(pattern: &'static str) -> Regex {
Regex::new(pattern).expect("expected a valid regex")
}