Mercurial > public > mercurial-scm > hg
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 |