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