configs: add rust support for loading dynamic and repo configs

Summary:
This threads the calls to load_dynamic and load_repo through the Rust
layer up to the Python bindings. This diff does 2 notable things:

1. It adds a reload API for reloading configs in place, versus creating a new
one. This will be used in localrepo.__init__ to construct a new config for the
repo while still maintaining the old pinned values from the copied ui.
2. It threads a repo path and readonly config list from Python down to the Rust
code. This allows load_dynamic and load_repo to operate on the repo path, and
allows the readonly filter to applied to all configs during reloading.

Reviewed By: quark-zju

Differential Revision: D22712623

fbshipit-source-id: a0f372f4971c5feac2f20e89a0fb3fe6d4a65d6f
This commit is contained in:
Durham Goode 2020-08-16 16:53:34 -07:00 committed by Facebook GitHub Bot
parent 6b0014490c
commit 2da121cb60
6 changed files with 86 additions and 44 deletions

View File

@ -121,7 +121,7 @@ class uiconfig(object):
"""Create a uiconfig and load global and user configs"""
u = cls()
try:
rcfg = configparser.config.load()
rcfg = configparser.config.load(None)
except Exception as ex:
raise error.ParseError(str(ex))
u._rcfg = localrcfg(rcfg)

View File

@ -14,7 +14,7 @@ use cpython::*;
use configparser::{
config::{ConfigSet, Options},
dynamicconfig::Generator,
hg::{generate_dynamicconfig, load as hg_load, parse_list, OptionsHgExt},
hg::{generate_dynamicconfig, parse_list, ConfigSetHgExt, OptionsHgExt},
};
use cpython_ext::{error::ResultPyErrExt, PyNone, PyPath, PyPathBuf, Str};
@ -147,11 +147,20 @@ py_class!(pub class config |py| {
}
@staticmethod
def load() -> PyResult<config> {
let cfg = hg_load().map_pyerr(py)?;
def load(repopath: Option<PyPathBuf>) -> PyResult<config> {
let repopath = repopath.as_ref().map(|p| p.as_path());
let mut cfg = ConfigSet::new();
cfg.load::<String, String>(repopath, None).map_pyerr(py)?;
config::create_instance(py, RefCell::new(cfg))
}
def reload(&self, repopath: Option<PyPathBuf>, readonly_items: Option<Vec<(String, String)>>) -> PyResult<PyNone> {
let repopath = repopath.as_ref().map(|p| p.as_path());
let mut cfg = self.cfg(py).borrow_mut();
cfg.load(repopath, readonly_items).map_pyerr(py)?;
Ok(PyNone)
}
def ensure_location_supersets(
&self,
superset_source: String,

View File

@ -9,7 +9,7 @@ use crate::remotestore::FakeRemoteStore;
use crate::treecontentstore::TreeContentStore;
use crate::utils::key_from_path_node_slice;
use anyhow::Result;
use configparser::config::ConfigSet;
use configparser::config::{ConfigSet, Options};
use configparser::hg::ConfigSetHgExt;
use edenapi::{Builder as EdenApiBuilder, EdenApi};
use log::warn;
@ -31,9 +31,10 @@ pub struct BackingStore {
impl BackingStore {
pub fn new<P: AsRef<Path>>(repository: P, use_edenapi: bool) -> Result<Self> {
let hg = repository.as_ref().join(".hg");
let options = Options::new();
let mut config = ConfigSet::new();
config.load_system();
config.load_user();
config.load_system(options.clone());
config.load_user(options.clone());
config.load_hgrc(hg.join("hgrc"), "repository");
let store_path = hg.join("store");

View File

@ -40,7 +40,9 @@ impl OptionalRepo {
let repo = Repo::from_raw_path(path)?;
Ok(OptionalRepo::Some(repo))
} else {
Ok(OptionalRepo::None(configparser::hg::load()?))
Ok(OptionalRepo::None(
configparser::hg::load::<String, String>(None, None)?,
))
}
}
@ -98,7 +100,7 @@ impl Repo {
{
let path = path.into();
assert!(path.is_absolute());
let mut config = configparser::hg::load()?;
let mut config = configparser::hg::load::<String, String>(None, None)?;
let mut errors = config.load_hgrc(path.join(".hg/hgrc"), "repository");
if let Some(error) = errors.pop() {
Err(error.into())

View File

@ -87,7 +87,7 @@ pub extern "C" fn hgrc_configset_load_system(cfg: *mut ConfigSet) -> *mut Text {
// Forces datapath to be the empty string as it doesn't
// appear to play a useful role in simply resolving config
// settings for Eden.
errors_to_bytes(cfg.load_system())
errors_to_bytes(cfg.load_system(Options::new()))
}
/// Load user config files
@ -96,7 +96,7 @@ pub extern "C" fn hgrc_configset_load_user(cfg: *mut ConfigSet) -> *mut Text {
debug_assert!(!cfg.is_null());
let cfg = unsafe { &mut *cfg };
errors_to_bytes(cfg.load_user())
errors_to_bytes(cfg.load_user(Options::new()))
}
/// Returns a Text object holding the configuration value for the corresponding

View File

@ -51,21 +51,27 @@ pub trait OptionsHgExt {
}
pub trait ConfigSetHgExt {
fn load<S: Into<Text>, N: Into<Text>>(
&mut self,
repo_path: Option<&Path>,
readonly_items: Option<Vec<(S, N)>>,
) -> Result<()>;
/// Load system config files if `$HGRCPATH` is not set.
/// Return errors parsing files.
fn load_system(&mut self) -> Vec<Error>;
fn load_system(&mut self, opts: Options) -> Vec<Error>;
/// Load the dynamic config files for the given repo path.
/// Returns errors parsing, generating, or fetching the configs.
fn load_dynamic(&mut self, repo_path: &Path) -> Result<Vec<Error>>;
fn load_dynamic(&mut self, repo_path: &Path, opts: Options) -> Result<Vec<Error>>;
/// Load user config files (and environment variables). If `$HGRCPATH` is
/// set, load files listed in that environment variable instead.
/// Return errors parsing files.
fn load_user(&mut self) -> Vec<Error>;
fn load_user(&mut self, opts: Options) -> Vec<Error>;
/// Load repo config files.
fn load_repo(&mut self, repo_path: &Path) -> Vec<Error>;
fn load_repo(&mut self, repo_path: &Path, opts: Options) -> Vec<Error>;
/// Load a specified config file. Respect HGPLAIN environment variables.
/// Return errors parsing files.
@ -98,22 +104,13 @@ pub trait FromConfigValue: Sized {
fn try_from_str(s: &str) -> Result<Self>;
}
/// Load system, user config files.
pub fn load() -> Result<ConfigSet> {
let mut set = ConfigSet::new();
let mut errors = vec![];
// Only load builtin configs if HGRCPATH is not set.
if std::env::var(HGRCPATH).is_err() {
errors.append(&mut set.parse(MERGE_TOOLS_CONFIG, &"merge-tools.rc".into()));
}
errors.append(&mut set.load_system());
errors.append(&mut set.load_user());
if !errors.is_empty() {
return Err(Errors(errors).into());
}
Ok(set)
pub fn load<S: Into<Text>, N: Into<Text>>(
repo_path: Option<&Path>,
readonly_items: Option<Vec<(S, N)>>,
) -> Result<ConfigSet> {
let mut cfg = ConfigSet::new();
cfg.load(repo_path, readonly_items)?;
Ok(cfg)
}
impl OptionsHgExt for Options {
@ -240,8 +237,41 @@ impl OptionsHgExt for Options {
}
impl ConfigSetHgExt for ConfigSet {
fn load_system(&mut self) -> Vec<Error> {
let opts = Options::new().source("system").process_hgplain();
/// Load system, user config files.
fn load<S: Into<Text>, N: Into<Text>>(
&mut self,
repo_path: Option<&Path>,
readonly_items: Option<Vec<(S, N)>>,
) -> Result<()> {
let mut errors = vec![];
let mut opts = Options::new();
if let Some(readonly_items) = readonly_items {
opts = opts.readonly_items(readonly_items);
}
// Only load builtin configs if HGRCPATH is not set.
if std::env::var(HGRCPATH).is_err() {
errors.append(&mut self.parse(MERGE_TOOLS_CONFIG, &"merge-tools.rc".into()));
}
errors.append(&mut self.load_system(opts.clone()));
if let Some(repo_path) = repo_path {
errors.append(&mut self.load_dynamic(&repo_path, opts.clone())?);
}
errors.append(&mut self.load_user(opts.clone()));
if let Some(repo_path) = repo_path {
errors.append(&mut self.load_repo(&repo_path, opts.clone()));
}
if !errors.is_empty() {
return Err(Errors(errors).into());
}
Ok(())
}
fn load_system(&mut self, opts: Options) -> Vec<Error> {
let opts = opts.source("system").process_hgplain();
let mut errors = Vec::new();
// If $HGRCPATH is set, use it instead.
@ -279,7 +309,7 @@ impl ConfigSetHgExt for ConfigSet {
errors
}
fn load_dynamic(&mut self, repo_path: &Path) -> Result<Vec<Error>> {
fn load_dynamic(&mut self, repo_path: &Path, opts: Options) -> Result<Vec<Error>> {
let mut errors = Vec::new();
if self.get_or_default::<bool>("configs", "loaddynamicconfig")? {
@ -317,10 +347,10 @@ impl ConfigSetHgExt for ConfigSet {
// repo config ahead of time to read the name.
let mut repo_hgrc_path = repo_path.join(".hg");
repo_hgrc_path.push("hgrc");
if !temp_config.load_user().is_empty() {
if !temp_config.load_user(opts.clone()).is_empty() {
bail!("unable to read user config to get user name");
}
let opts = Options::new().source("temp").process_hgplain();
let opts = opts.clone().source("temp").process_hgplain();
if !temp_config.load_path(repo_hgrc_path, &opts).is_empty() {
bail!("unable to read repo config to get repo name");
}
@ -342,7 +372,7 @@ impl ConfigSetHgExt for ConfigSet {
}
// Read hgrc.dynamic
let opts = Options::new().source("dynamic").process_hgplain();
let opts = opts.source("dynamic").process_hgplain();
errors.append(&mut self.load_path(&dynamic_path, &opts));
// Log config ages
@ -372,7 +402,7 @@ impl ConfigSetHgExt for ConfigSet {
Ok(errors)
}
fn load_user(&mut self) -> Vec<Error> {
fn load_user(&mut self, opts: Options) -> Vec<Error> {
let mut errors = Vec::new();
// Covert "$VISUAL", "$EDITOR" to "ui.editor".
@ -391,7 +421,7 @@ impl ConfigSetHgExt for ConfigSet {
"ui",
"editor",
Some(editor),
&Options::new().source(format!("${}", name)),
&opts.clone().source(format!("${}", name)),
);
break;
}
@ -402,7 +432,7 @@ impl ConfigSetHgExt for ConfigSet {
self.set("profiling", "type", Some(profiling_type), &"$HGPROF".into());
}
let opts = Options::new().source("user").process_hgplain();
let opts = opts.source("user").process_hgplain();
// If HGRCPATH is set, don't load user configs
if env::var("HGRCPATH").is_err() {
@ -422,10 +452,10 @@ impl ConfigSetHgExt for ConfigSet {
errors
}
fn load_repo(&mut self, repo_path: &Path) -> Vec<Error> {
fn load_repo(&mut self, repo_path: &Path, opts: Options) -> Vec<Error> {
let mut errors = Vec::new();
let opts = Options::new().source("repo").process_hgplain();
let opts = opts.source("repo").process_hgplain();
let hgrc_path = repo_path.join("hgrc");
errors.append(&mut self.load_path(hgrc_path, &opts));
@ -977,10 +1007,10 @@ mod tests {
let mut cfg = ConfigSet::new();
cfg.load_user();
cfg.load_user(Options::new());
assert!(cfg.sections().is_empty());
cfg.load_system();
cfg.load_system(Options::new());
assert_eq!(cfg.get("x", "a"), Some("1".into()));
assert_eq!(cfg.get("y", "b"), Some("2".into()));
}