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