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