auto_upgrade.py
254 lines
| 8.5 KiB
| text/x-python
|
PythonLexer
r50087 | # 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, | ||||
) | ||||
r50089 | 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, | ||||
) | ||||
r50087 | ||||
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', | ||||
) | ||||
r50234 | auto_upgrade_quiet = ui.configbool( | |||
b'format', | ||||
b'use-share-safe.automatic-upgrade-of-mismatching-repositories:quiet', | ||||
) | ||||
r50087 | ||||
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(): | ||||
r50234 | if not (ui.quiet or auto_upgrade_quiet): | |||
r50087 | 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(): | ||||
r50234 | if not (ui.quiet or auto_upgrade_quiet): | |||
r50087 | ui.write_err(msg) | |||
ui.write_err(hint) | ||||
requirements.discard(requirementsmod.SHARESAFE_REQUIREMENT) | ||||
scmutil.writereporequirements(repo, requirements) | ||||
return action | ||||
r50089 | 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', | ||||
) | ||||
r50237 | auto_upgrade_quiet = ui.configbool( | |||
b'format', | ||||
b'use-dirstate-tracked-hint.automatic-upgrade-of-mismatching-repositories:quiet', | ||||
) | ||||
r50089 | ||||
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(): | ||||
r50237 | if not (ui.quiet or auto_upgrade_quiet): | |||
r50089 | 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(): | ||||
r50237 | if not (ui.quiet or auto_upgrade_quiet): | |||
r50089 | 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 | ||||
r50090 | 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) | ||||
r50235 | auto_upgrade_dv2 = ui.configbool( | |||
r50090 | b'format', | |||
b'use-dirstate-v2.automatic-upgrade-of-mismatching-repositories', | ||||
) | ||||
r50236 | auto_upgrade_dv2_quiet = ui.configbool( | |||
b'format', | ||||
b'use-dirstate-v2.automatic-upgrade-of-mismatching-repositories:quiet', | ||||
) | ||||
r50090 | ||||
action = None | ||||
r50235 | if auto_upgrade_dv2: | |||
r50090 | 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(): | ||||
r50236 | if not (ui.quiet or auto_upgrade_dv2_quiet): | |||
r50090 | 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(): | ||||
r50236 | if not (ui.quiet or auto_upgrade_dv2_quiet): | |||
r50090 | 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 | ||||
r50087 | AUTO_UPGRADE_ACTIONS = [ | |||
r50090 | get_dirstate_v2_action, | |||
r50087 | get_share_safe_action, | |||
r50089 | get_tracked_hint_action, | |||
r50087 | ] | |||
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 | ||||
r50095 | 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 | ||||
r50087 | return repo | |||