mercurial/upgrade_utils/auto_upgrade.py
author Pierre-Yves David <pierre-yves.david@octobus.net>
Wed, 18 May 2022 16:50:55 +0100
changeset 49220 3376b5d9a697
parent 49200 71774d799de7
child 49339 9e203cda3238
permissions -rw-r--r--
debugdeltachain: glob variance of "test-generaldelta" We mostly care about generaldelta happening, the exact details of storage size variation is not really important so we can glob it instead of having multiple lines for each variances. This will make updating the output of the command simpler.

# 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,
)

from . import (
    actions,
    engine,
)


class AutoUpgradeOperation(actions.BaseOperation):
    """A limited Upgrade Operation used to run simple auto upgrade task

    (Expand it as needed in the future)
    """

    def __init__(self, req):
        super().__init__(
            new_requirements=req,
            backup_store=False,
        )


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


def get_tracked_hint_action(repo):
    """return an automatic-upgrade action for `tracked-hint` 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 = set(repo.requirements)
    auto_upgrade_tracked_hint = ui.configbool(
        b'format',
        b'use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories',
    )

    action = None

    if auto_upgrade_tracked_hint:
        th_config = ui.configbool(b'format', b'use-dirstate-tracked-hint')
        th_local = requirementsmod.DIRSTATE_TRACKED_HINT_V1 in requirements
        if th_config and not th_local:
            msg = _(
                b"automatically upgrading repository to the `tracked-hint`"
                b" feature\n"
            )
            hint = b"(see `hg help config.format.use-dirstate-tracked-hint` for details)\n"

            def action():
                if not ui.quiet:
                    ui.write_err(msg)
                    ui.write_err(hint)
                requirements.add(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
                op = AutoUpgradeOperation(requirements)
                engine.upgrade_tracked_hint(ui, repo, op, add=True)

        elif th_local and not th_config:
            msg = _(
                b"automatically downgrading repository from the `tracked-hint`"
                b" feature\n"
            )
            hint = b"(see `hg help config.format.use-dirstate-tracked-hint` for details)\n"

            def action():
                if not ui.quiet:
                    ui.write_err(msg)
                    ui.write_err(hint)
                requirements.discard(requirementsmod.DIRSTATE_TRACKED_HINT_V1)
                op = AutoUpgradeOperation(requirements)
                engine.upgrade_tracked_hint(ui, repo, op, add=False)

    return action


def get_dirstate_v2_action(repo):
    """return an automatic-upgrade action for `dirstate-v2` 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 = set(repo.requirements)
    auto_upgrade_tracked_hint = ui.configbool(
        b'format',
        b'use-dirstate-v2.automatic-upgrade-of-mismatching-repositories',
    )

    action = None

    if auto_upgrade_tracked_hint:
        d2_config = ui.configbool(b'format', b'use-dirstate-v2')
        d2_local = requirementsmod.DIRSTATE_V2_REQUIREMENT in requirements
        if d2_config and not d2_local:
            msg = _(
                b"automatically upgrading repository to the `dirstate-v2`"
                b" feature\n"
            )
            hint = (
                b"(see `hg help config.format.use-dirstate-v2` for details)\n"
            )

            def action():
                if not ui.quiet:
                    ui.write_err(msg)
                    ui.write_err(hint)
                requirements.add(requirementsmod.DIRSTATE_V2_REQUIREMENT)
                fake_op = AutoUpgradeOperation(requirements)
                engine.upgrade_dirstate(repo.ui, repo, fake_op, b'v1', b'v2')

        elif d2_local and not d2_config:
            msg = _(
                b"automatically downgrading repository from the `dirstate-v2`"
                b" feature\n"
            )
            hint = (
                b"(see `hg help config.format.use-dirstate-v2` for details)\n"
            )

            def action():
                if not ui.quiet:
                    ui.write_err(msg)
                    ui.write_err(hint)
                requirements.discard(requirementsmod.DIRSTATE_V2_REQUIREMENT)
                fake_op = AutoUpgradeOperation(requirements)
                engine.upgrade_dirstate(repo.ui, repo, fake_op, b'v2', b'v1')

    return action


AUTO_UPGRADE_ACTIONS = [
    get_dirstate_v2_action,
    get_share_safe_action,
    get_tracked_hint_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

    try:
        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()
    except error.LockError:
        # if we cannot get the lock, ignore the auto-upgrade attemps and
        # proceed. We might want to make this behavior configurable in the
        # future.
        pass

    return repo