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