Mercurial > hg-stable
changeset 49224:2ab79873786e
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
author | Pierre-Yves David <pierre-yves.david@octobus.net> |
---|---|
date | Tue, 05 Apr 2022 05:19:47 +0200 |
parents | 4450faeb52bb |
children | 566066826e7c |
files | mercurial/configitems.py mercurial/helptext/config.txt mercurial/localrepo.py mercurial/upgrade.py mercurial/upgrade_utils/auto_upgrade.py rust/rhg/src/main.rs tests/test-help.t |
diffstat | 7 files changed, 191 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- 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") {