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