##// END OF EJS Templates
debugformat: fix formatting for compression level...
marmoute -
r52285:e0fc40b9 stable
parent child Browse files
Show More
@@ -1,1121 +1,1121 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
9 9 from ..i18n import _
10 10 from .. import (
11 11 error,
12 12 localrepo,
13 13 pycompat,
14 14 requirements,
15 15 revlog,
16 16 util,
17 17 )
18 18
19 19 from ..utils import compression
20 20
21 21 if pycompat.TYPE_CHECKING:
22 22 from typing import (
23 23 List,
24 24 Type,
25 25 )
26 26
27 27
28 28 # list of requirements that request a clone of all revlog if added/removed
29 29 RECLONES_REQUIREMENTS = {
30 30 requirements.GENERALDELTA_REQUIREMENT,
31 31 requirements.SPARSEREVLOG_REQUIREMENT,
32 32 requirements.REVLOGV2_REQUIREMENT,
33 33 requirements.CHANGELOGV2_REQUIREMENT,
34 34 }
35 35
36 36
37 37 def preservedrequirements(repo):
38 38 preserved = {
39 39 requirements.SHARED_REQUIREMENT,
40 40 requirements.NARROW_REQUIREMENT,
41 41 }
42 42 return preserved & repo.requirements
43 43
44 44
45 45 FORMAT_VARIANT = b'deficiency'
46 46 OPTIMISATION = b'optimization'
47 47
48 48
49 49 class improvement:
50 50 """Represents an improvement that can be made as part of an upgrade."""
51 51
52 52 ### The following attributes should be defined for each subclass:
53 53
54 54 # Either ``FORMAT_VARIANT`` or ``OPTIMISATION``.
55 55 # A format variant is where we change the storage format. Not all format
56 56 # variant changes are an obvious problem.
57 57 # An optimization is an action (sometimes optional) that
58 58 # can be taken to further improve the state of the repository.
59 59 type = None
60 60
61 61 # machine-readable string uniquely identifying this improvement. it will be
62 62 # mapped to an action later in the upgrade process.
63 63 name = None
64 64
65 65 # message intended for humans explaining the improvement in more detail,
66 66 # including the implications of it ``FORMAT_VARIANT`` types, should be
67 67 # worded
68 68 # in the present tense.
69 69 description = None
70 70
71 71 # message intended for humans explaining what an upgrade addressing this
72 72 # issue will do. should be worded in the future tense.
73 73 upgrademessage = None
74 74
75 75 # value of current Mercurial default for new repository
76 76 default = None
77 77
78 78 # Message intended for humans which will be shown post an upgrade
79 79 # operation when the improvement will be added
80 80 postupgrademessage = None
81 81
82 82 # Message intended for humans which will be shown post an upgrade
83 83 # operation in which this improvement was removed
84 84 postdowngrademessage = None
85 85
86 86 # By default we assume that every improvement touches requirements and all revlogs
87 87
88 88 # Whether this improvement touches filelogs
89 89 touches_filelogs = True
90 90
91 91 # Whether this improvement touches manifests
92 92 touches_manifests = True
93 93
94 94 # Whether this improvement touches changelog
95 95 touches_changelog = True
96 96
97 97 # Whether this improvement changes repository requirements
98 98 touches_requirements = True
99 99
100 100 # Whether this improvement touches the dirstate
101 101 touches_dirstate = False
102 102
103 103 # Can this action be run on a share instead of its mains repository
104 104 compatible_with_share = False
105 105
106 106
107 107 allformatvariant = [] # type: List[Type['formatvariant']]
108 108
109 109
110 110 def registerformatvariant(cls):
111 111 allformatvariant.append(cls)
112 112 return cls
113 113
114 114
115 115 class formatvariant(improvement):
116 116 """an improvement subclass dedicated to repository format"""
117 117
118 118 type = FORMAT_VARIANT
119 119
120 120 @staticmethod
121 121 def fromrepo(repo):
122 122 """current value of the variant in the repository"""
123 123 raise NotImplementedError()
124 124
125 125 @staticmethod
126 126 def fromconfig(repo):
127 127 """current value of the variant in the configuration"""
128 128 raise NotImplementedError()
129 129
130 130
131 131 class requirementformatvariant(formatvariant):
132 132 """formatvariant based on a 'requirement' name.
133 133
134 134 Many format variant are controlled by a 'requirement'. We define a small
135 135 subclass to factor the code.
136 136 """
137 137
138 138 # the requirement that control this format variant
139 139 _requirement = None
140 140
141 141 @staticmethod
142 142 def _newreporequirements(ui):
143 143 return localrepo.newreporequirements(
144 144 ui, localrepo.defaultcreateopts(ui)
145 145 )
146 146
147 147 @classmethod
148 148 def fromrepo(cls, repo):
149 149 assert cls._requirement is not None
150 150 return cls._requirement in repo.requirements
151 151
152 152 @classmethod
153 153 def fromconfig(cls, repo):
154 154 assert cls._requirement is not None
155 155 return cls._requirement in cls._newreporequirements(repo.ui)
156 156
157 157
158 158 @registerformatvariant
159 159 class fncache(requirementformatvariant):
160 160 name = b'fncache'
161 161
162 162 _requirement = requirements.FNCACHE_REQUIREMENT
163 163
164 164 default = True
165 165
166 166 description = _(
167 167 b'long and reserved filenames may not work correctly; '
168 168 b'repository performance is sub-optimal'
169 169 )
170 170
171 171 upgrademessage = _(
172 172 b'repository will be more resilient to storing '
173 173 b'certain paths and performance of certain '
174 174 b'operations should be improved'
175 175 )
176 176
177 177
178 178 @registerformatvariant
179 179 class dirstatev2(requirementformatvariant):
180 180 name = b'dirstate-v2'
181 181 _requirement = requirements.DIRSTATE_V2_REQUIREMENT
182 182
183 183 default = False
184 184
185 185 description = _(
186 186 b'version 1 of the dirstate file format requires '
187 187 b'reading and parsing it all at once.\n'
188 188 b'Version 2 has a better structure,'
189 189 b'better information and lighter update mechanism'
190 190 )
191 191
192 192 upgrademessage = _(b'"hg status" will be faster')
193 193
194 194 touches_filelogs = False
195 195 touches_manifests = False
196 196 touches_changelog = False
197 197 touches_requirements = True
198 198 touches_dirstate = True
199 199 compatible_with_share = True
200 200
201 201
202 202 @registerformatvariant
203 203 class dirstatetrackedkey(requirementformatvariant):
204 204 name = b'tracked-hint'
205 205 _requirement = requirements.DIRSTATE_TRACKED_HINT_V1
206 206
207 207 default = False
208 208
209 209 description = _(
210 210 b'Add a small file to help external tooling that watch the tracked set'
211 211 )
212 212
213 213 upgrademessage = _(
214 214 b'external tools will be informated of potential change in the tracked set'
215 215 )
216 216
217 217 touches_filelogs = False
218 218 touches_manifests = False
219 219 touches_changelog = False
220 220 touches_requirements = True
221 221 touches_dirstate = True
222 222 compatible_with_share = True
223 223
224 224
225 225 @registerformatvariant
226 226 class dotencode(requirementformatvariant):
227 227 name = b'dotencode'
228 228
229 229 _requirement = requirements.DOTENCODE_REQUIREMENT
230 230
231 231 default = True
232 232
233 233 description = _(
234 234 b'storage of filenames beginning with a period or '
235 235 b'space may not work correctly'
236 236 )
237 237
238 238 upgrademessage = _(
239 239 b'repository will be better able to store files '
240 240 b'beginning with a space or period'
241 241 )
242 242
243 243
244 244 @registerformatvariant
245 245 class generaldelta(requirementformatvariant):
246 246 name = b'generaldelta'
247 247
248 248 _requirement = requirements.GENERALDELTA_REQUIREMENT
249 249
250 250 default = True
251 251
252 252 description = _(
253 253 b'deltas within internal storage are unable to '
254 254 b'choose optimal revisions; repository is larger and '
255 255 b'slower than it could be; interaction with other '
256 256 b'repositories may require extra network and CPU '
257 257 b'resources, making "hg push" and "hg pull" slower'
258 258 )
259 259
260 260 upgrademessage = _(
261 261 b'repository storage will be able to create '
262 262 b'optimal deltas; new repository data will be '
263 263 b'smaller and read times should decrease; '
264 264 b'interacting with other repositories using this '
265 265 b'storage model should require less network and '
266 266 b'CPU resources, making "hg push" and "hg pull" '
267 267 b'faster'
268 268 )
269 269
270 270
271 271 @registerformatvariant
272 272 class sharesafe(requirementformatvariant):
273 273 name = b'share-safe'
274 274 _requirement = requirements.SHARESAFE_REQUIREMENT
275 275
276 276 default = True
277 277
278 278 description = _(
279 279 b'old shared repositories do not share source repository '
280 280 b'requirements and config. This leads to various problems '
281 281 b'when the source repository format is upgraded or some new '
282 282 b'extensions are enabled.'
283 283 )
284 284
285 285 upgrademessage = _(
286 286 b'Upgrades a repository to share-safe format so that future '
287 287 b'shares of this repository share its requirements and configs.'
288 288 )
289 289
290 290 postdowngrademessage = _(
291 291 b'repository downgraded to not use share safe mode, '
292 292 b'existing shares will not work and need to be reshared.'
293 293 )
294 294
295 295 postupgrademessage = _(
296 296 b'repository upgraded to share safe mode, existing'
297 297 b' shares will still work in old non-safe mode. '
298 298 b'Re-share existing shares to use them in safe mode'
299 299 b' New shares will be created in safe mode.'
300 300 )
301 301
302 302 # upgrade only needs to change the requirements
303 303 touches_filelogs = False
304 304 touches_manifests = False
305 305 touches_changelog = False
306 306 touches_requirements = True
307 307
308 308
309 309 @registerformatvariant
310 310 class sparserevlog(requirementformatvariant):
311 311 name = b'sparserevlog'
312 312
313 313 _requirement = requirements.SPARSEREVLOG_REQUIREMENT
314 314
315 315 default = True
316 316
317 317 description = _(
318 318 b'in order to limit disk reading and memory usage on older '
319 319 b'version, the span of a delta chain from its root to its '
320 320 b'end is limited, whatever the relevant data in this span. '
321 321 b'This can severly limit Mercurial ability to build good '
322 322 b'chain of delta resulting is much more storage space being '
323 323 b'taken and limit reusability of on disk delta during '
324 324 b'exchange.'
325 325 )
326 326
327 327 upgrademessage = _(
328 328 b'Revlog supports delta chain with more unused data '
329 329 b'between payload. These gaps will be skipped at read '
330 330 b'time. This allows for better delta chains, making a '
331 331 b'better compression and faster exchange with server.'
332 332 )
333 333
334 334
335 335 @registerformatvariant
336 336 class persistentnodemap(requirementformatvariant):
337 337 name = b'persistent-nodemap'
338 338
339 339 _requirement = requirements.NODEMAP_REQUIREMENT
340 340
341 341 default = False
342 342
343 343 description = _(
344 344 b'persist the node -> rev mapping on disk to speedup lookup'
345 345 )
346 346
347 347 upgrademessage = _(b'Speedup revision lookup by node id.')
348 348
349 349
350 350 @registerformatvariant
351 351 class copiessdc(requirementformatvariant):
352 352 name = b'copies-sdc'
353 353
354 354 _requirement = requirements.COPIESSDC_REQUIREMENT
355 355
356 356 default = False
357 357
358 358 description = _(b'Stores copies information alongside changesets.')
359 359
360 360 upgrademessage = _(
361 361 b'Allows to use more efficient algorithm to deal with copy tracing.'
362 362 )
363 363
364 364 touches_filelogs = False
365 365 touches_manifests = False
366 366
367 367
368 368 @registerformatvariant
369 369 class revlogv2(requirementformatvariant):
370 370 name = b'revlog-v2'
371 371 _requirement = requirements.REVLOGV2_REQUIREMENT
372 372 default = False
373 373 description = _(b'Version 2 of the revlog.')
374 374 upgrademessage = _(b'very experimental')
375 375
376 376
377 377 @registerformatvariant
378 378 class changelogv2(requirementformatvariant):
379 379 name = b'changelog-v2'
380 380 _requirement = requirements.CHANGELOGV2_REQUIREMENT
381 381 default = False
382 382 description = _(b'An iteration of the revlog focussed on changelog needs.')
383 383 upgrademessage = _(b'quite experimental')
384 384
385 385 touches_filelogs = False
386 386 touches_manifests = False
387 387
388 388
389 389 @registerformatvariant
390 390 class removecldeltachain(formatvariant):
391 391 name = b'plain-cl-delta'
392 392
393 393 default = True
394 394
395 395 description = _(
396 396 b'changelog storage is using deltas instead of '
397 397 b'raw entries; changelog reading and any '
398 398 b'operation relying on changelog data are slower '
399 399 b'than they could be'
400 400 )
401 401
402 402 upgrademessage = _(
403 403 b'changelog storage will be reformated to '
404 404 b'store raw entries; changelog reading will be '
405 405 b'faster; changelog size may be reduced'
406 406 )
407 407
408 408 @staticmethod
409 409 def fromrepo(repo):
410 410 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
411 411 # changelogs with deltas.
412 412 cl = repo.changelog
413 413 chainbase = cl.chainbase
414 414 return all(rev == chainbase(rev) for rev in cl)
415 415
416 416 @staticmethod
417 417 def fromconfig(repo):
418 418 return True
419 419
420 420
421 421 _has_zstd = (
422 422 b'zstd' in util.compengines
423 423 and util.compengines[b'zstd'].available()
424 424 and util.compengines[b'zstd'].revlogheader()
425 425 )
426 426
427 427
428 428 @registerformatvariant
429 429 class compressionengine(formatvariant):
430 430 name = b'compression'
431 431
432 432 if _has_zstd:
433 433 default = b'zstd'
434 434 else:
435 435 default = b'zlib'
436 436
437 437 description = _(
438 438 b'Compresion algorithm used to compress data. '
439 439 b'Some engine are faster than other'
440 440 )
441 441
442 442 upgrademessage = _(
443 443 b'revlog content will be recompressed with the new algorithm.'
444 444 )
445 445
446 446 @classmethod
447 447 def fromrepo(cls, repo):
448 448 # we allow multiple compression engine requirement to co-exist because
449 449 # strickly speaking, revlog seems to support mixed compression style.
450 450 #
451 451 # The compression used for new entries will be "the last one"
452 452 compression = b'zlib'
453 453 for req in repo.requirements:
454 454 prefix = req.startswith
455 455 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
456 456 compression = req.split(b'-', 2)[2]
457 457 return compression
458 458
459 459 @classmethod
460 460 def fromconfig(cls, repo):
461 461 compengines = repo.ui.configlist(b'format', b'revlog-compression')
462 462 # return the first valid value as the selection code would do
463 463 for comp in compengines:
464 464 if comp in util.compengines:
465 465 e = util.compengines[comp]
466 466 if e.available() and e.revlogheader():
467 467 return comp
468 468
469 469 # no valide compression found lets display it all for clarity
470 470 return b','.join(compengines)
471 471
472 472
473 473 @registerformatvariant
474 474 class compressionlevel(formatvariant):
475 475 name = b'compression-level'
476 476 default = b'default'
477 477
478 478 description = _(b'compression level')
479 479
480 480 upgrademessage = _(b'revlog content will be recompressed')
481 481
482 482 @classmethod
483 483 def fromrepo(cls, repo):
484 484 comp = compressionengine.fromrepo(repo)
485 485 level = None
486 486 if comp == b'zlib':
487 487 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
488 488 elif comp == b'zstd':
489 489 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
490 490 if level is None:
491 491 return b'default'
492 return bytes(level)
492 return b"%d" % level
493 493
494 494 @classmethod
495 495 def fromconfig(cls, repo):
496 496 comp = compressionengine.fromconfig(repo)
497 497 level = None
498 498 if comp == b'zlib':
499 499 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
500 500 elif comp == b'zstd':
501 501 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
502 502 if level is None:
503 503 return b'default'
504 return bytes(level)
504 return b"%d" % level
505 505
506 506
507 507 def find_format_upgrades(repo):
508 508 """returns a list of format upgrades which can be perform on the repo"""
509 509 upgrades = []
510 510
511 511 # We could detect lack of revlogv1 and store here, but they were added
512 512 # in 0.9.2 and we don't support upgrading repos without these
513 513 # requirements, so let's not bother.
514 514
515 515 for fv in allformatvariant:
516 516 if not fv.fromrepo(repo):
517 517 upgrades.append(fv)
518 518
519 519 return upgrades
520 520
521 521
522 522 def find_format_downgrades(repo):
523 523 """returns a list of format downgrades which will be performed on the repo
524 524 because of disabled config option for them"""
525 525
526 526 downgrades = []
527 527
528 528 for fv in allformatvariant:
529 529 if fv.name == b'compression':
530 530 # If there is a compression change between repository
531 531 # and config, destination repository compression will change
532 532 # and current compression will be removed.
533 533 if fv.fromrepo(repo) != fv.fromconfig(repo):
534 534 downgrades.append(fv)
535 535 continue
536 536 # format variant exist in repo but does not exist in new repository
537 537 # config
538 538 if fv.fromrepo(repo) and not fv.fromconfig(repo):
539 539 downgrades.append(fv)
540 540
541 541 return downgrades
542 542
543 543
544 544 ALL_OPTIMISATIONS = []
545 545
546 546
547 547 def register_optimization(obj):
548 548 ALL_OPTIMISATIONS.append(obj)
549 549 return obj
550 550
551 551
552 552 class optimization(improvement):
553 553 """an improvement subclass dedicated to optimizations"""
554 554
555 555 type = OPTIMISATION
556 556
557 557
558 558 @register_optimization
559 559 class redeltaparents(optimization):
560 560 name = b're-delta-parent'
561 561
562 562 type = OPTIMISATION
563 563
564 564 description = _(
565 565 b'deltas within internal storage will be recalculated to '
566 566 b'choose an optimal base revision where this was not '
567 567 b'already done; the size of the repository may shrink and '
568 568 b'various operations may become faster; the first time '
569 569 b'this optimization is performed could slow down upgrade '
570 570 b'execution considerably; subsequent invocations should '
571 571 b'not run noticeably slower'
572 572 )
573 573
574 574 upgrademessage = _(
575 575 b'deltas within internal storage will choose a new '
576 576 b'base revision if needed'
577 577 )
578 578
579 579
580 580 @register_optimization
581 581 class redeltamultibase(optimization):
582 582 name = b're-delta-multibase'
583 583
584 584 type = OPTIMISATION
585 585
586 586 description = _(
587 587 b'deltas within internal storage will be recalculated '
588 588 b'against multiple base revision and the smallest '
589 589 b'difference will be used; the size of the repository may '
590 590 b'shrink significantly when there are many merges; this '
591 591 b'optimization will slow down execution in proportion to '
592 592 b'the number of merges in the repository and the amount '
593 593 b'of files in the repository; this slow down should not '
594 594 b'be significant unless there are tens of thousands of '
595 595 b'files and thousands of merges'
596 596 )
597 597
598 598 upgrademessage = _(
599 599 b'deltas within internal storage will choose an '
600 600 b'optimal delta by computing deltas against multiple '
601 601 b'parents; may slow down execution time '
602 602 b'significantly'
603 603 )
604 604
605 605
606 606 @register_optimization
607 607 class redeltaall(optimization):
608 608 name = b're-delta-all'
609 609
610 610 type = OPTIMISATION
611 611
612 612 description = _(
613 613 b'deltas within internal storage will always be '
614 614 b'recalculated without reusing prior deltas; this will '
615 615 b'likely make execution run several times slower; this '
616 616 b'optimization is typically not needed'
617 617 )
618 618
619 619 upgrademessage = _(
620 620 b'deltas within internal storage will be fully '
621 621 b'recomputed; this will likely drastically slow down '
622 622 b'execution time'
623 623 )
624 624
625 625
626 626 @register_optimization
627 627 class redeltafulladd(optimization):
628 628 name = b're-delta-fulladd'
629 629
630 630 type = OPTIMISATION
631 631
632 632 description = _(
633 633 b'every revision will be re-added as if it was new '
634 634 b'content. It will go through the full storage '
635 635 b'mechanism giving extensions a chance to process it '
636 636 b'(eg. lfs). This is similar to "re-delta-all" but even '
637 637 b'slower since more logic is involved.'
638 638 )
639 639
640 640 upgrademessage = _(
641 641 b'each revision will be added as new content to the '
642 642 b'internal storage; this will likely drastically slow '
643 643 b'down execution time, but some extensions might need '
644 644 b'it'
645 645 )
646 646
647 647
648 648 def findoptimizations(repo):
649 649 """Determine optimisation that could be used during upgrade"""
650 650 # These are unconditionally added. There is logic later that figures out
651 651 # which ones to apply.
652 652 return list(ALL_OPTIMISATIONS)
653 653
654 654
655 655 def determine_upgrade_actions(
656 656 repo, format_upgrades, optimizations, sourcereqs, destreqs
657 657 ):
658 658 """Determine upgrade actions that will be performed.
659 659
660 660 Given a list of improvements as returned by ``find_format_upgrades`` and
661 661 ``findoptimizations``, determine the list of upgrade actions that
662 662 will be performed.
663 663
664 664 The role of this function is to filter improvements if needed, apply
665 665 recommended optimizations from the improvements list that make sense,
666 666 etc.
667 667
668 668 Returns a list of action names.
669 669 """
670 670 newactions = []
671 671
672 672 for d in format_upgrades:
673 673 if hasattr(d, '_requirement'):
674 674 name = d._requirement
675 675 else:
676 676 name = None
677 677
678 678 # If the action is a requirement that doesn't show up in the
679 679 # destination requirements, prune the action.
680 680 if name is not None and name not in destreqs:
681 681 continue
682 682
683 683 newactions.append(d)
684 684
685 685 newactions.extend(
686 686 o
687 687 for o in sorted(optimizations, key=(lambda x: x.name))
688 688 if o not in newactions
689 689 )
690 690
691 691 # FUTURE consider adding some optimizations here for certain transitions.
692 692 # e.g. adding generaldelta could schedule parent redeltas.
693 693
694 694 return newactions
695 695
696 696
697 697 class BaseOperation:
698 698 """base class that contains the minimum for an upgrade to work
699 699
700 700 (this might need to be extended as the usage for subclass alternative to
701 701 UpgradeOperation extends)
702 702 """
703 703
704 704 def __init__(
705 705 self,
706 706 new_requirements,
707 707 backup_store,
708 708 ):
709 709 self.new_requirements = new_requirements
710 710 # should this operation create a backup of the store
711 711 self.backup_store = backup_store
712 712
713 713
714 714 class UpgradeOperation(BaseOperation):
715 715 """represent the work to be done during an upgrade"""
716 716
717 717 def __init__(
718 718 self,
719 719 ui,
720 720 new_requirements,
721 721 current_requirements,
722 722 upgrade_actions,
723 723 removed_actions,
724 724 revlogs_to_process,
725 725 backup_store,
726 726 ):
727 727 super().__init__(
728 728 new_requirements,
729 729 backup_store,
730 730 )
731 731 self.ui = ui
732 732 self.current_requirements = current_requirements
733 733 # list of upgrade actions the operation will perform
734 734 self.upgrade_actions = upgrade_actions
735 735 self.removed_actions = removed_actions
736 736 self.revlogs_to_process = revlogs_to_process
737 737 # requirements which will be added by the operation
738 738 self._added_requirements = (
739 739 self.new_requirements - self.current_requirements
740 740 )
741 741 # requirements which will be removed by the operation
742 742 self._removed_requirements = (
743 743 self.current_requirements - self.new_requirements
744 744 )
745 745 # requirements which will be preserved by the operation
746 746 self._preserved_requirements = (
747 747 self.current_requirements & self.new_requirements
748 748 )
749 749 # optimizations which are not used and it's recommended that they
750 750 # should use them
751 751 all_optimizations = findoptimizations(None)
752 752 self.unused_optimizations = [
753 753 i for i in all_optimizations if i not in self.upgrade_actions
754 754 ]
755 755
756 756 # delta reuse mode of this upgrade operation
757 757 upgrade_actions_names = self.upgrade_actions_names
758 758 self.delta_reuse_mode = revlog.revlog.DELTAREUSEALWAYS
759 759 if b're-delta-all' in upgrade_actions_names:
760 760 self.delta_reuse_mode = revlog.revlog.DELTAREUSENEVER
761 761 elif b're-delta-parent' in upgrade_actions_names:
762 762 self.delta_reuse_mode = revlog.revlog.DELTAREUSESAMEREVS
763 763 elif b're-delta-multibase' in upgrade_actions_names:
764 764 self.delta_reuse_mode = revlog.revlog.DELTAREUSESAMEREVS
765 765 elif b're-delta-fulladd' in upgrade_actions_names:
766 766 self.delta_reuse_mode = revlog.revlog.DELTAREUSEFULLADD
767 767
768 768 # should this operation force re-delta of both parents
769 769 self.force_re_delta_both_parents = (
770 770 b're-delta-multibase' in upgrade_actions_names
771 771 )
772 772
773 773 @property
774 774 def upgrade_actions_names(self):
775 775 return set([a.name for a in self.upgrade_actions])
776 776
777 777 @property
778 778 def requirements_only(self):
779 779 # does the operation only touches repository requirement
780 780 return (
781 781 self.touches_requirements
782 782 and not self.touches_filelogs
783 783 and not self.touches_manifests
784 784 and not self.touches_changelog
785 785 and not self.touches_dirstate
786 786 )
787 787
788 788 @property
789 789 def touches_filelogs(self):
790 790 for a in self.upgrade_actions:
791 791 # in optimisations, we re-process the revlogs again
792 792 if a.type == OPTIMISATION:
793 793 return True
794 794 elif a.touches_filelogs:
795 795 return True
796 796 for a in self.removed_actions:
797 797 if a.touches_filelogs:
798 798 return True
799 799 return False
800 800
801 801 @property
802 802 def touches_manifests(self):
803 803 for a in self.upgrade_actions:
804 804 # in optimisations, we re-process the revlogs again
805 805 if a.type == OPTIMISATION:
806 806 return True
807 807 elif a.touches_manifests:
808 808 return True
809 809 for a in self.removed_actions:
810 810 if a.touches_manifests:
811 811 return True
812 812 return False
813 813
814 814 @property
815 815 def touches_changelog(self):
816 816 for a in self.upgrade_actions:
817 817 # in optimisations, we re-process the revlogs again
818 818 if a.type == OPTIMISATION:
819 819 return True
820 820 elif a.touches_changelog:
821 821 return True
822 822 for a in self.removed_actions:
823 823 if a.touches_changelog:
824 824 return True
825 825 return False
826 826
827 827 @property
828 828 def touches_requirements(self):
829 829 for a in self.upgrade_actions:
830 830 # optimisations are used to re-process revlogs and does not result
831 831 # in a requirement being added or removed
832 832 if a.type == OPTIMISATION:
833 833 pass
834 834 elif a.touches_requirements:
835 835 return True
836 836 for a in self.removed_actions:
837 837 if a.touches_requirements:
838 838 return True
839 839
840 840 @property
841 841 def touches_dirstate(self):
842 842 for a in self.upgrade_actions:
843 843 # revlog optimisations do not affect the dirstate
844 844 if a.type == OPTIMISATION:
845 845 pass
846 846 elif a.touches_dirstate:
847 847 return True
848 848 for a in self.removed_actions:
849 849 if a.touches_dirstate:
850 850 return True
851 851
852 852 return False
853 853
854 854 def _write_labeled(self, l, label: bytes):
855 855 """
856 856 Utility function to aid writing of a list under one label
857 857 """
858 858 first = True
859 859 for r in sorted(l):
860 860 if not first:
861 861 self.ui.write(b', ')
862 862 self.ui.write(r, label=label)
863 863 first = False
864 864
865 865 def print_requirements(self):
866 866 self.ui.write(_(b'requirements\n'))
867 867 self.ui.write(_(b' preserved: '))
868 868 self._write_labeled(
869 869 self._preserved_requirements, b"upgrade-repo.requirement.preserved"
870 870 )
871 871 self.ui.write((b'\n'))
872 872 if self._removed_requirements:
873 873 self.ui.write(_(b' removed: '))
874 874 self._write_labeled(
875 875 self._removed_requirements, b"upgrade-repo.requirement.removed"
876 876 )
877 877 self.ui.write((b'\n'))
878 878 if self._added_requirements:
879 879 self.ui.write(_(b' added: '))
880 880 self._write_labeled(
881 881 self._added_requirements, b"upgrade-repo.requirement.added"
882 882 )
883 883 self.ui.write((b'\n'))
884 884 self.ui.write(b'\n')
885 885
886 886 def print_optimisations(self):
887 887 optimisations = [
888 888 a for a in self.upgrade_actions if a.type == OPTIMISATION
889 889 ]
890 890 optimisations.sort(key=lambda a: a.name)
891 891 if optimisations:
892 892 self.ui.write(_(b'optimisations: '))
893 893 self._write_labeled(
894 894 [a.name for a in optimisations],
895 895 b"upgrade-repo.optimisation.performed",
896 896 )
897 897 self.ui.write(b'\n\n')
898 898
899 899 def print_upgrade_actions(self):
900 900 for a in self.upgrade_actions:
901 901 self.ui.status(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
902 902
903 903 def print_affected_revlogs(self):
904 904 if not self.revlogs_to_process:
905 905 self.ui.write((b'no revlogs to process\n'))
906 906 else:
907 907 self.ui.write((b'processed revlogs:\n'))
908 908 for r in sorted(self.revlogs_to_process):
909 909 self.ui.write((b' - %s\n' % r))
910 910 self.ui.write((b'\n'))
911 911
912 912 def print_unused_optimizations(self):
913 913 for i in self.unused_optimizations:
914 914 self.ui.status(_(b'%s\n %s\n\n') % (i.name, i.description))
915 915
916 916 def has_upgrade_action(self, name):
917 917 """Check whether the upgrade operation will perform this action"""
918 918 return name in self._upgrade_actions_names
919 919
920 920 def print_post_op_messages(self):
921 921 """print post upgrade operation warning messages"""
922 922 for a in self.upgrade_actions:
923 923 if a.postupgrademessage is not None:
924 924 self.ui.warn(b'%s\n' % a.postupgrademessage)
925 925 for a in self.removed_actions:
926 926 if a.postdowngrademessage is not None:
927 927 self.ui.warn(b'%s\n' % a.postdowngrademessage)
928 928
929 929
930 930 ### Code checking if a repository can got through the upgrade process at all. #
931 931
932 932
933 933 def requiredsourcerequirements(repo):
934 934 """Obtain requirements required to be present to upgrade a repo.
935 935
936 936 An upgrade will not be allowed if the repository doesn't have the
937 937 requirements returned by this function.
938 938 """
939 939 return {
940 940 # Introduced in Mercurial 0.9.2.
941 941 requirements.STORE_REQUIREMENT,
942 942 }
943 943
944 944
945 945 def blocksourcerequirements(repo):
946 946 """Obtain requirements that will prevent an upgrade from occurring.
947 947
948 948 An upgrade cannot be performed if the source repository contains a
949 949 requirements in the returned set.
950 950 """
951 951 return {
952 952 # This was a precursor to generaldelta and was never enabled by default.
953 953 # It should (hopefully) not exist in the wild.
954 954 b'parentdelta',
955 955 }
956 956
957 957
958 958 def check_revlog_version(reqs):
959 959 """Check that the requirements contain at least one Revlog version"""
960 960 all_revlogs = {
961 961 requirements.REVLOGV1_REQUIREMENT,
962 962 requirements.REVLOGV2_REQUIREMENT,
963 963 }
964 964 if not all_revlogs.intersection(reqs):
965 965 msg = _(b'cannot upgrade repository; missing a revlog version')
966 966 raise error.Abort(msg)
967 967
968 968
969 969 def check_source_requirements(repo):
970 970 """Ensure that no existing requirements prevent the repository upgrade"""
971 971
972 972 check_revlog_version(repo.requirements)
973 973 required = requiredsourcerequirements(repo)
974 974 missingreqs = required - repo.requirements
975 975 if missingreqs:
976 976 msg = _(b'cannot upgrade repository; requirement missing: %s')
977 977 missingreqs = b', '.join(sorted(missingreqs))
978 978 raise error.Abort(msg % missingreqs)
979 979
980 980 blocking = blocksourcerequirements(repo)
981 981 blockingreqs = blocking & repo.requirements
982 982 if blockingreqs:
983 983 m = _(b'cannot upgrade repository; unsupported source requirement: %s')
984 984 blockingreqs = b', '.join(sorted(blockingreqs))
985 985 raise error.Abort(m % blockingreqs)
986 986 # Upgrade should operate on the actual store, not the shared link.
987 987
988 988 bad_share = (
989 989 requirements.SHARED_REQUIREMENT in repo.requirements
990 990 and requirements.SHARESAFE_REQUIREMENT not in repo.requirements
991 991 )
992 992 if bad_share:
993 993 m = _(b'cannot upgrade repository; share repository without share-safe')
994 994 h = _(b'check :hg:`help config.format.use-share-safe`')
995 995 raise error.Abort(m, hint=h)
996 996
997 997
998 998 ### Verify the validity of the planned requirement changes ####################
999 999
1000 1000
1001 1001 def supportremovedrequirements(repo):
1002 1002 """Obtain requirements that can be removed during an upgrade.
1003 1003
1004 1004 If an upgrade were to create a repository that dropped a requirement,
1005 1005 the dropped requirement must appear in the returned set for the upgrade
1006 1006 to be allowed.
1007 1007 """
1008 1008 supported = {
1009 1009 requirements.SPARSEREVLOG_REQUIREMENT,
1010 1010 requirements.COPIESSDC_REQUIREMENT,
1011 1011 requirements.NODEMAP_REQUIREMENT,
1012 1012 requirements.SHARESAFE_REQUIREMENT,
1013 1013 requirements.REVLOGV2_REQUIREMENT,
1014 1014 requirements.CHANGELOGV2_REQUIREMENT,
1015 1015 requirements.REVLOGV1_REQUIREMENT,
1016 1016 requirements.DIRSTATE_TRACKED_HINT_V1,
1017 1017 requirements.DIRSTATE_V2_REQUIREMENT,
1018 1018 }
1019 1019 for name in compression.compengines:
1020 1020 engine = compression.compengines[name]
1021 1021 if engine.available() and engine.revlogheader():
1022 1022 supported.add(b'exp-compression-%s' % name)
1023 1023 if engine.name() == b'zstd':
1024 1024 supported.add(b'revlog-compression-zstd')
1025 1025 return supported
1026 1026
1027 1027
1028 1028 def supporteddestrequirements(repo):
1029 1029 """Obtain requirements that upgrade supports in the destination.
1030 1030
1031 1031 If the result of the upgrade would have requirements not in this set,
1032 1032 the upgrade is disallowed.
1033 1033
1034 1034 Extensions should monkeypatch this to add their custom requirements.
1035 1035 """
1036 1036 supported = {
1037 1037 requirements.CHANGELOGV2_REQUIREMENT,
1038 1038 requirements.COPIESSDC_REQUIREMENT,
1039 1039 requirements.DIRSTATE_TRACKED_HINT_V1,
1040 1040 requirements.DIRSTATE_V2_REQUIREMENT,
1041 1041 requirements.DOTENCODE_REQUIREMENT,
1042 1042 requirements.FNCACHE_REQUIREMENT,
1043 1043 requirements.GENERALDELTA_REQUIREMENT,
1044 1044 requirements.NODEMAP_REQUIREMENT,
1045 1045 requirements.REVLOGV1_REQUIREMENT, # allowed in case of downgrade
1046 1046 requirements.REVLOGV2_REQUIREMENT,
1047 1047 requirements.SHARED_REQUIREMENT,
1048 1048 requirements.SHARESAFE_REQUIREMENT,
1049 1049 requirements.SPARSEREVLOG_REQUIREMENT,
1050 1050 requirements.STORE_REQUIREMENT,
1051 1051 requirements.TREEMANIFEST_REQUIREMENT,
1052 1052 requirements.NARROW_REQUIREMENT,
1053 1053 }
1054 1054 for name in compression.compengines:
1055 1055 engine = compression.compengines[name]
1056 1056 if engine.available() and engine.revlogheader():
1057 1057 supported.add(b'exp-compression-%s' % name)
1058 1058 if engine.name() == b'zstd':
1059 1059 supported.add(b'revlog-compression-zstd')
1060 1060 return supported
1061 1061
1062 1062
1063 1063 def allowednewrequirements(repo):
1064 1064 """Obtain requirements that can be added to a repository during upgrade.
1065 1065
1066 1066 This is used to disallow proposed requirements from being added when
1067 1067 they weren't present before.
1068 1068
1069 1069 We use a list of allowed requirement additions instead of a list of known
1070 1070 bad additions because the whitelist approach is safer and will prevent
1071 1071 future, unknown requirements from accidentally being added.
1072 1072 """
1073 1073 supported = {
1074 1074 requirements.DOTENCODE_REQUIREMENT,
1075 1075 requirements.FNCACHE_REQUIREMENT,
1076 1076 requirements.GENERALDELTA_REQUIREMENT,
1077 1077 requirements.SPARSEREVLOG_REQUIREMENT,
1078 1078 requirements.COPIESSDC_REQUIREMENT,
1079 1079 requirements.NODEMAP_REQUIREMENT,
1080 1080 requirements.SHARESAFE_REQUIREMENT,
1081 1081 requirements.REVLOGV1_REQUIREMENT,
1082 1082 requirements.REVLOGV2_REQUIREMENT,
1083 1083 requirements.CHANGELOGV2_REQUIREMENT,
1084 1084 requirements.DIRSTATE_TRACKED_HINT_V1,
1085 1085 requirements.DIRSTATE_V2_REQUIREMENT,
1086 1086 }
1087 1087 for name in compression.compengines:
1088 1088 engine = compression.compengines[name]
1089 1089 if engine.available() and engine.revlogheader():
1090 1090 supported.add(b'exp-compression-%s' % name)
1091 1091 if engine.name() == b'zstd':
1092 1092 supported.add(b'revlog-compression-zstd')
1093 1093 return supported
1094 1094
1095 1095
1096 1096 def check_requirements_changes(repo, new_reqs):
1097 1097 old_reqs = repo.requirements
1098 1098 check_revlog_version(repo.requirements)
1099 1099 support_removal = supportremovedrequirements(repo)
1100 1100 no_remove_reqs = old_reqs - new_reqs - support_removal
1101 1101 if no_remove_reqs:
1102 1102 msg = _(b'cannot upgrade repository; requirement would be removed: %s')
1103 1103 no_remove_reqs = b', '.join(sorted(no_remove_reqs))
1104 1104 raise error.Abort(msg % no_remove_reqs)
1105 1105
1106 1106 support_addition = allowednewrequirements(repo)
1107 1107 no_add_reqs = new_reqs - old_reqs - support_addition
1108 1108 if no_add_reqs:
1109 1109 m = _(b'cannot upgrade repository; do not support adding requirement: ')
1110 1110 no_add_reqs = b', '.join(sorted(no_add_reqs))
1111 1111 raise error.Abort(m + no_add_reqs)
1112 1112
1113 1113 supported = supporteddestrequirements(repo)
1114 1114 unsupported_reqs = new_reqs - supported
1115 1115 if unsupported_reqs:
1116 1116 msg = _(
1117 1117 b'cannot upgrade repository; do not support destination '
1118 1118 b'requirement: %s'
1119 1119 )
1120 1120 unsupported_reqs = b', '.join(sorted(unsupported_reqs))
1121 1121 raise error.Abort(msg % unsupported_reqs)
General Comments 0
You need to be logged in to leave comments. Login now