--- a/mercurial/repair.py Sun Dec 18 16:16:54 2016 -0800
+++ b/mercurial/repair.py Sun Dec 18 16:51:09 2016 -0800
@@ -431,11 +431,218 @@
'generaldelta',
])
+deficiency = 'deficiency'
+optimisation = 'optimization'
+
+class upgradeimprovement(object):
+ """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
+ Either ``deficiency`` or ``optimisation``. A deficiency is an obvious
+ 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,
+ including the implications of it. For ``deficiency`` types, should be
+ worded in the present tense. For ``optimisation`` types, should be
+ 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.
+
+ fromdefault (``deficiency`` types only)
+ Boolean indicating whether the current (deficient) state deviates
+ from Mercurial's default configuration.
+
+ fromconfig (``deficiency`` types only)
+ Boolean indicating whether the current (deficient) state deviates
+ from the current Mercurial configuration.
+ """
+ def __init__(self, name, type, description, upgrademessage, **kwargs):
+ self.name = name
+ self.type = type
+ self.description = description
+ self.upgrademessage = upgrademessage
+
+ for k, v in kwargs.items():
+ setattr(self, k, v)
+
+def upgradefindimprovements(repo):
+ """Determine improvements that can be made to the repo during upgrade.
+
+ Returns a list of ``upgradeimprovement`` describing repository deficiencies
+ and optimizations.
+ """
+ # Avoid cycle: cmdutil -> repair -> localrepo -> cmdutil
+ from . import localrepo
+
+ newreporeqs = localrepo.newreporequirements(repo)
+
+ improvements = []
+
+ # 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.
+
+ if 'fncache' not in repo.requirements:
+ improvements.append(upgradeimprovement(
+ name='fncache',
+ type=deficiency,
+ description=_('long and reserved filenames may not work correctly; '
+ 'repository performance is sub-optimal'),
+ upgrademessage=_('repository will be more resilient to storing '
+ 'certain paths and performance of certain '
+ 'operations should be improved'),
+ fromdefault=True,
+ fromconfig='fncache' in newreporeqs))
+
+ if 'dotencode' not in repo.requirements:
+ improvements.append(upgradeimprovement(
+ name='dotencode',
+ type=deficiency,
+ description=_('storage of filenames beginning with a period or '
+ 'space may not work correctly'),
+ upgrademessage=_('repository will be better able to store files '
+ 'beginning with a space or period'),
+ fromdefault=True,
+ fromconfig='dotencode' in newreporeqs))
+
+ if 'generaldelta' not in repo.requirements:
+ improvements.append(upgradeimprovement(
+ name='generaldelta',
+ type=deficiency,
+ description=_('deltas within internal storage are unable to '
+ 'choose optimal revisions; repository is larger and '
+ 'slower than it could be; interaction with other '
+ 'repositories may require extra network and CPU '
+ 'resources, making "hg push" and "hg pull" slower'),
+ upgrademessage=_('repository storage will be able to create '
+ 'optimal deltas; new repository data will be '
+ 'smaller and read times should decrease; '
+ 'interacting with other repositories using this '
+ 'storage model should require less network and '
+ 'CPU resources, making "hg push" and "hg pull" '
+ 'faster'),
+ fromdefault=True,
+ fromconfig='generaldelta' in newreporeqs))
+
+ # Mercurial 4.0 changed changelogs to not use delta chains. Search for
+ # changelogs with deltas.
+ cl = repo.changelog
+ for rev in cl:
+ chainbase = cl.chainbase(rev)
+ if chainbase != rev:
+ improvements.append(upgradeimprovement(
+ name='removecldeltachain',
+ type=deficiency,
+ description=_('changelog storage is using deltas instead of '
+ 'raw entries; changelog reading and any '
+ 'operation relying on changelog data are slower '
+ 'than they could be'),
+ upgrademessage=_('changelog storage will be reformated to '
+ 'store raw entries; changelog reading will be '
+ 'faster; changelog size may be reduced'),
+ fromdefault=True,
+ fromconfig=True))
+ break
+
+ # Now for the optimizations.
+
+ # These are unconditionally added. There is logic later that figures out
+ # which ones to apply.
+
+ improvements.append(upgradeimprovement(
+ name='redeltaparent',
+ type=optimisation,
+ description=_('deltas within internal storage will be recalculated to '
+ 'choose an optimal base revision where this was not '
+ 'already done; the size of the repository may shrink and '
+ 'various operations may become faster; the first time '
+ 'this optimization is performed could slow down upgrade '
+ 'execution considerably; subsequent invocations should '
+ 'not run noticeably slower'),
+ upgrademessage=_('deltas within internal storage will choose a new '
+ 'base revision if needed')))
+
+ improvements.append(upgradeimprovement(
+ name='redeltamultibase',
+ type=optimisation,
+ description=_('deltas within internal storage will be recalculated '
+ 'against multiple base revision and the smallest '
+ 'difference will be used; the size of the repository may '
+ 'shrink significantly when there are many merges; this '
+ 'optimization will slow down execution in proportion to '
+ 'the number of merges in the repository and the amount '
+ 'of files in the repository; this slow down should not '
+ 'be significant unless there are tens of thousands of '
+ 'files and thousands of merges'),
+ upgrademessage=_('deltas within internal storage will choose an '
+ 'optimal delta by computing deltas against multiple '
+ 'parents; may slow down execution time '
+ 'significantly')))
+
+ improvements.append(upgradeimprovement(
+ name='redeltaall',
+ type=optimisation,
+ description=_('deltas within internal storage will always be '
+ 'recalculated without reusing prior deltas; this will '
+ 'likely make execution run several times slower; this '
+ 'optimization is typically not needed'),
+ upgrademessage=_('deltas within internal storage will be fully '
+ 'recomputed; this will likely drastically slow down '
+ 'execution time')))
+
+ return improvements
+
+def upgradedetermineactions(repo, improvements, sourcereqs, destreqs,
+ optimize):
+ """Determine upgrade actions that will be performed.
+
+ Given a list of improvements as returned by ``upgradefindimprovements``,
+ determine the list of upgrade actions that will be performed.
+
+ 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 = []
+
+ knownreqs = upgradesupporteddestrequirements(repo)
+
+ for i in improvements:
+ name = i.name
+
+ # If the action is a requirement that doesn't show up in the
+ # destination requirements, prune the action.
+ if name in knownreqs and name not in destreqs:
+ continue
+
+ if i.type == deficiency:
+ newactions.append(name)
+
+ newactions.extend(o for o in sorted(optimize) if o not in newactions)
+
+ # FUTURE consider adding some optimizations here for certain transitions.
+ # e.g. adding generaldelta could schedule parent redeltas.
+
+ return newactions
+
def upgraderepo(ui, repo, run=False, optimize=None):
"""Upgrade a repository in place."""
# Avoid cycle: cmdutil -> repair -> localrepo -> cmdutil
from . import localrepo
+ optimize = set(optimize or [])
repo = repo.unfiltered()
# Ensure the repository can be upgraded.
@@ -473,6 +680,25 @@
'destination requirement: %s') %
_(', ').join(sorted(unsupportedreqs)))
+ # Find and validate all improvements that can be made.
+ improvements = upgradefindimprovements(repo)
+ for i in improvements:
+ if i.type not in (deficiency, optimisation):
+ raise error.Abort(_('unexpected improvement type %s for %s') % (
+ i.type, i.name))
+
+ # Validate arguments.
+ unknownoptimize = optimize - set(i.name for i in improvements
+ if i.type == optimisation)
+ if unknownoptimize:
+ raise error.Abort(_('unknown optimization action requested: %s') %
+ ', '.join(sorted(unknownoptimize)),
+ hint=_('run without arguments to see valid '
+ 'optimizations'))
+
+ actions = upgradedetermineactions(repo, improvements, repo.requirements,
+ newreqs, optimize)
+
def printrequirements():
ui.write(_('requirements\n'))
ui.write(_(' preserved: %s\n') %
@@ -488,8 +714,60 @@
ui.write('\n')
+ def printupgradeactions():
+ for action in actions:
+ for i in improvements:
+ if i.name == action:
+ ui.write('%s\n %s\n\n' %
+ (i.name, i.upgrademessage))
+
if not run:
+ fromdefault = []
+ fromconfig = []
+ optimizations = []
+
+ for i in improvements:
+ assert i.type in (deficiency, optimisation)
+ if i.type == deficiency:
+ if i.fromdefault:
+ fromdefault.append(i)
+ if i.fromconfig:
+ fromconfig.append(i)
+ else:
+ optimizations.append(i)
+
+ if fromdefault or fromconfig:
+ fromconfignames = set(x.name for x in fromconfig)
+ onlydefault = [i for i in fromdefault
+ if i.name not in fromconfignames]
+
+ if fromconfig:
+ ui.write(_('repository lacks features recommended by '
+ 'current config options:\n\n'))
+ for i in fromconfig:
+ ui.write('%s\n %s\n\n' % (i.name, i.description))
+
+ if onlydefault:
+ ui.write(_('repository lacks features used by the default '
+ 'config options:\n\n'))
+ for i in onlydefault:
+ ui.write('%s\n %s\n\n' % (i.name, i.description))
+
+ ui.write('\n')
+ else:
+ ui.write(_('(no feature deficiencies found in existing '
+ 'repository)\n'))
+
ui.write(_('performing an upgrade with "--run" will make the following '
'changes:\n\n'))
printrequirements()
+ printupgradeactions()
+
+ unusedoptimize = [i for i in improvements
+ if i.name not in actions and i.type == optimisation]
+ if unusedoptimize:
+ ui.write(_('additional optimizations are available by specifying '
+ '"--optimize <name>":\n\n'))
+ for i in unusedoptimize:
+ ui.write(_('%s\n %s\n\n') % (i.name, i.description))
--- a/tests/test-upgrade-repo.t Sun Dec 18 16:16:54 2016 -0800
+++ b/tests/test-upgrade-repo.t Sun Dec 18 16:51:09 2016 -0800
@@ -49,3 +49,134 @@
$ hg -R disallowaddedreq --config experimental.manifestv2=true --config experimental.treemanifest=true debugupgraderepo
abort: cannot upgrade repository; do not support adding requirement: manifestv2, treemanifest
[255]
+
+An upgrade of a repository created with recommended settings only suggests optimizations
+
+ $ hg init empty
+ $ cd empty
+ $ hg debugupgraderepo
+ (no feature deficiencies found in existing repository)
+ performing an upgrade with "--run" will make the following changes:
+
+ requirements
+ preserved: dotencode, fncache, generaldelta, revlogv1, store
+
+ additional optimizations are available by specifying "--optimize <name>":
+
+ redeltaparent
+ deltas within internal storage will be recalculated to choose an optimal base revision where this was not already done; the size of the repository may shrink and various operations may become faster; the first time this optimization is performed could slow down upgrade execution considerably; subsequent invocations should not run noticeably slower
+
+ redeltamultibase
+ deltas within internal storage will be recalculated against multiple base revision and the smallest difference will be used; the size of the repository may shrink significantly when there are many merges; this optimization will slow down execution in proportion to the number of merges in the repository and the amount of files in the repository; this slow down should not be significant unless there are tens of thousands of files and thousands of merges
+
+ redeltaall
+ deltas within internal storage will always be recalculated without reusing prior deltas; this will likely make execution run several times slower; this optimization is typically not needed
+
+
+--optimize can be used to add optimizations
+
+ $ hg debugupgrade --optimize redeltaparent
+ (no feature deficiencies found in existing repository)
+ performing an upgrade with "--run" will make the following changes:
+
+ requirements
+ preserved: dotencode, fncache, generaldelta, revlogv1, store
+
+ redeltaparent
+ deltas within internal storage will choose a new base revision if needed
+
+ additional optimizations are available by specifying "--optimize <name>":
+
+ redeltamultibase
+ deltas within internal storage will be recalculated against multiple base revision and the smallest difference will be used; the size of the repository may shrink significantly when there are many merges; this optimization will slow down execution in proportion to the number of merges in the repository and the amount of files in the repository; this slow down should not be significant unless there are tens of thousands of files and thousands of merges
+
+ redeltaall
+ deltas within internal storage will always be recalculated without reusing prior deltas; this will likely make execution run several times slower; this optimization is typically not needed
+
+
+Various sub-optimal detections work
+
+ $ cat > .hg/requires << EOF
+ > revlogv1
+ > store
+ > EOF
+
+ $ hg debugupgraderepo
+ repository lacks features recommended by current config options:
+
+ fncache
+ long and reserved filenames may not work correctly; repository performance is sub-optimal
+
+ dotencode
+ storage of filenames beginning with a period or space may not work correctly
+
+ generaldelta
+ deltas within internal storage are unable to choose optimal revisions; repository is larger and slower than it could be; interaction with other repositories may require extra network and CPU resources, making "hg push" and "hg pull" slower
+
+
+ performing an upgrade with "--run" will make the following changes:
+
+ requirements
+ preserved: revlogv1, store
+ added: dotencode, fncache, generaldelta
+
+ fncache
+ repository will be more resilient to storing certain paths and performance of certain operations should be improved
+
+ dotencode
+ repository will be better able to store files beginning with a space or period
+
+ generaldelta
+ repository storage will be able to create optimal deltas; new repository data will be smaller and read times should decrease; interacting with other repositories using this storage model should require less network and CPU resources, making "hg push" and "hg pull" faster
+
+ additional optimizations are available by specifying "--optimize <name>":
+
+ redeltaparent
+ deltas within internal storage will be recalculated to choose an optimal base revision where this was not already done; the size of the repository may shrink and various operations may become faster; the first time this optimization is performed could slow down upgrade execution considerably; subsequent invocations should not run noticeably slower
+
+ redeltamultibase
+ deltas within internal storage will be recalculated against multiple base revision and the smallest difference will be used; the size of the repository may shrink significantly when there are many merges; this optimization will slow down execution in proportion to the number of merges in the repository and the amount of files in the repository; this slow down should not be significant unless there are tens of thousands of files and thousands of merges
+
+ redeltaall
+ deltas within internal storage will always be recalculated without reusing prior deltas; this will likely make execution run several times slower; this optimization is typically not needed
+
+
+ $ hg --config format.dotencode=false debugupgraderepo
+ repository lacks features recommended by current config options:
+
+ fncache
+ long and reserved filenames may not work correctly; repository performance is sub-optimal
+
+ generaldelta
+ deltas within internal storage are unable to choose optimal revisions; repository is larger and slower than it could be; interaction with other repositories may require extra network and CPU resources, making "hg push" and "hg pull" slower
+
+ repository lacks features used by the default config options:
+
+ dotencode
+ storage of filenames beginning with a period or space may not work correctly
+
+
+ performing an upgrade with "--run" will make the following changes:
+
+ requirements
+ preserved: revlogv1, store
+ added: fncache, generaldelta
+
+ fncache
+ repository will be more resilient to storing certain paths and performance of certain operations should be improved
+
+ generaldelta
+ repository storage will be able to create optimal deltas; new repository data will be smaller and read times should decrease; interacting with other repositories using this storage model should require less network and CPU resources, making "hg push" and "hg pull" faster
+
+ additional optimizations are available by specifying "--optimize <name>":
+
+ redeltaparent
+ deltas within internal storage will be recalculated to choose an optimal base revision where this was not already done; the size of the repository may shrink and various operations may become faster; the first time this optimization is performed could slow down upgrade execution considerably; subsequent invocations should not run noticeably slower
+
+ redeltamultibase
+ deltas within internal storage will be recalculated against multiple base revision and the smallest difference will be used; the size of the repository may shrink significantly when there are many merges; this optimization will slow down execution in proportion to the number of merges in the repository and the amount of files in the repository; this slow down should not be significant unless there are tens of thousands of files and thousands of merges
+
+ redeltaall
+ deltas within internal storage will always be recalculated without reusing prior deltas; this will likely make execution run several times slower; this optimization is typically not needed
+
+