##// END OF EJS Templates
upgrade: byteify requirement changes written to output...
Matt Harbison -
r50471:d1244676 stable
parent child Browse files
Show More
@@ -1,1124 +1,1124 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 needs to'
293 293 b' be reshared.'
294 294 )
295 295
296 296 postupgrademessage = _(
297 297 b'repository upgraded to share safe mode, existing'
298 298 b' shares will still work in old non-safe mode. '
299 299 b'Re-share existing shares to use them in safe mode'
300 300 b' New shares will be created in safe mode.'
301 301 )
302 302
303 303 # upgrade only needs to change the requirements
304 304 touches_filelogs = False
305 305 touches_manifests = False
306 306 touches_changelog = False
307 307 touches_requirements = True
308 308
309 309
310 310 @registerformatvariant
311 311 class sparserevlog(requirementformatvariant):
312 312 name = b'sparserevlog'
313 313
314 314 _requirement = requirements.SPARSEREVLOG_REQUIREMENT
315 315
316 316 default = True
317 317
318 318 description = _(
319 319 b'in order to limit disk reading and memory usage on older '
320 320 b'version, the span of a delta chain from its root to its '
321 321 b'end is limited, whatever the relevant data in this span. '
322 322 b'This can severly limit Mercurial ability to build good '
323 323 b'chain of delta resulting is much more storage space being '
324 324 b'taken and limit reusability of on disk delta during '
325 325 b'exchange.'
326 326 )
327 327
328 328 upgrademessage = _(
329 329 b'Revlog supports delta chain with more unused data '
330 330 b'between payload. These gaps will be skipped at read '
331 331 b'time. This allows for better delta chains, making a '
332 332 b'better compression and faster exchange with server.'
333 333 )
334 334
335 335
336 336 @registerformatvariant
337 337 class persistentnodemap(requirementformatvariant):
338 338 name = b'persistent-nodemap'
339 339
340 340 _requirement = requirements.NODEMAP_REQUIREMENT
341 341
342 342 default = False
343 343
344 344 description = _(
345 345 b'persist the node -> rev mapping on disk to speedup lookup'
346 346 )
347 347
348 348 upgrademessage = _(b'Speedup revision lookup by node id.')
349 349
350 350
351 351 @registerformatvariant
352 352 class copiessdc(requirementformatvariant):
353 353 name = b'copies-sdc'
354 354
355 355 _requirement = requirements.COPIESSDC_REQUIREMENT
356 356
357 357 default = False
358 358
359 359 description = _(b'Stores copies information alongside changesets.')
360 360
361 361 upgrademessage = _(
362 362 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
363 363 )
364 364
365 365 touches_filelogs = False
366 366 touches_manifests = False
367 367
368 368
369 369 @registerformatvariant
370 370 class revlogv2(requirementformatvariant):
371 371 name = b'revlog-v2'
372 372 _requirement = requirements.REVLOGV2_REQUIREMENT
373 373 default = False
374 374 description = _(b'Version 2 of the revlog.')
375 375 upgrademessage = _(b'very experimental')
376 376
377 377
378 378 @registerformatvariant
379 379 class changelogv2(requirementformatvariant):
380 380 name = b'changelog-v2'
381 381 _requirement = requirements.CHANGELOGV2_REQUIREMENT
382 382 default = False
383 383 description = _(b'An iteration of the revlog focussed on changelog needs.')
384 384 upgrademessage = _(b'quite experimental')
385 385
386 386 touches_filelogs = False
387 387 touches_manifests = False
388 388
389 389
390 390 @registerformatvariant
391 391 class removecldeltachain(formatvariant):
392 392 name = b'plain-cl-delta'
393 393
394 394 default = True
395 395
396 396 description = _(
397 397 b'changelog storage is using deltas instead of '
398 398 b'raw entries; changelog reading and any '
399 399 b'operation relying on changelog data are slower '
400 400 b'than they could be'
401 401 )
402 402
403 403 upgrademessage = _(
404 404 b'changelog storage will be reformated to '
405 405 b'store raw entries; changelog reading will be '
406 406 b'faster; changelog size may be reduced'
407 407 )
408 408
409 409 @staticmethod
410 410 def fromrepo(repo):
411 411 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
412 412 # changelogs with deltas.
413 413 cl = repo.changelog
414 414 chainbase = cl.chainbase
415 415 return all(rev == chainbase(rev) for rev in cl)
416 416
417 417 @staticmethod
418 418 def fromconfig(repo):
419 419 return True
420 420
421 421
422 422 _has_zstd = (
423 423 b'zstd' in util.compengines
424 424 and util.compengines[b'zstd'].available()
425 425 and util.compengines[b'zstd'].revlogheader()
426 426 )
427 427
428 428
429 429 @registerformatvariant
430 430 class compressionengine(formatvariant):
431 431 name = b'compression'
432 432
433 433 if _has_zstd:
434 434 default = b'zstd'
435 435 else:
436 436 default = b'zlib'
437 437
438 438 description = _(
439 439 b'Compresion algorithm used to compress data. '
440 440 b'Some engine are faster than other'
441 441 )
442 442
443 443 upgrademessage = _(
444 444 b'revlog content will be recompressed with the new algorithm.'
445 445 )
446 446
447 447 @classmethod
448 448 def fromrepo(cls, repo):
449 449 # we allow multiple compression engine requirement to co-exist because
450 450 # strickly speaking, revlog seems to support mixed compression style.
451 451 #
452 452 # The compression used for new entries will be "the last one"
453 453 compression = b'zlib'
454 454 for req in repo.requirements:
455 455 prefix = req.startswith
456 456 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
457 457 compression = req.split(b'-', 2)[2]
458 458 return compression
459 459
460 460 @classmethod
461 461 def fromconfig(cls, repo):
462 462 compengines = repo.ui.configlist(b'format', b'revlog-compression')
463 463 # return the first valid value as the selection code would do
464 464 for comp in compengines:
465 465 if comp in util.compengines:
466 466 e = util.compengines[comp]
467 467 if e.available() and e.revlogheader():
468 468 return comp
469 469
470 470 # no valide compression found lets display it all for clarity
471 471 return b','.join(compengines)
472 472
473 473
474 474 @registerformatvariant
475 475 class compressionlevel(formatvariant):
476 476 name = b'compression-level'
477 477 default = b'default'
478 478
479 479 description = _(b'compression level')
480 480
481 481 upgrademessage = _(b'revlog content will be recompressed')
482 482
483 483 @classmethod
484 484 def fromrepo(cls, repo):
485 485 comp = compressionengine.fromrepo(repo)
486 486 level = None
487 487 if comp == b'zlib':
488 488 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
489 489 elif comp == b'zstd':
490 490 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
491 491 if level is None:
492 492 return b'default'
493 493 return bytes(level)
494 494
495 495 @classmethod
496 496 def fromconfig(cls, repo):
497 497 comp = compressionengine.fromconfig(repo)
498 498 level = None
499 499 if comp == b'zlib':
500 500 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
501 501 elif comp == b'zstd':
502 502 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
503 503 if level is None:
504 504 return b'default'
505 505 return bytes(level)
506 506
507 507
508 508 def find_format_upgrades(repo):
509 509 """returns a list of format upgrades which can be perform on the repo"""
510 510 upgrades = []
511 511
512 512 # We could detect lack of revlogv1 and store here, but they were added
513 513 # in 0.9.2 and we don't support upgrading repos without these
514 514 # requirements, so let's not bother.
515 515
516 516 for fv in allformatvariant:
517 517 if not fv.fromrepo(repo):
518 518 upgrades.append(fv)
519 519
520 520 return upgrades
521 521
522 522
523 523 def find_format_downgrades(repo):
524 524 """returns a list of format downgrades which will be performed on the repo
525 525 because of disabled config option for them"""
526 526
527 527 downgrades = []
528 528
529 529 for fv in allformatvariant:
530 530 if fv.name == b'compression':
531 531 # If there is a compression change between repository
532 532 # and config, destination repository compression will change
533 533 # and current compression will be removed.
534 534 if fv.fromrepo(repo) != fv.fromconfig(repo):
535 535 downgrades.append(fv)
536 536 continue
537 537 # format variant exist in repo but does not exist in new repository
538 538 # config
539 539 if fv.fromrepo(repo) and not fv.fromconfig(repo):
540 540 downgrades.append(fv)
541 541
542 542 return downgrades
543 543
544 544
545 545 ALL_OPTIMISATIONS = []
546 546
547 547
548 548 def register_optimization(obj):
549 549 ALL_OPTIMISATIONS.append(obj)
550 550 return obj
551 551
552 552
553 553 class optimization(improvement):
554 554 """an improvement subclass dedicated to optimizations"""
555 555
556 556 type = OPTIMISATION
557 557
558 558
559 559 @register_optimization
560 560 class redeltaparents(optimization):
561 561 name = b're-delta-parent'
562 562
563 563 type = OPTIMISATION
564 564
565 565 description = _(
566 566 b'deltas within internal storage will be recalculated to '
567 567 b'choose an optimal base revision where this was not '
568 568 b'already done; the size of the repository may shrink and '
569 569 b'various operations may become faster; the first time '
570 570 b'this optimization is performed could slow down upgrade '
571 571 b'execution considerably; subsequent invocations should '
572 572 b'not run noticeably slower'
573 573 )
574 574
575 575 upgrademessage = _(
576 576 b'deltas within internal storage will choose a new '
577 577 b'base revision if needed'
578 578 )
579 579
580 580
581 581 @register_optimization
582 582 class redeltamultibase(optimization):
583 583 name = b're-delta-multibase'
584 584
585 585 type = OPTIMISATION
586 586
587 587 description = _(
588 588 b'deltas within internal storage will be recalculated '
589 589 b'against multiple base revision and the smallest '
590 590 b'difference will be used; the size of the repository may '
591 591 b'shrink significantly when there are many merges; this '
592 592 b'optimization will slow down execution in proportion to '
593 593 b'the number of merges in the repository and the amount '
594 594 b'of files in the repository; this slow down should not '
595 595 b'be significant unless there are tens of thousands of '
596 596 b'files and thousands of merges'
597 597 )
598 598
599 599 upgrademessage = _(
600 600 b'deltas within internal storage will choose an '
601 601 b'optimal delta by computing deltas against multiple '
602 602 b'parents; may slow down execution time '
603 603 b'significantly'
604 604 )
605 605
606 606
607 607 @register_optimization
608 608 class redeltaall(optimization):
609 609 name = b're-delta-all'
610 610
611 611 type = OPTIMISATION
612 612
613 613 description = _(
614 614 b'deltas within internal storage will always be '
615 615 b'recalculated without reusing prior deltas; this will '
616 616 b'likely make execution run several times slower; this '
617 617 b'optimization is typically not needed'
618 618 )
619 619
620 620 upgrademessage = _(
621 621 b'deltas within internal storage will be fully '
622 622 b'recomputed; this will likely drastically slow down '
623 623 b'execution time'
624 624 )
625 625
626 626
627 627 @register_optimization
628 628 class redeltafulladd(optimization):
629 629 name = b're-delta-fulladd'
630 630
631 631 type = OPTIMISATION
632 632
633 633 description = _(
634 634 b'every revision will be re-added as if it was new '
635 635 b'content. It will go through the full storage '
636 636 b'mechanism giving extensions a chance to process it '
637 637 b'(eg. lfs). This is similar to "re-delta-all" but even '
638 638 b'slower since more logic is involved.'
639 639 )
640 640
641 641 upgrademessage = _(
642 642 b'each revision will be added as new content to the '
643 643 b'internal storage; this will likely drastically slow '
644 644 b'down execution time, but some extensions might need '
645 645 b'it'
646 646 )
647 647
648 648
649 649 def findoptimizations(repo):
650 650 """Determine optimisation that could be used during upgrade"""
651 651 # These are unconditionally added. There is logic later that figures out
652 652 # which ones to apply.
653 653 return list(ALL_OPTIMISATIONS)
654 654
655 655
656 656 def determine_upgrade_actions(
657 657 repo, format_upgrades, optimizations, sourcereqs, destreqs
658 658 ):
659 659 """Determine upgrade actions that will be performed.
660 660
661 661 Given a list of improvements as returned by ``find_format_upgrades`` and
662 662 ``findoptimizations``, determine the list of upgrade actions that
663 663 will be performed.
664 664
665 665 The role of this function is to filter improvements if needed, apply
666 666 recommended optimizations from the improvements list that make sense,
667 667 etc.
668 668
669 669 Returns a list of action names.
670 670 """
671 671 newactions = []
672 672
673 673 for d in format_upgrades:
674 674 if util.safehasattr(d, '_requirement'):
675 675 name = d._requirement
676 676 else:
677 677 name = None
678 678
679 679 # If the action is a requirement that doesn't show up in the
680 680 # destination requirements, prune the action.
681 681 if name is not None and name not in destreqs:
682 682 continue
683 683
684 684 newactions.append(d)
685 685
686 686 newactions.extend(
687 687 o
688 688 for o in sorted(optimizations, key=(lambda x: x.name))
689 689 if o not in newactions
690 690 )
691 691
692 692 # FUTURE consider adding some optimizations here for certain transitions.
693 693 # e.g. adding generaldelta could schedule parent redeltas.
694 694
695 695 return newactions
696 696
697 697
698 698 class BaseOperation:
699 699 """base class that contains the minimum for an upgrade to work
700 700
701 701 (this might need to be extended as the usage for subclass alternative to
702 702 UpgradeOperation extends)
703 703 """
704 704
705 705 def __init__(
706 706 self,
707 707 new_requirements,
708 708 backup_store,
709 709 ):
710 710 self.new_requirements = new_requirements
711 711 # should this operation create a backup of the store
712 712 self.backup_store = backup_store
713 713
714 714
715 715 class UpgradeOperation(BaseOperation):
716 716 """represent the work to be done during an upgrade"""
717 717
718 718 def __init__(
719 719 self,
720 720 ui,
721 721 new_requirements,
722 722 current_requirements,
723 723 upgrade_actions,
724 724 removed_actions,
725 725 revlogs_to_process,
726 726 backup_store,
727 727 ):
728 728 super().__init__(
729 729 new_requirements,
730 730 backup_store,
731 731 )
732 732 self.ui = ui
733 733 self.current_requirements = current_requirements
734 734 # list of upgrade actions the operation will perform
735 735 self.upgrade_actions = upgrade_actions
736 736 self.removed_actions = removed_actions
737 737 self.revlogs_to_process = revlogs_to_process
738 738 # requirements which will be added by the operation
739 739 self._added_requirements = (
740 740 self.new_requirements - self.current_requirements
741 741 )
742 742 # requirements which will be removed by the operation
743 743 self._removed_requirements = (
744 744 self.current_requirements - self.new_requirements
745 745 )
746 746 # requirements which will be preserved by the operation
747 747 self._preserved_requirements = (
748 748 self.current_requirements & self.new_requirements
749 749 )
750 750 # optimizations which are not used and it's recommended that they
751 751 # should use them
752 752 all_optimizations = findoptimizations(None)
753 753 self.unused_optimizations = [
754 754 i for i in all_optimizations if i not in self.upgrade_actions
755 755 ]
756 756
757 757 # delta reuse mode of this upgrade operation
758 758 upgrade_actions_names = self.upgrade_actions_names
759 759 self.delta_reuse_mode = revlog.revlog.DELTAREUSEALWAYS
760 760 if b're-delta-all' in upgrade_actions_names:
761 761 self.delta_reuse_mode = revlog.revlog.DELTAREUSENEVER
762 762 elif b're-delta-parent' in upgrade_actions_names:
763 763 self.delta_reuse_mode = revlog.revlog.DELTAREUSESAMEREVS
764 764 elif b're-delta-multibase' in upgrade_actions_names:
765 765 self.delta_reuse_mode = revlog.revlog.DELTAREUSESAMEREVS
766 766 elif b're-delta-fulladd' in upgrade_actions_names:
767 767 self.delta_reuse_mode = revlog.revlog.DELTAREUSEFULLADD
768 768
769 769 # should this operation force re-delta of both parents
770 770 self.force_re_delta_both_parents = (
771 771 b're-delta-multibase' in upgrade_actions_names
772 772 )
773 773
774 774 @property
775 775 def upgrade_actions_names(self):
776 776 return set([a.name for a in self.upgrade_actions])
777 777
778 778 @property
779 779 def requirements_only(self):
780 780 # does the operation only touches repository requirement
781 781 return (
782 782 self.touches_requirements
783 783 and not self.touches_filelogs
784 784 and not self.touches_manifests
785 785 and not self.touches_changelog
786 786 and not self.touches_dirstate
787 787 )
788 788
789 789 @property
790 790 def touches_filelogs(self):
791 791 for a in self.upgrade_actions:
792 792 # in optimisations, we re-process the revlogs again
793 793 if a.type == OPTIMISATION:
794 794 return True
795 795 elif a.touches_filelogs:
796 796 return True
797 797 for a in self.removed_actions:
798 798 if a.touches_filelogs:
799 799 return True
800 800 return False
801 801
802 802 @property
803 803 def touches_manifests(self):
804 804 for a in self.upgrade_actions:
805 805 # in optimisations, we re-process the revlogs again
806 806 if a.type == OPTIMISATION:
807 807 return True
808 808 elif a.touches_manifests:
809 809 return True
810 810 for a in self.removed_actions:
811 811 if a.touches_manifests:
812 812 return True
813 813 return False
814 814
815 815 @property
816 816 def touches_changelog(self):
817 817 for a in self.upgrade_actions:
818 818 # in optimisations, we re-process the revlogs again
819 819 if a.type == OPTIMISATION:
820 820 return True
821 821 elif a.touches_changelog:
822 822 return True
823 823 for a in self.removed_actions:
824 824 if a.touches_changelog:
825 825 return True
826 826 return False
827 827
828 828 @property
829 829 def touches_requirements(self):
830 830 for a in self.upgrade_actions:
831 831 # optimisations are used to re-process revlogs and does not result
832 832 # in a requirement being added or removed
833 833 if a.type == OPTIMISATION:
834 834 pass
835 835 elif a.touches_requirements:
836 836 return True
837 837 for a in self.removed_actions:
838 838 if a.touches_requirements:
839 839 return True
840 840
841 841 @property
842 842 def touches_dirstate(self):
843 843 for a in self.upgrade_actions:
844 844 # revlog optimisations do not affect the dirstate
845 845 if a.type == OPTIMISATION:
846 846 pass
847 847 elif a.touches_dirstate:
848 848 return True
849 849 for a in self.removed_actions:
850 850 if a.touches_dirstate:
851 851 return True
852 852
853 853 return False
854 854
855 def _write_labeled(self, l, label):
855 def _write_labeled(self, l, label: bytes):
856 856 """
857 857 Utility function to aid writing of a list under one label
858 858 """
859 859 first = True
860 860 for r in sorted(l):
861 861 if not first:
862 862 self.ui.write(b', ')
863 863 self.ui.write(r, label=label)
864 864 first = False
865 865
866 866 def print_requirements(self):
867 867 self.ui.write(_(b'requirements\n'))
868 868 self.ui.write(_(b' preserved: '))
869 869 self._write_labeled(
870 self._preserved_requirements, "upgrade-repo.requirement.preserved"
870 self._preserved_requirements, b"upgrade-repo.requirement.preserved"
871 871 )
872 872 self.ui.write((b'\n'))
873 873 if self._removed_requirements:
874 874 self.ui.write(_(b' removed: '))
875 875 self._write_labeled(
876 self._removed_requirements, "upgrade-repo.requirement.removed"
876 self._removed_requirements, b"upgrade-repo.requirement.removed"
877 877 )
878 878 self.ui.write((b'\n'))
879 879 if self._added_requirements:
880 880 self.ui.write(_(b' added: '))
881 881 self._write_labeled(
882 self._added_requirements, "upgrade-repo.requirement.added"
882 self._added_requirements, b"upgrade-repo.requirement.added"
883 883 )
884 884 self.ui.write((b'\n'))
885 885 self.ui.write(b'\n')
886 886
887 887 def print_optimisations(self):
888 888 optimisations = [
889 889 a for a in self.upgrade_actions if a.type == OPTIMISATION
890 890 ]
891 891 optimisations.sort(key=lambda a: a.name)
892 892 if optimisations:
893 893 self.ui.write(_(b'optimisations: '))
894 894 self._write_labeled(
895 895 [a.name for a in optimisations],
896 "upgrade-repo.optimisation.performed",
896 b"upgrade-repo.optimisation.performed",
897 897 )
898 898 self.ui.write(b'\n\n')
899 899
900 900 def print_upgrade_actions(self):
901 901 for a in self.upgrade_actions:
902 902 self.ui.status(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
903 903
904 904 def print_affected_revlogs(self):
905 905 if not self.revlogs_to_process:
906 906 self.ui.write((b'no revlogs to process\n'))
907 907 else:
908 908 self.ui.write((b'processed revlogs:\n'))
909 909 for r in sorted(self.revlogs_to_process):
910 910 self.ui.write((b' - %s\n' % r))
911 911 self.ui.write((b'\n'))
912 912
913 913 def print_unused_optimizations(self):
914 914 for i in self.unused_optimizations:
915 915 self.ui.status(_(b'%s\n %s\n\n') % (i.name, i.description))
916 916
917 917 def has_upgrade_action(self, name):
918 918 """Check whether the upgrade operation will perform this action"""
919 919 return name in self._upgrade_actions_names
920 920
921 921 def print_post_op_messages(self):
922 922 """print post upgrade operation warning messages"""
923 923 for a in self.upgrade_actions:
924 924 if a.postupgrademessage is not None:
925 925 self.ui.warn(b'%s\n' % a.postupgrademessage)
926 926 for a in self.removed_actions:
927 927 if a.postdowngrademessage is not None:
928 928 self.ui.warn(b'%s\n' % a.postdowngrademessage)
929 929
930 930
931 931 ### Code checking if a repository can got through the upgrade process at all. #
932 932
933 933
934 934 def requiredsourcerequirements(repo):
935 935 """Obtain requirements required to be present to upgrade a repo.
936 936
937 937 An upgrade will not be allowed if the repository doesn't have the
938 938 requirements returned by this function.
939 939 """
940 940 return {
941 941 # Introduced in Mercurial 0.9.2.
942 942 requirements.STORE_REQUIREMENT,
943 943 }
944 944
945 945
946 946 def blocksourcerequirements(repo):
947 947 """Obtain requirements that will prevent an upgrade from occurring.
948 948
949 949 An upgrade cannot be performed if the source repository contains a
950 950 requirements in the returned set.
951 951 """
952 952 return {
953 953 # The upgrade code does not yet support these experimental features.
954 954 # This is an artificial limitation.
955 955 requirements.TREEMANIFEST_REQUIREMENT,
956 956 # This was a precursor to generaldelta and was never enabled by default.
957 957 # It should (hopefully) not exist in the wild.
958 958 b'parentdelta',
959 959 }
960 960
961 961
962 962 def check_revlog_version(reqs):
963 963 """Check that the requirements contain at least one Revlog version"""
964 964 all_revlogs = {
965 965 requirements.REVLOGV1_REQUIREMENT,
966 966 requirements.REVLOGV2_REQUIREMENT,
967 967 }
968 968 if not all_revlogs.intersection(reqs):
969 969 msg = _(b'cannot upgrade repository; missing a revlog version')
970 970 raise error.Abort(msg)
971 971
972 972
973 973 def check_source_requirements(repo):
974 974 """Ensure that no existing requirements prevent the repository upgrade"""
975 975
976 976 check_revlog_version(repo.requirements)
977 977 required = requiredsourcerequirements(repo)
978 978 missingreqs = required - repo.requirements
979 979 if missingreqs:
980 980 msg = _(b'cannot upgrade repository; requirement missing: %s')
981 981 missingreqs = b', '.join(sorted(missingreqs))
982 982 raise error.Abort(msg % missingreqs)
983 983
984 984 blocking = blocksourcerequirements(repo)
985 985 blockingreqs = blocking & repo.requirements
986 986 if blockingreqs:
987 987 m = _(b'cannot upgrade repository; unsupported source requirement: %s')
988 988 blockingreqs = b', '.join(sorted(blockingreqs))
989 989 raise error.Abort(m % blockingreqs)
990 990 # Upgrade should operate on the actual store, not the shared link.
991 991
992 992 bad_share = (
993 993 requirements.SHARED_REQUIREMENT in repo.requirements
994 994 and requirements.SHARESAFE_REQUIREMENT not in repo.requirements
995 995 )
996 996 if bad_share:
997 997 m = _(b'cannot upgrade repository; share repository without share-safe')
998 998 h = _(b'check :hg:`help config.format.use-share-safe`')
999 999 raise error.Abort(m, hint=h)
1000 1000
1001 1001
1002 1002 ### Verify the validity of the planned requirement changes ####################
1003 1003
1004 1004
1005 1005 def supportremovedrequirements(repo):
1006 1006 """Obtain requirements that can be removed during an upgrade.
1007 1007
1008 1008 If an upgrade were to create a repository that dropped a requirement,
1009 1009 the dropped requirement must appear in the returned set for the upgrade
1010 1010 to be allowed.
1011 1011 """
1012 1012 supported = {
1013 1013 requirements.SPARSEREVLOG_REQUIREMENT,
1014 1014 requirements.COPIESSDC_REQUIREMENT,
1015 1015 requirements.NODEMAP_REQUIREMENT,
1016 1016 requirements.SHARESAFE_REQUIREMENT,
1017 1017 requirements.REVLOGV2_REQUIREMENT,
1018 1018 requirements.CHANGELOGV2_REQUIREMENT,
1019 1019 requirements.REVLOGV1_REQUIREMENT,
1020 1020 requirements.DIRSTATE_TRACKED_HINT_V1,
1021 1021 requirements.DIRSTATE_V2_REQUIREMENT,
1022 1022 }
1023 1023 for name in compression.compengines:
1024 1024 engine = compression.compengines[name]
1025 1025 if engine.available() and engine.revlogheader():
1026 1026 supported.add(b'exp-compression-%s' % name)
1027 1027 if engine.name() == b'zstd':
1028 1028 supported.add(b'revlog-compression-zstd')
1029 1029 return supported
1030 1030
1031 1031
1032 1032 def supporteddestrequirements(repo):
1033 1033 """Obtain requirements that upgrade supports in the destination.
1034 1034
1035 1035 If the result of the upgrade would have requirements not in this set,
1036 1036 the upgrade is disallowed.
1037 1037
1038 1038 Extensions should monkeypatch this to add their custom requirements.
1039 1039 """
1040 1040 supported = {
1041 1041 requirements.CHANGELOGV2_REQUIREMENT,
1042 1042 requirements.COPIESSDC_REQUIREMENT,
1043 1043 requirements.DIRSTATE_TRACKED_HINT_V1,
1044 1044 requirements.DIRSTATE_V2_REQUIREMENT,
1045 1045 requirements.DOTENCODE_REQUIREMENT,
1046 1046 requirements.FNCACHE_REQUIREMENT,
1047 1047 requirements.GENERALDELTA_REQUIREMENT,
1048 1048 requirements.NODEMAP_REQUIREMENT,
1049 1049 requirements.REVLOGV1_REQUIREMENT, # allowed in case of downgrade
1050 1050 requirements.REVLOGV2_REQUIREMENT,
1051 1051 requirements.SHARED_REQUIREMENT,
1052 1052 requirements.SHARESAFE_REQUIREMENT,
1053 1053 requirements.SPARSEREVLOG_REQUIREMENT,
1054 1054 requirements.STORE_REQUIREMENT,
1055 1055 requirements.NARROW_REQUIREMENT,
1056 1056 }
1057 1057 for name in compression.compengines:
1058 1058 engine = compression.compengines[name]
1059 1059 if engine.available() and engine.revlogheader():
1060 1060 supported.add(b'exp-compression-%s' % name)
1061 1061 if engine.name() == b'zstd':
1062 1062 supported.add(b'revlog-compression-zstd')
1063 1063 return supported
1064 1064
1065 1065
1066 1066 def allowednewrequirements(repo):
1067 1067 """Obtain requirements that can be added to a repository during upgrade.
1068 1068
1069 1069 This is used to disallow proposed requirements from being added when
1070 1070 they weren't present before.
1071 1071
1072 1072 We use a list of allowed requirement additions instead of a list of known
1073 1073 bad additions because the whitelist approach is safer and will prevent
1074 1074 future, unknown requirements from accidentally being added.
1075 1075 """
1076 1076 supported = {
1077 1077 requirements.DOTENCODE_REQUIREMENT,
1078 1078 requirements.FNCACHE_REQUIREMENT,
1079 1079 requirements.GENERALDELTA_REQUIREMENT,
1080 1080 requirements.SPARSEREVLOG_REQUIREMENT,
1081 1081 requirements.COPIESSDC_REQUIREMENT,
1082 1082 requirements.NODEMAP_REQUIREMENT,
1083 1083 requirements.SHARESAFE_REQUIREMENT,
1084 1084 requirements.REVLOGV1_REQUIREMENT,
1085 1085 requirements.REVLOGV2_REQUIREMENT,
1086 1086 requirements.CHANGELOGV2_REQUIREMENT,
1087 1087 requirements.DIRSTATE_TRACKED_HINT_V1,
1088 1088 requirements.DIRSTATE_V2_REQUIREMENT,
1089 1089 }
1090 1090 for name in compression.compengines:
1091 1091 engine = compression.compengines[name]
1092 1092 if engine.available() and engine.revlogheader():
1093 1093 supported.add(b'exp-compression-%s' % name)
1094 1094 if engine.name() == b'zstd':
1095 1095 supported.add(b'revlog-compression-zstd')
1096 1096 return supported
1097 1097
1098 1098
1099 1099 def check_requirements_changes(repo, new_reqs):
1100 1100 old_reqs = repo.requirements
1101 1101 check_revlog_version(repo.requirements)
1102 1102 support_removal = supportremovedrequirements(repo)
1103 1103 no_remove_reqs = old_reqs - new_reqs - support_removal
1104 1104 if no_remove_reqs:
1105 1105 msg = _(b'cannot upgrade repository; requirement would be removed: %s')
1106 1106 no_remove_reqs = b', '.join(sorted(no_remove_reqs))
1107 1107 raise error.Abort(msg % no_remove_reqs)
1108 1108
1109 1109 support_addition = allowednewrequirements(repo)
1110 1110 no_add_reqs = new_reqs - old_reqs - support_addition
1111 1111 if no_add_reqs:
1112 1112 m = _(b'cannot upgrade repository; do not support adding requirement: ')
1113 1113 no_add_reqs = b', '.join(sorted(no_add_reqs))
1114 1114 raise error.Abort(m + no_add_reqs)
1115 1115
1116 1116 supported = supporteddestrequirements(repo)
1117 1117 unsupported_reqs = new_reqs - supported
1118 1118 if unsupported_reqs:
1119 1119 msg = _(
1120 1120 b'cannot upgrade repository; do not support destination '
1121 1121 b'requirement: %s'
1122 1122 )
1123 1123 unsupported_reqs = b', '.join(sorted(unsupported_reqs))
1124 1124 raise error.Abort(msg % unsupported_reqs)
General Comments 0
You need to be logged in to leave comments. Login now