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