upgrade.py
1481 lines
| 45.7 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 __future__ import absolute_import | ||||
import stat | ||||
from .i18n import _ | ||||
|
r43359 | from .pycompat import getattr | ||
|
r31864 | from . import ( | ||
changelog, | ||||
error, | ||||
|
r35345 | filelog, | ||
|
r35344 | hg, | ||
|
r31893 | localrepo, | ||
|
r31864 | manifest, | ||
r45466 | metadata, | |||
|
r38183 | pycompat, | ||
|
r45932 | requirements, | ||
|
r31864 | revlog, | ||
scmutil, | ||||
util, | ||||
vfs as vfsmod, | ||||
) | ||||
|
r43346 | from .utils import compression | ||
r42306 | ||||
r43100 | # list of requirements that request a clone of all revlog if added/removed | |||
RECLONES_REQUIREMENTS = { | ||||
|
r43347 | b'generaldelta', | ||
|
r45933 | requirements.SPARSEREVLOG_REQUIREMENT, | ||
r43100 | } | |||
|
r43346 | |||
|
r31865 | def requiredsourcerequirements(repo): | ||
|
r31864 | """Obtain requirements required to be present to upgrade a repo. | ||
An upgrade will not be allowed if the repository doesn't have the | ||||
requirements returned by this function. | ||||
""" | ||||
|
r32291 | return { | ||
|
r31864 | # Introduced in Mercurial 0.9.2. | ||
|
r43347 | b'revlogv1', | ||
|
r31864 | # Introduced in Mercurial 0.9.2. | ||
|
r43347 | b'store', | ||
|
r32291 | } | ||
|
r31864 | |||
|
r43346 | |||
|
r31866 | def blocksourcerequirements(repo): | ||
|
r31864 | """Obtain requirements that will prevent an upgrade from occurring. | ||
An upgrade cannot be performed if the source repository contains a | ||||
requirements in the returned set. | ||||
""" | ||||
|
r32291 | return { | ||
|
r31864 | # The upgrade code does not yet support these experimental features. | ||
# This is an artificial limitation. | ||||
|
r45932 | requirements.TREEMANIFEST_REQUIREMENT, | ||
|
r31864 | # This was a precursor to generaldelta and was never enabled by default. | ||
# It should (hopefully) not exist in the wild. | ||||
|
r43347 | b'parentdelta', | ||
|
r31864 | # Upgrade should operate on the actual store, not the shared link. | ||
|
r45946 | requirements.SHARED_REQUIREMENT, | ||
|
r32291 | } | ||
|
r31864 | |||
|
r43346 | |||
|
r31867 | def supportremovedrequirements(repo): | ||
|
r31864 | """Obtain requirements that can be removed during an upgrade. | ||
If an upgrade were to create a repository that dropped a requirement, | ||||
the dropped requirement must appear in the returned set for the upgrade | ||||
to be allowed. | ||||
""" | ||||
r42306 | supported = { | |||
|
r45933 | requirements.SPARSEREVLOG_REQUIREMENT, | ||
requirements.SIDEDATA_REQUIREMENT, | ||||
requirements.COPIESSDC_REQUIREMENT, | ||||
requirements.NODEMAP_REQUIREMENT, | ||||
|
r46618 | requirements.SHARESAFE_REQUIREMENT, | ||
|
r38742 | } | ||
r42306 | for name in compression.compengines: | |||
engine = compression.compengines[name] | ||||
if engine.available() and engine.revlogheader(): | ||||
supported.add(b'exp-compression-%s' % name) | ||||
|
r43347 | if engine.name() == b'zstd': | ||
r42306 | supported.add(b'revlog-compression-zstd') | |||
return supported | ||||
|
r31864 | |||
|
r43346 | |||
|
r31870 | def supporteddestrequirements(repo): | ||
|
r31864 | """Obtain requirements that upgrade supports in the destination. | ||
If the result of the upgrade would create requirements not in this set, | ||||
the upgrade is disallowed. | ||||
Extensions should monkeypatch this to add their custom requirements. | ||||
""" | ||||
r42306 | supported = { | |||
|
r43347 | b'dotencode', | ||
b'fncache', | ||||
b'generaldelta', | ||||
b'revlogv1', | ||||
b'store', | ||||
|
r45933 | requirements.SPARSEREVLOG_REQUIREMENT, | ||
requirements.SIDEDATA_REQUIREMENT, | ||||
requirements.COPIESSDC_REQUIREMENT, | ||||
requirements.NODEMAP_REQUIREMENT, | ||||
|
r46059 | requirements.SHARESAFE_REQUIREMENT, | ||
|
r32291 | } | ||
r42306 | for name in compression.compengines: | |||
engine = compression.compengines[name] | ||||
if engine.available() and engine.revlogheader(): | ||||
supported.add(b'exp-compression-%s' % name) | ||||
|
r43347 | if engine.name() == b'zstd': | ||
r42306 | supported.add(b'revlog-compression-zstd') | |||
return supported | ||||
|
r31864 | |||
|
r43346 | |||
|
r31869 | def allowednewrequirements(repo): | ||
|
r31864 | """Obtain requirements that can be added to a repository during upgrade. | ||
This is used to disallow proposed requirements from being added when | ||||
they weren't present before. | ||||
We use a list of allowed requirement additions instead of a list of known | ||||
bad additions because the whitelist approach is safer and will prevent | ||||
future, unknown requirements from accidentally being added. | ||||
""" | ||||
r42306 | supported = { | |||
|
r43347 | b'dotencode', | ||
b'fncache', | ||||
b'generaldelta', | ||||
|
r45933 | requirements.SPARSEREVLOG_REQUIREMENT, | ||
requirements.SIDEDATA_REQUIREMENT, | ||||
requirements.COPIESSDC_REQUIREMENT, | ||||
requirements.NODEMAP_REQUIREMENT, | ||||
|
r46617 | requirements.SHARESAFE_REQUIREMENT, | ||
|
r32291 | } | ||
r42306 | for name in compression.compengines: | |||
engine = compression.compengines[name] | ||||
if engine.available() and engine.revlogheader(): | ||||
supported.add(b'exp-compression-%s' % name) | ||||
|
r43347 | if engine.name() == b'zstd': | ||
r42306 | supported.add(b'revlog-compression-zstd') | |||
return supported | ||||
|
r31864 | |||
|
r43346 | |||
|
r35303 | def preservedrequirements(repo): | ||
return set() | ||||
|
r43346 | |||
r46608 | DEFICIENCY = b'deficiency' | |||
r46609 | OPTIMISATION = b'optimization' | |||
|
r31864 | |||
|
r43346 | |||
|
r31868 | class improvement(object): | ||
|
r31864 | """Represents an improvement that can be made as part of an upgrade. | ||
The following attributes are defined on each instance: | ||||
name | ||||
Machine-readable string uniquely identifying this improvement. It | ||||
will be mapped to an action later in the upgrade process. | ||||
type | ||||
r46609 | Either ``DEFICIENCY`` or ``OPTIMISATION``. A deficiency is an obvious | |||
|
r31864 | problem. An optimization is an action (sometimes optional) that | ||
can be taken to further improve the state of the repository. | ||||
description | ||||
Message intended for humans explaining the improvement in more detail, | ||||
r46608 | including the implications of it. For ``DEFICIENCY`` types, should be | |||
r46609 | worded in the present tense. For ``OPTIMISATION`` types, should be | |||
|
r31864 | worded in the future tense. | ||
upgrademessage | ||||
Message intended for humans explaining what an upgrade addressing this | ||||
issue will do. Should be worded in the future tense. | ||||
""" | ||||
|
r43346 | |||
|
r32030 | def __init__(self, name, type, description, upgrademessage): | ||
|
r31864 | self.name = name | ||
self.type = type | ||||
self.description = description | ||||
self.upgrademessage = upgrademessage | ||||
|
r31902 | def __eq__(self, other): | ||
if not isinstance(other, improvement): | ||||
# This is what python tell use to do | ||||
return NotImplemented | ||||
return self.name == other.name | ||||
|
r32028 | def __ne__(self, other): | ||
|
r41033 | return not (self == other) | ||
|
r32028 | |||
|
r32029 | def __hash__(self): | ||
return hash(self.name) | ||||
|
r43346 | |||
|
r32032 | allformatvariant = [] | ||
|
r43346 | |||
|
r32032 | def registerformatvariant(cls): | ||
allformatvariant.append(cls) | ||||
return cls | ||||
|
r43346 | |||
|
r32030 | class formatvariant(improvement): | ||
|
r32031 | """an improvement subclass dedicated to repository format""" | ||
|
r43346 | |||
r46608 | type = DEFICIENCY | |||
|
r32031 | ### The following attributes should be defined for each class: | ||
# machine-readable string uniquely identifying this improvement. it will be | ||||
# mapped to an action later in the upgrade process. | ||||
name = None | ||||
|
r32030 | |||
|
r32031 | # message intended for humans explaining the improvement in more detail, | ||
r46608 | # including the implications of it ``DEFICIENCY`` types, should be worded | |||
|
r32031 | # in the present tense. | ||
description = None | ||||
# message intended for humans explaining what an upgrade addressing this | ||||
# issue will do. should be worded in the future tense. | ||||
upgrademessage = None | ||||
|
r32030 | |||
|
r32031 | # value of current Mercurial default for new repository | ||
default = None | ||||
def __init__(self): | ||||
raise NotImplementedError() | ||||
@staticmethod | ||||
def fromrepo(repo): | ||||
"""current value of the variant in the repository""" | ||||
raise NotImplementedError() | ||||
|
r32030 | |||
|
r32031 | @staticmethod | ||
def fromconfig(repo): | ||||
"""current value of the variant in the configuration""" | ||||
raise NotImplementedError() | ||||
|
r43346 | |||
|
r32031 | class requirementformatvariant(formatvariant): | ||
"""formatvariant based on a 'requirement' name. | ||||
Many format variant are controlled by a 'requirement'. We define a small | ||||
subclass to factor the code. | ||||
|
r32030 | """ | ||
|
r32031 | # the requirement that control this format variant | ||
_requirement = None | ||||
@staticmethod | ||||
|
r39583 | def _newreporequirements(ui): | ||
|
r40032 | return localrepo.newreporequirements( | ||
|
r43346 | ui, localrepo.defaultcreateopts(ui) | ||
) | ||||
|
r32031 | |||
@classmethod | ||||
def fromrepo(cls, repo): | ||||
assert cls._requirement is not None | ||||
return cls._requirement in repo.requirements | ||||
@classmethod | ||||
def fromconfig(cls, repo): | ||||
assert cls._requirement is not None | ||||
|
r39583 | return cls._requirement in cls._newreporequirements(repo.ui) | ||
|
r32031 | |||
|
r43346 | |||
|
r32032 | @registerformatvariant | ||
|
r32031 | class fncache(requirementformatvariant): | ||
|
r43347 | name = b'fncache' | ||
|
r32031 | |||
|
r43347 | _requirement = b'fncache' | ||
|
r32031 | |||
default = True | ||||
|
r43346 | description = _( | ||
|
r43347 | b'long and reserved filenames may not work correctly; ' | ||
b'repository performance is sub-optimal' | ||||
|
r43346 | ) | ||
|
r32031 | |||
|
r43346 | upgrademessage = _( | ||
|
r43347 | b'repository will be more resilient to storing ' | ||
b'certain paths and performance of certain ' | ||||
b'operations should be improved' | ||||
|
r43346 | ) | ||
|
r32031 | |||
|
r32032 | @registerformatvariant | ||
|
r32031 | class dotencode(requirementformatvariant): | ||
|
r43347 | name = b'dotencode' | ||
|
r32031 | |||
|
r43347 | _requirement = b'dotencode' | ||
|
r32031 | |||
default = True | ||||
|
r43346 | description = _( | ||
|
r43347 | b'storage of filenames beginning with a period or ' | ||
b'space may not work correctly' | ||||
|
r43346 | ) | ||
|
r32031 | |||
|
r43346 | upgrademessage = _( | ||
|
r43347 | b'repository will be better able to store files ' | ||
b'beginning with a space or period' | ||||
|
r43346 | ) | ||
|
r32031 | |||
|
r32032 | @registerformatvariant | ||
|
r32031 | class generaldelta(requirementformatvariant): | ||
|
r43347 | name = b'generaldelta' | ||
|
r32031 | |||
|
r43347 | _requirement = b'generaldelta' | ||
|
r32031 | |||
default = True | ||||
|
r43346 | description = _( | ||
|
r43347 | b'deltas within internal storage are unable to ' | ||
b'choose optimal revisions; repository is larger and ' | ||||
b'slower than it could be; interaction with other ' | ||||
b'repositories may require extra network and CPU ' | ||||
b'resources, making "hg push" and "hg pull" slower' | ||||
|
r43346 | ) | ||
|
r32031 | |||
|
r43346 | upgrademessage = _( | ||
|
r43347 | b'repository storage will be able to create ' | ||
b'optimal deltas; new repository data will be ' | ||||
b'smaller and read times should decrease; ' | ||||
b'interacting with other repositories using this ' | ||||
b'storage model should require less network and ' | ||||
b'CPU resources, making "hg push" and "hg pull" ' | ||||
b'faster' | ||||
|
r43346 | ) | ||
|
r32031 | |||
|
r32032 | @registerformatvariant | ||
|
r46617 | class sharedsafe(requirementformatvariant): | ||
name = b'exp-sharesafe' | ||||
_requirement = requirements.SHARESAFE_REQUIREMENT | ||||
default = False | ||||
description = _( | ||||
b'old shared repositories do not share source repository ' | ||||
b'requirements and config. This leads to various problems ' | ||||
b'when the source repository format is upgraded or some new ' | ||||
b'extensions are enabled.' | ||||
) | ||||
upgrademessage = _( | ||||
b'Upgrades a repository to share-safe format so that future ' | ||||
b'shares of this repository share its requirements and configs.' | ||||
) | ||||
@registerformatvariant | ||||
|
r38741 | class sparserevlog(requirementformatvariant): | ||
|
r43347 | name = b'sparserevlog' | ||
|
r38741 | |||
|
r45933 | _requirement = requirements.SPARSEREVLOG_REQUIREMENT | ||
|
r38741 | |||
|
r40954 | default = True | ||
|
r38741 | |||
|
r43346 | description = _( | ||
|
r43347 | b'in order to limit disk reading and memory usage on older ' | ||
b'version, the span of a delta chain from its root to its ' | ||||
b'end is limited, whatever the relevant data in this span. ' | ||||
b'This can severly limit Mercurial ability to build good ' | ||||
b'chain of delta resulting is much more storage space being ' | ||||
b'taken and limit reusability of on disk delta during ' | ||||
b'exchange.' | ||||
|
r43346 | ) | ||
|
r38741 | |||
|
r43346 | upgrademessage = _( | ||
|
r43347 | b'Revlog supports delta chain with more unused data ' | ||
b'between payload. These gaps will be skipped at read ' | ||||
b'time. This allows for better delta chains, making a ' | ||||
b'better compression and faster exchange with server.' | ||||
|
r43346 | ) | ||
|
r38741 | |||
@registerformatvariant | ||||
r43299 | class sidedata(requirementformatvariant): | |||
|
r43347 | name = b'sidedata' | ||
r43299 | ||||
|
r45933 | _requirement = requirements.SIDEDATA_REQUIREMENT | ||
r43299 | ||||
default = False | ||||
|
r43346 | description = _( | ||
|
r43347 | b'Allows storage of extra data alongside a revision, ' | ||
b'unlocking various caching options.' | ||||
|
r43346 | ) | ||
r43299 | ||||
|
r43347 | upgrademessage = _(b'Allows storage of extra data alongside a revision.') | ||
r43299 | ||||
|
r43346 | |||
r43299 | @registerformatvariant | |||
r45303 | class persistentnodemap(requirementformatvariant): | |||
name = b'persistent-nodemap' | ||||
|
r45933 | _requirement = requirements.NODEMAP_REQUIREMENT | ||
r45303 | ||||
default = False | ||||
description = _( | ||||
b'persist the node -> rev mapping on disk to speedup lookup' | ||||
) | ||||
upgrademessage = _(b'Speedup revision lookup by node id.') | ||||
@registerformatvariant | ||||
r43408 | class copiessdc(requirementformatvariant): | |||
name = b'copies-sdc' | ||||
|
r45933 | _requirement = requirements.COPIESSDC_REQUIREMENT | ||
r43408 | ||||
default = False | ||||
description = _(b'Stores copies information alongside changesets.') | ||||
upgrademessage = _( | ||||
b'Allows to use more efficient algorithm to deal with ' b'copy tracing.' | ||||
) | ||||
@registerformatvariant | ||||
|
r32031 | class removecldeltachain(formatvariant): | ||
|
r43347 | name = b'plain-cl-delta' | ||
|
r32031 | |||
default = True | ||||
|
r43346 | description = _( | ||
|
r43347 | b'changelog storage is using deltas instead of ' | ||
b'raw entries; changelog reading and any ' | ||||
b'operation relying on changelog data are slower ' | ||||
b'than they could be' | ||||
|
r43346 | ) | ||
|
r32031 | |||
|
r43346 | upgrademessage = _( | ||
|
r43347 | b'changelog storage will be reformated to ' | ||
b'store raw entries; changelog reading will be ' | ||||
b'faster; changelog size may be reduced' | ||||
|
r43346 | ) | ||
|
r32031 | |||
@staticmethod | ||||
def fromrepo(repo): | ||||
# Mercurial 4.0 changed changelogs to not use delta chains. Search for | ||||
# changelogs with deltas. | ||||
cl = repo.changelog | ||||
chainbase = cl.chainbase | ||||
return all(rev == chainbase(rev) for rev in cl) | ||||
@staticmethod | ||||
def fromconfig(repo): | ||||
return True | ||||
|
r32030 | |||
|
r43346 | |||
|
r35341 | @registerformatvariant | ||
class compressionengine(formatvariant): | ||||
|
r43347 | name = b'compression' | ||
default = b'zlib' | ||||
|
r35341 | |||
|
r43346 | description = _( | ||
|
r43347 | b'Compresion algorithm used to compress data. ' | ||
b'Some engine are faster than other' | ||||
|
r43346 | ) | ||
|
r35341 | |||
|
r43346 | upgrademessage = _( | ||
|
r43387 | b'revlog content will be recompressed with the new algorithm.' | ||
|
r43346 | ) | ||
|
r35341 | |||
@classmethod | ||||
def fromrepo(cls, repo): | ||||
r42305 | # we allow multiple compression engine requirement to co-exist because | |||
# strickly speaking, revlog seems to support mixed compression style. | ||||
# | ||||
# The compression used for new entries will be "the last one" | ||||
|
r43347 | compression = b'zlib' | ||
|
r35341 | for req in repo.requirements: | ||
r42305 | prefix = req.startswith | |||
|
r43347 | if prefix(b'revlog-compression-') or prefix(b'exp-compression-'): | ||
compression = req.split(b'-', 2)[2] | ||||
r42305 | return compression | |||
|
r35341 | |||
@classmethod | ||||
def fromconfig(cls, repo): | ||||
r44866 | compengines = repo.ui.configlist(b'format', b'revlog-compression') | |||
# return the first valid value as the selection code would do | ||||
for comp in compengines: | ||||
if comp in util.compengines: | ||||
return comp | ||||
# no valide compression found lets display it all for clarity | ||||
return b','.join(compengines) | ||||
|
r35341 | |||
|
r43346 | |||
r42212 | @registerformatvariant | |||
class compressionlevel(formatvariant): | ||||
|
r43347 | name = b'compression-level' | ||
default = b'default' | ||||
r42212 | ||||
|
r43347 | description = _(b'compression level') | ||
r42212 | ||||
|
r43347 | upgrademessage = _(b'revlog content will be recompressed') | ||
r42212 | ||||
@classmethod | ||||
def fromrepo(cls, repo): | ||||
comp = compressionengine.fromrepo(repo) | ||||
level = None | ||||
|
r43347 | if comp == b'zlib': | ||
level = repo.ui.configint(b'storage', b'revlog.zlib.level') | ||||
elif comp == b'zstd': | ||||
level = repo.ui.configint(b'storage', b'revlog.zstd.level') | ||||
r42212 | if level is None: | |||
|
r43347 | return b'default' | ||
r42212 | return bytes(level) | |||
@classmethod | ||||
def fromconfig(cls, repo): | ||||
comp = compressionengine.fromconfig(repo) | ||||
level = None | ||||
|
r43347 | if comp == b'zlib': | ||
level = repo.ui.configint(b'storage', b'revlog.zlib.level') | ||||
elif comp == b'zstd': | ||||
level = repo.ui.configint(b'storage', b'revlog.zstd.level') | ||||
r42212 | if level is None: | |||
|
r43347 | return b'default' | ||
r42212 | return bytes(level) | |||
|
r43346 | |||
|
r31896 | def finddeficiencies(repo): | ||
"""returns a list of deficiencies that the repo suffer from""" | ||||
deficiencies = [] | ||||
|
r31864 | |||
# We could detect lack of revlogv1 and store here, but they were added | ||||
# in 0.9.2 and we don't support upgrading repos without these | ||||
# requirements, so let's not bother. | ||||
|
r32032 | for fv in allformatvariant: | ||
if not fv.fromrepo(repo): | ||||
deficiencies.append(fv) | ||||
|
r31864 | |||
|
r31896 | return deficiencies | ||
|
r31864 | |||
|
r43346 | |||
|
r41120 | # 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 = { | ||||
|
r43347 | b'redeltaparent': b're-delta-parent', | ||
b'redeltamultibase': b're-delta-multibase', | ||||
b'redeltaall': b're-delta-all', | ||||
b'redeltafulladd': b're-delta-fulladd', | ||||
|
r41120 | } | ||
r46610 | ALL_OPTIMISATIONS = [] | |||
def register_optimization(obj): | ||||
ALL_OPTIMISATIONS.append(obj) | ||||
return obj | ||||
register_optimization( | ||||
improvement( | ||||
name=b're-delta-parent', | ||||
type=OPTIMISATION, | ||||
description=_( | ||||
b'deltas within internal storage will be recalculated to ' | ||||
b'choose an optimal base revision where this was not ' | ||||
b'already done; the size of the repository may shrink and ' | ||||
b'various operations may become faster; the first time ' | ||||
b'this optimization is performed could slow down upgrade ' | ||||
b'execution considerably; subsequent invocations should ' | ||||
b'not run noticeably slower' | ||||
), | ||||
upgrademessage=_( | ||||
b'deltas within internal storage will choose a new ' | ||||
b'base revision if needed' | ||||
), | ||||
) | ||||
) | ||||
register_optimization( | ||||
improvement( | ||||
name=b're-delta-multibase', | ||||
type=OPTIMISATION, | ||||
description=_( | ||||
b'deltas within internal storage will be recalculated ' | ||||
b'against multiple base revision and the smallest ' | ||||
b'difference will be used; the size of the repository may ' | ||||
b'shrink significantly when there are many merges; this ' | ||||
b'optimization will slow down execution in proportion to ' | ||||
b'the number of merges in the repository and the amount ' | ||||
b'of files in the repository; this slow down should not ' | ||||
b'be significant unless there are tens of thousands of ' | ||||
b'files and thousands of merges' | ||||
), | ||||
upgrademessage=_( | ||||
b'deltas within internal storage will choose an ' | ||||
b'optimal delta by computing deltas against multiple ' | ||||
b'parents; may slow down execution time ' | ||||
b'significantly' | ||||
), | ||||
) | ||||
) | ||||
register_optimization( | ||||
improvement( | ||||
name=b're-delta-all', | ||||
type=OPTIMISATION, | ||||
description=_( | ||||
b'deltas within internal storage will always be ' | ||||
b'recalculated without reusing prior deltas; this will ' | ||||
b'likely make execution run several times slower; this ' | ||||
b'optimization is typically not needed' | ||||
), | ||||
upgrademessage=_( | ||||
b'deltas within internal storage will be fully ' | ||||
b'recomputed; this will likely drastically slow down ' | ||||
b'execution time' | ||||
), | ||||
) | ||||
) | ||||
register_optimization( | ||||
improvement( | ||||
name=b're-delta-fulladd', | ||||
type=OPTIMISATION, | ||||
description=_( | ||||
b'every revision will be re-added as if it was new ' | ||||
b'content. It will go through the full storage ' | ||||
b'mechanism giving extensions a chance to process it ' | ||||
b'(eg. lfs). This is similar to "re-delta-all" but even ' | ||||
b'slower since more logic is involved.' | ||||
), | ||||
upgrademessage=_( | ||||
b'each revision will be added as new content to the ' | ||||
b'internal storage; this will likely drastically slow ' | ||||
b'down execution time, but some extensions might need ' | ||||
b'it' | ||||
), | ||||
) | ||||
) | ||||
|
r43346 | |||
|
r31896 | def findoptimizations(repo): | ||
"""Determine optimisation that could be used during upgrade""" | ||||
|
r31864 | # These are unconditionally added. There is logic later that figures out | ||
# which ones to apply. | ||||
r46610 | return list(ALL_OPTIMISATIONS) | |||
|
r31864 | |||
|
r43346 | |||
|
r31900 | def determineactions(repo, deficiencies, sourcereqs, destreqs): | ||
|
r31864 | """Determine upgrade actions that will be performed. | ||
|
r31896 | Given a list of improvements as returned by ``finddeficiencies`` and | ||
``findoptimizations``, determine the list of upgrade actions that | ||||
will be performed. | ||||
|
r31864 | |||
The role of this function is to filter improvements if needed, apply | ||||
recommended optimizations from the improvements list that make sense, | ||||
etc. | ||||
Returns a list of action names. | ||||
""" | ||||
newactions = [] | ||||
|
r31900 | for d in deficiencies: | ||
r45248 | name = d._requirement | |||
|
r31864 | |||
# If the action is a requirement that doesn't show up in the | ||||
# destination requirements, prune the action. | ||||
r45248 | if name is not None and name not in destreqs: | |||
|
r31864 | continue | ||
|
r31903 | newactions.append(d) | ||
|
r31864 | |||
# FUTURE consider adding some optimizations here for certain transitions. | ||||
# e.g. adding generaldelta could schedule parent redeltas. | ||||
return newactions | ||||
|
r43346 | |||
|
r31864 | def _revlogfrompath(repo, path): | ||
"""Obtain a revlog from a repo path. | ||||
An instance of the appropriate class is returned. | ||||
""" | ||||
|
r43347 | if path == b'00changelog.i': | ||
|
r31864 | return changelog.changelog(repo.svfs) | ||
|
r43347 | elif path.endswith(b'00manifest.i'): | ||
mandir = path[: -len(b'00manifest.i')] | ||||
|
r39279 | return manifest.manifestrevlog(repo.svfs, tree=mandir) | ||
|
r31864 | else: | ||
|
r43346 | # reverse of "/".join(("data", path + ".i")) | ||
|
r35345 | return filelog.filelog(repo.svfs, path[5:-2]) | ||
|
r31864 | |||
|
r43346 | |||
r42918 | def _copyrevlog(tr, destrepo, oldrl, unencodedname): | |||
"""copy all relevant files for `oldrl` into `destrepo` store | ||||
Files are copied "as is" without any transformation. The copy is performed | ||||
without extra checks. Callers are responsible for making sure the copied | ||||
content is compatible with format of the destination repository. | ||||
""" | ||||
oldrl = getattr(oldrl, '_revlog', oldrl) | ||||
newrl = _revlogfrompath(destrepo, unencodedname) | ||||
newrl = getattr(newrl, '_revlog', newrl) | ||||
oldvfs = oldrl.opener | ||||
newvfs = newrl.opener | ||||
oldindex = oldvfs.join(oldrl.indexfile) | ||||
newindex = newvfs.join(newrl.indexfile) | ||||
olddata = oldvfs.join(oldrl.datafile) | ||||
newdata = newvfs.join(newrl.datafile) | ||||
|
r43347 | with newvfs(newrl.indexfile, b'w'): | ||
|
r43346 | pass # create all the directories | ||
r42918 | ||||
util.copyfile(oldindex, newindex) | ||||
r43272 | copydata = oldrl.opener.exists(oldrl.datafile) | |||
if copydata: | ||||
r42918 | util.copyfile(olddata, newdata) | |||
|
r43346 | if not ( | ||
|
r43347 | unencodedname.endswith(b'00changelog.i') | ||
or unencodedname.endswith(b'00manifest.i') | ||||
|
r43346 | ): | ||
r42918 | destrepo.svfs.fncache.add(unencodedname) | |||
r43272 | if copydata: | |||
|
r43347 | destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d') | ||
r42918 | ||||
|
r43346 | |||
r42921 | UPGRADE_CHANGELOG = object() | |||
UPGRADE_MANIFEST = object() | ||||
r46594 | UPGRADE_FILELOGS = object() | |||
r42921 | ||||
|
r43346 | UPGRADE_ALL_REVLOGS = frozenset( | ||
r46594 | [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOGS] | |||
|
r43346 | ) | ||
r42921 | ||||
r43405 | def getsidedatacompanion(srcrepo, dstrepo): | |||
sidedatacompanion = None | ||||
removedreqs = srcrepo.requirements - dstrepo.requirements | ||||
r43418 | addedreqs = dstrepo.requirements - srcrepo.requirements | |||
|
r45933 | if requirements.SIDEDATA_REQUIREMENT in removedreqs: | ||
r43405 | ||||
def sidedatacompanion(rl, rev): | ||||
rl = getattr(rl, '_revlog', rl) | ||||
if rl.flags(rev) & revlog.REVIDX_SIDEDATA: | ||||
r46327 | return True, (), {}, 0, 0 | |||
return False, (), {}, 0, 0 | ||||
r43405 | ||||
|
r45933 | elif requirements.COPIESSDC_REQUIREMENT in addedreqs: | ||
r45466 | sidedatacompanion = metadata.getsidedataadder(srcrepo, dstrepo) | |||
|
r45933 | elif requirements.COPIESSDC_REQUIREMENT in removedreqs: | ||
r45466 | sidedatacompanion = metadata.getsidedataremover(srcrepo, dstrepo) | |||
r43405 | return sidedatacompanion | |||
r43404 | ||||
r42921 | def matchrevlog(revlogfilter, entry): | |||
|
r46286 | """check if a revlog is selected for cloning. | ||
In other words, are there any updates which need to be done on revlog | ||||
or it can be blindly copied. | ||||
r42921 | ||||
The store entry is checked against the passed filter""" | ||||
|
r43347 | if entry.endswith(b'00changelog.i'): | ||
r42921 | return UPGRADE_CHANGELOG in revlogfilter | |||
|
r43347 | elif entry.endswith(b'00manifest.i'): | ||
r42921 | return UPGRADE_MANIFEST in revlogfilter | |||
r46594 | return UPGRADE_FILELOGS in revlogfilter | |||
r42921 | ||||
|
r43346 | |||
def _clonerevlogs( | ||||
ui, | ||||
srcrepo, | ||||
dstrepo, | ||||
tr, | ||||
deltareuse, | ||||
forcedeltabothparents, | ||||
revlogs=UPGRADE_ALL_REVLOGS, | ||||
): | ||||
|
r31864 | """Copy revlogs between 2 repos.""" | ||
revcount = 0 | ||||
srcsize = 0 | ||||
srcrawsize = 0 | ||||
dstsize = 0 | ||||
fcount = 0 | ||||
frevcount = 0 | ||||
fsrcsize = 0 | ||||
frawsize = 0 | ||||
fdstsize = 0 | ||||
mcount = 0 | ||||
mrevcount = 0 | ||||
msrcsize = 0 | ||||
mrawsize = 0 | ||||
mdstsize = 0 | ||||
crevcount = 0 | ||||
csrcsize = 0 | ||||
crawsize = 0 | ||||
cdstsize = 0 | ||||
r42916 | alldatafiles = list(srcrepo.store.walk()) | |||
|
r31864 | # Perform a pass to collect metadata. This validates we can open all | ||
# source files and allows a unified progress bar to be displayed. | ||||
r42916 | for unencoded, encoded, size in alldatafiles: | |||
|
r43347 | if unencoded.endswith(b'.d'): | ||
|
r31864 | continue | ||
rl = _revlogfrompath(srcrepo, unencoded) | ||||
|
r43346 | info = rl.storageinfo( | ||
exclusivefiles=True, | ||||
revisionscount=True, | ||||
trackedsize=True, | ||||
storedsize=True, | ||||
) | ||||
|
r39893 | |||
|
r43347 | revcount += info[b'revisionscount'] or 0 | ||
datasize = info[b'storedsize'] or 0 | ||||
rawsize = info[b'trackedsize'] or 0 | ||||
|
r31864 | |||
srcsize += datasize | ||||
srcrawsize += rawsize | ||||
# This is for the separate progress bars. | ||||
if isinstance(rl, changelog.changelog): | ||||
crevcount += len(rl) | ||||
csrcsize += datasize | ||||
crawsize += rawsize | ||||
elif isinstance(rl, manifest.manifestrevlog): | ||||
mcount += 1 | ||||
mrevcount += len(rl) | ||||
msrcsize += datasize | ||||
mrawsize += rawsize | ||||
|
r37462 | elif isinstance(rl, filelog.filelog): | ||
|
r31864 | fcount += 1 | ||
frevcount += len(rl) | ||||
fsrcsize += datasize | ||||
frawsize += rawsize | ||||
|
r37462 | else: | ||
|
r43347 | error.ProgrammingError(b'unknown revlog type') | ||
|
r31864 | |||
if not revcount: | ||||
return | ||||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'migrating %d total revisions (%d in filelogs, %d in manifests, ' | ||
b'%d in changelog)\n' | ||||
|
r43346 | ) | ||
% (revcount, frevcount, mrevcount, crevcount) | ||||
) | ||||
r45302 | ui.status( | |||
|
r43347 | _(b'migrating %s in store; %s tracked data\n') | ||
|
r43346 | % ((util.bytecount(srcsize), util.bytecount(srcrawsize))) | ||
) | ||||
|
r31864 | |||
# Used to keep track of progress. | ||||
|
r38418 | progress = None | ||
|
r43346 | |||
|
r31864 | def oncopiedrevision(rl, rev, node): | ||
|
r38418 | progress.increment() | ||
|
r31864 | |||
r43404 | sidedatacompanion = getsidedatacompanion(srcrepo, dstrepo) | |||
|
r31864 | # Do the actual copying. | ||
# FUTURE this operation can be farmed off to worker processes. | ||||
seen = set() | ||||
r42916 | for unencoded, encoded, size in alldatafiles: | |||
|
r43347 | if unencoded.endswith(b'.d'): | ||
|
r31864 | continue | ||
oldrl = _revlogfrompath(srcrepo, unencoded) | ||||
|
r43347 | if isinstance(oldrl, changelog.changelog) and b'c' not in seen: | ||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'finished migrating %d manifest revisions across %d ' | ||
b'manifests; change in size: %s\n' | ||||
|
r43346 | ) | ||
% (mrevcount, mcount, util.bytecount(mdstsize - msrcsize)) | ||||
) | ||||
|
r31864 | |||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'migrating changelog containing %d revisions ' | ||
b'(%s in store; %s tracked data)\n' | ||||
|
r43346 | ) | ||
% ( | ||||
crevcount, | ||||
util.bytecount(csrcsize), | ||||
util.bytecount(crawsize), | ||||
) | ||||
) | ||||
|
r43347 | seen.add(b'c') | ||
|
r43346 | progress = srcrepo.ui.makeprogress( | ||
|
r43347 | _(b'changelog revisions'), total=crevcount | ||
|
r43346 | ) | ||
|
r43347 | elif isinstance(oldrl, manifest.manifestrevlog) and b'm' not in seen: | ||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'finished migrating %d filelog revisions across %d ' | ||
b'filelogs; change in size: %s\n' | ||||
|
r43346 | ) | ||
% (frevcount, fcount, util.bytecount(fdstsize - fsrcsize)) | ||||
) | ||||
|
r31864 | |||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'migrating %d manifests containing %d revisions ' | ||
b'(%s in store; %s tracked data)\n' | ||||
|
r43346 | ) | ||
% ( | ||||
mcount, | ||||
mrevcount, | ||||
util.bytecount(msrcsize), | ||||
util.bytecount(mrawsize), | ||||
) | ||||
) | ||||
|
r43347 | seen.add(b'm') | ||
|
r38417 | if progress: | ||
|
r38418 | progress.complete() | ||
|
r43346 | progress = srcrepo.ui.makeprogress( | ||
|
r43347 | _(b'manifest revisions'), total=mrevcount | ||
|
r43346 | ) | ||
|
r43347 | elif b'f' not in seen: | ||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'migrating %d filelogs containing %d revisions ' | ||
b'(%s in store; %s tracked data)\n' | ||||
|
r43346 | ) | ||
% ( | ||||
fcount, | ||||
frevcount, | ||||
util.bytecount(fsrcsize), | ||||
util.bytecount(frawsize), | ||||
) | ||||
) | ||||
|
r43347 | seen.add(b'f') | ||
|
r38417 | if progress: | ||
|
r38418 | progress.complete() | ||
|
r43346 | progress = srcrepo.ui.makeprogress( | ||
|
r43347 | _(b'file revisions'), total=frevcount | ||
|
r43346 | ) | ||
|
r31864 | |||
r42921 | if matchrevlog(revlogs, unencoded): | |||
|
r43346 | ui.note( | ||
|
r43347 | _(b'cloning %d revisions from %s\n') % (len(oldrl), unencoded) | ||
|
r43346 | ) | ||
r42921 | newrl = _revlogfrompath(dstrepo, unencoded) | |||
|
r43346 | oldrl.clone( | ||
tr, | ||||
newrl, | ||||
addrevisioncb=oncopiedrevision, | ||||
deltareuse=deltareuse, | ||||
forcedeltabothparents=forcedeltabothparents, | ||||
r43404 | sidedatacompanion=sidedatacompanion, | |||
|
r43346 | ) | ||
r42921 | else: | |||
|
r43347 | msg = _(b'blindly copying %s containing %i revisions\n') | ||
r42921 | ui.note(msg % (unencoded, len(oldrl))) | |||
_copyrevlog(tr, dstrepo, oldrl, unencoded) | ||||
|
r31864 | |||
r42921 | newrl = _revlogfrompath(dstrepo, unencoded) | |||
|
r31864 | |||
|
r39906 | info = newrl.storageinfo(storedsize=True) | ||
|
r43347 | datasize = info[b'storedsize'] or 0 | ||
|
r31864 | |||
dstsize += datasize | ||||
if isinstance(newrl, changelog.changelog): | ||||
cdstsize += datasize | ||||
elif isinstance(newrl, manifest.manifestrevlog): | ||||
mdstsize += datasize | ||||
else: | ||||
fdstsize += datasize | ||||
|
r38418 | progress.complete() | ||
|
r31864 | |||
r45302 | ui.status( | |||
|
r43347 | _( | ||
b'finished migrating %d changelog revisions; change in size: ' | ||||
b'%s\n' | ||||
) | ||||
|
r43346 | % (crevcount, util.bytecount(cdstsize - csrcsize)) | ||
) | ||||
|
r31864 | |||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'finished migrating %d total revisions; total change in store ' | ||
b'size: %s\n' | ||||
|
r43346 | ) | ||
% (revcount, util.bytecount(dstsize - srcsize)) | ||||
) | ||||
|
r31864 | |||
|
r31873 | def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st): | ||
|
r31864 | """Determine whether to copy a store file during upgrade. | ||
This function is called when migrating store files from ``srcrepo`` to | ||||
``dstrepo`` as part of upgrading a repository. | ||||
Args: | ||||
srcrepo: repo we are copying from | ||||
dstrepo: repo we are copying to | ||||
requirements: set of requirements for ``dstrepo`` | ||||
path: store file being examined | ||||
mode: the ``ST_MODE`` file type of ``path`` | ||||
st: ``stat`` data structure for ``path`` | ||||
Function should return ``True`` if the file is to be copied. | ||||
""" | ||||
# Skip revlogs. | ||||
r45356 | if path.endswith((b'.i', b'.d', b'.n', b'.nd')): | |||
|
r31864 | return False | ||
# Skip transaction related files. | ||||
|
r43347 | if path.startswith(b'undo'): | ||
|
r31864 | return False | ||
# Only copy regular files. | ||||
if mode != stat.S_IFREG: | ||||
return False | ||||
# Skip other skipped files. | ||||
|
r43347 | if path in (b'lock', b'fncache'): | ||
|
r31864 | return False | ||
return True | ||||
|
r43346 | |||
|
r31874 | def _finishdatamigration(ui, srcrepo, dstrepo, requirements): | ||
|
r31864 | """Hook point for extensions to perform additional actions during upgrade. | ||
This function is called after revlogs and store files have been copied but | ||||
before the new store is swapped into the original location. | ||||
""" | ||||
|
r43346 | |||
def _upgraderepo( | ||||
ui, srcrepo, dstrepo, requirements, actions, revlogs=UPGRADE_ALL_REVLOGS | ||||
): | ||||
|
r31864 | """Do the low-level work of upgrading a repository. | ||
The upgrade is effectively performed as a copy between a source | ||||
repository and a temporary destination repository. | ||||
The source repository is unmodified for as long as possible so the | ||||
upgrade can abort at any time without causing loss of service for | ||||
readers and without corrupting the source repository. | ||||
""" | ||||
assert srcrepo.currentwlock() | ||||
assert dstrepo.currentwlock() | ||||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'(it is safe to interrupt this process any time before ' | ||
b'data migration completes)\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r31864 | |||
|
r43347 | if b're-delta-all' in actions: | ||
|
r31864 | deltareuse = revlog.revlog.DELTAREUSENEVER | ||
|
r43347 | elif b're-delta-parent' in actions: | ||
|
r31864 | deltareuse = revlog.revlog.DELTAREUSESAMEREVS | ||
|
r43347 | elif b're-delta-multibase' in actions: | ||
|
r31864 | deltareuse = revlog.revlog.DELTAREUSESAMEREVS | ||
|
r43347 | elif b're-delta-fulladd' in actions: | ||
|
r35346 | deltareuse = revlog.revlog.DELTAREUSEFULLADD | ||
|
r31864 | else: | ||
deltareuse = revlog.revlog.DELTAREUSEALWAYS | ||||
|
r43347 | with dstrepo.transaction(b'upgrade') as tr: | ||
|
r43346 | _clonerevlogs( | ||
ui, | ||||
srcrepo, | ||||
dstrepo, | ||||
tr, | ||||
deltareuse, | ||||
|
r43347 | b're-delta-multibase' in actions, | ||
|
r43346 | revlogs=revlogs, | ||
) | ||||
|
r31864 | |||
# Now copy other files in the store directory. | ||||
|
r31875 | # The sorted() makes execution deterministic. | ||
|
r43347 | for p, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)): | ||
|
r43346 | if not _filterstorefile(srcrepo, dstrepo, requirements, p, kind, st): | ||
|
r31864 | continue | ||
r45302 | srcrepo.ui.status(_(b'copying %s\n') % p) | |||
|
r31875 | src = srcrepo.store.rawvfs.join(p) | ||
dst = dstrepo.store.rawvfs.join(p) | ||||
|
r31864 | util.copyfile(src, dst, copystat=True) | ||
|
r31874 | _finishdatamigration(ui, srcrepo, dstrepo, requirements) | ||
|
r31864 | |||
r45302 | ui.status(_(b'data fully migrated to temporary repository\n')) | |||
|
r31864 | |||
|
r43347 | backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path) | ||
|
r31864 | backupvfs = vfsmod.vfs(backuppath) | ||
# Make a backup of requires file first, as it is the first to be modified. | ||||
|
r43347 | util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires')) | ||
|
r31864 | |||
# We install an arbitrary requirement that clients must not support | ||||
# as a mechanism to lock out new clients during the data swap. This is | ||||
# better than allowing a client to continue while the repository is in | ||||
# an inconsistent state. | ||||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'marking source repository as being upgraded; clients will be ' | ||
b'unable to read from repository\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r45666 | scmutil.writereporequirements( | ||
srcrepo, srcrepo.requirements | {b'upgradeinprogress'} | ||||
|
r43346 | ) | ||
|
r31864 | |||
r45302 | ui.status(_(b'starting in-place swap of repository data\n')) | |||
ui.status(_(b'replaced files will be backed up at %s\n') % backuppath) | ||||
|
r31864 | |||
# Now swap in the new store directory. Doing it as a rename should make | ||||
# the operation nearly instantaneous and atomic (at least in well-behaved | ||||
# environments). | ||||
r45302 | ui.status(_(b'replacing store...\n')) | |||
|
r31864 | tstart = util.timer() | ||
|
r43347 | util.rename(srcrepo.spath, backupvfs.join(b'store')) | ||
|
r31864 | util.rename(dstrepo.spath, srcrepo.spath) | ||
elapsed = util.timer() - tstart | ||||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'store replacement complete; repository was inconsistent for ' | ||
b'%0.1fs\n' | ||||
|
r43346 | ) | ||
% elapsed | ||||
) | ||||
|
r31864 | |||
# We first write the requirements file. Any new requirements will lock | ||||
# out legacy clients. | ||||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'finalizing requirements file and making repository readable ' | ||
b'again\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r45666 | scmutil.writereporequirements(srcrepo, requirements) | ||
|
r31864 | |||
# The lock file from the old store won't be removed because nothing has a | ||||
# reference to its new location. So clean it up manually. Alternatively, we | ||||
# could update srcrepo.svfs and other variables to point to the new | ||||
# location. This is simpler. | ||||
|
r43347 | backupvfs.unlink(b'store/lock') | ||
|
r31864 | |||
return backuppath | ||||
|
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: | ||
optimize = [] | ||||
|
r44937 | optimize = {legacy_opts_map.get(o, o) for o in optimize} | ||
|
r31864 | repo = repo.unfiltered() | ||
r43098 | revlogs = set(UPGRADE_ALL_REVLOGS) | |||
r46595 | specentries = ( | |||
(UPGRADE_CHANGELOG, changelog), | ||||
r46612 | (UPGRADE_MANIFEST, manifest), | |||
(UPGRADE_FILELOGS, filelogs), | ||||
r46595 | ) | |||
r43098 | 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() | ||||
r46595 | for upgrade, enabled in specified: | |||
r43098 | if enabled: | |||
r46595 | revlogs.add(upgrade) | |||
r43098 | else: | |||
# none are enabled | ||||
r46595 | for upgrade, __ in specified: | |||
revlogs.discard(upgrade) | ||||
r43098 | ||||
|
r31864 | # Ensure the repository can be upgraded. | ||
|
r31865 | missingreqs = requiredsourcerequirements(repo) - repo.requirements | ||
|
r31864 | if missingreqs: | ||
|
r43346 | raise error.Abort( | ||
|
r43387 | _(b'cannot upgrade repository; requirement missing: %s') | ||
|
r43347 | % _(b', ').join(sorted(missingreqs)) | ||
|
r43346 | ) | ||
|
r31864 | |||
|
r31866 | blockedreqs = blocksourcerequirements(repo) & repo.requirements | ||
|
r31864 | if blockedreqs: | ||
|
r43346 | raise error.Abort( | ||
_( | ||||
|
r43347 | b'cannot upgrade repository; unsupported source ' | ||
b'requirement: %s' | ||||
|
r43346 | ) | ||
|
r43347 | % _(b', ').join(sorted(blockedreqs)) | ||
|
r43346 | ) | ||
|
r31864 | |||
# FUTURE there is potentially a need to control the wanted requirements via | ||||
# command arguments or via an extension hook point. | ||||
|
r40032 | newreqs = localrepo.newreporequirements( | ||
|
r43346 | repo.ui, localrepo.defaultcreateopts(repo.ui) | ||
) | ||||
|
r35303 | newreqs.update(preservedrequirements(repo)) | ||
|
r31864 | |||
|
r43346 | noremovereqs = ( | ||
repo.requirements - newreqs - supportremovedrequirements(repo) | ||||
) | ||||
|
r31864 | if noremovereqs: | ||
|
r43346 | raise error.Abort( | ||
|
r43347 | _( | ||
b'cannot upgrade repository; requirement would be ' | ||||
b'removed: %s' | ||||
) | ||||
% _(b', ').join(sorted(noremovereqs)) | ||||
|
r43346 | ) | ||
|
r31864 | |||
|
r43346 | noaddreqs = newreqs - repo.requirements - allowednewrequirements(repo) | ||
|
r31864 | if noaddreqs: | ||
|
r43346 | raise error.Abort( | ||
_( | ||||
|
r43347 | b'cannot upgrade repository; do not support adding ' | ||
b'requirement: %s' | ||||
|
r43346 | ) | ||
|
r43347 | % _(b', ').join(sorted(noaddreqs)) | ||
|
r43346 | ) | ||
|
r31864 | |||
|
r31870 | unsupportedreqs = newreqs - supporteddestrequirements(repo) | ||
|
r31864 | if unsupportedreqs: | ||
|
r43346 | raise error.Abort( | ||
_( | ||||
|
r43347 | b'cannot upgrade repository; do not support ' | ||
b'destination requirement: %s' | ||||
|
r43346 | ) | ||
|
r43347 | % _(b', ').join(sorted(unsupportedreqs)) | ||
|
r43346 | ) | ||
|
r31864 | |||
# Find and validate all improvements that can be made. | ||||
|
r31899 | alloptimizations = 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 | |||
|
r31897 | deficiencies = finddeficiencies(repo) | ||
|
r31899 | actions = determineactions(repo, deficiencies, repo.requirements, newreqs) | ||
|
r43346 | actions.extend( | ||
o | ||||
for o in sorted(optimizations) | ||||
# determineactions could have added optimisation | ||||
if o not in actions | ||||
) | ||||
|
r31864 | |||
r43100 | removedreqs = repo.requirements - newreqs | |||
addedreqs = newreqs - repo.requirements | ||||
if revlogs != UPGRADE_ALL_REVLOGS: | ||||
incompatible = RECLONES_REQUIREMENTS & (removedreqs | addedreqs) | ||||
if incompatible: | ||||
|
r43346 | msg = _( | ||
|
r43347 | b'ignoring revlogs selection flags, format requirements ' | ||
b'change: %s\n' | ||||
|
r43346 | ) | ||
|
r43347 | ui.warn(msg % b', '.join(sorted(incompatible))) | ||
r43100 | revlogs = UPGRADE_ALL_REVLOGS | |||
r44257 | 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 | ||||
|
r31864 | def printrequirements(): | ||
|
r43347 | ui.write(_(b'requirements\n')) | ||
r44257 | ui.write(_(b' preserved: ')) | |||
write_labeled( | ||||
newreqs & repo.requirements, "upgrade-repo.requirement.preserved" | ||||
|
r43346 | ) | ||
r44257 | ui.write((b'\n')) | |||
removed = repo.requirements - newreqs | ||||
|
r31864 | if repo.requirements - newreqs: | ||
r44257 | 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')) | ||||
|
r43347 | ui.write(b'\n') | ||
|
r31864 | |||
r45301 | def printoptimisations(): | |||
r46609 | optimisations = [a for a in actions if a.type == OPTIMISATION] | |||
r45301 | 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') | ||||
|
r31864 | def printupgradeactions(): | ||
|
r31903 | for a in actions: | ||
r45302 | ui.status(b'%s\n %s\n\n' % (a.name, a.upgrademessage)) | |||
|
r31864 | |||
if not run: | ||||
fromconfig = [] | ||||
|
r31904 | onlydefault = [] | ||
|
r31864 | |||
|
r31901 | for d in deficiencies: | ||
|
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: | ||
r45302 | ui.status( | |||
|
r43347 | _( | ||
b'(no feature deficiencies found in existing ' | ||||
b'repository)\n' | ||||
) | ||||
|
r43346 | ) | ||
|
r31864 | |||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'performing an upgrade with "--run" will make the following ' | ||
b'changes:\n\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r31864 | |||
printrequirements() | ||||
r45301 | printoptimisations() | |||
|
r31864 | printupgradeactions() | ||
|
r31903 | unusedoptimize = [i for i in alloptimizations if i not in actions] | ||
|
r31864 | if unusedoptimize: | ||
r45302 | ui.status( | |||
|
r43346 | _( | ||
|
r43347 | b'additional optimizations are available by specifying ' | ||
b'"--optimize <name>":\n\n' | ||||
|
r43346 | ) | ||
) | ||||
|
r31864 | for i in unusedoptimize: | ||
r45302 | ui.status(_(b'%s\n %s\n\n') % (i.name, i.description)) | |||
|
r31864 | return | ||
# Else we're in the run=true case. | ||||
|
r43347 | ui.write(_(b'upgrade will perform the following actions:\n\n')) | ||
|
r31864 | printrequirements() | ||
r45301 | printoptimisations() | |||
|
r31864 | printupgradeactions() | ||
|
r31903 | upgradeactions = [a.name for a in actions] | ||
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 | _( | ||
|
r43347 | b'creating temporary repository to stage migrated ' | ||
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(): | ||
|
r43346 | backuppath = _upgraderepo( | ||
ui, repo, dstrepo, newreqs, upgradeactions, revlogs=revlogs | ||||
) | ||||
|
r41121 | if not (backup or backuppath is None): | ||
r45302 | ui.status( | |||
_(b'removing old repository content%s\n') % backuppath | ||||
) | ||||
|
r41121 | repo.vfs.rmtree(backuppath, forcibly=True) | ||
backuppath = None | ||||
|
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 | |||
if sharedsafe.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' | ||||
) | ||||
) | ||||
|
r46618 | if sharedsafe.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' | ||||
) | ||||
) | ||||