mercurial/upgrade_utils/actions.py
changeset 46056 08802795ae90
parent 46055 72b7b4bf3e65
child 46060 c407513a44a3
equal deleted inserted replaced
46055:72b7b4bf3e65 46056:08802795ae90
    20 # list of requirements that request a clone of all revlog if added/removed
    20 # list of requirements that request a clone of all revlog if added/removed
    21 RECLONES_REQUIREMENTS = {
    21 RECLONES_REQUIREMENTS = {
    22     b'generaldelta',
    22     b'generaldelta',
    23     requirements.SPARSEREVLOG_REQUIREMENT,
    23     requirements.SPARSEREVLOG_REQUIREMENT,
    24 }
    24 }
       
    25 
       
    26 
       
    27 def preservedrequirements(repo):
       
    28     return set()
       
    29 
       
    30 
       
    31 DEFICIENCY = b'deficiency'
       
    32 OPTIMISATION = b'optimization'
       
    33 
       
    34 
       
    35 class improvement(object):
       
    36     """Represents an improvement that can be made as part of an upgrade.
       
    37 
       
    38     The following attributes are defined on each instance:
       
    39 
       
    40     name
       
    41        Machine-readable string uniquely identifying this improvement. It
       
    42        will be mapped to an action later in the upgrade process.
       
    43 
       
    44     type
       
    45        Either ``DEFICIENCY`` or ``OPTIMISATION``. A deficiency is an obvious
       
    46        problem. An optimization is an action (sometimes optional) that
       
    47        can be taken to further improve the state of the repository.
       
    48 
       
    49     description
       
    50        Message intended for humans explaining the improvement in more detail,
       
    51        including the implications of it. For ``DEFICIENCY`` types, should be
       
    52        worded in the present tense. For ``OPTIMISATION`` types, should be
       
    53        worded in the future tense.
       
    54 
       
    55     upgrademessage
       
    56        Message intended for humans explaining what an upgrade addressing this
       
    57        issue will do. Should be worded in the future tense.
       
    58     """
       
    59 
       
    60     def __init__(self, name, type, description, upgrademessage):
       
    61         self.name = name
       
    62         self.type = type
       
    63         self.description = description
       
    64         self.upgrademessage = upgrademessage
       
    65 
       
    66     def __eq__(self, other):
       
    67         if not isinstance(other, improvement):
       
    68             # This is what python tell use to do
       
    69             return NotImplemented
       
    70         return self.name == other.name
       
    71 
       
    72     def __ne__(self, other):
       
    73         return not (self == other)
       
    74 
       
    75     def __hash__(self):
       
    76         return hash(self.name)
       
    77 
       
    78 
       
    79 allformatvariant = []
       
    80 
       
    81 
       
    82 def registerformatvariant(cls):
       
    83     allformatvariant.append(cls)
       
    84     return cls
       
    85 
       
    86 
       
    87 class formatvariant(improvement):
       
    88     """an improvement subclass dedicated to repository format"""
       
    89 
       
    90     type = DEFICIENCY
       
    91     ### The following attributes should be defined for each class:
       
    92 
       
    93     # machine-readable string uniquely identifying this improvement. it will be
       
    94     # mapped to an action later in the upgrade process.
       
    95     name = None
       
    96 
       
    97     # message intended for humans explaining the improvement in more detail,
       
    98     # including the implications of it ``DEFICIENCY`` types, should be worded
       
    99     # in the present tense.
       
   100     description = None
       
   101 
       
   102     # message intended for humans explaining what an upgrade addressing this
       
   103     # issue will do. should be worded in the future tense.
       
   104     upgrademessage = None
       
   105 
       
   106     # value of current Mercurial default for new repository
       
   107     default = None
       
   108 
       
   109     def __init__(self):
       
   110         raise NotImplementedError()
       
   111 
       
   112     @staticmethod
       
   113     def fromrepo(repo):
       
   114         """current value of the variant in the repository"""
       
   115         raise NotImplementedError()
       
   116 
       
   117     @staticmethod
       
   118     def fromconfig(repo):
       
   119         """current value of the variant in the configuration"""
       
   120         raise NotImplementedError()
       
   121 
       
   122 
       
   123 class requirementformatvariant(formatvariant):
       
   124     """formatvariant based on a 'requirement' name.
       
   125 
       
   126     Many format variant are controlled by a 'requirement'. We define a small
       
   127     subclass to factor the code.
       
   128     """
       
   129 
       
   130     # the requirement that control this format variant
       
   131     _requirement = None
       
   132 
       
   133     @staticmethod
       
   134     def _newreporequirements(ui):
       
   135         return localrepo.newreporequirements(
       
   136             ui, localrepo.defaultcreateopts(ui)
       
   137         )
       
   138 
       
   139     @classmethod
       
   140     def fromrepo(cls, repo):
       
   141         assert cls._requirement is not None
       
   142         return cls._requirement in repo.requirements
       
   143 
       
   144     @classmethod
       
   145     def fromconfig(cls, repo):
       
   146         assert cls._requirement is not None
       
   147         return cls._requirement in cls._newreporequirements(repo.ui)
       
   148 
       
   149 
       
   150 @registerformatvariant
       
   151 class fncache(requirementformatvariant):
       
   152     name = b'fncache'
       
   153 
       
   154     _requirement = b'fncache'
       
   155 
       
   156     default = True
       
   157 
       
   158     description = _(
       
   159         b'long and reserved filenames may not work correctly; '
       
   160         b'repository performance is sub-optimal'
       
   161     )
       
   162 
       
   163     upgrademessage = _(
       
   164         b'repository will be more resilient to storing '
       
   165         b'certain paths and performance of certain '
       
   166         b'operations should be improved'
       
   167     )
       
   168 
       
   169 
       
   170 @registerformatvariant
       
   171 class dotencode(requirementformatvariant):
       
   172     name = b'dotencode'
       
   173 
       
   174     _requirement = b'dotencode'
       
   175 
       
   176     default = True
       
   177 
       
   178     description = _(
       
   179         b'storage of filenames beginning with a period or '
       
   180         b'space may not work correctly'
       
   181     )
       
   182 
       
   183     upgrademessage = _(
       
   184         b'repository will be better able to store files '
       
   185         b'beginning with a space or period'
       
   186     )
       
   187 
       
   188 
       
   189 @registerformatvariant
       
   190 class generaldelta(requirementformatvariant):
       
   191     name = b'generaldelta'
       
   192 
       
   193     _requirement = b'generaldelta'
       
   194 
       
   195     default = True
       
   196 
       
   197     description = _(
       
   198         b'deltas within internal storage are unable to '
       
   199         b'choose optimal revisions; repository is larger and '
       
   200         b'slower than it could be; interaction with other '
       
   201         b'repositories may require extra network and CPU '
       
   202         b'resources, making "hg push" and "hg pull" slower'
       
   203     )
       
   204 
       
   205     upgrademessage = _(
       
   206         b'repository storage will be able to create '
       
   207         b'optimal deltas; new repository data will be '
       
   208         b'smaller and read times should decrease; '
       
   209         b'interacting with other repositories using this '
       
   210         b'storage model should require less network and '
       
   211         b'CPU resources, making "hg push" and "hg pull" '
       
   212         b'faster'
       
   213     )
       
   214 
       
   215 
       
   216 @registerformatvariant
       
   217 class sharesafe(requirementformatvariant):
       
   218     name = b'exp-sharesafe'
       
   219     _requirement = requirements.SHARESAFE_REQUIREMENT
       
   220 
       
   221     default = False
       
   222 
       
   223     description = _(
       
   224         b'old shared repositories do not share source repository '
       
   225         b'requirements and config. This leads to various problems '
       
   226         b'when the source repository format is upgraded or some new '
       
   227         b'extensions are enabled.'
       
   228     )
       
   229 
       
   230     upgrademessage = _(
       
   231         b'Upgrades a repository to share-safe format so that future '
       
   232         b'shares of this repository share its requirements and configs.'
       
   233     )
       
   234 
       
   235 
       
   236 @registerformatvariant
       
   237 class sparserevlog(requirementformatvariant):
       
   238     name = b'sparserevlog'
       
   239 
       
   240     _requirement = requirements.SPARSEREVLOG_REQUIREMENT
       
   241 
       
   242     default = True
       
   243 
       
   244     description = _(
       
   245         b'in order to limit disk reading and memory usage on older '
       
   246         b'version, the span of a delta chain from its root to its '
       
   247         b'end is limited, whatever the relevant data in this span. '
       
   248         b'This can severly limit Mercurial ability to build good '
       
   249         b'chain of delta resulting is much more storage space being '
       
   250         b'taken and limit reusability of on disk delta during '
       
   251         b'exchange.'
       
   252     )
       
   253 
       
   254     upgrademessage = _(
       
   255         b'Revlog supports delta chain with more unused data '
       
   256         b'between payload. These gaps will be skipped at read '
       
   257         b'time. This allows for better delta chains, making a '
       
   258         b'better compression and faster exchange with server.'
       
   259     )
       
   260 
       
   261 
       
   262 @registerformatvariant
       
   263 class sidedata(requirementformatvariant):
       
   264     name = b'sidedata'
       
   265 
       
   266     _requirement = requirements.SIDEDATA_REQUIREMENT
       
   267 
       
   268     default = False
       
   269 
       
   270     description = _(
       
   271         b'Allows storage of extra data alongside a revision, '
       
   272         b'unlocking various caching options.'
       
   273     )
       
   274 
       
   275     upgrademessage = _(b'Allows storage of extra data alongside a revision.')
       
   276 
       
   277 
       
   278 @registerformatvariant
       
   279 class persistentnodemap(requirementformatvariant):
       
   280     name = b'persistent-nodemap'
       
   281 
       
   282     _requirement = requirements.NODEMAP_REQUIREMENT
       
   283 
       
   284     default = False
       
   285 
       
   286     description = _(
       
   287         b'persist the node -> rev mapping on disk to speedup lookup'
       
   288     )
       
   289 
       
   290     upgrademessage = _(b'Speedup revision lookup by node id.')
       
   291 
       
   292 
       
   293 @registerformatvariant
       
   294 class copiessdc(requirementformatvariant):
       
   295     name = b'copies-sdc'
       
   296 
       
   297     _requirement = requirements.COPIESSDC_REQUIREMENT
       
   298 
       
   299     default = False
       
   300 
       
   301     description = _(b'Stores copies information alongside changesets.')
       
   302 
       
   303     upgrademessage = _(
       
   304         b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
       
   305     )
       
   306 
       
   307 
       
   308 @registerformatvariant
       
   309 class removecldeltachain(formatvariant):
       
   310     name = b'plain-cl-delta'
       
   311 
       
   312     default = True
       
   313 
       
   314     description = _(
       
   315         b'changelog storage is using deltas instead of '
       
   316         b'raw entries; changelog reading and any '
       
   317         b'operation relying on changelog data are slower '
       
   318         b'than they could be'
       
   319     )
       
   320 
       
   321     upgrademessage = _(
       
   322         b'changelog storage will be reformated to '
       
   323         b'store raw entries; changelog reading will be '
       
   324         b'faster; changelog size may be reduced'
       
   325     )
       
   326 
       
   327     @staticmethod
       
   328     def fromrepo(repo):
       
   329         # Mercurial 4.0 changed changelogs to not use delta chains. Search for
       
   330         # changelogs with deltas.
       
   331         cl = repo.changelog
       
   332         chainbase = cl.chainbase
       
   333         return all(rev == chainbase(rev) for rev in cl)
       
   334 
       
   335     @staticmethod
       
   336     def fromconfig(repo):
       
   337         return True
       
   338 
       
   339 
       
   340 @registerformatvariant
       
   341 class compressionengine(formatvariant):
       
   342     name = b'compression'
       
   343     default = b'zlib'
       
   344 
       
   345     description = _(
       
   346         b'Compresion algorithm used to compress data. '
       
   347         b'Some engine are faster than other'
       
   348     )
       
   349 
       
   350     upgrademessage = _(
       
   351         b'revlog content will be recompressed with the new algorithm.'
       
   352     )
       
   353 
       
   354     @classmethod
       
   355     def fromrepo(cls, repo):
       
   356         # we allow multiple compression engine requirement to co-exist because
       
   357         # strickly speaking, revlog seems to support mixed compression style.
       
   358         #
       
   359         # The compression used for new entries will be "the last one"
       
   360         compression = b'zlib'
       
   361         for req in repo.requirements:
       
   362             prefix = req.startswith
       
   363             if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
       
   364                 compression = req.split(b'-', 2)[2]
       
   365         return compression
       
   366 
       
   367     @classmethod
       
   368     def fromconfig(cls, repo):
       
   369         compengines = repo.ui.configlist(b'format', b'revlog-compression')
       
   370         # return the first valid value as the selection code would do
       
   371         for comp in compengines:
       
   372             if comp in util.compengines:
       
   373                 return comp
       
   374 
       
   375         # no valide compression found lets display it all for clarity
       
   376         return b','.join(compengines)
       
   377 
       
   378 
       
   379 @registerformatvariant
       
   380 class compressionlevel(formatvariant):
       
   381     name = b'compression-level'
       
   382     default = b'default'
       
   383 
       
   384     description = _(b'compression level')
       
   385 
       
   386     upgrademessage = _(b'revlog content will be recompressed')
       
   387 
       
   388     @classmethod
       
   389     def fromrepo(cls, repo):
       
   390         comp = compressionengine.fromrepo(repo)
       
   391         level = None
       
   392         if comp == b'zlib':
       
   393             level = repo.ui.configint(b'storage', b'revlog.zlib.level')
       
   394         elif comp == b'zstd':
       
   395             level = repo.ui.configint(b'storage', b'revlog.zstd.level')
       
   396         if level is None:
       
   397             return b'default'
       
   398         return bytes(level)
       
   399 
       
   400     @classmethod
       
   401     def fromconfig(cls, repo):
       
   402         comp = compressionengine.fromconfig(repo)
       
   403         level = None
       
   404         if comp == b'zlib':
       
   405             level = repo.ui.configint(b'storage', b'revlog.zlib.level')
       
   406         elif comp == b'zstd':
       
   407             level = repo.ui.configint(b'storage', b'revlog.zstd.level')
       
   408         if level is None:
       
   409             return b'default'
       
   410         return bytes(level)
       
   411 
       
   412 
       
   413 def finddeficiencies(repo):
       
   414     """returns a list of deficiencies that the repo suffer from"""
       
   415     deficiencies = []
       
   416 
       
   417     # We could detect lack of revlogv1 and store here, but they were added
       
   418     # in 0.9.2 and we don't support upgrading repos without these
       
   419     # requirements, so let's not bother.
       
   420 
       
   421     for fv in allformatvariant:
       
   422         if not fv.fromrepo(repo):
       
   423             deficiencies.append(fv)
       
   424 
       
   425     return deficiencies
       
   426 
       
   427 
       
   428 ALL_OPTIMISATIONS = []
       
   429 
       
   430 
       
   431 def register_optimization(obj):
       
   432     ALL_OPTIMISATIONS.append(obj)
       
   433     return obj
       
   434 
       
   435 
       
   436 register_optimization(
       
   437     improvement(
       
   438         name=b're-delta-parent',
       
   439         type=OPTIMISATION,
       
   440         description=_(
       
   441             b'deltas within internal storage will be recalculated to '
       
   442             b'choose an optimal base revision where this was not '
       
   443             b'already done; the size of the repository may shrink and '
       
   444             b'various operations may become faster; the first time '
       
   445             b'this optimization is performed could slow down upgrade '
       
   446             b'execution considerably; subsequent invocations should '
       
   447             b'not run noticeably slower'
       
   448         ),
       
   449         upgrademessage=_(
       
   450             b'deltas within internal storage will choose a new '
       
   451             b'base revision if needed'
       
   452         ),
       
   453     )
       
   454 )
       
   455 
       
   456 register_optimization(
       
   457     improvement(
       
   458         name=b're-delta-multibase',
       
   459         type=OPTIMISATION,
       
   460         description=_(
       
   461             b'deltas within internal storage will be recalculated '
       
   462             b'against multiple base revision and the smallest '
       
   463             b'difference will be used; the size of the repository may '
       
   464             b'shrink significantly when there are many merges; this '
       
   465             b'optimization will slow down execution in proportion to '
       
   466             b'the number of merges in the repository and the amount '
       
   467             b'of files in the repository; this slow down should not '
       
   468             b'be significant unless there are tens of thousands of '
       
   469             b'files and thousands of merges'
       
   470         ),
       
   471         upgrademessage=_(
       
   472             b'deltas within internal storage will choose an '
       
   473             b'optimal delta by computing deltas against multiple '
       
   474             b'parents; may slow down execution time '
       
   475             b'significantly'
       
   476         ),
       
   477     )
       
   478 )
       
   479 
       
   480 register_optimization(
       
   481     improvement(
       
   482         name=b're-delta-all',
       
   483         type=OPTIMISATION,
       
   484         description=_(
       
   485             b'deltas within internal storage will always be '
       
   486             b'recalculated without reusing prior deltas; this will '
       
   487             b'likely make execution run several times slower; this '
       
   488             b'optimization is typically not needed'
       
   489         ),
       
   490         upgrademessage=_(
       
   491             b'deltas within internal storage will be fully '
       
   492             b'recomputed; this will likely drastically slow down '
       
   493             b'execution time'
       
   494         ),
       
   495     )
       
   496 )
       
   497 
       
   498 register_optimization(
       
   499     improvement(
       
   500         name=b're-delta-fulladd',
       
   501         type=OPTIMISATION,
       
   502         description=_(
       
   503             b'every revision will be re-added as if it was new '
       
   504             b'content. It will go through the full storage '
       
   505             b'mechanism giving extensions a chance to process it '
       
   506             b'(eg. lfs). This is similar to "re-delta-all" but even '
       
   507             b'slower since more logic is involved.'
       
   508         ),
       
   509         upgrademessage=_(
       
   510             b'each revision will be added as new content to the '
       
   511             b'internal storage; this will likely drastically slow '
       
   512             b'down execution time, but some extensions might need '
       
   513             b'it'
       
   514         ),
       
   515     )
       
   516 )
       
   517 
       
   518 
       
   519 def findoptimizations(repo):
       
   520     """Determine optimisation that could be used during upgrade"""
       
   521     # These are unconditionally added. There is logic later that figures out
       
   522     # which ones to apply.
       
   523     return list(ALL_OPTIMISATIONS)
       
   524 
       
   525 
       
   526 def determineactions(repo, deficiencies, sourcereqs, destreqs):
       
   527     """Determine upgrade actions that will be performed.
       
   528 
       
   529     Given a list of improvements as returned by ``finddeficiencies`` and
       
   530     ``findoptimizations``, determine the list of upgrade actions that
       
   531     will be performed.
       
   532 
       
   533     The role of this function is to filter improvements if needed, apply
       
   534     recommended optimizations from the improvements list that make sense,
       
   535     etc.
       
   536 
       
   537     Returns a list of action names.
       
   538     """
       
   539     newactions = []
       
   540 
       
   541     for d in deficiencies:
       
   542         name = d._requirement
       
   543 
       
   544         # If the action is a requirement that doesn't show up in the
       
   545         # destination requirements, prune the action.
       
   546         if name is not None and name not in destreqs:
       
   547             continue
       
   548 
       
   549         newactions.append(d)
       
   550 
       
   551     # FUTURE consider adding some optimizations here for certain transitions.
       
   552     # e.g. adding generaldelta could schedule parent redeltas.
       
   553 
       
   554     return newactions
       
   555 
       
   556 
       
   557 ###  Code checking if a repository can got through the upgrade process at all. #
       
   558 
       
   559 
       
   560 def requiredsourcerequirements(repo):
       
   561     """Obtain requirements required to be present to upgrade a repo.
       
   562 
       
   563     An upgrade will not be allowed if the repository doesn't have the
       
   564     requirements returned by this function.
       
   565     """
       
   566     return {
       
   567         # Introduced in Mercurial 0.9.2.
       
   568         b'revlogv1',
       
   569         # Introduced in Mercurial 0.9.2.
       
   570         b'store',
       
   571     }
       
   572 
       
   573 
       
   574 def blocksourcerequirements(repo):
       
   575     """Obtain requirements that will prevent an upgrade from occurring.
       
   576 
       
   577     An upgrade cannot be performed if the source repository contains a
       
   578     requirements in the returned set.
       
   579     """
       
   580     return {
       
   581         # The upgrade code does not yet support these experimental features.
       
   582         # This is an artificial limitation.
       
   583         requirements.TREEMANIFEST_REQUIREMENT,
       
   584         # This was a precursor to generaldelta and was never enabled by default.
       
   585         # It should (hopefully) not exist in the wild.
       
   586         b'parentdelta',
       
   587         # Upgrade should operate on the actual store, not the shared link.
       
   588         requirements.SHARED_REQUIREMENT,
       
   589     }
       
   590 
       
   591 
       
   592 def check_source_requirements(repo):
       
   593     """Ensure that no existing requirements prevent the repository upgrade"""
       
   594 
       
   595     required = requiredsourcerequirements(repo)
       
   596     missingreqs = required - repo.requirements
       
   597     if missingreqs:
       
   598         msg = _(b'cannot upgrade repository; requirement missing: %s')
       
   599         missingreqs = b', '.join(sorted(missingreqs))
       
   600         raise error.Abort(msg % missingreqs)
       
   601 
       
   602     blocking = blocksourcerequirements(repo)
       
   603     blockingreqs = blocking & repo.requirements
       
   604     if blockingreqs:
       
   605         m = _(b'cannot upgrade repository; unsupported source requirement: %s')
       
   606         blockingreqs = b', '.join(sorted(blockingreqs))
       
   607         raise error.Abort(m % blockingreqs)
       
   608 
       
   609 
       
   610 ### Verify the validity of the planned requirement changes ####################
    25 
   611 
    26 
   612 
    27 def supportremovedrequirements(repo):
   613 def supportremovedrequirements(repo):
    28     """Obtain requirements that can be removed during an upgrade.
   614     """Obtain requirements that can be removed during an upgrade.
    29 
   615 
   103             if engine.name() == b'zstd':
   689             if engine.name() == b'zstd':
   104                 supported.add(b'revlog-compression-zstd')
   690                 supported.add(b'revlog-compression-zstd')
   105     return supported
   691     return supported
   106 
   692 
   107 
   693 
   108 def preservedrequirements(repo):
       
   109     return set()
       
   110 
       
   111 
       
   112 DEFICIENCY = b'deficiency'
       
   113 OPTIMISATION = b'optimization'
       
   114 
       
   115 
       
   116 class improvement(object):
       
   117     """Represents an improvement that can be made as part of an upgrade.
       
   118 
       
   119     The following attributes are defined on each instance:
       
   120 
       
   121     name
       
   122        Machine-readable string uniquely identifying this improvement. It
       
   123        will be mapped to an action later in the upgrade process.
       
   124 
       
   125     type
       
   126        Either ``DEFICIENCY`` or ``OPTIMISATION``. A deficiency is an obvious
       
   127        problem. An optimization is an action (sometimes optional) that
       
   128        can be taken to further improve the state of the repository.
       
   129 
       
   130     description
       
   131        Message intended for humans explaining the improvement in more detail,
       
   132        including the implications of it. For ``DEFICIENCY`` types, should be
       
   133        worded in the present tense. For ``OPTIMISATION`` types, should be
       
   134        worded in the future tense.
       
   135 
       
   136     upgrademessage
       
   137        Message intended for humans explaining what an upgrade addressing this
       
   138        issue will do. Should be worded in the future tense.
       
   139     """
       
   140 
       
   141     def __init__(self, name, type, description, upgrademessage):
       
   142         self.name = name
       
   143         self.type = type
       
   144         self.description = description
       
   145         self.upgrademessage = upgrademessage
       
   146 
       
   147     def __eq__(self, other):
       
   148         if not isinstance(other, improvement):
       
   149             # This is what python tell use to do
       
   150             return NotImplemented
       
   151         return self.name == other.name
       
   152 
       
   153     def __ne__(self, other):
       
   154         return not (self == other)
       
   155 
       
   156     def __hash__(self):
       
   157         return hash(self.name)
       
   158 
       
   159 
       
   160 allformatvariant = []
       
   161 
       
   162 
       
   163 def registerformatvariant(cls):
       
   164     allformatvariant.append(cls)
       
   165     return cls
       
   166 
       
   167 
       
   168 class formatvariant(improvement):
       
   169     """an improvement subclass dedicated to repository format"""
       
   170 
       
   171     type = DEFICIENCY
       
   172     ### The following attributes should be defined for each class:
       
   173 
       
   174     # machine-readable string uniquely identifying this improvement. it will be
       
   175     # mapped to an action later in the upgrade process.
       
   176     name = None
       
   177 
       
   178     # message intended for humans explaining the improvement in more detail,
       
   179     # including the implications of it ``DEFICIENCY`` types, should be worded
       
   180     # in the present tense.
       
   181     description = None
       
   182 
       
   183     # message intended for humans explaining what an upgrade addressing this
       
   184     # issue will do. should be worded in the future tense.
       
   185     upgrademessage = None
       
   186 
       
   187     # value of current Mercurial default for new repository
       
   188     default = None
       
   189 
       
   190     def __init__(self):
       
   191         raise NotImplementedError()
       
   192 
       
   193     @staticmethod
       
   194     def fromrepo(repo):
       
   195         """current value of the variant in the repository"""
       
   196         raise NotImplementedError()
       
   197 
       
   198     @staticmethod
       
   199     def fromconfig(repo):
       
   200         """current value of the variant in the configuration"""
       
   201         raise NotImplementedError()
       
   202 
       
   203 
       
   204 class requirementformatvariant(formatvariant):
       
   205     """formatvariant based on a 'requirement' name.
       
   206 
       
   207     Many format variant are controlled by a 'requirement'. We define a small
       
   208     subclass to factor the code.
       
   209     """
       
   210 
       
   211     # the requirement that control this format variant
       
   212     _requirement = None
       
   213 
       
   214     @staticmethod
       
   215     def _newreporequirements(ui):
       
   216         return localrepo.newreporequirements(
       
   217             ui, localrepo.defaultcreateopts(ui)
       
   218         )
       
   219 
       
   220     @classmethod
       
   221     def fromrepo(cls, repo):
       
   222         assert cls._requirement is not None
       
   223         return cls._requirement in repo.requirements
       
   224 
       
   225     @classmethod
       
   226     def fromconfig(cls, repo):
       
   227         assert cls._requirement is not None
       
   228         return cls._requirement in cls._newreporequirements(repo.ui)
       
   229 
       
   230 
       
   231 @registerformatvariant
       
   232 class fncache(requirementformatvariant):
       
   233     name = b'fncache'
       
   234 
       
   235     _requirement = b'fncache'
       
   236 
       
   237     default = True
       
   238 
       
   239     description = _(
       
   240         b'long and reserved filenames may not work correctly; '
       
   241         b'repository performance is sub-optimal'
       
   242     )
       
   243 
       
   244     upgrademessage = _(
       
   245         b'repository will be more resilient to storing '
       
   246         b'certain paths and performance of certain '
       
   247         b'operations should be improved'
       
   248     )
       
   249 
       
   250 
       
   251 @registerformatvariant
       
   252 class dotencode(requirementformatvariant):
       
   253     name = b'dotencode'
       
   254 
       
   255     _requirement = b'dotencode'
       
   256 
       
   257     default = True
       
   258 
       
   259     description = _(
       
   260         b'storage of filenames beginning with a period or '
       
   261         b'space may not work correctly'
       
   262     )
       
   263 
       
   264     upgrademessage = _(
       
   265         b'repository will be better able to store files '
       
   266         b'beginning with a space or period'
       
   267     )
       
   268 
       
   269 
       
   270 @registerformatvariant
       
   271 class generaldelta(requirementformatvariant):
       
   272     name = b'generaldelta'
       
   273 
       
   274     _requirement = b'generaldelta'
       
   275 
       
   276     default = True
       
   277 
       
   278     description = _(
       
   279         b'deltas within internal storage are unable to '
       
   280         b'choose optimal revisions; repository is larger and '
       
   281         b'slower than it could be; interaction with other '
       
   282         b'repositories may require extra network and CPU '
       
   283         b'resources, making "hg push" and "hg pull" slower'
       
   284     )
       
   285 
       
   286     upgrademessage = _(
       
   287         b'repository storage will be able to create '
       
   288         b'optimal deltas; new repository data will be '
       
   289         b'smaller and read times should decrease; '
       
   290         b'interacting with other repositories using this '
       
   291         b'storage model should require less network and '
       
   292         b'CPU resources, making "hg push" and "hg pull" '
       
   293         b'faster'
       
   294     )
       
   295 
       
   296 
       
   297 @registerformatvariant
       
   298 class sharesafe(requirementformatvariant):
       
   299     name = b'exp-sharesafe'
       
   300     _requirement = requirements.SHARESAFE_REQUIREMENT
       
   301 
       
   302     default = False
       
   303 
       
   304     description = _(
       
   305         b'old shared repositories do not share source repository '
       
   306         b'requirements and config. This leads to various problems '
       
   307         b'when the source repository format is upgraded or some new '
       
   308         b'extensions are enabled.'
       
   309     )
       
   310 
       
   311     upgrademessage = _(
       
   312         b'Upgrades a repository to share-safe format so that future '
       
   313         b'shares of this repository share its requirements and configs.'
       
   314     )
       
   315 
       
   316 
       
   317 @registerformatvariant
       
   318 class sparserevlog(requirementformatvariant):
       
   319     name = b'sparserevlog'
       
   320 
       
   321     _requirement = requirements.SPARSEREVLOG_REQUIREMENT
       
   322 
       
   323     default = True
       
   324 
       
   325     description = _(
       
   326         b'in order to limit disk reading and memory usage on older '
       
   327         b'version, the span of a delta chain from its root to its '
       
   328         b'end is limited, whatever the relevant data in this span. '
       
   329         b'This can severly limit Mercurial ability to build good '
       
   330         b'chain of delta resulting is much more storage space being '
       
   331         b'taken and limit reusability of on disk delta during '
       
   332         b'exchange.'
       
   333     )
       
   334 
       
   335     upgrademessage = _(
       
   336         b'Revlog supports delta chain with more unused data '
       
   337         b'between payload. These gaps will be skipped at read '
       
   338         b'time. This allows for better delta chains, making a '
       
   339         b'better compression and faster exchange with server.'
       
   340     )
       
   341 
       
   342 
       
   343 @registerformatvariant
       
   344 class sidedata(requirementformatvariant):
       
   345     name = b'sidedata'
       
   346 
       
   347     _requirement = requirements.SIDEDATA_REQUIREMENT
       
   348 
       
   349     default = False
       
   350 
       
   351     description = _(
       
   352         b'Allows storage of extra data alongside a revision, '
       
   353         b'unlocking various caching options.'
       
   354     )
       
   355 
       
   356     upgrademessage = _(b'Allows storage of extra data alongside a revision.')
       
   357 
       
   358 
       
   359 @registerformatvariant
       
   360 class persistentnodemap(requirementformatvariant):
       
   361     name = b'persistent-nodemap'
       
   362 
       
   363     _requirement = requirements.NODEMAP_REQUIREMENT
       
   364 
       
   365     default = False
       
   366 
       
   367     description = _(
       
   368         b'persist the node -> rev mapping on disk to speedup lookup'
       
   369     )
       
   370 
       
   371     upgrademessage = _(b'Speedup revision lookup by node id.')
       
   372 
       
   373 
       
   374 @registerformatvariant
       
   375 class copiessdc(requirementformatvariant):
       
   376     name = b'copies-sdc'
       
   377 
       
   378     _requirement = requirements.COPIESSDC_REQUIREMENT
       
   379 
       
   380     default = False
       
   381 
       
   382     description = _(b'Stores copies information alongside changesets.')
       
   383 
       
   384     upgrademessage = _(
       
   385         b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
       
   386     )
       
   387 
       
   388 
       
   389 @registerformatvariant
       
   390 class removecldeltachain(formatvariant):
       
   391     name = b'plain-cl-delta'
       
   392 
       
   393     default = True
       
   394 
       
   395     description = _(
       
   396         b'changelog storage is using deltas instead of '
       
   397         b'raw entries; changelog reading and any '
       
   398         b'operation relying on changelog data are slower '
       
   399         b'than they could be'
       
   400     )
       
   401 
       
   402     upgrademessage = _(
       
   403         b'changelog storage will be reformated to '
       
   404         b'store raw entries; changelog reading will be '
       
   405         b'faster; changelog size may be reduced'
       
   406     )
       
   407 
       
   408     @staticmethod
       
   409     def fromrepo(repo):
       
   410         # Mercurial 4.0 changed changelogs to not use delta chains. Search for
       
   411         # changelogs with deltas.
       
   412         cl = repo.changelog
       
   413         chainbase = cl.chainbase
       
   414         return all(rev == chainbase(rev) for rev in cl)
       
   415 
       
   416     @staticmethod
       
   417     def fromconfig(repo):
       
   418         return True
       
   419 
       
   420 
       
   421 @registerformatvariant
       
   422 class compressionengine(formatvariant):
       
   423     name = b'compression'
       
   424     default = b'zlib'
       
   425 
       
   426     description = _(
       
   427         b'Compresion algorithm used to compress data. '
       
   428         b'Some engine are faster than other'
       
   429     )
       
   430 
       
   431     upgrademessage = _(
       
   432         b'revlog content will be recompressed with the new algorithm.'
       
   433     )
       
   434 
       
   435     @classmethod
       
   436     def fromrepo(cls, repo):
       
   437         # we allow multiple compression engine requirement to co-exist because
       
   438         # strickly speaking, revlog seems to support mixed compression style.
       
   439         #
       
   440         # The compression used for new entries will be "the last one"
       
   441         compression = b'zlib'
       
   442         for req in repo.requirements:
       
   443             prefix = req.startswith
       
   444             if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
       
   445                 compression = req.split(b'-', 2)[2]
       
   446         return compression
       
   447 
       
   448     @classmethod
       
   449     def fromconfig(cls, repo):
       
   450         compengines = repo.ui.configlist(b'format', b'revlog-compression')
       
   451         # return the first valid value as the selection code would do
       
   452         for comp in compengines:
       
   453             if comp in util.compengines:
       
   454                 return comp
       
   455 
       
   456         # no valide compression found lets display it all for clarity
       
   457         return b','.join(compengines)
       
   458 
       
   459 
       
   460 @registerformatvariant
       
   461 class compressionlevel(formatvariant):
       
   462     name = b'compression-level'
       
   463     default = b'default'
       
   464 
       
   465     description = _(b'compression level')
       
   466 
       
   467     upgrademessage = _(b'revlog content will be recompressed')
       
   468 
       
   469     @classmethod
       
   470     def fromrepo(cls, repo):
       
   471         comp = compressionengine.fromrepo(repo)
       
   472         level = None
       
   473         if comp == b'zlib':
       
   474             level = repo.ui.configint(b'storage', b'revlog.zlib.level')
       
   475         elif comp == b'zstd':
       
   476             level = repo.ui.configint(b'storage', b'revlog.zstd.level')
       
   477         if level is None:
       
   478             return b'default'
       
   479         return bytes(level)
       
   480 
       
   481     @classmethod
       
   482     def fromconfig(cls, repo):
       
   483         comp = compressionengine.fromconfig(repo)
       
   484         level = None
       
   485         if comp == b'zlib':
       
   486             level = repo.ui.configint(b'storage', b'revlog.zlib.level')
       
   487         elif comp == b'zstd':
       
   488             level = repo.ui.configint(b'storage', b'revlog.zstd.level')
       
   489         if level is None:
       
   490             return b'default'
       
   491         return bytes(level)
       
   492 
       
   493 
       
   494 def finddeficiencies(repo):
       
   495     """returns a list of deficiencies that the repo suffer from"""
       
   496     deficiencies = []
       
   497 
       
   498     # We could detect lack of revlogv1 and store here, but they were added
       
   499     # in 0.9.2 and we don't support upgrading repos without these
       
   500     # requirements, so let's not bother.
       
   501 
       
   502     for fv in allformatvariant:
       
   503         if not fv.fromrepo(repo):
       
   504             deficiencies.append(fv)
       
   505 
       
   506     return deficiencies
       
   507 
       
   508 
       
   509 ALL_OPTIMISATIONS = []
       
   510 
       
   511 
       
   512 def register_optimization(obj):
       
   513     ALL_OPTIMISATIONS.append(obj)
       
   514     return obj
       
   515 
       
   516 
       
   517 register_optimization(
       
   518     improvement(
       
   519         name=b're-delta-parent',
       
   520         type=OPTIMISATION,
       
   521         description=_(
       
   522             b'deltas within internal storage will be recalculated to '
       
   523             b'choose an optimal base revision where this was not '
       
   524             b'already done; the size of the repository may shrink and '
       
   525             b'various operations may become faster; the first time '
       
   526             b'this optimization is performed could slow down upgrade '
       
   527             b'execution considerably; subsequent invocations should '
       
   528             b'not run noticeably slower'
       
   529         ),
       
   530         upgrademessage=_(
       
   531             b'deltas within internal storage will choose a new '
       
   532             b'base revision if needed'
       
   533         ),
       
   534     )
       
   535 )
       
   536 
       
   537 register_optimization(
       
   538     improvement(
       
   539         name=b're-delta-multibase',
       
   540         type=OPTIMISATION,
       
   541         description=_(
       
   542             b'deltas within internal storage will be recalculated '
       
   543             b'against multiple base revision and the smallest '
       
   544             b'difference will be used; the size of the repository may '
       
   545             b'shrink significantly when there are many merges; this '
       
   546             b'optimization will slow down execution in proportion to '
       
   547             b'the number of merges in the repository and the amount '
       
   548             b'of files in the repository; this slow down should not '
       
   549             b'be significant unless there are tens of thousands of '
       
   550             b'files and thousands of merges'
       
   551         ),
       
   552         upgrademessage=_(
       
   553             b'deltas within internal storage will choose an '
       
   554             b'optimal delta by computing deltas against multiple '
       
   555             b'parents; may slow down execution time '
       
   556             b'significantly'
       
   557         ),
       
   558     )
       
   559 )
       
   560 
       
   561 register_optimization(
       
   562     improvement(
       
   563         name=b're-delta-all',
       
   564         type=OPTIMISATION,
       
   565         description=_(
       
   566             b'deltas within internal storage will always be '
       
   567             b'recalculated without reusing prior deltas; this will '
       
   568             b'likely make execution run several times slower; this '
       
   569             b'optimization is typically not needed'
       
   570         ),
       
   571         upgrademessage=_(
       
   572             b'deltas within internal storage will be fully '
       
   573             b'recomputed; this will likely drastically slow down '
       
   574             b'execution time'
       
   575         ),
       
   576     )
       
   577 )
       
   578 
       
   579 register_optimization(
       
   580     improvement(
       
   581         name=b're-delta-fulladd',
       
   582         type=OPTIMISATION,
       
   583         description=_(
       
   584             b'every revision will be re-added as if it was new '
       
   585             b'content. It will go through the full storage '
       
   586             b'mechanism giving extensions a chance to process it '
       
   587             b'(eg. lfs). This is similar to "re-delta-all" but even '
       
   588             b'slower since more logic is involved.'
       
   589         ),
       
   590         upgrademessage=_(
       
   591             b'each revision will be added as new content to the '
       
   592             b'internal storage; this will likely drastically slow '
       
   593             b'down execution time, but some extensions might need '
       
   594             b'it'
       
   595         ),
       
   596     )
       
   597 )
       
   598 
       
   599 
       
   600 def findoptimizations(repo):
       
   601     """Determine optimisation that could be used during upgrade"""
       
   602     # These are unconditionally added. There is logic later that figures out
       
   603     # which ones to apply.
       
   604     return list(ALL_OPTIMISATIONS)
       
   605 
       
   606 
       
   607 def determineactions(repo, deficiencies, sourcereqs, destreqs):
       
   608     """Determine upgrade actions that will be performed.
       
   609 
       
   610     Given a list of improvements as returned by ``finddeficiencies`` and
       
   611     ``findoptimizations``, determine the list of upgrade actions that
       
   612     will be performed.
       
   613 
       
   614     The role of this function is to filter improvements if needed, apply
       
   615     recommended optimizations from the improvements list that make sense,
       
   616     etc.
       
   617 
       
   618     Returns a list of action names.
       
   619     """
       
   620     newactions = []
       
   621 
       
   622     for d in deficiencies:
       
   623         name = d._requirement
       
   624 
       
   625         # If the action is a requirement that doesn't show up in the
       
   626         # destination requirements, prune the action.
       
   627         if name is not None and name not in destreqs:
       
   628             continue
       
   629 
       
   630         newactions.append(d)
       
   631 
       
   632     # FUTURE consider adding some optimizations here for certain transitions.
       
   633     # e.g. adding generaldelta could schedule parent redeltas.
       
   634 
       
   635     return newactions
       
   636 
       
   637 
       
   638 ###  Code checking if a repository can got through the upgrade process at all. #
       
   639 
       
   640 
       
   641 def requiredsourcerequirements(repo):
       
   642     """Obtain requirements required to be present to upgrade a repo.
       
   643 
       
   644     An upgrade will not be allowed if the repository doesn't have the
       
   645     requirements returned by this function.
       
   646     """
       
   647     return {
       
   648         # Introduced in Mercurial 0.9.2.
       
   649         b'revlogv1',
       
   650         # Introduced in Mercurial 0.9.2.
       
   651         b'store',
       
   652     }
       
   653 
       
   654 
       
   655 def blocksourcerequirements(repo):
       
   656     """Obtain requirements that will prevent an upgrade from occurring.
       
   657 
       
   658     An upgrade cannot be performed if the source repository contains a
       
   659     requirements in the returned set.
       
   660     """
       
   661     return {
       
   662         # The upgrade code does not yet support these experimental features.
       
   663         # This is an artificial limitation.
       
   664         requirements.TREEMANIFEST_REQUIREMENT,
       
   665         # This was a precursor to generaldelta and was never enabled by default.
       
   666         # It should (hopefully) not exist in the wild.
       
   667         b'parentdelta',
       
   668         # Upgrade should operate on the actual store, not the shared link.
       
   669         requirements.SHARED_REQUIREMENT,
       
   670     }
       
   671 
       
   672 
       
   673 def check_source_requirements(repo):
       
   674     """Ensure that no existing requirements prevent the repository upgrade"""
       
   675 
       
   676     required = requiredsourcerequirements(repo)
       
   677     missingreqs = required - repo.requirements
       
   678     if missingreqs:
       
   679         msg = _(b'cannot upgrade repository; requirement missing: %s')
       
   680         missingreqs = b', '.join(sorted(missingreqs))
       
   681         raise error.Abort(msg % missingreqs)
       
   682 
       
   683     blocking = blocksourcerequirements(repo)
       
   684     blockingreqs = blocking & repo.requirements
       
   685     if blockingreqs:
       
   686         m = _(b'cannot upgrade repository; unsupported source requirement: %s')
       
   687         blockingreqs = b', '.join(sorted(blockingreqs))
       
   688         raise error.Abort(m % blockingreqs)
       
   689 
       
   690 
       
   691 ### Verify the validity of the planned requirement changes ####################
       
   692 
       
   693 
       
   694 def check_requirements_changes(repo, new_reqs):
   694 def check_requirements_changes(repo, new_reqs):
   695     old_reqs = repo.requirements
   695     old_reqs = repo.requirements
   696 
   696 
   697     support_removal = supportremovedrequirements(repo)
   697     support_removal = supportremovedrequirements(repo)
   698     no_remove_reqs = old_reqs - new_reqs - support_removal
   698     no_remove_reqs = old_reqs - new_reqs - support_removal