config: load .hg/hgrc.dynamic

Summary:
Adds .hg/hgrc.dynamic to the default load path, before .hg/hgrc though,
so it can be override.

Reviewed By: quark-zju

Differential Revision: D21310921

fbshipit-source-id: 288a2a2ba671943a9f8532489c29e819f9d891e1
This commit is contained in:
Durham Goode 2020-05-05 18:15:26 -07:00 committed by Facebook GitHub Bot
parent dc90e2ca04
commit 6e7f85b949
6 changed files with 129 additions and 15 deletions

View File

@ -46,6 +46,7 @@ from . import (
registrar,
scmutil,
ui as uimod,
uiconfig,
util,
)
from .i18n import _
@ -943,12 +944,15 @@ def _log_exception(lui, e):
pass
def _getlocal(ui, rpath, wd=None):
def _getlocal(ui, rpath):
"""Return (path, local ui object) for the given target path.
Takes paths in [cwd]/.hg/hgrc into account."
"""
if wd is None:
if rpath:
path = ui.expandpath(rpath)
lui = ui.copy()
else:
try:
wd = pycompat.getcwd()
except OSError as e:
@ -964,16 +968,17 @@ def _getlocal(ui, rpath, wd=None):
_("error getting current working directory: %s")
% encoding.strtolocal(e.strerror)
)
path = cmdutil.findrepo(wd) or ""
if not path:
lui = ui
else:
lui = ui.copy()
lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
path = cmdutil.findrepo(wd) or ""
if rpath:
path = lui.expandpath(rpath)
lui = ui.copy()
if not path:
lui = ui
else:
lui = ui.copy()
if path:
uiconfig.loaddynamicconfig(lui, path)
# Load the primary config after the dynamic one, so it overwrites it
lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
return path, lui

View File

@ -66,6 +66,7 @@ from . import (
transaction,
treestate,
txnutil,
uiconfig,
util,
vfs as vfsmod,
visibility,
@ -439,6 +440,8 @@ class localrepository(object):
# This list it to be filled by extension during repo setup
self._phasedefaults = []
try:
uiconfig.loaddynamicconfig(self.ui, self.root)
# Load the primary config after the dynamic one, so it overwrites it
self.ui.readconfig(self.localvfs.join("hgrc"), self.root)
self._loadextensions()
except IOError:

View File

@ -12,10 +12,13 @@ from __future__ import absolute_import
import contextlib
import os
import subprocess
import time
from typing import List, Optional, Tuple
from bindings import configparser
from ..hgext.extutil import runbgcommand
from . import configitems, error, pycompat, util
from .encoding import unifromlocal, unitolocal
from .i18n import _
@ -544,3 +547,29 @@ def parselist(value):
return [unitolocal(v) for v in configparser.parselist(unifromlocal(value))]
else:
return value
def loaddynamicconfig(ui, path):
if ui.configbool("configs", "loaddynamicconfig", False):
hgrcdyn = os.path.join(path, ".hg", "hgrc.dynamic")
if ui.configbool("configs", "autogeneratedynamicconfig", False):
if not os.path.exists(hgrcdyn):
try:
ui.debug("synchronously generating dynamic config\n")
subprocess.check_call(["hg", "debugdynamicconfig"])
except Exception as ex:
raise error.Abort(
_("unable to generate dynamic Mercurial config: %s") % ex
)
ui.readconfig(hgrcdyn, path)
generationtime = ui.configint("configs", "generationtime", -1)
if generationtime != -1:
mtimelimit = time.time() - generationtime
if not os.path.exists(hgrcdyn) or os.lstat(hgrcdyn).st_mtime < mtimelimit:
# TODO: some how prevent kicking off the background process if
# the file is read-only or if the previous kick offs failed.
ui.debug("background generating dynamic config\n")
runbgcommand(["hg", "debugdynamicconfig"], os.environ.copy())

View File

@ -8,6 +8,7 @@ anyhow = "1.0.19"
configparser = { path = "../configparser" }
hgtime = { path = "../hgtime" }
hostname = "0.3"
minibytes = { path = "../minibytes" }
os_info = "2.0.1"
[dev-dependencies]

View File

@ -14,6 +14,7 @@ use std::path::Path;
use anyhow::{anyhow, bail, Result};
use hostname;
use minibytes::Text;
use configparser::config::ConfigSet;
use hgtime::HgTime;
@ -196,12 +197,16 @@ impl Generator {
}
#[allow(dead_code)]
pub(crate) fn load_hgrc(&mut self, value: &'static str) -> Result<()> {
pub(crate) fn load_hgrc(
&mut self,
value: impl Into<Text> + Clone + std::fmt::Display,
) -> Result<()> {
let value_copy = value.clone();
let errors = self.config.parse(value, &"dynamicconfigs".into());
if !errors.is_empty() {
bail!(
"invalid dynamic config blob: '{}'\nerrors: '{:?}'",
value,
value_copy,
errors
);
}
@ -209,8 +214,12 @@ impl Generator {
}
pub fn execute(mut self) -> Result<ConfigSet> {
#[cfg(feature = "fb")]
self._execute(fb::fb_rules)?;
if std::env::var("HG_TEST_DYNAMICCONFIG").is_ok() {
self._execute(test_rules)?;
} else {
#[cfg(feature = "fb")]
self._execute(fb::fb_rules)?;
}
Ok(self.config)
}
@ -254,6 +263,16 @@ fn get_hg_group(tiers: &HashSet<String>, shard: u8) -> HgGroup {
}
}
/// Rules used in our integration test environment
fn test_rules(gen: &mut Generator) -> Result<()> {
if let Ok(path) = std::env::var("HG_TEST_DYNAMICCONFIG") {
let hgrc = std::fs::read_to_string(path).unwrap();
gen.load_hgrc(hgrc).unwrap();
}
Ok(())
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;

View File

@ -0,0 +1,57 @@
#chg-compatible
$ configure modern
$ setconfig configs.loaddynamicconfig=True
$ export HG_TEST_DYNAMICCONFIG="$TESTTMP/test_hgrc"
$ cat > test_hgrc <<EOF
> [section]
> key=value
> EOF
$ hg init client
$ cd client
Verify it can be manually generated
$ hg debugdynamicconfig
$ cat .hg/hgrc.dynamic
[section]
key=value
$ hg config section.key
value
Verify it can be automatically synchronously generated
$ rm .hg/hgrc.dynamic
$ hg config section.key
[1]
$ hg config section.key --config configs.autogeneratedynamicconfig=True
value
$ cat .hg/hgrc.dynamic
[section]
key=value
Verify it can be automatically asynchronously regenerated
$ cat > $TESTTMP/test_hgrc <<EOF
> [section]
> key=value
> [section2]
> key2=value2
> EOF
$ hg config section2.key2 --config configs.generationtime=30 # No regen, because too soon
[1]
$ sleep 1
$ hg status --config configs.generationtime=1 # Regen, because lower time limit
$ sleep 0.5 # Time for background process to complete
$ hg config section2.key2
value2
$ cat .hg/hgrc.dynamic
[section]
key=value
[section2]
key2=value2