mercurial/upgrade_utils/auto_upgrade.py
author Pierre-Yves David <pierre-yves.david@octobus.net>
Tue, 05 Apr 2022 05:19:47 +0200
changeset 49192 2ab79873786e
child 49194 e4b31016e194
permissions -rw-r--r--
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

# 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