configs: version dynamic configs

Summary:
If we release a new version of Mercurial, we want to ensure that it's
builtin configs are used immediately. To do so, let's write a version number
into the generated config file, and if the version number doesn't match, we
force a synchronous regeneration of the config file.

For now, if regeneration fails, we just log it. In the future we'll probably
throw an exception and block the user since we want to ensure people are running
with modern configuration.

Reviewed By: quark-zju

Differential Revision: D21651317

fbshipit-source-id: 3edbaf6777f4ca2363d8617fad03c21204b468a2
This commit is contained in:
Durham Goode 2020-05-20 13:30:22 -07:00 committed by Facebook GitHub Bot
parent cd92c10363
commit 9f6f200a08
6 changed files with 73 additions and 20 deletions

View File

@ -165,7 +165,6 @@ coreconfigitem("commit", "description-size-limit", default=None)
coreconfigitem("commit", "extras-size-limit", default=None)
coreconfigitem("committemplate", ".*", default=None, generic=True)
coreconfigitem("connectionpool", "lifetime", default=None)
coreconfigitem("configs", "autogeneratedynamicconfig", default=False)
coreconfigitem("configs", "generationtime", default=-1)
coreconfigitem("configs", "loaddynamicconfig", default=False)
coreconfigitem("configs", "mismatchsampling", default=10000)

View File

