mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 14:58:03 +03:00
configs: apply dynamicconfig during clone
Summary: During clone the hgrc.dynamic file doesn't exist and doesn't even have a place for us to generate it to. Let's instead generate and apply the config in memory. In the future, if generate fetches data from the network, this will mean clone would depend on the network, since if generate fails the clone would fail. In some situations this is desirable, since users shouldn't be cloning without our approved configs, but if it causes problems we could probably tweak generate to support an offline mode. Reviewed By: quark-zju Differential Revision: D21643086 fbshipit-source-id: d9a758207738d5983213d95725061517e0aa17db
This commit is contained in:
parent
428cfc7b6b
commit
f0d7044aff
@ -165,6 +165,12 @@ 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)
|
||||
coreconfigitem("configs", "mismatchwarn", default=False)
|
||||
coreconfigitem("configs", "validatedynamicconfig", default=False)
|
||||
coreconfigitem("convert", "git.committeractions", default=lambda: ["messagedifferent"])
|
||||
coreconfigitem("convert", "git.extrakeys", default=list)
|
||||
coreconfigitem("convert", "git.findcopiesharder", default=False)
|
||||
|
@ -439,14 +439,21 @@ class localrepository(object):
|
||||
# Callback are in the form: func(repo, roots) --> processed root.
|
||||
# This list it to be filled by extension during repo setup
|
||||
self._phasedefaults = []
|
||||
try:
|
||||
|
||||
reponame = self.ui.config("remotefilelog", "reponame", "")
|
||||
# If the repo already exists, load the existing configs
|
||||
if self.localvfs.isdir():
|
||||
uiconfig.loaddynamicconfig(self.ui, self.path)
|
||||
# Load the primary config after the dynamic one, so it overwrites it
|
||||
self.ui.readconfig(self.localvfs.join("hgrc"), self.root)
|
||||
uiconfig.validatedynamicconfig(self.ui)
|
||||
self._loadextensions()
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
# If the repo does not already exists, load the dynamic configs in
|
||||
# memory only. They will be written to disk later once the localvfs
|
||||
# is created.
|
||||
uiconfig.applydynamicconfig(self.ui, reponame)
|
||||
|
||||
self._loadextensions()
|
||||
|
||||
cacheaudited = self.ui.configbool("unsafe", "wvfsauditorcache")
|
||||
self.wvfs.audit._cached = cacheaudited
|
||||
@ -463,8 +470,10 @@ class localrepository(object):
|
||||
if engine.revlogheader():
|
||||
self.supported.add("exp-compression-%s" % name)
|
||||
|
||||
created = False
|
||||
if not self.localvfs.isdir():
|
||||
if create:
|
||||
created = True
|
||||
self.requirements = newreporequirements(self)
|
||||
if "store" in self.requirements:
|
||||
self.storerequirements = newrepostorerequirements(self)
|
||||
@ -523,6 +532,12 @@ class localrepository(object):
|
||||
raise
|
||||
self.sharedvfs = self.localvfs
|
||||
|
||||
# If this is a new repo, generate the dynamic configs. We must do this
|
||||
# after the sharedvfs is set up so we can generate the dynamic config in
|
||||
# the shared vfs.
|
||||
if created:
|
||||
uiconfig.generatedynamicconfig(self.ui, reponame, self.sharedpath)
|
||||
|
||||
self.store = store.store(
|
||||
self.requirements,
|
||||
self.sharedpath,
|
||||
|
@ -17,7 +17,7 @@ import subprocess
|
||||
import time
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from bindings import configparser
|
||||
from bindings import configparser, dynamicconfig
|
||||
|
||||
from ..hgext.extutil import runbgcommand
|
||||
from . import configitems, error, pycompat, util
|
||||
@ -554,7 +554,7 @@ def parselist(value):
|
||||
|
||||
|
||||
def loaddynamicconfig(ui, path):
|
||||
if ui.configbool("configs", "loaddynamicconfig", False):
|
||||
if ui.configbool("configs", "loaddynamicconfig"):
|
||||
sharedpathfile = os.path.join(path, "sharedpath")
|
||||
if os.path.exists(sharedpathfile):
|
||||
with open(sharedpathfile, "r") as f:
|
||||
@ -562,7 +562,7 @@ def loaddynamicconfig(ui, path):
|
||||
|
||||
hgrcdyn = os.path.join(path, "hgrc.dynamic")
|
||||
|
||||
if ui.configbool("configs", "autogeneratedynamicconfig", False):
|
||||
if ui.configbool("configs", "autogeneratedynamicconfig"):
|
||||
if not os.path.exists(hgrcdyn):
|
||||
try:
|
||||
ui.debug("synchronously generating dynamic config\n")
|
||||
@ -574,7 +574,7 @@ def loaddynamicconfig(ui, path):
|
||||
|
||||
ui.readconfig(hgrcdyn, path)
|
||||
|
||||
generationtime = ui.configint("configs", "generationtime", -1)
|
||||
generationtime = ui.configint("configs", "generationtime")
|
||||
if generationtime != -1:
|
||||
mtimelimit = time.time() - generationtime
|
||||
if not os.path.exists(hgrcdyn) or os.lstat(hgrcdyn).st_mtime < mtimelimit:
|
||||
@ -586,8 +586,8 @@ def loaddynamicconfig(ui, path):
|
||||
|
||||
def validatedynamicconfig(ui):
|
||||
if not (
|
||||
ui.configbool("configs", "loaddynamicconfig", False)
|
||||
and ui.configbool("configs", "validatedynamicconfig", False)
|
||||
ui.configbool("configs", "loaddynamicconfig")
|
||||
and ui.configbool("configs", "validatedynamicconfig")
|
||||
):
|
||||
return
|
||||
|
||||
@ -634,10 +634,10 @@ def validatedynamicconfig(ui):
|
||||
dynamic_value,
|
||||
file_value,
|
||||
)
|
||||
if ui.configbool("configs", "mismatchwarn", False):
|
||||
if ui.configbool("configs", "mismatchwarn"):
|
||||
ui.warn(msg)
|
||||
|
||||
samplerate = ui.configint("configs", "mismatchsampling", 10000)
|
||||
samplerate = ui.configint("configs", "mismatchsampling")
|
||||
if random.randint(1, samplerate) == 1:
|
||||
ui.log(
|
||||
"config_mismatch",
|
||||
@ -646,3 +646,13 @@ def validatedynamicconfig(ui):
|
||||
expected=file_value,
|
||||
actual=dynamic_value,
|
||||
)
|
||||
|
||||
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)
|
||||
|
@ -26,6 +26,7 @@ pyconfigparser = { path = "modules/pyconfigparser" }
|
||||
pydag = { path = "modules/pydag" }
|
||||
pydiffhelpers = { path = "modules/pydiffhelpers" }
|
||||
pydirs = { path = "modules/pydirs" }
|
||||
pydynamicconfig = { path = "modules/pydynamicconfig" }
|
||||
pyedenapi = { path = "modules/pyedenapi" }
|
||||
pyerror = { path = "modules/pyerror" }
|
||||
pyfs = { path = "modules/pyfs" }
|
||||
|
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "pydynamicconfig"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
cpython = { version = "0.5", default-features = false }
|
||||
cpython-ext = { path = "../../../../lib/cpython-ext", default-features = false }
|
||||
dynamicconfig = { path = "../../../../lib/dynamicconfig" }
|
||||
configparser = { path = "../../../../lib/configparser" }
|
||||
pyconfigparser = { path = "../pyconfigparser" }
|
||||
|
||||
[features]
|
||||
python2 = ["cpython/python27-sys", "cpython-ext/python2"]
|
||||
python3 = ["cpython/python3-sys", "cpython-ext/python3"]
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This software may be used and distributed according to the terms of the
|
||||
* GNU General Public License version 2.
|
||||
*/
|
||||
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
use std::fs;
|
||||
|
||||
use anyhow::{format_err, Error};
|
||||
use cpython::*;
|
||||
use cpython_ext::{error::ResultPyErrExt, PyNone, PyPathBuf};
|
||||
|
||||
use dynamicconfig::Generator;
|
||||
use pyconfigparser::config;
|
||||
|
||||
pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
|
||||
let name = [package, "dynamicconfig"].join(".");
|
||||
let m = PyModule::new(py, &name)?;
|
||||
m.add(
|
||||
py,
|
||||
"applydynamicconfig",
|
||||
py_fn!(py, applydynamicconfig(config: config, repo_name: String)),
|
||||
)?;
|
||||
m.add(
|
||||
py,
|
||||
"generatedynamicconfig",
|
||||
py_fn!(
|
||||
py,
|
||||
generatedynamicconfig(repo_name: String, shared_path: PyPathBuf)
|
||||
),
|
||||
)?;
|
||||
Ok(m)
|
||||
}
|
||||
|
||||
fn applydynamicconfig(py: Python, config: config, repo_name: String) -> PyResult<PyNone> {
|
||||
let dyn_cfg = Generator::new(repo_name)
|
||||
.map_pyerr(py)?
|
||||
.execute()
|
||||
.map_pyerr(py)?;
|
||||
for section in dyn_cfg.sections() {
|
||||
for key in dyn_cfg.keys(section.clone()).iter_mut() {
|
||||
if let Some(value) = dyn_cfg.get(section.clone(), key.clone()) {
|
||||
config.set(
|
||||
py,
|
||||
section.to_string(),
|
||||
key.to_string(),
|
||||
Some(value.to_string()),
|
||||
"hgrc.dynamic".into(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PyNone)
|
||||
}
|
||||
|
||||
fn generatedynamicconfig(
|
||||
py: Python,
|
||||
repo_name: String,
|
||||
shared_path: PyPathBuf,
|
||||
) -> PyResult<PyNone> {
|
||||
let config = Generator::new(repo_name)
|
||||
.map_pyerr(py)?
|
||||
.execute()
|
||||
.map_pyerr(py)?;
|
||||
let config_str = config.to_string();
|
||||
let config_str = format!(
|
||||
"# Generated by `hg debugdynamicconfig` - DO NOT MODIFY\n{}",
|
||||
config_str
|
||||
);
|
||||
|
||||
fs::write(shared_path.as_path().join("hgrc.dynamic"), config_str).map_pyerr(py)?;
|
||||
Ok(PyNone)
|
||||
}
|
@ -26,6 +26,11 @@ pub fn populate_module(py: Python<'_>, module: &PyModule) -> PyResult<PyNone> {
|
||||
m.add(py, "dag", pydag::init_module(py, &name)?)?;
|
||||
m.add(py, "diffhelpers", pydiffhelpers::init_module(py, &name)?)?;
|
||||
m.add(py, "dirs", pydirs::init_module(py, &name)?)?;
|
||||
m.add(
|
||||
py,
|
||||
"dynamicconfig",
|
||||
pydynamicconfig::init_module(py, &name)?,
|
||||
)?;
|
||||
m.add(py, "edenapi", pyedenapi::init_module(py, &name)?)?;
|
||||
m.add(py, "error", pyerror::init_module(py, &name)?)?;
|
||||
m.add(py, "fs", pyfs::init_module(py, &name)?)?;
|
||||
|
@ -92,3 +92,24 @@ Verify we generate and load from a shared repo
|
||||
|
||||
$ hg config section.key
|
||||
value
|
||||
|
||||
Verify we load dynamicconfigs during clone
|
||||
$ newserver server
|
||||
$ cd $TESTTMP
|
||||
$ export HG_TEST_DYNAMICCONFIG="$TESTTMP/test_hgrc"
|
||||
$ cat > test_hgrc <<EOF
|
||||
> [hooks]
|
||||
> pretxnclose = echo "Hook ran!"
|
||||
> EOF
|
||||
$ hg clone ssh://user@dummy/server client2
|
||||
no changes found
|
||||
Hook ran!
|
||||
updating to branch default
|
||||
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
|
||||
Hook ran!
|
||||
Hook ran!
|
||||
$ cat client2/.hg/hgrc.dynamic
|
||||
# Generated by `hg debugdynamicconfig` - DO NOT MODIFY
|
||||
[hooks]
|
||||
pretxnclose=echo "Hook ran!"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user