##// END OF EJS Templates
upgrade: support running upgrade if repository has share-safe requirement...
Pulkit Goyal -
r46059:78f0bb37 default
parent child Browse files
Show More
@@ -1,1432 +1,1433 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 import stat
11 11
12 12 from .i18n import _
13 13 from .pycompat import getattr
14 14 from . import (
15 15 changelog,
16 16 error,
17 17 filelog,
18 18 hg,
19 19 localrepo,
20 20 manifest,
21 21 metadata,
22 22 pycompat,
23 23 requirements,
24 24 revlog,
25 25 scmutil,
26 26 util,
27 27 vfs as vfsmod,
28 28 )
29 29
30 30 from .utils import compression
31 31
32 32 # list of requirements that request a clone of all revlog if added/removed
33 33 RECLONES_REQUIREMENTS = {
34 34 b'generaldelta',
35 35 requirements.SPARSEREVLOG_REQUIREMENT,
36 36 }
37 37
38 38
39 39 def requiredsourcerequirements(repo):
40 40 """Obtain requirements required to be present to upgrade a repo.
41 41
42 42 An upgrade will not be allowed if the repository doesn't have the
43 43 requirements returned by this function.
44 44 """
45 45 return {
46 46 # Introduced in Mercurial 0.9.2.
47 47 b'revlogv1',
48 48 # Introduced in Mercurial 0.9.2.
49 49 b'store',
50 50 }
51 51
52 52
53 53 def blocksourcerequirements(repo):
54 54 """Obtain requirements that will prevent an upgrade from occurring.
55 55
56 56 An upgrade cannot be performed if the source repository contains a
57 57 requirements in the returned set.
58 58 """
59 59 return {
60 60 # The upgrade code does not yet support these experimental features.
61 61 # This is an artificial limitation.
62 62 requirements.TREEMANIFEST_REQUIREMENT,
63 63 # This was a precursor to generaldelta and was never enabled by default.
64 64 # It should (hopefully) not exist in the wild.
65 65 b'parentdelta',
66 66 # Upgrade should operate on the actual store, not the shared link.
67 67 requirements.SHARED_REQUIREMENT,
68 68 }
69 69
70 70
71 71 def supportremovedrequirements(repo):
72 72 """Obtain requirements that can be removed during an upgrade.
73 73
74 74 If an upgrade were to create a repository that dropped a requirement,
75 75 the dropped requirement must appear in the returned set for the upgrade
76 76 to be allowed.
77 77 """
78 78 supported = {
79 79 requirements.SPARSEREVLOG_REQUIREMENT,
80 80 requirements.SIDEDATA_REQUIREMENT,
81 81 requirements.COPIESSDC_REQUIREMENT,
82 82 requirements.NODEMAP_REQUIREMENT,
83 83 }
84 84 for name in compression.compengines:
85 85 engine = compression.compengines[name]
86 86 if engine.available() and engine.revlogheader():
87 87 supported.add(b'exp-compression-%s' % name)
88 88 if engine.name() == b'zstd':
89 89 supported.add(b'revlog-compression-zstd')
90 90 return supported
91 91
92 92
93 93 def supporteddestrequirements(repo):
94 94 """Obtain requirements that upgrade supports in the destination.
95 95
96 96 If the result of the upgrade would create requirements not in this set,
97 97 the upgrade is disallowed.
98 98
99 99 Extensions should monkeypatch this to add their custom requirements.
100 100 """
101 101 supported = {
102 102 b'dotencode',
103 103 b'fncache',
104 104 b'generaldelta',
105 105 b'revlogv1',
106 106 b'store',
107 107 requirements.SPARSEREVLOG_REQUIREMENT,
108 108 requirements.SIDEDATA_REQUIREMENT,
109 109 requirements.COPIESSDC_REQUIREMENT,
110 110 requirements.NODEMAP_REQUIREMENT,
111 requirements.SHARESAFE_REQUIREMENT,
111 112 }
112 113 for name in compression.compengines:
113 114 engine = compression.compengines[name]
114 115 if engine.available() and engine.revlogheader():
115 116 supported.add(b'exp-compression-%s' % name)
116 117 if engine.name() == b'zstd':
117 118 supported.add(b'revlog-compression-zstd')
118 119 return supported
119 120
120 121
121 122 def allowednewrequirements(repo):
122 123 """Obtain requirements that can be added to a repository during upgrade.
123 124
124 125 This is used to disallow proposed requirements from being added when
125 126 they weren't present before.
126 127
127 128 We use a list of allowed requirement additions instead of a list of known
128 129 bad additions because the whitelist approach is safer and will prevent
129 130 future, unknown requirements from accidentally being added.
130 131 """
131 132 supported = {
132 133 b'dotencode',
133 134 b'fncache',
134 135 b'generaldelta',
135 136 requirements.SPARSEREVLOG_REQUIREMENT,
136 137 requirements.SIDEDATA_REQUIREMENT,
137 138 requirements.COPIESSDC_REQUIREMENT,
138 139 requirements.NODEMAP_REQUIREMENT,
139 140 }
140 141 for name in compression.compengines:
141 142 engine = compression.compengines[name]
142 143 if engine.available() and engine.revlogheader():
143 144 supported.add(b'exp-compression-%s' % name)
144 145 if engine.name() == b'zstd':
145 146 supported.add(b'revlog-compression-zstd')
146 147 return supported
147 148
148 149
149 150 def preservedrequirements(repo):
150 151 return set()
151 152
152 153
153 154 deficiency = b'deficiency'
154 155 optimisation = b'optimization'
155 156
156 157
157 158 class improvement(object):
158 159 """Represents an improvement that can be made as part of an upgrade.
159 160
160 161 The following attributes are defined on each instance:
161 162
162 163 name
163 164 Machine-readable string uniquely identifying this improvement. It
164 165 will be mapped to an action later in the upgrade process.
165 166
166 167 type
167 168 Either ``deficiency`` or ``optimisation``. A deficiency is an obvious
168 169 problem. An optimization is an action (sometimes optional) that
169 170 can be taken to further improve the state of the repository.
170 171
171 172 description
172 173 Message intended for humans explaining the improvement in more detail,
173 174 including the implications of it. For ``deficiency`` types, should be
174 175 worded in the present tense. For ``optimisation`` types, should be
175 176 worded in the future tense.
176 177
177 178 upgrademessage
178 179 Message intended for humans explaining what an upgrade addressing this
179 180 issue will do. Should be worded in the future tense.
180 181 """
181 182
182 183 def __init__(self, name, type, description, upgrademessage):
183 184 self.name = name
184 185 self.type = type
185 186 self.description = description
186 187 self.upgrademessage = upgrademessage
187 188
188 189 def __eq__(self, other):
189 190 if not isinstance(other, improvement):
190 191 # This is what python tell use to do
191 192 return NotImplemented
192 193 return self.name == other.name
193 194
194 195 def __ne__(self, other):
195 196 return not (self == other)
196 197
197 198 def __hash__(self):
198 199 return hash(self.name)
199 200
200 201
201 202 allformatvariant = []
202 203
203 204
204 205 def registerformatvariant(cls):
205 206 allformatvariant.append(cls)
206 207 return cls
207 208
208 209
209 210 class formatvariant(improvement):
210 211 """an improvement subclass dedicated to repository format"""
211 212
212 213 type = deficiency
213 214 ### The following attributes should be defined for each class:
214 215
215 216 # machine-readable string uniquely identifying this improvement. it will be
216 217 # mapped to an action later in the upgrade process.
217 218 name = None
218 219
219 220 # message intended for humans explaining the improvement in more detail,
220 221 # including the implications of it ``deficiency`` types, should be worded
221 222 # in the present tense.
222 223 description = None
223 224
224 225 # message intended for humans explaining what an upgrade addressing this
225 226 # issue will do. should be worded in the future tense.
226 227 upgrademessage = None
227 228
228 229 # value of current Mercurial default for new repository
229 230 default = None
230 231
231 232 def __init__(self):
232 233 raise NotImplementedError()
233 234
234 235 @staticmethod
235 236 def fromrepo(repo):
236 237 """current value of the variant in the repository"""
237 238 raise NotImplementedError()
238 239
239 240 @staticmethod
240 241 def fromconfig(repo):
241 242 """current value of the variant in the configuration"""
242 243 raise NotImplementedError()
243 244
244 245
245 246 class requirementformatvariant(formatvariant):
246 247 """formatvariant based on a 'requirement' name.
247 248
248 249 Many format variant are controlled by a 'requirement'. We define a small
249 250 subclass to factor the code.
250 251 """
251 252
252 253 # the requirement that control this format variant
253 254 _requirement = None
254 255
255 256 @staticmethod
256 257 def _newreporequirements(ui):
257 258 return localrepo.newreporequirements(
258 259 ui, localrepo.defaultcreateopts(ui)
259 260 )
260 261
261 262 @classmethod
262 263 def fromrepo(cls, repo):
263 264 assert cls._requirement is not None
264 265 return cls._requirement in repo.requirements
265 266
266 267 @classmethod
267 268 def fromconfig(cls, repo):
268 269 assert cls._requirement is not None
269 270 return cls._requirement in cls._newreporequirements(repo.ui)
270 271
271 272
272 273 @registerformatvariant
273 274 class fncache(requirementformatvariant):
274 275 name = b'fncache'
275 276
276 277 _requirement = b'fncache'
277 278
278 279 default = True
279 280
280 281 description = _(
281 282 b'long and reserved filenames may not work correctly; '
282 283 b'repository performance is sub-optimal'
283 284 )
284 285
285 286 upgrademessage = _(
286 287 b'repository will be more resilient to storing '
287 288 b'certain paths and performance of certain '
288 289 b'operations should be improved'
289 290 )
290 291
291 292
292 293 @registerformatvariant
293 294 class dotencode(requirementformatvariant):
294 295 name = b'dotencode'
295 296
296 297 _requirement = b'dotencode'
297 298
298 299 default = True
299 300
300 301 description = _(
301 302 b'storage of filenames beginning with a period or '
302 303 b'space may not work correctly'
303 304 )
304 305
305 306 upgrademessage = _(
306 307 b'repository will be better able to store files '
307 308 b'beginning with a space or period'
308 309 )
309 310
310 311
311 312 @registerformatvariant
312 313 class generaldelta(requirementformatvariant):
313 314 name = b'generaldelta'
314 315
315 316 _requirement = b'generaldelta'
316 317
317 318 default = True
318 319
319 320 description = _(
320 321 b'deltas within internal storage are unable to '
321 322 b'choose optimal revisions; repository is larger and '
322 323 b'slower than it could be; interaction with other '
323 324 b'repositories may require extra network and CPU '
324 325 b'resources, making "hg push" and "hg pull" slower'
325 326 )
326 327
327 328 upgrademessage = _(
328 329 b'repository storage will be able to create '
329 330 b'optimal deltas; new repository data will be '
330 331 b'smaller and read times should decrease; '
331 332 b'interacting with other repositories using this '
332 333 b'storage model should require less network and '
333 334 b'CPU resources, making "hg push" and "hg pull" '
334 335 b'faster'
335 336 )
336 337
337 338
338 339 @registerformatvariant
339 340 class sparserevlog(requirementformatvariant):
340 341 name = b'sparserevlog'
341 342
342 343 _requirement = requirements.SPARSEREVLOG_REQUIREMENT
343 344
344 345 default = True
345 346
346 347 description = _(
347 348 b'in order to limit disk reading and memory usage on older '
348 349 b'version, the span of a delta chain from its root to its '
349 350 b'end is limited, whatever the relevant data in this span. '
350 351 b'This can severly limit Mercurial ability to build good '
351 352 b'chain of delta resulting is much more storage space being '
352 353 b'taken and limit reusability of on disk delta during '
353 354 b'exchange.'
354 355 )
355 356
356 357 upgrademessage = _(
357 358 b'Revlog supports delta chain with more unused data '
358 359 b'between payload. These gaps will be skipped at read '
359 360 b'time. This allows for better delta chains, making a '
360 361 b'better compression and faster exchange with server.'
361 362 )
362 363
363 364
364 365 @registerformatvariant
365 366 class sidedata(requirementformatvariant):
366 367 name = b'sidedata'
367 368
368 369 _requirement = requirements.SIDEDATA_REQUIREMENT
369 370
370 371 default = False
371 372
372 373 description = _(
373 374 b'Allows storage of extra data alongside a revision, '
374 375 b'unlocking various caching options.'
375 376 )
376 377
377 378 upgrademessage = _(b'Allows storage of extra data alongside a revision.')
378 379
379 380
380 381 @registerformatvariant
381 382 class persistentnodemap(requirementformatvariant):
382 383 name = b'persistent-nodemap'
383 384
384 385 _requirement = requirements.NODEMAP_REQUIREMENT
385 386
386 387 default = False
387 388
388 389 description = _(
389 390 b'persist the node -> rev mapping on disk to speedup lookup'
390 391 )
391 392
392 393 upgrademessage = _(b'Speedup revision lookup by node id.')
393 394
394 395
395 396 @registerformatvariant
396 397 class copiessdc(requirementformatvariant):
397 398 name = b'copies-sdc'
398 399
399 400 _requirement = requirements.COPIESSDC_REQUIREMENT
400 401
401 402 default = False
402 403
403 404 description = _(b'Stores copies information alongside changesets.')
404 405
405 406 upgrademessage = _(
406 407 b'Allows to use more efficient algorithm to deal with ' b'copy tracing.'
407 408 )
408 409
409 410
410 411 @registerformatvariant
411 412 class removecldeltachain(formatvariant):
412 413 name = b'plain-cl-delta'
413 414
414 415 default = True
415 416
416 417 description = _(
417 418 b'changelog storage is using deltas instead of '
418 419 b'raw entries; changelog reading and any '
419 420 b'operation relying on changelog data are slower '
420 421 b'than they could be'
421 422 )
422 423
423 424 upgrademessage = _(
424 425 b'changelog storage will be reformated to '
425 426 b'store raw entries; changelog reading will be '
426 427 b'faster; changelog size may be reduced'
427 428 )
428 429
429 430 @staticmethod
430 431 def fromrepo(repo):
431 432 # Mercurial 4.0 changed changelogs to not use delta chains. Search for
432 433 # changelogs with deltas.
433 434 cl = repo.changelog
434 435 chainbase = cl.chainbase
435 436 return all(rev == chainbase(rev) for rev in cl)
436 437
437 438 @staticmethod
438 439 def fromconfig(repo):
439 440 return True
440 441
441 442
442 443 @registerformatvariant
443 444 class compressionengine(formatvariant):
444 445 name = b'compression'
445 446 default = b'zlib'
446 447
447 448 description = _(
448 449 b'Compresion algorithm used to compress data. '
449 450 b'Some engine are faster than other'
450 451 )
451 452
452 453 upgrademessage = _(
453 454 b'revlog content will be recompressed with the new algorithm.'
454 455 )
455 456
456 457 @classmethod
457 458 def fromrepo(cls, repo):
458 459 # we allow multiple compression engine requirement to co-exist because
459 460 # strickly speaking, revlog seems to support mixed compression style.
460 461 #
461 462 # The compression used for new entries will be "the last one"
462 463 compression = b'zlib'
463 464 for req in repo.requirements:
464 465 prefix = req.startswith
465 466 if prefix(b'revlog-compression-') or prefix(b'exp-compression-'):
466 467 compression = req.split(b'-', 2)[2]
467 468 return compression
468 469
469 470 @classmethod
470 471 def fromconfig(cls, repo):
471 472 compengines = repo.ui.configlist(b'format', b'revlog-compression')
472 473 # return the first valid value as the selection code would do
473 474 for comp in compengines:
474 475 if comp in util.compengines:
475 476 return comp
476 477
477 478 # no valide compression found lets display it all for clarity
478 479 return b','.join(compengines)
479 480
480 481
481 482 @registerformatvariant
482 483 class compressionlevel(formatvariant):
483 484 name = b'compression-level'
484 485 default = b'default'
485 486
486 487 description = _(b'compression level')
487 488
488 489 upgrademessage = _(b'revlog content will be recompressed')
489 490
490 491 @classmethod
491 492 def fromrepo(cls, repo):
492 493 comp = compressionengine.fromrepo(repo)
493 494 level = None
494 495 if comp == b'zlib':
495 496 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
496 497 elif comp == b'zstd':
497 498 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
498 499 if level is None:
499 500 return b'default'
500 501 return bytes(level)
501 502
502 503 @classmethod
503 504 def fromconfig(cls, repo):
504 505 comp = compressionengine.fromconfig(repo)
505 506 level = None
506 507 if comp == b'zlib':
507 508 level = repo.ui.configint(b'storage', b'revlog.zlib.level')
508 509 elif comp == b'zstd':
509 510 level = repo.ui.configint(b'storage', b'revlog.zstd.level')
510 511 if level is None:
511 512 return b'default'
512 513 return bytes(level)
513 514
514 515
515 516 def finddeficiencies(repo):
516 517 """returns a list of deficiencies that the repo suffer from"""
517 518 deficiencies = []
518 519
519 520 # We could detect lack of revlogv1 and store here, but they were added
520 521 # in 0.9.2 and we don't support upgrading repos without these
521 522 # requirements, so let's not bother.
522 523
523 524 for fv in allformatvariant:
524 525 if not fv.fromrepo(repo):
525 526 deficiencies.append(fv)
526 527
527 528 return deficiencies
528 529
529 530
530 531 # search without '-' to support older form on newer client.
531 532 #
532 533 # We don't enforce backward compatibility for debug command so this
533 534 # might eventually be dropped. However, having to use two different
534 535 # forms in script when comparing result is anoying enough to add
535 536 # backward compatibility for a while.
536 537 legacy_opts_map = {
537 538 b'redeltaparent': b're-delta-parent',
538 539 b'redeltamultibase': b're-delta-multibase',
539 540 b'redeltaall': b're-delta-all',
540 541 b'redeltafulladd': b're-delta-fulladd',
541 542 }
542 543
543 544
544 545 def findoptimizations(repo):
545 546 """Determine optimisation that could be used during upgrade"""
546 547 # These are unconditionally added. There is logic later that figures out
547 548 # which ones to apply.
548 549 optimizations = []
549 550
550 551 optimizations.append(
551 552 improvement(
552 553 name=b're-delta-parent',
553 554 type=optimisation,
554 555 description=_(
555 556 b'deltas within internal storage will be recalculated to '
556 557 b'choose an optimal base revision where this was not '
557 558 b'already done; the size of the repository may shrink and '
558 559 b'various operations may become faster; the first time '
559 560 b'this optimization is performed could slow down upgrade '
560 561 b'execution considerably; subsequent invocations should '
561 562 b'not run noticeably slower'
562 563 ),
563 564 upgrademessage=_(
564 565 b'deltas within internal storage will choose a new '
565 566 b'base revision if needed'
566 567 ),
567 568 )
568 569 )
569 570
570 571 optimizations.append(
571 572 improvement(
572 573 name=b're-delta-multibase',
573 574 type=optimisation,
574 575 description=_(
575 576 b'deltas within internal storage will be recalculated '
576 577 b'against multiple base revision and the smallest '
577 578 b'difference will be used; the size of the repository may '
578 579 b'shrink significantly when there are many merges; this '
579 580 b'optimization will slow down execution in proportion to '
580 581 b'the number of merges in the repository and the amount '
581 582 b'of files in the repository; this slow down should not '
582 583 b'be significant unless there are tens of thousands of '
583 584 b'files and thousands of merges'
584 585 ),
585 586 upgrademessage=_(
586 587 b'deltas within internal storage will choose an '
587 588 b'optimal delta by computing deltas against multiple '
588 589 b'parents; may slow down execution time '
589 590 b'significantly'
590 591 ),
591 592 )
592 593 )
593 594
594 595 optimizations.append(
595 596 improvement(
596 597 name=b're-delta-all',
597 598 type=optimisation,
598 599 description=_(
599 600 b'deltas within internal storage will always be '
600 601 b'recalculated without reusing prior deltas; this will '
601 602 b'likely make execution run several times slower; this '
602 603 b'optimization is typically not needed'
603 604 ),
604 605 upgrademessage=_(
605 606 b'deltas within internal storage will be fully '
606 607 b'recomputed; this will likely drastically slow down '
607 608 b'execution time'
608 609 ),
609 610 )
610 611 )
611 612
612 613 optimizations.append(
613 614 improvement(
614 615 name=b're-delta-fulladd',
615 616 type=optimisation,
616 617 description=_(
617 618 b'every revision will be re-added as if it was new '
618 619 b'content. It will go through the full storage '
619 620 b'mechanism giving extensions a chance to process it '
620 621 b'(eg. lfs). This is similar to "re-delta-all" but even '
621 622 b'slower since more logic is involved.'
622 623 ),
623 624 upgrademessage=_(
624 625 b'each revision will be added as new content to the '
625 626 b'internal storage; this will likely drastically slow '
626 627 b'down execution time, but some extensions might need '
627 628 b'it'
628 629 ),
629 630 )
630 631 )
631 632
632 633 return optimizations
633 634
634 635
635 636 def determineactions(repo, deficiencies, sourcereqs, destreqs):
636 637 """Determine upgrade actions that will be performed.
637 638
638 639 Given a list of improvements as returned by ``finddeficiencies`` and
639 640 ``findoptimizations``, determine the list of upgrade actions that
640 641 will be performed.
641 642
642 643 The role of this function is to filter improvements if needed, apply
643 644 recommended optimizations from the improvements list that make sense,
644 645 etc.
645 646
646 647 Returns a list of action names.
647 648 """
648 649 newactions = []
649 650
650 651 for d in deficiencies:
651 652 name = d._requirement
652 653
653 654 # If the action is a requirement that doesn't show up in the
654 655 # destination requirements, prune the action.
655 656 if name is not None and name not in destreqs:
656 657 continue
657 658
658 659 newactions.append(d)
659 660
660 661 # FUTURE consider adding some optimizations here for certain transitions.
661 662 # e.g. adding generaldelta could schedule parent redeltas.
662 663
663 664 return newactions
664 665
665 666
666 667 def _revlogfrompath(repo, path):
667 668 """Obtain a revlog from a repo path.
668 669
669 670 An instance of the appropriate class is returned.
670 671 """
671 672 if path == b'00changelog.i':
672 673 return changelog.changelog(repo.svfs)
673 674 elif path.endswith(b'00manifest.i'):
674 675 mandir = path[: -len(b'00manifest.i')]
675 676 return manifest.manifestrevlog(repo.svfs, tree=mandir)
676 677 else:
677 678 # reverse of "/".join(("data", path + ".i"))
678 679 return filelog.filelog(repo.svfs, path[5:-2])
679 680
680 681
681 682 def _copyrevlog(tr, destrepo, oldrl, unencodedname):
682 683 """copy all relevant files for `oldrl` into `destrepo` store
683 684
684 685 Files are copied "as is" without any transformation. The copy is performed
685 686 without extra checks. Callers are responsible for making sure the copied
686 687 content is compatible with format of the destination repository.
687 688 """
688 689 oldrl = getattr(oldrl, '_revlog', oldrl)
689 690 newrl = _revlogfrompath(destrepo, unencodedname)
690 691 newrl = getattr(newrl, '_revlog', newrl)
691 692
692 693 oldvfs = oldrl.opener
693 694 newvfs = newrl.opener
694 695 oldindex = oldvfs.join(oldrl.indexfile)
695 696 newindex = newvfs.join(newrl.indexfile)
696 697 olddata = oldvfs.join(oldrl.datafile)
697 698 newdata = newvfs.join(newrl.datafile)
698 699
699 700 with newvfs(newrl.indexfile, b'w'):
700 701 pass # create all the directories
701 702
702 703 util.copyfile(oldindex, newindex)
703 704 copydata = oldrl.opener.exists(oldrl.datafile)
704 705 if copydata:
705 706 util.copyfile(olddata, newdata)
706 707
707 708 if not (
708 709 unencodedname.endswith(b'00changelog.i')
709 710 or unencodedname.endswith(b'00manifest.i')
710 711 ):
711 712 destrepo.svfs.fncache.add(unencodedname)
712 713 if copydata:
713 714 destrepo.svfs.fncache.add(unencodedname[:-2] + b'.d')
714 715
715 716
716 717 UPGRADE_CHANGELOG = object()
717 718 UPGRADE_MANIFEST = object()
718 719 UPGRADE_FILELOG = object()
719 720
720 721 UPGRADE_ALL_REVLOGS = frozenset(
721 722 [UPGRADE_CHANGELOG, UPGRADE_MANIFEST, UPGRADE_FILELOG]
722 723 )
723 724
724 725
725 726 def getsidedatacompanion(srcrepo, dstrepo):
726 727 sidedatacompanion = None
727 728 removedreqs = srcrepo.requirements - dstrepo.requirements
728 729 addedreqs = dstrepo.requirements - srcrepo.requirements
729 730 if requirements.SIDEDATA_REQUIREMENT in removedreqs:
730 731
731 732 def sidedatacompanion(rl, rev):
732 733 rl = getattr(rl, '_revlog', rl)
733 734 if rl.flags(rev) & revlog.REVIDX_SIDEDATA:
734 735 return True, (), {}
735 736 return False, (), {}
736 737
737 738 elif requirements.COPIESSDC_REQUIREMENT in addedreqs:
738 739 sidedatacompanion = metadata.getsidedataadder(srcrepo, dstrepo)
739 740 elif requirements.COPIESSDC_REQUIREMENT in removedreqs:
740 741 sidedatacompanion = metadata.getsidedataremover(srcrepo, dstrepo)
741 742 return sidedatacompanion
742 743
743 744
744 745 def matchrevlog(revlogfilter, entry):
745 746 """check is a revlog is selected for cloning
746 747
747 748 The store entry is checked against the passed filter"""
748 749 if entry.endswith(b'00changelog.i'):
749 750 return UPGRADE_CHANGELOG in revlogfilter
750 751 elif entry.endswith(b'00manifest.i'):
751 752 return UPGRADE_MANIFEST in revlogfilter
752 753 return UPGRADE_FILELOG in revlogfilter
753 754
754 755
755 756 def _clonerevlogs(
756 757 ui,
757 758 srcrepo,
758 759 dstrepo,
759 760 tr,
760 761 deltareuse,
761 762 forcedeltabothparents,
762 763 revlogs=UPGRADE_ALL_REVLOGS,
763 764 ):
764 765 """Copy revlogs between 2 repos."""
765 766 revcount = 0
766 767 srcsize = 0
767 768 srcrawsize = 0
768 769 dstsize = 0
769 770 fcount = 0
770 771 frevcount = 0
771 772 fsrcsize = 0
772 773 frawsize = 0
773 774 fdstsize = 0
774 775 mcount = 0
775 776 mrevcount = 0
776 777 msrcsize = 0
777 778 mrawsize = 0
778 779 mdstsize = 0
779 780 crevcount = 0
780 781 csrcsize = 0
781 782 crawsize = 0
782 783 cdstsize = 0
783 784
784 785 alldatafiles = list(srcrepo.store.walk())
785 786
786 787 # Perform a pass to collect metadata. This validates we can open all
787 788 # source files and allows a unified progress bar to be displayed.
788 789 for unencoded, encoded, size in alldatafiles:
789 790 if unencoded.endswith(b'.d'):
790 791 continue
791 792
792 793 rl = _revlogfrompath(srcrepo, unencoded)
793 794
794 795 info = rl.storageinfo(
795 796 exclusivefiles=True,
796 797 revisionscount=True,
797 798 trackedsize=True,
798 799 storedsize=True,
799 800 )
800 801
801 802 revcount += info[b'revisionscount'] or 0
802 803 datasize = info[b'storedsize'] or 0
803 804 rawsize = info[b'trackedsize'] or 0
804 805
805 806 srcsize += datasize
806 807 srcrawsize += rawsize
807 808
808 809 # This is for the separate progress bars.
809 810 if isinstance(rl, changelog.changelog):
810 811 crevcount += len(rl)
811 812 csrcsize += datasize
812 813 crawsize += rawsize
813 814 elif isinstance(rl, manifest.manifestrevlog):
814 815 mcount += 1
815 816 mrevcount += len(rl)
816 817 msrcsize += datasize
817 818 mrawsize += rawsize
818 819 elif isinstance(rl, filelog.filelog):
819 820 fcount += 1
820 821 frevcount += len(rl)
821 822 fsrcsize += datasize
822 823 frawsize += rawsize
823 824 else:
824 825 error.ProgrammingError(b'unknown revlog type')
825 826
826 827 if not revcount:
827 828 return
828 829
829 830 ui.status(
830 831 _(
831 832 b'migrating %d total revisions (%d in filelogs, %d in manifests, '
832 833 b'%d in changelog)\n'
833 834 )
834 835 % (revcount, frevcount, mrevcount, crevcount)
835 836 )
836 837 ui.status(
837 838 _(b'migrating %s in store; %s tracked data\n')
838 839 % ((util.bytecount(srcsize), util.bytecount(srcrawsize)))
839 840 )
840 841
841 842 # Used to keep track of progress.
842 843 progress = None
843 844
844 845 def oncopiedrevision(rl, rev, node):
845 846 progress.increment()
846 847
847 848 sidedatacompanion = getsidedatacompanion(srcrepo, dstrepo)
848 849
849 850 # Do the actual copying.
850 851 # FUTURE this operation can be farmed off to worker processes.
851 852 seen = set()
852 853 for unencoded, encoded, size in alldatafiles:
853 854 if unencoded.endswith(b'.d'):
854 855 continue
855 856
856 857 oldrl = _revlogfrompath(srcrepo, unencoded)
857 858
858 859 if isinstance(oldrl, changelog.changelog) and b'c' not in seen:
859 860 ui.status(
860 861 _(
861 862 b'finished migrating %d manifest revisions across %d '
862 863 b'manifests; change in size: %s\n'
863 864 )
864 865 % (mrevcount, mcount, util.bytecount(mdstsize - msrcsize))
865 866 )
866 867
867 868 ui.status(
868 869 _(
869 870 b'migrating changelog containing %d revisions '
870 871 b'(%s in store; %s tracked data)\n'
871 872 )
872 873 % (
873 874 crevcount,
874 875 util.bytecount(csrcsize),
875 876 util.bytecount(crawsize),
876 877 )
877 878 )
878 879 seen.add(b'c')
879 880 progress = srcrepo.ui.makeprogress(
880 881 _(b'changelog revisions'), total=crevcount
881 882 )
882 883 elif isinstance(oldrl, manifest.manifestrevlog) and b'm' not in seen:
883 884 ui.status(
884 885 _(
885 886 b'finished migrating %d filelog revisions across %d '
886 887 b'filelogs; change in size: %s\n'
887 888 )
888 889 % (frevcount, fcount, util.bytecount(fdstsize - fsrcsize))
889 890 )
890 891
891 892 ui.status(
892 893 _(
893 894 b'migrating %d manifests containing %d revisions '
894 895 b'(%s in store; %s tracked data)\n'
895 896 )
896 897 % (
897 898 mcount,
898 899 mrevcount,
899 900 util.bytecount(msrcsize),
900 901 util.bytecount(mrawsize),
901 902 )
902 903 )
903 904 seen.add(b'm')
904 905 if progress:
905 906 progress.complete()
906 907 progress = srcrepo.ui.makeprogress(
907 908 _(b'manifest revisions'), total=mrevcount
908 909 )
909 910 elif b'f' not in seen:
910 911 ui.status(
911 912 _(
912 913 b'migrating %d filelogs containing %d revisions '
913 914 b'(%s in store; %s tracked data)\n'
914 915 )
915 916 % (
916 917 fcount,
917 918 frevcount,
918 919 util.bytecount(fsrcsize),
919 920 util.bytecount(frawsize),
920 921 )
921 922 )
922 923 seen.add(b'f')
923 924 if progress:
924 925 progress.complete()
925 926 progress = srcrepo.ui.makeprogress(
926 927 _(b'file revisions'), total=frevcount
927 928 )
928 929
929 930 if matchrevlog(revlogs, unencoded):
930 931 ui.note(
931 932 _(b'cloning %d revisions from %s\n') % (len(oldrl), unencoded)
932 933 )
933 934 newrl = _revlogfrompath(dstrepo, unencoded)
934 935 oldrl.clone(
935 936 tr,
936 937 newrl,
937 938 addrevisioncb=oncopiedrevision,
938 939 deltareuse=deltareuse,
939 940 forcedeltabothparents=forcedeltabothparents,
940 941 sidedatacompanion=sidedatacompanion,
941 942 )
942 943 else:
943 944 msg = _(b'blindly copying %s containing %i revisions\n')
944 945 ui.note(msg % (unencoded, len(oldrl)))
945 946 _copyrevlog(tr, dstrepo, oldrl, unencoded)
946 947
947 948 newrl = _revlogfrompath(dstrepo, unencoded)
948 949
949 950 info = newrl.storageinfo(storedsize=True)
950 951 datasize = info[b'storedsize'] or 0
951 952
952 953 dstsize += datasize
953 954
954 955 if isinstance(newrl, changelog.changelog):
955 956 cdstsize += datasize
956 957 elif isinstance(newrl, manifest.manifestrevlog):
957 958 mdstsize += datasize
958 959 else:
959 960 fdstsize += datasize
960 961
961 962 progress.complete()
962 963
963 964 ui.status(
964 965 _(
965 966 b'finished migrating %d changelog revisions; change in size: '
966 967 b'%s\n'
967 968 )
968 969 % (crevcount, util.bytecount(cdstsize - csrcsize))
969 970 )
970 971
971 972 ui.status(
972 973 _(
973 974 b'finished migrating %d total revisions; total change in store '
974 975 b'size: %s\n'
975 976 )
976 977 % (revcount, util.bytecount(dstsize - srcsize))
977 978 )
978 979
979 980
980 981 def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
981 982 """Determine whether to copy a store file during upgrade.
982 983
983 984 This function is called when migrating store files from ``srcrepo`` to
984 985 ``dstrepo`` as part of upgrading a repository.
985 986
986 987 Args:
987 988 srcrepo: repo we are copying from
988 989 dstrepo: repo we are copying to
989 990 requirements: set of requirements for ``dstrepo``
990 991 path: store file being examined
991 992 mode: the ``ST_MODE`` file type of ``path``
992 993 st: ``stat`` data structure for ``path``
993 994
994 995 Function should return ``True`` if the file is to be copied.
995 996 """
996 997 # Skip revlogs.
997 998 if path.endswith((b'.i', b'.d', b'.n', b'.nd')):
998 999 return False
999 1000 # Skip transaction related files.
1000 1001 if path.startswith(b'undo'):
1001 1002 return False
1002 1003 # Only copy regular files.
1003 1004 if mode != stat.S_IFREG:
1004 1005 return False
1005 1006 # Skip other skipped files.
1006 1007 if path in (b'lock', b'fncache'):
1007 1008 return False
1008 1009
1009 1010 return True
1010 1011
1011 1012
1012 1013 def _finishdatamigration(ui, srcrepo, dstrepo, requirements):
1013 1014 """Hook point for extensions to perform additional actions during upgrade.
1014 1015
1015 1016 This function is called after revlogs and store files have been copied but
1016 1017 before the new store is swapped into the original location.
1017 1018 """
1018 1019
1019 1020
1020 1021 def _upgraderepo(
1021 1022 ui, srcrepo, dstrepo, requirements, actions, revlogs=UPGRADE_ALL_REVLOGS
1022 1023 ):
1023 1024 """Do the low-level work of upgrading a repository.
1024 1025
1025 1026 The upgrade is effectively performed as a copy between a source
1026 1027 repository and a temporary destination repository.
1027 1028
1028 1029 The source repository is unmodified for as long as possible so the
1029 1030 upgrade can abort at any time without causing loss of service for
1030 1031 readers and without corrupting the source repository.
1031 1032 """
1032 1033 assert srcrepo.currentwlock()
1033 1034 assert dstrepo.currentwlock()
1034 1035
1035 1036 ui.status(
1036 1037 _(
1037 1038 b'(it is safe to interrupt this process any time before '
1038 1039 b'data migration completes)\n'
1039 1040 )
1040 1041 )
1041 1042
1042 1043 if b're-delta-all' in actions:
1043 1044 deltareuse = revlog.revlog.DELTAREUSENEVER
1044 1045 elif b're-delta-parent' in actions:
1045 1046 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1046 1047 elif b're-delta-multibase' in actions:
1047 1048 deltareuse = revlog.revlog.DELTAREUSESAMEREVS
1048 1049 elif b're-delta-fulladd' in actions:
1049 1050 deltareuse = revlog.revlog.DELTAREUSEFULLADD
1050 1051 else:
1051 1052 deltareuse = revlog.revlog.DELTAREUSEALWAYS
1052 1053
1053 1054 with dstrepo.transaction(b'upgrade') as tr:
1054 1055 _clonerevlogs(
1055 1056 ui,
1056 1057 srcrepo,
1057 1058 dstrepo,
1058 1059 tr,
1059 1060 deltareuse,
1060 1061 b're-delta-multibase' in actions,
1061 1062 revlogs=revlogs,
1062 1063 )
1063 1064
1064 1065 # Now copy other files in the store directory.
1065 1066 # The sorted() makes execution deterministic.
1066 1067 for p, kind, st in sorted(srcrepo.store.vfs.readdir(b'', stat=True)):
1067 1068 if not _filterstorefile(srcrepo, dstrepo, requirements, p, kind, st):
1068 1069 continue
1069 1070
1070 1071 srcrepo.ui.status(_(b'copying %s\n') % p)
1071 1072 src = srcrepo.store.rawvfs.join(p)
1072 1073 dst = dstrepo.store.rawvfs.join(p)
1073 1074 util.copyfile(src, dst, copystat=True)
1074 1075
1075 1076 _finishdatamigration(ui, srcrepo, dstrepo, requirements)
1076 1077
1077 1078 ui.status(_(b'data fully migrated to temporary repository\n'))
1078 1079
1079 1080 backuppath = pycompat.mkdtemp(prefix=b'upgradebackup.', dir=srcrepo.path)
1080 1081 backupvfs = vfsmod.vfs(backuppath)
1081 1082
1082 1083 # Make a backup of requires file first, as it is the first to be modified.
1083 1084 util.copyfile(srcrepo.vfs.join(b'requires'), backupvfs.join(b'requires'))
1084 1085
1085 1086 # We install an arbitrary requirement that clients must not support
1086 1087 # as a mechanism to lock out new clients during the data swap. This is
1087 1088 # better than allowing a client to continue while the repository is in
1088 1089 # an inconsistent state.
1089 1090 ui.status(
1090 1091 _(
1091 1092 b'marking source repository as being upgraded; clients will be '
1092 1093 b'unable to read from repository\n'
1093 1094 )
1094 1095 )
1095 1096 scmutil.writereporequirements(
1096 1097 srcrepo, srcrepo.requirements | {b'upgradeinprogress'}
1097 1098 )
1098 1099
1099 1100 ui.status(_(b'starting in-place swap of repository data\n'))
1100 1101 ui.status(_(b'replaced files will be backed up at %s\n') % backuppath)
1101 1102
1102 1103 # Now swap in the new store directory. Doing it as a rename should make
1103 1104 # the operation nearly instantaneous and atomic (at least in well-behaved
1104 1105 # environments).
1105 1106 ui.status(_(b'replacing store...\n'))
1106 1107 tstart = util.timer()
1107 1108 util.rename(srcrepo.spath, backupvfs.join(b'store'))
1108 1109 util.rename(dstrepo.spath, srcrepo.spath)
1109 1110 elapsed = util.timer() - tstart
1110 1111 ui.status(
1111 1112 _(
1112 1113 b'store replacement complete; repository was inconsistent for '
1113 1114 b'%0.1fs\n'
1114 1115 )
1115 1116 % elapsed
1116 1117 )
1117 1118
1118 1119 # We first write the requirements file. Any new requirements will lock
1119 1120 # out legacy clients.
1120 1121 ui.status(
1121 1122 _(
1122 1123 b'finalizing requirements file and making repository readable '
1123 1124 b'again\n'
1124 1125 )
1125 1126 )
1126 1127 scmutil.writereporequirements(srcrepo, requirements)
1127 1128
1128 1129 # The lock file from the old store won't be removed because nothing has a
1129 1130 # reference to its new location. So clean it up manually. Alternatively, we
1130 1131 # could update srcrepo.svfs and other variables to point to the new
1131 1132 # location. This is simpler.
1132 1133 backupvfs.unlink(b'store/lock')
1133 1134
1134 1135 return backuppath
1135 1136
1136 1137
1137 1138 def upgraderepo(
1138 1139 ui,
1139 1140 repo,
1140 1141 run=False,
1141 1142 optimize=None,
1142 1143 backup=True,
1143 1144 manifest=None,
1144 1145 changelog=None,
1145 1146 ):
1146 1147 """Upgrade a repository in place."""
1147 1148 if optimize is None:
1148 1149 optimize = []
1149 1150 optimize = {legacy_opts_map.get(o, o) for o in optimize}
1150 1151 repo = repo.unfiltered()
1151 1152
1152 1153 revlogs = set(UPGRADE_ALL_REVLOGS)
1153 1154 specentries = ((b'c', changelog), (b'm', manifest))
1154 1155 specified = [(y, x) for (y, x) in specentries if x is not None]
1155 1156 if specified:
1156 1157 # we have some limitation on revlogs to be recloned
1157 1158 if any(x for y, x in specified):
1158 1159 revlogs = set()
1159 1160 for r, enabled in specified:
1160 1161 if enabled:
1161 1162 if r == b'c':
1162 1163 revlogs.add(UPGRADE_CHANGELOG)
1163 1164 elif r == b'm':
1164 1165 revlogs.add(UPGRADE_MANIFEST)
1165 1166 else:
1166 1167 # none are enabled
1167 1168 for r, __ in specified:
1168 1169 if r == b'c':
1169 1170 revlogs.discard(UPGRADE_CHANGELOG)
1170 1171 elif r == b'm':
1171 1172 revlogs.discard(UPGRADE_MANIFEST)
1172 1173
1173 1174 # Ensure the repository can be upgraded.
1174 1175 missingreqs = requiredsourcerequirements(repo) - repo.requirements
1175 1176 if missingreqs:
1176 1177 raise error.Abort(
1177 1178 _(b'cannot upgrade repository; requirement missing: %s')
1178 1179 % _(b', ').join(sorted(missingreqs))
1179 1180 )
1180 1181
1181 1182 blockedreqs = blocksourcerequirements(repo) & repo.requirements
1182 1183 if blockedreqs:
1183 1184 raise error.Abort(
1184 1185 _(
1185 1186 b'cannot upgrade repository; unsupported source '
1186 1187 b'requirement: %s'
1187 1188 )
1188 1189 % _(b', ').join(sorted(blockedreqs))
1189 1190 )
1190 1191
1191 1192 # FUTURE there is potentially a need to control the wanted requirements via
1192 1193 # command arguments or via an extension hook point.
1193 1194 newreqs = localrepo.newreporequirements(
1194 1195 repo.ui, localrepo.defaultcreateopts(repo.ui)
1195 1196 )
1196 1197 newreqs.update(preservedrequirements(repo))
1197 1198
1198 1199 noremovereqs = (
1199 1200 repo.requirements - newreqs - supportremovedrequirements(repo)
1200 1201 )
1201 1202 if noremovereqs:
1202 1203 raise error.Abort(
1203 1204 _(
1204 1205 b'cannot upgrade repository; requirement would be '
1205 1206 b'removed: %s'
1206 1207 )
1207 1208 % _(b', ').join(sorted(noremovereqs))
1208 1209 )
1209 1210
1210 1211 noaddreqs = newreqs - repo.requirements - allowednewrequirements(repo)
1211 1212 if noaddreqs:
1212 1213 raise error.Abort(
1213 1214 _(
1214 1215 b'cannot upgrade repository; do not support adding '
1215 1216 b'requirement: %s'
1216 1217 )
1217 1218 % _(b', ').join(sorted(noaddreqs))
1218 1219 )
1219 1220
1220 1221 unsupportedreqs = newreqs - supporteddestrequirements(repo)
1221 1222 if unsupportedreqs:
1222 1223 raise error.Abort(
1223 1224 _(
1224 1225 b'cannot upgrade repository; do not support '
1225 1226 b'destination requirement: %s'
1226 1227 )
1227 1228 % _(b', ').join(sorted(unsupportedreqs))
1228 1229 )
1229 1230
1230 1231 # Find and validate all improvements that can be made.
1231 1232 alloptimizations = findoptimizations(repo)
1232 1233
1233 1234 # Apply and Validate arguments.
1234 1235 optimizations = []
1235 1236 for o in alloptimizations:
1236 1237 if o.name in optimize:
1237 1238 optimizations.append(o)
1238 1239 optimize.discard(o.name)
1239 1240
1240 1241 if optimize: # anything left is unknown
1241 1242 raise error.Abort(
1242 1243 _(b'unknown optimization action requested: %s')
1243 1244 % b', '.join(sorted(optimize)),
1244 1245 hint=_(b'run without arguments to see valid optimizations'),
1245 1246 )
1246 1247
1247 1248 deficiencies = finddeficiencies(repo)
1248 1249 actions = determineactions(repo, deficiencies, repo.requirements, newreqs)
1249 1250 actions.extend(
1250 1251 o
1251 1252 for o in sorted(optimizations)
1252 1253 # determineactions could have added optimisation
1253 1254 if o not in actions
1254 1255 )
1255 1256
1256 1257 removedreqs = repo.requirements - newreqs
1257 1258 addedreqs = newreqs - repo.requirements
1258 1259
1259 1260 if revlogs != UPGRADE_ALL_REVLOGS:
1260 1261 incompatible = RECLONES_REQUIREMENTS & (removedreqs | addedreqs)
1261 1262 if incompatible:
1262 1263 msg = _(
1263 1264 b'ignoring revlogs selection flags, format requirements '
1264 1265 b'change: %s\n'
1265 1266 )
1266 1267 ui.warn(msg % b', '.join(sorted(incompatible)))
1267 1268 revlogs = UPGRADE_ALL_REVLOGS
1268 1269
1269 1270 def write_labeled(l, label):
1270 1271 first = True
1271 1272 for r in sorted(l):
1272 1273 if not first:
1273 1274 ui.write(b', ')
1274 1275 ui.write(r, label=label)
1275 1276 first = False
1276 1277
1277 1278 def printrequirements():
1278 1279 ui.write(_(b'requirements\n'))
1279 1280 ui.write(_(b' preserved: '))
1280 1281 write_labeled(
1281 1282 newreqs & repo.requirements, "upgrade-repo.requirement.preserved"
1282 1283 )
1283 1284 ui.write((b'\n'))
1284 1285 removed = repo.requirements - newreqs
1285 1286 if repo.requirements - newreqs:
1286 1287 ui.write(_(b' removed: '))
1287 1288 write_labeled(removed, "upgrade-repo.requirement.removed")
1288 1289 ui.write((b'\n'))
1289 1290 added = newreqs - repo.requirements
1290 1291 if added:
1291 1292 ui.write(_(b' added: '))
1292 1293 write_labeled(added, "upgrade-repo.requirement.added")
1293 1294 ui.write((b'\n'))
1294 1295 ui.write(b'\n')
1295 1296
1296 1297 def printoptimisations():
1297 1298 optimisations = [a for a in actions if a.type == optimisation]
1298 1299 optimisations.sort(key=lambda a: a.name)
1299 1300 if optimisations:
1300 1301 ui.write(_(b'optimisations: '))
1301 1302 write_labeled(
1302 1303 [a.name for a in optimisations],
1303 1304 "upgrade-repo.optimisation.performed",
1304 1305 )
1305 1306 ui.write(b'\n\n')
1306 1307
1307 1308 def printupgradeactions():
1308 1309 for a in actions:
1309 1310 ui.status(b'%s\n %s\n\n' % (a.name, a.upgrademessage))
1310 1311
1311 1312 if not run:
1312 1313 fromconfig = []
1313 1314 onlydefault = []
1314 1315
1315 1316 for d in deficiencies:
1316 1317 if d.fromconfig(repo):
1317 1318 fromconfig.append(d)
1318 1319 elif d.default:
1319 1320 onlydefault.append(d)
1320 1321
1321 1322 if fromconfig or onlydefault:
1322 1323
1323 1324 if fromconfig:
1324 1325 ui.status(
1325 1326 _(
1326 1327 b'repository lacks features recommended by '
1327 1328 b'current config options:\n\n'
1328 1329 )
1329 1330 )
1330 1331 for i in fromconfig:
1331 1332 ui.status(b'%s\n %s\n\n' % (i.name, i.description))
1332 1333
1333 1334 if onlydefault:
1334 1335 ui.status(
1335 1336 _(
1336 1337 b'repository lacks features used by the default '
1337 1338 b'config options:\n\n'
1338 1339 )
1339 1340 )
1340 1341 for i in onlydefault:
1341 1342 ui.status(b'%s\n %s\n\n' % (i.name, i.description))
1342 1343
1343 1344 ui.status(b'\n')
1344 1345 else:
1345 1346 ui.status(
1346 1347 _(
1347 1348 b'(no feature deficiencies found in existing '
1348 1349 b'repository)\n'
1349 1350 )
1350 1351 )
1351 1352
1352 1353 ui.status(
1353 1354 _(
1354 1355 b'performing an upgrade with "--run" will make the following '
1355 1356 b'changes:\n\n'
1356 1357 )
1357 1358 )
1358 1359
1359 1360 printrequirements()
1360 1361 printoptimisations()
1361 1362 printupgradeactions()
1362 1363
1363 1364 unusedoptimize = [i for i in alloptimizations if i not in actions]
1364 1365
1365 1366 if unusedoptimize:
1366 1367 ui.status(
1367 1368 _(
1368 1369 b'additional optimizations are available by specifying '
1369 1370 b'"--optimize <name>":\n\n'
1370 1371 )
1371 1372 )
1372 1373 for i in unusedoptimize:
1373 1374 ui.status(_(b'%s\n %s\n\n') % (i.name, i.description))
1374 1375 return
1375 1376
1376 1377 # Else we're in the run=true case.
1377 1378 ui.write(_(b'upgrade will perform the following actions:\n\n'))
1378 1379 printrequirements()
1379 1380 printoptimisations()
1380 1381 printupgradeactions()
1381 1382
1382 1383 upgradeactions = [a.name for a in actions]
1383 1384
1384 1385 ui.status(_(b'beginning upgrade...\n'))
1385 1386 with repo.wlock(), repo.lock():
1386 1387 ui.status(_(b'repository locked and read-only\n'))
1387 1388 # Our strategy for upgrading the repository is to create a new,
1388 1389 # temporary repository, write data to it, then do a swap of the
1389 1390 # data. There are less heavyweight ways to do this, but it is easier
1390 1391 # to create a new repo object than to instantiate all the components
1391 1392 # (like the store) separately.
1392 1393 tmppath = pycompat.mkdtemp(prefix=b'upgrade.', dir=repo.path)
1393 1394 backuppath = None
1394 1395 try:
1395 1396 ui.status(
1396 1397 _(
1397 1398 b'creating temporary repository to stage migrated '
1398 1399 b'data: %s\n'
1399 1400 )
1400 1401 % tmppath
1401 1402 )
1402 1403
1403 1404 # clone ui without using ui.copy because repo.ui is protected
1404 1405 repoui = repo.ui.__class__(repo.ui)
1405 1406 dstrepo = hg.repository(repoui, path=tmppath, create=True)
1406 1407
1407 1408 with dstrepo.wlock(), dstrepo.lock():
1408 1409 backuppath = _upgraderepo(
1409 1410 ui, repo, dstrepo, newreqs, upgradeactions, revlogs=revlogs
1410 1411 )
1411 1412 if not (backup or backuppath is None):
1412 1413 ui.status(
1413 1414 _(b'removing old repository content%s\n') % backuppath
1414 1415 )
1415 1416 repo.vfs.rmtree(backuppath, forcibly=True)
1416 1417 backuppath = None
1417 1418
1418 1419 finally:
1419 1420 ui.status(_(b'removing temporary repository %s\n') % tmppath)
1420 1421 repo.vfs.rmtree(tmppath, forcibly=True)
1421 1422
1422 1423 if backuppath and not ui.quiet:
1423 1424 ui.warn(
1424 1425 _(b'copy of old repository backed up at %s\n') % backuppath
1425 1426 )
1426 1427 ui.warn(
1427 1428 _(
1428 1429 b'the old repository will not be deleted; remove '
1429 1430 b'it to free up disk space once the upgraded '
1430 1431 b'repository is verified\n'
1431 1432 )
1432 1433 )
@@ -1,189 +1,221 b''
1 1 setup
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > share =
6 6 > [format]
7 7 > exp-share-safe = True
8 8 > EOF
9 9
10 10 prepare source repo
11 11
12 12 $ hg init source
13 13 $ cd source
14 14 $ cat .hg/requires
15 15 exp-sharesafe
16 16 $ cat .hg/store/requires
17 17 dotencode
18 18 fncache
19 19 generaldelta
20 20 revlogv1
21 21 sparserevlog
22 22 store
23 23 $ hg debugrequirements
24 24 dotencode
25 25 exp-sharesafe
26 26 fncache
27 27 generaldelta
28 28 revlogv1
29 29 sparserevlog
30 30 store
31 31
32 32 $ echo a > a
33 33 $ hg ci -Aqm "added a"
34 34 $ echo b > b
35 35 $ hg ci -Aqm "added b"
36 36
37 37 $ HGEDITOR=cat hg config --shared
38 38 abort: repository is not shared; can't use --shared
39 39 [255]
40 40 $ cd ..
41 41
42 42 Create a shared repo and check the requirements are shared and read correctly
43 43 $ hg share source shared1
44 44 updating working directory
45 45 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 46 $ cd shared1
47 47 $ cat .hg/requires
48 48 exp-sharesafe
49 49 shared
50 50
51 51 $ hg debugrequirements -R ../source
52 52 dotencode
53 53 exp-sharesafe
54 54 fncache
55 55 generaldelta
56 56 revlogv1
57 57 sparserevlog
58 58 store
59 59
60 60 $ hg debugrequirements
61 61 dotencode
62 62 exp-sharesafe
63 63 fncache
64 64 generaldelta
65 65 revlogv1
66 66 shared
67 67 sparserevlog
68 68 store
69 69
70 70 $ echo c > c
71 71 $ hg ci -Aqm "added c"
72 72
73 73 Check that config of the source repository is also loaded
74 74
75 75 $ hg showconfig ui.curses
76 76 [1]
77 77
78 78 $ echo "[ui]" >> ../source/.hg/hgrc
79 79 $ echo "curses=true" >> ../source/.hg/hgrc
80 80
81 81 $ hg showconfig ui.curses
82 82 true
83 83
84 84 However, local .hg/hgrc should override the config set by share source
85 85
86 86 $ echo "[ui]" >> .hg/hgrc
87 87 $ echo "curses=false" >> .hg/hgrc
88 88
89 89 $ hg showconfig ui.curses
90 90 false
91 91
92 92 $ HGEDITOR=cat hg config --shared
93 93 [ui]
94 94 curses=true
95 95
96 96 $ HGEDITOR=cat hg config --local
97 97 [ui]
98 98 curses=false
99 99
100 100 Testing that hooks set in source repository also runs in shared repo
101 101
102 102 $ cd ../source
103 103 $ cat <<EOF >> .hg/hgrc
104 104 > [extensions]
105 105 > hooklib=
106 106 > [hooks]
107 107 > pretxnchangegroup.reject_merge_commits = \
108 108 > python:hgext.hooklib.reject_merge_commits.hook
109 109 > EOF
110 110
111 111 $ cd ..
112 112 $ hg clone source cloned
113 113 updating to branch default
114 114 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
115 115 $ cd cloned
116 116 $ hg up 0
117 117 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
118 118 $ echo bar > bar
119 119 $ hg ci -Aqm "added bar"
120 120 $ hg merge
121 121 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 122 (branch merge, don't forget to commit)
123 123 $ hg ci -m "merge commit"
124 124
125 125 $ hg push ../source
126 126 pushing to ../source
127 127 searching for changes
128 128 adding changesets
129 129 adding manifests
130 130 adding file changes
131 131 error: pretxnchangegroup.reject_merge_commits hook failed: bcde3522682d rejected as merge on the same branch. Please consider rebase.
132 132 transaction abort!
133 133 rollback completed
134 134 abort: bcde3522682d rejected as merge on the same branch. Please consider rebase.
135 135 [255]
136 136
137 137 $ hg push ../shared1
138 138 pushing to ../shared1
139 139 searching for changes
140 140 adding changesets
141 141 adding manifests
142 142 adding file changes
143 143 error: pretxnchangegroup.reject_merge_commits hook failed: bcde3522682d rejected as merge on the same branch. Please consider rebase.
144 144 transaction abort!
145 145 rollback completed
146 146 abort: bcde3522682d rejected as merge on the same branch. Please consider rebase.
147 147 [255]
148 148
149 149 Test that if share source config is untrusted, we dont read it
150 150
151 151 $ cd ../shared1
152 152
153 153 $ cat << EOF > $TESTTMP/untrusted.py
154 154 > from mercurial import scmutil, util
155 155 > def uisetup(ui):
156 156 > class untrustedui(ui.__class__):
157 157 > def _trusted(self, fp, f):
158 158 > if util.normpath(fp.name).endswith(b'source/.hg/hgrc'):
159 159 > return False
160 160 > return super(untrustedui, self)._trusted(fp, f)
161 161 > ui.__class__ = untrustedui
162 162 > EOF
163 163
164 164 $ hg showconfig hooks
165 165 hooks.pretxnchangegroup.reject_merge_commits=python:hgext.hooklib.reject_merge_commits.hook
166 166
167 167 $ hg showconfig hooks --config extensions.untrusted=$TESTTMP/untrusted.py
168 168 [1]
169 169
170 Update the source repository format and check that shared repo works
171
172 $ cd ../source
173 $ echo "[format]" >> .hg/hgrc
174 $ echo "revlog-compression=zstd" >> .hg/hgrc
175
176 $ hg debugupgraderepo --run -q -R ../shared1
177 abort: cannot upgrade repository; unsupported source requirement: shared
178 [255]
179
180 $ hg debugupgraderepo --run -q
181 upgrade will perform the following actions:
182
183 requirements
184 preserved: dotencode, exp-sharesafe, fncache, generaldelta, revlogv1, sparserevlog, store
185 added: revlog-compression-zstd
186
187 $ hg log -r .
188 changeset: 1:5f6d8a4bf34a
189 user: test
190 date: Thu Jan 01 00:00:00 1970 +0000
191 summary: added b
192
193 Shared one should work
194 $ cd ../shared1
195 $ hg log -r .
196 changeset: 2:155349b645be
197 tag: tip
198 user: test
199 date: Thu Jan 01 00:00:00 1970 +0000
200 summary: added c
201
170 202 Unsharing works
171 203
172 204 $ hg unshare
173 205
174 206 Test that source config is added to the shared one after unshare, and the config
175 207 of current repo is still respected over the config which came from source config
176 208 $ cd ../cloned
177 209 $ hg push ../shared1
178 210 pushing to ../shared1
179 211 searching for changes
180 212 adding changesets
181 213 adding manifests
182 214 adding file changes
183 215 error: pretxnchangegroup.reject_merge_commits hook failed: bcde3522682d rejected as merge on the same branch. Please consider rebase.
184 216 transaction abort!
185 217 rollback completed
186 218 abort: bcde3522682d rejected as merge on the same branch. Please consider rebase.
187 219 [255]
188 220 $ hg showconfig ui.curses -R ../shared1
189 221 false
General Comments 0
You need to be logged in to leave comments. Login now