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