Patchwork D12611: auto-upgrade: introduce a way to auto-upgrade to/from share-safe

login
register
mail settings
Submitter phabricator
Date May 6, 2022, 8:38 a.m.
Message ID <differential-rev-PHID-DREV-pnpxpqx55ikjkwtk2k7s-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/50965/
State New
Headers show

Comments

phabricator - May 6, 2022, 8:38 a.m.
marmoute created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  This is the first "automatic-upgrade" capabilities. In the following commits,
  similar features are coming for other "fast to upgrade" format.
  
  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 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 infinite loop for now, but it will get
  proper attention in a later changesets.

REPOSITORY
  rHG Mercurial

BRANCH
  default

REVISION DETAIL
  https://phab.mercurial-scm.org/D12611

AFFECTED 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

CHANGE DETAILS




To: marmoute, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/tests/test-help.t b/tests/test-help.t
--- a/tests/test-help.t
+++ b/tests/test-help.t
@@ -1603,6 +1603,8 @@ 
   
       "use-share-safe"
   
+      "use-share-safe.automatic-upgrade-of-mismatching-repositories"
+  
       "usestore"
   
       "sparse-revlog"
diff --git a/rust/rhg/src/main.rs b/rust/rhg/src/main.rs
--- a/rust/rhg/src/main.rs
+++ b/rust/rhg/src/main.rs
@@ -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") {
diff --git a/mercurial/upgrade_utils/auto_upgrade.py b/mercurial/upgrade_utils/auto_upgrade.py
new file mode 100644
--- /dev/null
+++ b/mercurial/upgrade_utils/auto_upgrade.py
@@ -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
diff --git a/mercurial/upgrade.py b/mercurial/upgrade.py
--- a/mercurial/upgrade.py
+++ b/mercurial/upgrade.py
@@ -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
 
 
diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
--- a/mercurial/localrepo.py
+++ b/mercurial/localrepo.py
@@ -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):
diff --git a/mercurial/helptext/config.txt b/mercurial/helptext/config.txt
--- a/mercurial/helptext/config.txt
+++ b/mercurial/helptext/config.txt
@@ -1032,6 +1032,24 @@ 
 
     Enabled by default in Mercurial 6.1.
 
+``use-share-safe.automatic-upgrade-of-mismatching-repositories``
+   When enable, automatic upgrade will be triggered when a repository format
+   mismatch its `use-share-safe` config.
+
+   This is an advanced behavior that most user will not needs. We recommand you
+   don't use this unless you are a seasoned administrator of a Mercurial install
+   base.
+
+   Automatic upgrade mean 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 apply to operation that would have been
+   read-only (like hg status).
+
+   This configuration will apply for move 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 recommand
+   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
diff --git a/mercurial/configitems.py b/mercurial/configitems.py
--- a/mercurial/configitems.py
+++ b/mercurial/configitems.py
@@ -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,