# upgrade.py - functions for in place upgrade of Mercurial repository # # Copyright (c) 2016-present, Gregory Szorc # # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. from __future__ import absolute_import from .i18n import _ from . import ( error, hg, localrepo, pycompat, ) from .upgrade_utils import ( actions as upgrade_actions, engine as upgrade_engine, ) allformatvariant = upgrade_actions.allformatvariant # search without '-' to support older form on newer client. # # We don't enforce backward compatibility for debug command so this # might eventually be dropped. However, having to use two different # forms in script when comparing result is anoying enough to add # backward compatibility for a while. legacy_opts_map = { b'redeltaparent': b're-delta-parent', b'redeltamultibase': b're-delta-multibase', b'redeltaall': b're-delta-all', b'redeltafulladd': b're-delta-fulladd', } def upgraderepo( ui, repo, run=False, optimize=None, backup=True, manifest=None, changelog=None, filelogs=None, ): """Upgrade a repository in place.""" if optimize is None: optimize = [] optimize = {legacy_opts_map.get(o, o) for o in optimize} repo = repo.unfiltered() revlogs = set(upgrade_engine.UPGRADE_ALL_REVLOGS) specentries = ( (upgrade_engine.UPGRADE_CHANGELOG, changelog), (upgrade_engine.UPGRADE_MANIFEST, manifest), (upgrade_engine.UPGRADE_FILELOGS, filelogs), ) specified = [(y, x) for (y, x) in specentries if x is not None] if specified: # we have some limitation on revlogs to be recloned if any(x for y, x in specified): revlogs = set() for upgrade, enabled in specified: if enabled: revlogs.add(upgrade) else: # none are enabled for upgrade, __ in specified: revlogs.discard(upgrade) # Ensure the repository can be upgraded. upgrade_actions.check_source_requirements(repo) default_options = localrepo.defaultcreateopts(repo.ui) newreqs = localrepo.newreporequirements(repo.ui, default_options) newreqs.update(upgrade_actions.preservedrequirements(repo)) upgrade_actions.check_requirements_changes(repo, newreqs) # Find and validate all improvements that can be made. alloptimizations = upgrade_actions.findoptimizations(repo) # Apply and Validate arguments. optimizations = [] for o in alloptimizations: if o.name in optimize: optimizations.append(o) optimize.discard(o.name) if optimize: # anything left is unknown raise error.Abort( _(b'unknown optimization action requested: %s') % b', '.join(sorted(optimize)), hint=_(b'run without arguments to see valid optimizations'), ) deficiencies = upgrade_actions.finddeficiencies(repo) actions = upgrade_actions.determineactions( repo, deficiencies, repo.requirements, newreqs ) actions.extend( o for o in sorted(optimizations) # determineactions could have added optimisation if o not in actions ) removedreqs = repo.requirements - newreqs addedreqs = newreqs - repo.requirements if revlogs != upgrade_engine.UPGRADE_ALL_REVLOGS: incompatible = upgrade_actions.RECLONES_REQUIREMENTS & ( removedreqs | addedreqs ) if incompatible: msg = _( b'ignoring revlogs selection flags, format requirements ' b'change: %s\n' ) ui.warn(msg % b', '.join(sorted(incompatible))) revlogs = upgrade_engine.UPGRADE_ALL_REVLOGS def write_labeled(l, label): first = True for r in sorted(l): if not first: ui.write(b', ') ui.write(r, label=label) first = False def printrequirements(): ui.write(_(b'requirements\n')) ui.write(_(b' preserved: ')) write_labeled( newreqs & repo.requirements, "upgrade-repo.requirement.preserved" ) ui.write((b'\n')) removed = repo.requirements - newreqs if repo.requirements - newreqs: ui.write(_(b' removed: ')) write_labeled(removed, "upgrade-repo.requirement.removed") ui.write((b'\n')) added = newreqs - repo.requirements if added: ui.write(_(b' added: ')) write_labeled(added, "upgrade-repo.requirement.added") ui.write((b'\n')) ui.write(b'\n') def printoptimisations(): optimisations = [ a for a in actions if a.type == upgrade_actions.OPTIMISATION ] optimisations.sort(key=lambda a: a.name) if optimisations: ui.write(_(b'optimisations: ')) write_labeled( [a.name for a in optimisations], "upgrade-repo.optimisation.performed", ) ui.write(b'\n\n') def printupgradeactions(): for a in actions: ui.status(b'%s\n %s\n\n' % (a.name, a.upgrademessage)) def print_affected_revlogs(): if not revlogs: ui.write((b'no revlogs to process\n')) else: ui.write((b'processed revlogs:\n')) for r in sorted(revlogs): ui.write((b' - %s\n' % r)) ui.write((b'\n')) upgrade_op = upgrade_actions.UpgradeOperation( newreqs, [a.name for a in actions], revlogs, ) if not run: fromconfig = [] onlydefault = [] for d in deficiencies: if d.fromconfig(repo): fromconfig.append(d) elif d.default: onlydefault.append(d) if fromconfig or onlydefault: if fromconfig: ui.status( _( b'repository lacks features recommended by ' b'current config options:\n\n' ) ) for i in fromconfig: ui.status(b'%s\n %s\n\n' % (i.name, i.description)) if onlydefault: ui.status( _( b'repository lacks features used by the default ' b'config options:\n\n' ) ) for i in onlydefault: ui.status(b'%s\n %s\n\n' % (i.name, i.description)) ui.status(b'\n') else: ui.status( _( b'(no feature deficiencies found in existing ' b'repository)\n' ) ) ui.status( _( b'performing an upgrade with "--run" will make the following ' b'changes:\n\n' ) ) printrequirements() printoptimisations() printupgradeactions() print_affected_revlogs() unusedoptimize = [i for i in alloptimizations if i not in actions] if unusedoptimize: ui.status( _( b'additional optimizations are available by specifying ' b'"--optimize ":\n\n' ) ) for i in unusedoptimize: ui.status(_(b'%s\n %s\n\n') % (i.name, i.description)) return # Else we're in the run=true case. ui.write(_(b'upgrade will perform the following actions:\n\n')) printrequirements() printoptimisations() printupgradeactions() print_affected_revlogs() ui.status(_(b'beginning upgrade...\n')) with repo.wlock(), repo.lock(): ui.status(_(b'repository locked and read-only\n')) # 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. tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path) backuppath = None try: ui.status( _( b'creating temporary repository to stage migrated ' b'data: %s\n' ) % tmppath ) # 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) with dstrepo.wlock(), dstrepo.lock(): backuppath = upgrade_engine.upgrade( ui, repo, dstrepo, upgrade_op ) if not (backup or backuppath is None): ui.status( _(b'removing old repository content%s\n') % backuppath ) repo.vfs.rmtree(backuppath, forcibly=True) backuppath = None finally: ui.status(_(b'removing temporary repository %s\n') % tmppath) repo.vfs.rmtree(tmppath, forcibly=True) if backuppath and not ui.quiet: ui.warn( _(b'copy of old repository backed up at %s\n') % backuppath ) ui.warn( _( b'the old repository will not be deleted; remove ' b'it to free up disk space once the upgraded ' b'repository is verified\n' ) ) if upgrade_actions.sharesafe.name in addedreqs: ui.warn( _( b'repository upgraded to share safe mode, existing' b' shares will still work in old non-safe mode. ' b'Re-share existing shares to use them in safe mode' b' New shares will be created in safe mode.\n' ) ) if upgrade_actions.sharesafe.name in removedreqs: ui.warn( _( b'repository downgraded to not use share safe mode, ' b'existing shares will not work and needs to' b' be reshared.\n' ) )