upgrade.py
416 lines
| 14.6 KiB
| text/x-python
|
PythonLexer
/ mercurial / upgrade.py
|
r31894 | # upgrade.py - functions for in place upgrade of Mercurial repository | ||
|
r31864 | # | ||
|
r31895 | # Copyright (c) 2016-present, Gregory Szorc | ||
|
r31864 | # | ||
# 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, | ||||
|
r35344 | hg, | ||
|
r31893 | localrepo, | ||
|
r46852 | lock as lockmod, | ||
|
r38183 | pycompat, | ||
|
r46852 | requirements as requirementsmod, | ||
scmutil, | ||||
r46661 | ) | |||
from .upgrade_utils import ( | ||||
r46662 | actions as upgrade_actions, | |||
r46661 | engine as upgrade_engine, | |||
|
r31864 | ) | ||
|
r46852 | from .utils import ( | ||
stringutil, | ||||
) | ||||
r46662 | allformatvariant = upgrade_actions.allformatvariant | |||
|
r43346 | |||
def upgraderepo( | ||||
ui, | ||||
repo, | ||||
run=False, | ||||
optimize=None, | ||||
backup=True, | ||||
manifest=None, | ||||
changelog=None, | ||||
r46612 | filelogs=None, | |||
|
r43346 | ): | ||
|
r31864 | """Upgrade a repository in place.""" | ||
|
r41120 | if optimize is None: | ||
|
r49303 | optimize = set() | ||
|
r31864 | repo = repo.unfiltered() | ||
r49274 | specified_revlogs = {} | |||
if changelog is not None: | ||||
specified_revlogs[upgrade_engine.UPGRADE_CHANGELOG] = changelog | ||||
if manifest is not None: | ||||
specified_revlogs[upgrade_engine.UPGRADE_MANIFEST] = manifest | ||||
if filelogs is not None: | ||||
specified_revlogs[upgrade_engine.UPGRADE_FILELOGS] = filelogs | ||||
r43098 | ||||
|
r31864 | # Ensure the repository can be upgraded. | ||
r46663 | upgrade_actions.check_source_requirements(repo) | |||
|
r31864 | |||
r46666 | default_options = localrepo.defaultcreateopts(repo.ui) | |||
newreqs = localrepo.newreporequirements(repo.ui, default_options) | ||||
r46662 | newreqs.update(upgrade_actions.preservedrequirements(repo)) | |||
|
r31864 | |||
r46666 | upgrade_actions.check_requirements_changes(repo, newreqs) | |||
|
r31864 | |||
# Find and validate all improvements that can be made. | ||||
r46662 | alloptimizations = upgrade_actions.findoptimizations(repo) | |||
|
r31864 | |||
|
r31899 | # Apply and Validate arguments. | ||
optimizations = [] | ||||
for o in alloptimizations: | ||||
if o.name in optimize: | ||||
optimizations.append(o) | ||||
optimize.discard(o.name) | ||||
|
r43346 | if optimize: # anything left is unknown | ||
raise error.Abort( | ||||
|
r43347 | _(b'unknown optimization action requested: %s') | ||
% b', '.join(sorted(optimize)), | ||||
|
r43387 | hint=_(b'run without arguments to see valid optimizations'), | ||
|
r43346 | ) | ||
|
r31864 | |||
|
r46822 | format_upgrades = upgrade_actions.find_format_upgrades(repo) | ||
|
r46827 | up_actions = upgrade_actions.determine_upgrade_actions( | ||
|
r46826 | repo, format_upgrades, optimizations, repo.requirements, newreqs | ||
|
r43346 | ) | ||
|
r46830 | removed_actions = upgrade_actions.find_format_downgrades(repo) | ||
|
r31864 | |||
r49273 | # check if we need to touch revlog and if so, which ones | |||
r49275 | touched_revlogs = set() | |||
overwrite_msg = _(b'warning: ignoring %14s, as upgrade is changing: %s\n') | ||||
r49276 | select_msg = _(b'note: selecting %s for processing to change: %s\n') | |||
msg_issued = 0 | ||||
r49275 | ||||
FL = upgrade_engine.UPGRADE_FILELOGS | ||||
MN = upgrade_engine.UPGRADE_MANIFEST | ||||
CL = upgrade_engine.UPGRADE_CHANGELOG | ||||
r49277 | if optimizations: | |||
if any(specified_revlogs.values()): | ||||
# we have some limitation on revlogs to be recloned | ||||
for rl, enabled in specified_revlogs.items(): | ||||
if enabled: | ||||
touched_revlogs.add(rl) | ||||
else: | ||||
touched_revlogs = set(upgrade_engine.UPGRADE_ALL_REVLOGS) | ||||
for rl, enabled in specified_revlogs.items(): | ||||
if not enabled: | ||||
touched_revlogs.discard(rl) | ||||
r49628 | if repo.shared(): | |||
unsafe_actions = set() | ||||
unsafe_actions.update(up_actions) | ||||
unsafe_actions.update(removed_actions) | ||||
unsafe_actions.update(optimizations) | ||||
unsafe_actions = [ | ||||
a for a in unsafe_actions if not a.compatible_with_share | ||||
] | ||||
unsafe_actions.sort(key=lambda a: a.name) | ||||
if unsafe_actions: | ||||
m = _(b'cannot use these actions on a share repository: %s') | ||||
h = _(b'upgrade the main repository directly') | ||||
actions = b', '.join(a.name for a in unsafe_actions) | ||||
m %= actions | ||||
raise error.Abort(m, hint=h) | ||||
r49275 | for action in sorted(up_actions + removed_actions, key=lambda a: a.name): | |||
# optimisation does not "requires anything, they just needs it. | ||||
if action.type != upgrade_actions.FORMAT_VARIANT: | ||||
continue | ||||
r43100 | ||||
r49275 | if action.touches_filelogs and FL not in touched_revlogs: | |||
if FL in specified_revlogs: | ||||
if not specified_revlogs[FL]: | ||||
msg = overwrite_msg % (b'--no-filelogs', action.name) | ||||
ui.warn(msg) | ||||
r49276 | msg_issued = 2 | |||
else: | ||||
msg = select_msg % (b'all-filelogs', action.name) | ||||
ui.status(msg) | ||||
if not ui.quiet: | ||||
msg_issued = 1 | ||||
r49275 | touched_revlogs.add(FL) | |||
r49276 | ||||
r49275 | if action.touches_manifests and MN not in touched_revlogs: | |||
if MN in specified_revlogs: | ||||
if not specified_revlogs[MN]: | ||||
msg = overwrite_msg % (b'--no-manifest', action.name) | ||||
ui.warn(msg) | ||||
r49276 | msg_issued = 2 | |||
else: | ||||
msg = select_msg % (b'all-manifestlogs', action.name) | ||||
ui.status(msg) | ||||
if not ui.quiet: | ||||
msg_issued = 1 | ||||
r49275 | touched_revlogs.add(MN) | |||
r49276 | ||||
r49275 | if action.touches_changelog and CL not in touched_revlogs: | |||
if CL in specified_revlogs: | ||||
if not specified_revlogs[CL]: | ||||
msg = overwrite_msg % (b'--no-changelog', action.name) | ||||
ui.warn(msg) | ||||
msg_issued = True | ||||
r49276 | else: | |||
msg = select_msg % (b'changelog', action.name) | ||||
ui.status(msg) | ||||
if not ui.quiet: | ||||
msg_issued = 1 | ||||
r49275 | touched_revlogs.add(CL) | |||
r49276 | if msg_issued >= 2: | |||
r49275 | ui.warn((b"\n")) | |||
r49276 | elif msg_issued >= 1: | |||
ui.status((b"\n")) | ||||
r43100 | ||||
r46673 | upgrade_op = upgrade_actions.UpgradeOperation( | |||
|
r46804 | ui, | ||
r46673 | newreqs, | |||
|
r46807 | repo.requirements, | ||
|
r46827 | up_actions, | ||
|
r46830 | removed_actions, | ||
r49277 | touched_revlogs, | |||
|
r47092 | backup, | ||
r46673 | ) | |||
|
r31864 | if not run: | ||
fromconfig = [] | ||||
|
r31904 | onlydefault = [] | ||
|
r31864 | |||
|
r46822 | for d in format_upgrades: | ||
|
r32031 | if d.fromconfig(repo): | ||
|
r31901 | fromconfig.append(d) | ||
|
r32031 | elif d.default: | ||
|
r31904 | onlydefault.append(d) | ||
|
r31864 | |||
|
r31904 | if fromconfig or onlydefault: | ||
|
r31864 | |||
if fromconfig: | ||||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'repository lacks features recommended by ' | ||
b'current config options:\n\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r31864 | for i in fromconfig: | ||
r45302 | ui.status(b'%s\n %s\n\n' % (i.name, i.description)) | |||
|
r31864 | |||
if onlydefault: | ||||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'repository lacks features used by the default ' | ||
b'config options:\n\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r31864 | for i in onlydefault: | ||
r45302 | ui.status(b'%s\n %s\n\n' % (i.name, i.description)) | |||
|
r31864 | |||
r45302 | ui.status(b'\n') | |||
|
r31864 | else: | ||
|
r46822 | ui.status(_(b'(no format upgrades found in existing repository)\n')) | ||
|
r31864 | |||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'performing an upgrade with "--run" will make the following ' | ||
b'changes:\n\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r31864 | |||
|
r46807 | upgrade_op.print_requirements() | ||
|
r46806 | upgrade_op.print_optimisations() | ||
|
r46805 | upgrade_op.print_upgrade_actions() | ||
|
r46804 | upgrade_op.print_affected_revlogs() | ||
|
r31864 | |||
|
r46808 | if upgrade_op.unused_optimizations: | ||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'additional optimizations are available by specifying ' | ||
b'"--optimize <name>":\n\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r46808 | upgrade_op.print_unused_optimizations() | ||
|
r31864 | return | ||
|
r46848 | if not (upgrade_op.upgrade_actions or upgrade_op.removed_actions): | ||
ui.status(_(b'nothing to do\n')) | ||||
return | ||||
|
r31864 | # Else we're in the run=true case. | ||
|
r43347 | ui.write(_(b'upgrade will perform the following actions:\n\n')) | ||
|
r46807 | upgrade_op.print_requirements() | ||
|
r46806 | upgrade_op.print_optimisations() | ||
|
r46805 | upgrade_op.print_upgrade_actions() | ||
|
r46804 | upgrade_op.print_affected_revlogs() | ||
|
r31864 | |||
r45302 | ui.status(_(b'beginning upgrade...\n')) | |||
|
r33438 | with repo.wlock(), repo.lock(): | ||
r45302 | ui.status(_(b'repository locked and read-only\n')) | |||
|
r33438 | # Our strategy for upgrading the repository is to create a new, | ||
# temporary repository, write data to it, then do a swap of the | ||||
# data. There are less heavyweight ways to do this, but it is easier | ||||
# to create a new repo object than to instantiate all the components | ||||
# (like the store) separately. | ||||
|
r43347 | tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path) | ||
|
r33438 | backuppath = None | ||
try: | ||||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r46839 | b'creating temporary repository to stage upgraded ' | ||
|
r43347 | b'data: %s\n' | ||
|
r43346 | ) | ||
% tmppath | ||||
) | ||||
|
r35343 | |||
|
r35380 | # clone ui without using ui.copy because repo.ui is protected | ||
repoui = repo.ui.__class__(repo.ui) | ||||
dstrepo = hg.repository(repoui, path=tmppath, create=True) | ||||
|
r31864 | |||
|
r33438 | with dstrepo.wlock(), dstrepo.lock(): | ||
r46661 | backuppath = upgrade_engine.upgrade( | |||
r46673 | ui, repo, dstrepo, upgrade_op | |||
|
r43346 | ) | ||
|
r31864 | |||
|
r33438 | finally: | ||
r45302 | ui.status(_(b'removing temporary repository %s\n') % tmppath) | |||
|
r33438 | repo.vfs.rmtree(tmppath, forcibly=True) | ||
|
r31864 | |||
r45302 | if backuppath and not ui.quiet: | |||
|
r43346 | ui.warn( | ||
|
r43347 | _(b'copy of old repository backed up at %s\n') % backuppath | ||
|
r43346 | ) | ||
ui.warn( | ||||
_( | ||||
|
r43347 | b'the old repository will not be deleted; remove ' | ||
b'it to free up disk space once the upgraded ' | ||||
b'repository is verified\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r46617 | |||
|
r46830 | upgrade_op.print_post_op_messages() | ||
|
r46852 | |||
|
r47050 | def upgrade_share_to_safe( | ||
|
r47051 | ui, | ||
hgvfs, | ||||
storevfs, | ||||
current_requirements, | ||||
mismatch_config, | ||||
mismatch_warn, | ||||
|
r47050 | ): | ||
|
r46852 | """Upgrades a share to use share-safe mechanism""" | ||
wlock = None | ||||
|
r47047 | store_requirements = localrepo._readrequires(storevfs, False) | ||
|
r47048 | original_crequirements = current_requirements.copy() | ||
|
r47047 | # after upgrade, store requires will be shared, so lets find | ||
# the requirements which are not present in store and | ||||
# write them to share's .hg/requires | ||||
diffrequires = current_requirements - store_requirements | ||||
# add share-safe requirement as it will mark the share as share-safe | ||||
diffrequires.add(requirementsmod.SHARESAFE_REQUIREMENT) | ||||
current_requirements.add(requirementsmod.SHARESAFE_REQUIREMENT) | ||||
|
r47050 | # in `allow` case, we don't try to upgrade, we just respect the source | ||
# state, update requirements and continue | ||||
if mismatch_config == b'allow': | ||||
return | ||||
|
r46852 | try: | ||
wlock = lockmod.trylock(ui, hgvfs, b'wlock', 0, 0) | ||||
|
r47048 | # some process might change the requirement in between, re-read | ||
# and update current_requirements | ||||
locked_requirements = localrepo._readrequires(hgvfs, True) | ||||
if locked_requirements != original_crequirements: | ||||
removed = current_requirements - locked_requirements | ||||
# update current_requirements in place because it's passed | ||||
# as reference | ||||
current_requirements -= removed | ||||
current_requirements |= locked_requirements | ||||
diffrequires = current_requirements - store_requirements | ||||
# add share-safe requirement as it will mark the share as share-safe | ||||
diffrequires.add(requirementsmod.SHARESAFE_REQUIREMENT) | ||||
current_requirements.add(requirementsmod.SHARESAFE_REQUIREMENT) | ||||
|
r46852 | scmutil.writerequires(hgvfs, diffrequires) | ||
ui.warn(_(b'repository upgraded to use share-safe mode\n')) | ||||
except error.LockError as e: | ||||
r47077 | hint = _( | |||
|
r49280 | b"see `hg help config.format.use-share-safe` for more information" | ||
r47077 | ) | |||
|
r47050 | if mismatch_config == b'upgrade-abort': | ||
|
r46856 | raise error.Abort( | ||
_(b'failed to upgrade share, got error: %s') | ||||
r47077 | % stringutil.forcebytestr(e.strerror), | |||
hint=hint, | ||||
|
r46856 | ) | ||
|
r47051 | elif mismatch_warn: | ||
|
r46855 | ui.warn( | ||
_(b'failed to upgrade share, got error: %s\n') | ||||
r47077 | % stringutil.forcebytestr(e.strerror), | |||
hint=hint, | ||||
|
r46855 | ) | ||
|
r46852 | finally: | ||
if wlock: | ||||
wlock.release() | ||||
|
r46853 | |||
def downgrade_share_to_non_safe( | ||||
ui, | ||||
hgvfs, | ||||
sharedvfs, | ||||
current_requirements, | ||||
|
r47050 | mismatch_config, | ||
|
r47051 | mismatch_warn, | ||
|
r46853 | ): | ||
"""Downgrades a share which use share-safe to not use it""" | ||||
wlock = None | ||||
|
r47047 | source_requirements = localrepo._readrequires(sharedvfs, True) | ||
|
r47048 | original_crequirements = current_requirements.copy() | ||
|
r47047 | # we cannot be 100% sure on which requirements were present in store when | ||
# the source supported share-safe. However, we do know that working | ||||
# directory requirements were not there. Hence we remove them | ||||
source_requirements -= requirementsmod.WORKING_DIR_REQUIREMENTS | ||||
current_requirements |= source_requirements | ||||
current_requirements.remove(requirementsmod.SHARESAFE_REQUIREMENT) | ||||
|
r47050 | if mismatch_config == b'allow': | ||
return | ||||
|
r47047 | |||
|
r46853 | try: | ||
wlock = lockmod.trylock(ui, hgvfs, b'wlock', 0, 0) | ||||
|
r47048 | # some process might change the requirement in between, re-read | ||
# and update current_requirements | ||||
locked_requirements = localrepo._readrequires(hgvfs, True) | ||||
if locked_requirements != original_crequirements: | ||||
removed = current_requirements - locked_requirements | ||||
# update current_requirements in place because it's passed | ||||
# as reference | ||||
current_requirements -= removed | ||||
current_requirements |= locked_requirements | ||||
current_requirements |= source_requirements | ||||
current_requirements -= set(requirementsmod.SHARESAFE_REQUIREMENT) | ||||
|
r46853 | scmutil.writerequires(hgvfs, current_requirements) | ||
ui.warn(_(b'repository downgraded to not use share-safe mode\n')) | ||||
except error.LockError as e: | ||||
r47077 | hint = _( | |||
|
r49280 | b"see `hg help config.format.use-share-safe` for more information" | ||
r47077 | ) | |||
|
r47050 | # If upgrade-abort is set, abort when upgrade fails, else let the | ||
# process continue as `upgrade-allow` is set | ||||
if mismatch_config == b'downgrade-abort': | ||||
raise error.Abort( | ||||
_(b'failed to downgrade share, got error: %s') | ||||
r47077 | % stringutil.forcebytestr(e.strerror), | |||
hint=hint, | ||||
|
r47050 | ) | ||
|
r47051 | elif mismatch_warn: | ||
ui.warn( | ||||
_(b'failed to downgrade share, got error: %s\n') | ||||
r47077 | % stringutil.forcebytestr(e.strerror), | |||
hint=hint, | ||||
|
r47051 | ) | ||
|
r46853 | finally: | ||
if wlock: | ||||
wlock.release() | ||||