rhg: Add support for environment variables in config include paths
Some tests rely on this.
Differential Revision: https://phab.mercurial-scm.org/D10140
--- a/rust/hg-core/src/config/layer.rs Mon Mar 08 08:55:48 2021 +0100
+++ b/rust/hg-core/src/config/layer.rs Mon Mar 08 15:35:32 2021 +0100
@@ -150,6 +150,7 @@
let line = Some(index + 1);
if let Some(m) = INCLUDE_RE.captures(&bytes) {
let filename_bytes = &m[1];
+ let filename_bytes = crate::utils::expand_vars(filename_bytes);
// `Path::parent` only fails for the root directory,
// which `src` can’t be since we’ve managed to open it as a
// file.
--- a/rust/hg-core/src/utils.rs Mon Mar 08 08:55:48 2021 +0100
+++ b/rust/hg-core/src/utils.rs Mon Mar 08 15:35:32 2021 +0100
@@ -241,6 +241,59 @@
})
}
+/// Expand `$FOO` and `${FOO}` environment variables in the given byte string
+pub fn expand_vars(s: &[u8]) -> std::borrow::Cow<[u8]> {
+ lazy_static::lazy_static! {
+ /// https://github.com/python/cpython/blob/3.9/Lib/posixpath.py#L301
+ /// The `x` makes whitespace ignored.
+ /// `-u` disables the Unicode flag, which makes `\w` like Python with the ASCII flag.
+ static ref VAR_RE: regex::bytes::Regex =
+ regex::bytes::Regex::new(r"(?x-u)
+ \$
+ (?:
+ (\w+)
+ |
+ \{
+ ([^}]*)
+ \}
+ )
+ ").unwrap();
+ }
+ VAR_RE.replace_all(s, |captures: ®ex::bytes::Captures| {
+ let var_name = files::get_os_str_from_bytes(
+ captures
+ .get(1)
+ .or_else(|| captures.get(2))
+ .expect("either side of `|` must participate in match")
+ .as_bytes(),
+ );
+ std::env::var_os(var_name)
+ .map(files::get_bytes_from_os_str)
+ .unwrap_or_else(|| {
+ // Referencing an environment variable that does not exist.
+ // Leave the $FOO reference as-is.
+ captures[0].to_owned()
+ })
+ })
+}
+
+#[test]
+fn test_expand_vars() {
+ // Modifying process-global state in a test isn’t great,
+ // but hopefully this won’t collide with anything.
+ std::env::set_var("TEST_EXPAND_VAR", "1");
+ assert_eq!(
+ expand_vars(b"before/$TEST_EXPAND_VAR/after"),
+ &b"before/1/after"[..]
+ );
+ assert_eq!(
+ expand_vars(b"before${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}${TEST_EXPAND_VAR}after"),
+ &b"before111after"[..]
+ );
+ let s = b"before $SOME_LONG_NAME_THAT_WE_ASSUME_IS_NOT_AN_ACTUAL_ENV_VAR after";
+ assert_eq!(expand_vars(s), &s[..]);
+}
+
pub(crate) enum MergeResult<V> {
UseLeftValue,
UseRightValue,
--- a/rust/hg-core/src/utils/files.rs Mon Mar 08 08:55:48 2021 +0100
+++ b/rust/hg-core/src/utils/files.rs Mon Mar 08 15:35:32 2021 +0100
@@ -23,7 +23,7 @@
use std::ops::Deref;
use std::path::{Path, PathBuf};
-pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
+pub fn get_os_str_from_bytes(bytes: &[u8]) -> &OsStr {
let os_str;
#[cfg(unix)]
{
@@ -33,8 +33,11 @@
// TODO Handle other platforms
// TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
// Perhaps, the return type would have to be Result<PathBuf>.
+ os_str
+}
- Path::new(os_str)
+pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
+ Path::new(get_os_str_from_bytes(bytes))
}
// TODO: need to convert from WTF8 to MBCS bytes on Windows.