@ -13,6 +13,7 @@ from __future__ import absolute_import
import contextlib
import os
import random
import re
import subprocess
import time
from typing import List, Optional, Tuple
@ -562,15 +563,33 @@ def loaddynamicconfig(ui, path):
hgrcdyn = os.path.join(path, "hgrc.dynamic")
if ui.configbool("configs", "autogeneratedynamicconfig"):
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
)
# Check the version of the existing generated config. If it doesn't
# match the current version, regenerate it immediately.
try:
with open(hgrcdyn, "r") as f:
content = f.read()
matches = re.search("^# version=(.*)$", content, re.MULTILINE)
version = matches.group(1) if matches else None
except IOError:
version = None
if version is None or version != util.version():
try:
ui.debug(
"synchronously generating dynamic config - new version %s, old version %s\n"
% (util.version(), version)
)
reponame = ui.config("remotefilelog", "reponame") or ""
generatedynamicconfig(ui, reponame, path)
except Exception as ex:
# TODO: Eventually this should throw an exception, once we're
# confident it's reliable.
ui.log(
"exceptions",
"unable to generate dynamicconfig",
exception_type="DynamicconfigGeneration",
exception_msg="unable to generate dynamicconfig: %s" % str(ex),
)
ui.readconfig(hgrcdyn, path)
@ -617,15 +636,14 @@ def validatedynamicconfig(ui):
]
# Configs that are allowed to be different, usually because they must come
# from external configuration (like hotfixes).
whitelist = [
("extensions", "hotfix"),
]
whitelist = [("extensions", "hotfix")]
testrcs = ui.configlist("configs", "testdynamicconfigsubset")
if testrcs:
originalrcs.extend(testrcs)
issues = ui._uiconfig._rcfg.ensure_location_supersets("hgrc.dynamic",
originalrcs, whitelist)
issues = ui._uiconfig._rcfg.ensure_location_supersets(
"hgrc.dynamic", originalrcs, whitelist
)
for section, key, dynamic_value, file_value in issues:
msg = _("Config mismatch: %s.%s has '%s' (dynamic) vs '%s' (file)\n") % (
@ -639,20 +657,24 @@ def validatedynamicconfig(ui):
samplerate = ui.configint("configs", "mismatchsampling")
if random.randint(1, samplerate) == 1:
reponame = ui.config("remotefilelog", "reponame")
ui.log(
"config_mismatch",
msg,
config="%s.%s" % (section, key),
expected=file_value,
actual=dynamic_value,
repo=reponame or "unknown",
)
def applydynamicconfig(ui, reponame):
if ui.configbool("configs", "loaddynamicconfig"):
dynamicconfig.applydynamicconfig(ui._uiconfig._rcfg._rcfg, reponame)
validatedynamicconfig(ui)
def generatedynamicconfig(ui, reponame, sharedpath):
if ui.configbool("configs", "loaddynamicconfig"):
dynamicconfig.generatedynamicconfig(reponame, sharedpath)

View File

@ -10,6 +10,7 @@ cpython-ext = { path = "../../../../lib/cpython-ext", default-features = false }
dynamicconfig = { path = "../../../../lib/dynamicconfig" }
configparser = { path = "../../../../lib/configparser" }
pyconfigparser = { path = "../pyconfigparser" }
version = { path = "../../../../lib/version" }
[features]
python2 = ["cpython/python27-sys", "cpython-ext/python2"]

View File

@ -9,7 +9,6 @@
use std::fs;
use anyhow::{format_err, Error};
use cpython::*;
use cpython_ext::{error::ResultPyErrExt, PyNone, PyPathBuf};
@ -68,7 +67,8 @@ fn generatedynamicconfig(
.map_pyerr(py)?;
let config_str = config.to_string();
let config_str = format!(
"# Generated by `hg debugdynamicconfig` - DO NOT MODIFY\n{}",
"# version={}\n# Generated by `hg debugdynamicconfig` - DO NOT MODIFY\n{}",
::version::VERSION,
config_str
);

View File

@ -298,7 +298,8 @@ pub fn debugdynamicconfig(_opts: NoOpts, _io: &mut IO, repo: Repo) -> Result<u8>
let config = Generator::new(repo_name)?.execute()?;
let config_str = config.to_string();
let config_str = format!(
"# Generated by `hg debugdynamicconfig` - DO NOT MODIFY\n{}",
"# version={}\n# Generated by `hg debugdynamicconfig` - DO NOT MODIFY\n{}",
::version::VERSION,
config_str
);

View File

@ -16,6 +16,7 @@ Verify it can be manually generated
$ hg debugdynamicconfig
$ cat .hg/hgrc.dynamic
# version=4.4.2* (glob)
# Generated by `hg debugdynamicconfig` - DO NOT MODIFY
[section]
key=value
@ -27,10 +28,9 @@ 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
# version=4.4.2* (glob)
# Generated by `hg debugdynamicconfig` - DO NOT MODIFY
[section]
key=value
@ -51,6 +51,7 @@ Verify it can be automatically asynchronously regenerated
$ hg config section2.key2
value2
$ cat .hg/hgrc.dynamic
# version=4.4.2* (glob)
# Generated by `hg debugdynamicconfig` - DO NOT MODIFY
[section]
key=value
@ -83,6 +84,7 @@ Verify we generate and load from a shared repo
$ test -f .hg/hgrc.dynamic
[1]
$ cat ../shared/.hg/hgrc.dynamic
# version=4.4.2* (glob)
# Generated by `hg debugdynamicconfig` - DO NOT MODIFY
[section]
key=value
@ -93,6 +95,33 @@ Verify we generate and load from a shared repo
$ hg config section.key
value
Verify we regenerate configs if the Mercurial version differs
$ cat > ../shared/.hg/hgrc.dynamic <<EOF
> # version=1
> [section3]
> key3=value3
> EOF
$ hg config section3.key3
[1]
$ hg config section.key
value
$ cat ../shared/.hg/hgrc.dynamic
# version=4.4.2* (glob)
# Generated by `hg debugdynamicconfig` - DO NOT MODIFY
[section]
key=value
[section2]
key2=value2
Verify we don't regenerate configs if the Mercurial version hasn't changed
$ cat >> ../shared/.hg/hgrc.dynamic <<EOF
> [section3]
> key3=value3
> EOF
$ hg config section3.key3
value3
Verify we load dynamicconfigs during clone
$ newserver server
$ cd $TESTTMP
@ -109,6 +138,7 @@ Verify we load dynamicconfigs during clone
Hook ran!
Hook ran!
$ cat client2/.hg/hgrc.dynamic
# version=4.4.2* (glob)
# Generated by `hg debugdynamicconfig` - DO NOT MODIFY
[hooks]
pretxnclose=echo "Hook ran!"