auto-upgrade: introduce a way to auto-upgrade to/from share-safe
This is the first "automatic-upgrade" capability. In the following commits,
similar features are coming for other "fast to upgrade" formats.
This is different from the `safe-mismatch.source-not-safe` and
`safe-mismatch.source-safe` configuration that deal with mismatch between a
share and its share-source. Here we are dealing with mismatch between a
repository configuration and its actual format.
We will need further work for cases were the repository cannot be locked. A
basic protection is in place to avoid a infinite loop for now, but it will get
proper attention in a later changeset.
Differential Revision: https://phab.mercurial-scm.org/D12611
--- a/mercurial/configitems.py Fri Apr 15 22:02:07 2022 +0200
+++ b/mercurial/configitems.py Tue Apr 05 05:19:47 2022 +0200
@@ -1386,6 +1386,12 @@
)
coreconfigitem(
b'format',
+ b'use-share-safe.automatic-upgrade-of-mismatching-repositories',
+ default=False,
+ experimental=True,
+)
+coreconfigitem(
+ b'format',
b'internal-phase',
default=False,
experimental=True,
--- a/mercurial/helptext/config.txt Fri Apr 15 22:02:07 2022 +0200
+++ b/mercurial/helptext/config.txt Tue Apr 05 05:19:47 2022 +0200
@@ -1032,6 +1032,24 @@
Enabled by default in Mercurial 6.1.
+``use-share-safe.automatic-upgrade-of-mismatching-repositories``
+ When enabled, an automatic upgrade will be triggered when a repository format
+ does not match its `use-share-safe` config.
+
+ This is an advanced behavior that most users will not need. We recommend you
+ don't use this unless you are a seasoned administrator of a Mercurial install
+ base.
+
+ Automatic upgrade means that any process accessing the repository will
+ upgrade the repository format to use `share-safe`. This only triggers if a
+ change is needed. This also applies to operation that would have been
+ read-only (like hg status).
+
+ This configuration will apply for moves in any direction, either adding the
+ `share-safe` format if `format.use-share-safe=yes` or removing the
+ `share-safe` requirement if `format.use-share-safe=no`. So we recommend
+ setting both this value and `format.use-share-safe` at the same time.
+
``usestore``
Enable or disable the "store" repository format which improves
compatibility with systems that fold case or otherwise mangle
--- a/mercurial/localrepo.py Fri Apr 15 22:02:07 2022 +0200
+++ b/mercurial/localrepo.py Tue Apr 05 05:19:47 2022 +0200
@@ -3516,11 +3516,20 @@
def instance(ui, path, create, intents=None, createopts=None):
+
+ # prevent cyclic import localrepo -> upgrade -> localrepo
+ from . import upgrade
+
localpath = urlutil.urllocalpath(path)
if create:
createrepository(ui, localpath, createopts=createopts)
- return makelocalrepository(ui, localpath, intents=intents)
+ def repo_maker():
+ return makelocalrepository(ui, localpath, intents=intents)
+
+ repo = repo_maker()
+ repo = upgrade.may_auto_upgrade(repo, repo_maker)
+ return repo
def islocal(path):
--- a/mercurial/upgrade.py Fri Apr 15 22:02:07 2022 +0200
+++ b/mercurial/upgrade.py Tue Apr 05 05:19:47 2022 +0200
@@ -19,6 +19,7 @@
from .upgrade_utils import (
actions as upgrade_actions,
+ auto_upgrade,
engine as upgrade_engine,
)
@@ -26,6 +27,7 @@
stringutil,
)
+may_auto_upgrade = auto_upgrade.may_auto_upgrade
allformatvariant = upgrade_actions.allformatvariant
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/upgrade_utils/auto_upgrade.py Tue Apr 05 05:19:47 2022 +0200
@@ -0,0 +1,107 @@
+# upgrade.py - functions for automatic upgrade of Mercurial repository
+#
+# Copyright (c) 2022-present, Pierre-Yves David
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+from ..i18n import _
+
+from .. import (
+ error,
+ requirements as requirementsmod,
+ scmutil,
+)
+
+
+def get_share_safe_action(repo):
+ """return an automatic-upgrade action for `share-safe` if applicable
+
+ If no action is needed, return None, otherwise return a callback to upgrade
+ or downgrade the repository according the configuration and repository
+ format.
+ """
+ ui = repo.ui
+ requirements = repo.requirements
+ auto_upgrade_share_source = ui.configbool(
+ b'format',
+ b'use-share-safe.automatic-upgrade-of-mismatching-repositories',
+ )
+
+ action = None
+
+ if (
+ auto_upgrade_share_source
+ and requirementsmod.SHARED_REQUIREMENT not in requirements
+ ):
+ sf_config = ui.configbool(b'format', b'use-share-safe')
+ sf_local = requirementsmod.SHARESAFE_REQUIREMENT in requirements
+ if sf_config and not sf_local:
+ msg = _(
+ b"automatically upgrading repository to the `share-safe`"
+ b" feature\n"
+ )
+ hint = b"(see `hg help config.format.use-share-safe` for details)\n"
+
+ def action():
+ if not ui.quiet:
+ ui.write_err(msg)
+ ui.write_err(hint)
+ requirements.add(requirementsmod.SHARESAFE_REQUIREMENT)
+ scmutil.writereporequirements(repo, requirements)
+
+ elif sf_local and not sf_config:
+ msg = _(
+ b"automatically downgrading repository from the `share-safe`"
+ b" feature\n"
+ )
+ hint = b"(see `hg help config.format.use-share-safe` for details)\n"
+
+ def action():
+ if not ui.quiet:
+ ui.write_err(msg)
+ ui.write_err(hint)
+ requirements.discard(requirementsmod.SHARESAFE_REQUIREMENT)
+ scmutil.writereporequirements(repo, requirements)
+
+ return action
+
+
+AUTO_UPGRADE_ACTIONS = [
+ get_share_safe_action,
+]
+
+
+def may_auto_upgrade(repo, maker_func):
+ """potentially perform auto-upgrade and return the final repository to use
+
+ Auto-upgrade are "quick" repository upgrade that might automatically be run
+ by "any" repository access. See `hg help config.format` for automatic
+ upgrade documentation.
+
+ note: each relevant upgrades are done one after the other for simplicity.
+ This avoid having repository is partially inconsistent state while
+ upgrading.
+
+ repo: the current repository instance
+ maker_func: a factory function that can recreate a repository after an upgrade
+ """
+ clear = False
+
+ loop = 0
+
+ while not clear:
+ loop += 1
+ if loop > 100:
+ # XXX basic protection against infinite loop, make it better.
+ raise error.ProgrammingError("Too many auto upgrade loops")
+ clear = True
+ for get_action in AUTO_UPGRADE_ACTIONS:
+ action = get_action(repo)
+ if action is not None:
+ clear = False
+ with repo.wlock(wait=False), repo.lock(wait=False):
+ action = get_action(repo)
+ if action is not None:
+ action()
+ repo = maker_func()
+ return repo
--- a/rust/rhg/src/main.rs Fri Apr 15 22:02:07 2022 +0200
+++ b/rust/rhg/src/main.rs Tue Apr 05 05:19:47 2022 +0200
@@ -7,10 +7,10 @@
use clap::ArgMatches;
use format_bytes::{format_bytes, join};
use hg::config::{Config, ConfigSource};
-use hg::exit_codes;
use hg::repo::{Repo, RepoError};
use hg::utils::files::{get_bytes_from_os_str, get_path_from_bytes};
use hg::utils::SliceExt;
+use hg::{exit_codes, requirements};
use std::collections::HashSet;
use std::ffi::OsString;
use std::os::unix::prelude::CommandExt;
@@ -724,6 +724,50 @@
}
}
+/// Array of tuples of (auto upgrade conf, feature conf, local requirement)
+const AUTO_UPGRADES: &[((&str, &str), (&str, &str), &str)] = &[
+ (
+ ("format", "use-share-safe.automatic-upgrade-of-mismatching-repositories"),
+ ("format", "use-share-safe"),
+ requirements::SHARESAFE_REQUIREMENT,
+ ),
+];
+
+/// Mercurial allows users to automatically upgrade their repository.
+/// `rhg` does not have the ability to upgrade yet, so fallback if an upgrade
+/// is needed.
+fn check_auto_upgrade(
+ config: &Config,
+ reqs: &HashSet<String>,
+) -> Result<(), CommandError> {
+ for (upgrade_conf, feature_conf, local_req) in AUTO_UPGRADES.iter() {
+ let auto_upgrade = config
+ .get_bool(upgrade_conf.0.as_bytes(), upgrade_conf.1.as_bytes())?;
+
+ if auto_upgrade {
+ let want_it = config.get_bool(
+ feature_conf.0.as_bytes(),
+ feature_conf.1.as_bytes(),
+ )?;
+ let have_it = reqs.contains(*local_req);
+
+ let action = match (want_it, have_it) {
+ (true, false) => Some("upgrade"),
+ (false, true) => Some("downgrade"),
+ _ => None,
+ };
+ if let Some(action) = action {
+ let message = format!(
+ "automatic {} {}.{}",
+ action, upgrade_conf.0, upgrade_conf.1
+ );
+ return Err(CommandError::unsupported(message));
+ }
+ }
+ }
+ Ok(())
+}
+
fn check_unsupported(
config: &Config,
repo: Result<&Repo, &NoRepoInCwdError>,
@@ -740,6 +784,7 @@
if repo.has_subrepos()? {
Err(CommandError::unsupported("sub-repositories"))?
}
+ check_auto_upgrade(config, repo.requirements())?;
}
if config.has_non_empty_section(b"encode") {
--- a/tests/test-help.t Fri Apr 15 22:02:07 2022 +0200
+++ b/tests/test-help.t Tue Apr 05 05:19:47 2022 +0200
@@ -1606,6 +1606,8 @@
"use-share-safe"
+ "use-share-safe.automatic-upgrade-of-mismatching-repositories"
+
"usestore"
"sparse-revlog"