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