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