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