comparison mercurial/upgrade_utils/actions.py @ 46052:08802795ae90

upgrade: gather code about requirement checking together They just moved in the same file, now they are grouped together and documented. Differential Revision: https://phab.mercurial-scm.org/D9486
author Pierre-Yves David <pierre-yves.david@octobus.net>
date Tue, 01 Dec 2020 20:35:19 +0100
parents 72b7b4bf3e65
children c407513a44a3
comparison
equal deleted inserted replaced
46051:72b7b4bf3e65 46052: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