This file is indexed.

/usr/lib/python2.7/dist-packages/mercurial/upgrade.py is in mercurial-common 4.5.3-1ubuntu2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
# upgrade.py - functions for in place upgrade of Mercurial repository
#
# Copyright (c) 2016-present, Gregory Szorc
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

from __future__ import absolute_import

import stat
import tempfile

from .i18n import _
from . import (
    changelog,
    error,
    filelog,
    hg,
    localrepo,
    manifest,
    revlog,
    scmutil,
    util,
    vfs as vfsmod,
)

def requiredsourcerequirements(repo):
    """Obtain requirements required to be present to upgrade a repo.

    An upgrade will not be allowed if the repository doesn't have the
    requirements returned by this function.
    """
    return {
        # Introduced in Mercurial 0.9.2.
        'revlogv1',
        # Introduced in Mercurial 0.9.2.
        'store',
    }

def blocksourcerequirements(repo):
    """Obtain requirements that will prevent an upgrade from occurring.

    An upgrade cannot be performed if the source repository contains a
    requirements in the returned set.
    """
    return {
        # The upgrade code does not yet support these experimental features.
        # This is an artificial limitation.
        'manifestv2',
        'treemanifest',
        # This was a precursor to generaldelta and was never enabled by default.
        # It should (hopefully) not exist in the wild.
        'parentdelta',
        # Upgrade should operate on the actual store, not the shared link.
        'shared',
    }

def supportremovedrequirements(repo):
    """Obtain requirements that can be removed during an upgrade.

    If an upgrade were to create a repository that dropped a requirement,
    the dropped requirement must appear in the returned set for the upgrade
    to be allowed.
    """
    return set()

def supporteddestrequirements(repo):
    """Obtain requirements that upgrade supports in the destination.

    If the result of the upgrade would create requirements not in this set,
    the upgrade is disallowed.

    Extensions should monkeypatch this to add their custom requirements.
    """
    return {
        'dotencode',
        'fncache',
        'generaldelta',
        'revlogv1',
        'store',
    }

def allowednewrequirements(repo):
    """Obtain requirements that can be added to a repository during upgrade.

    This is used to disallow proposed requirements from being added when
    they weren't present before.

    We use a list of allowed requirement additions instead of a list of known
    bad additions because the whitelist approach is safer and will prevent
    future, unknown requirements from accidentally being added.
    """
    return {
        'dotencode',
        'fncache',
        'generaldelta',
    }

def preservedrequirements(repo):
    return set()

deficiency = 'deficiency'
optimisation = 'optimization'

class improvement(object):
    """Represents an improvement that can be made as part of an upgrade.

    The following attributes are defined on each instance:

    name
       Machine-readable string uniquely identifying this improvement. It
       will be mapped to an action later in the upgrade process.

    type
       Either ``deficiency`` or ``optimisation``. A deficiency is an obvious
       problem. An optimization is an action (sometimes optional) that
       can be taken to further improve the state of the repository.

    description
       Message intended for humans explaining the improvement in more detail,
       including the implications of it. For ``deficiency`` types, should be
       worded in the present tense. For ``optimisation`` types, should be
       worded in the future tense.

    upgrademessage
       Message intended for humans explaining what an upgrade addressing this
       issue will do. Should be worded in the future tense.
    """
    def __init__(self, name, type, description, upgrademessage):
        self.name = name
        self.type = type
        self.description = description
        self.upgrademessage = upgrademessage

    def __eq__(self, other):
        if not isinstance(other, improvement):
            # This is what python tell use to do
            return NotImplemented
        return self.name == other.name

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        return hash(self.name)

allformatvariant = []

def registerformatvariant(cls):
    allformatvariant.append(cls)
    return cls

class formatvariant(improvement):
    """an improvement subclass dedicated to repository format"""
    type = deficiency
    ### The following attributes should be defined for each class:

    # machine-readable string uniquely identifying this improvement. it will be
    # mapped to an action later in the upgrade process.
    name = None

    # message intended for humans explaining the improvement in more detail,
    # including the implications of it ``deficiency`` types, should be worded
    # in the present tense.
    description = None

    # message intended for humans explaining what an upgrade addressing this
    # issue will do. should be worded in the future tense.
    upgrademessage = None

    # value of current Mercurial default for new repository
    default = None

    def __init__(self):
        raise NotImplementedError()

    @staticmethod
    def fromrepo(repo):
        """current value of the variant in the repository"""
        raise NotImplementedError()

    @staticmethod
    def fromconfig(repo):
        """current value of the variant in the configuration"""
        raise NotImplementedError()

class requirementformatvariant(formatvariant):
    """formatvariant based on a 'requirement' name.

    Many format variant are controlled by a 'requirement'. We define a small
    subclass to factor the code.
    """

    # the requirement that control this format variant
    _requirement = None

    @staticmethod
    def _newreporequirements(repo):
        return localrepo.newreporequirements(repo)

    @classmethod
    def fromrepo(cls, repo):
        assert cls._requirement is not None
        return cls._requirement in repo.requirements

    @classmethod
    def fromconfig(cls, repo):
        assert cls._requirement is not None
        return cls._requirement in cls._newreporequirements(repo)

@registerformatvariant
class fncache(requirementformatvariant):
    name = 'fncache'

    _requirement = 'fncache'

    default = True

    description = _('long and reserved filenames may not work correctly; '
                    'repository performance is sub-optimal')

    upgrademessage = _('repository will be more resilient to storing '
                       'certain paths and performance of certain '
                       'operations should be improved')

@registerformatvariant
class dotencode(requirementformatvariant):
    name = 'dotencode'

    _requirement = 'dotencode'

    default = True

    description = _('storage of filenames beginning with a period or '
                    'space may not work correctly')

    upgrademessage = _('repository will be better able to store files '
                       'beginning with a space or period')

@registerformatvariant
class generaldelta(requirementformatvariant):
    name = 'generaldelta'

    _requirement = 'generaldelta'

    default = True

    description = _('deltas within internal storage are unable to '
                    'choose optimal revisions; repository is larger and '
                    'slower than it could be; interaction with other '
                    'repositories may require extra network and CPU '
                    'resources, making "hg push" and "hg pull" slower')

    upgrademessage = _('repository storage will be able to create '
                       'optimal deltas; new repository data will be '
                       'smaller and read times should decrease; '
                       'interacting with other repositories using this '
                       'storage model should require less network and '
                       'CPU resources, making "hg push" and "hg pull" '
                       'faster')

@registerformatvariant
class removecldeltachain(formatvariant):
    name = 'plain-cl-delta'

    default = True

    description = _('changelog storage is using deltas instead of '
                    'raw entries; changelog reading and any '
                    'operation relying on changelog data are slower '
                    'than they could be')

    upgrademessage = _('changelog storage will be reformated to '
                       'store raw entries; changelog reading will be '
                       'faster; changelog size may be reduced')

    @staticmethod
    def fromrepo(repo):
        # Mercurial 4.0 changed changelogs to not use delta chains. Search for
        # changelogs with deltas.
        cl = repo.changelog
        chainbase = cl.chainbase
        return all(rev == chainbase(rev) for rev in cl)

    @staticmethod
    def fromconfig(repo):
        return True

@registerformatvariant
class compressionengine(formatvariant):
    name = 'compression'
    default = 'zlib'

    description = _('Compresion algorithm used to compress data. '
                    'Some engine are faster than other')

    upgrademessage = _('revlog content will be recompressed with the new '
                       'algorithm.')

    @classmethod
    def fromrepo(cls, repo):
        for req in repo.requirements:
            if req.startswith('exp-compression-'):
                return req.split('-', 2)[2]
        return 'zlib'

    @classmethod
    def fromconfig(cls, repo):
        return repo.ui.config('experimental', 'format.compression')

def finddeficiencies(repo):
    """returns a list of deficiencies that the repo suffer from"""
    deficiencies = []

    # We could detect lack of revlogv1 and store here, but they were added
    # in 0.9.2 and we don't support upgrading repos without these
    # requirements, so let's not bother.

    for fv in allformatvariant:
        if not fv.fromrepo(repo):
            deficiencies.append(fv)

    return deficiencies

def findoptimizations(repo):
    """Determine optimisation that could be used during upgrade"""
    # These are unconditionally added. There is logic later that figures out
    # which ones to apply.
    optimizations = []

    optimizations.append(improvement(
        name='redeltaparent',
        type=optimisation,
        description=_('deltas within internal storage will be recalculated to '
                      'choose an optimal base revision where this was not '
                      'already done; the size of the repository may shrink and '
                      'various operations may become faster; the first time '
                      'this optimization is performed could slow down upgrade '
                      'execution considerably; subsequent invocations should '
                      'not run noticeably slower'),
        upgrademessage=_('deltas within internal storage will choose a new '
                         'base revision if needed')))

    optimizations.append(improvement(
        name='redeltamultibase',
        type=optimisation,
        description=_('deltas within internal storage will be recalculated '
                      'against multiple base revision and the smallest '
                      'difference will be used; the size of the repository may '
                      'shrink significantly when there are many merges; this '
                      'optimization will slow down execution in proportion to '
                      'the number of merges in the repository and the amount '
                      'of files in the repository; this slow down should not '
                      'be significant unless there are tens of thousands of '
                      'files and thousands of merges'),
        upgrademessage=_('deltas within internal storage will choose an '
                         'optimal delta by computing deltas against multiple '
                         'parents; may slow down execution time '
                         'significantly')))

    optimizations.append(improvement(
        name='redeltaall',
        type=optimisation,
        description=_('deltas within internal storage will always be '
                      'recalculated without reusing prior deltas; this will '
                      'likely make execution run several times slower; this '
                      'optimization is typically not needed'),
        upgrademessage=_('deltas within internal storage will be fully '
                         'recomputed; this will likely drastically slow down '
                         'execution time')))

    optimizations.append(improvement(
        name='redeltafulladd',
        type=optimisation,
        description=_('every revision will be re-added as if it was new '
                      'content. It will go through the full storage '
                      'mechanism giving extensions a chance to process it '
                      '(eg. lfs). This is similar to "redeltaall" but even '
                      'slower since more logic is involved.'),
        upgrademessage=_('each revision will be added as new content to the '
                         'internal storage; this will likely drastically slow '
                         'down execution time, but some extensions might need '
                         'it')))

    return optimizations

def determineactions(repo, deficiencies, sourcereqs, destreqs):
    """Determine upgrade actions that will be performed.

    Given a list of improvements as returned by ``finddeficiencies`` and
    ``findoptimizations``, determine the list of upgrade actions that
    will be performed.

    The role of this function is to filter improvements if needed, apply
    recommended optimizations from the improvements list that make sense,
    etc.

    Returns a list of action names.
    """
    newactions = []

    knownreqs = supporteddestrequirements(repo)

    for d in deficiencies:
        name = d.name

        # If the action is a requirement that doesn't show up in the
        # destination requirements, prune the action.
        if name in knownreqs and name not in destreqs:
            continue

        newactions.append(d)

    # FUTURE consider adding some optimizations here for certain transitions.
    # e.g. adding generaldelta could schedule parent redeltas.

    return newactions

def _revlogfrompath(repo, path):
    """Obtain a revlog from a repo path.

    An instance of the appropriate class is returned.
    """
    if path == '00changelog.i':
        return changelog.changelog(repo.svfs)
    elif path.endswith('00manifest.i'):
        mandir = path[:-len('00manifest.i')]
        return manifest.manifestrevlog(repo.svfs, dir=mandir)
    else:
        #reverse of "/".join(("data", path + ".i"))
        return filelog.filelog(repo.svfs, path[5:-2])

def _copyrevlogs(ui, srcrepo, dstrepo, tr, deltareuse, aggressivemergedeltas):
    """Copy revlogs between 2 repos."""
    revcount = 0
    srcsize = 0
    srcrawsize = 0
    dstsize = 0
    fcount = 0
    frevcount = 0
    fsrcsize = 0
    frawsize = 0
    fdstsize = 0
    mcount = 0
    mrevcount = 0
    msrcsize = 0
    mrawsize = 0
    mdstsize = 0
    crevcount = 0
    csrcsize = 0
    crawsize = 0
    cdstsize = 0

    # Perform a pass to collect metadata. This validates we can open all
    # source files and allows a unified progress bar to be displayed.
    for unencoded, encoded, size in srcrepo.store.walk():
        if unencoded.endswith('.d'):
            continue

        rl = _revlogfrompath(srcrepo, unencoded)
        revcount += len(rl)

        datasize = 0
        rawsize = 0
        idx = rl.index
        for rev in rl:
            e = idx[rev]
            datasize += e[1]
            rawsize += e[2]

        srcsize += datasize
        srcrawsize += rawsize

        # This is for the separate progress bars.
        if isinstance(rl, changelog.changelog):
            crevcount += len(rl)
            csrcsize += datasize
            crawsize += rawsize
        elif isinstance(rl, manifest.manifestrevlog):
            mcount += 1
            mrevcount += len(rl)
            msrcsize += datasize
            mrawsize += rawsize
        elif isinstance(rl, revlog.revlog):
            fcount += 1
            frevcount += len(rl)
            fsrcsize += datasize
            frawsize += rawsize

    if not revcount:
        return

    ui.write(_('migrating %d total revisions (%d in filelogs, %d in manifests, '
               '%d in changelog)\n') %
             (revcount, frevcount, mrevcount, crevcount))
    ui.write(_('migrating %s in store; %s tracked data\n') % (
             (util.bytecount(srcsize), util.bytecount(srcrawsize))))

    # Used to keep track of progress.
    progress = []
    def oncopiedrevision(rl, rev, node):
        progress[1] += 1
        srcrepo.ui.progress(progress[0], progress[1], total=progress[2])

    # Do the actual copying.
    # FUTURE this operation can be farmed off to worker processes.
    seen = set()
    for unencoded, encoded, size in srcrepo.store.walk():
        if unencoded.endswith('.d'):
            continue

        oldrl = _revlogfrompath(srcrepo, unencoded)
        newrl = _revlogfrompath(dstrepo, unencoded)

        if isinstance(oldrl, changelog.changelog) and 'c' not in seen:
            ui.write(_('finished migrating %d manifest revisions across %d '
                       'manifests; change in size: %s\n') %
                     (mrevcount, mcount, util.bytecount(mdstsize - msrcsize)))

            ui.write(_('migrating changelog containing %d revisions '
                       '(%s in store; %s tracked data)\n') %
                     (crevcount, util.bytecount(csrcsize),
                      util.bytecount(crawsize)))
            seen.add('c')
            progress[:] = [_('changelog revisions'), 0, crevcount]
        elif isinstance(oldrl, manifest.manifestrevlog) and 'm' not in seen:
            ui.write(_('finished migrating %d filelog revisions across %d '
                       'filelogs; change in size: %s\n') %
                     (frevcount, fcount, util.bytecount(fdstsize - fsrcsize)))

            ui.write(_('migrating %d manifests containing %d revisions '
                       '(%s in store; %s tracked data)\n') %
                     (mcount, mrevcount, util.bytecount(msrcsize),
                      util.bytecount(mrawsize)))
            seen.add('m')
            progress[:] = [_('manifest revisions'), 0, mrevcount]
        elif 'f' not in seen:
            ui.write(_('migrating %d filelogs containing %d revisions '
                       '(%s in store; %s tracked data)\n') %
                     (fcount, frevcount, util.bytecount(fsrcsize),
                      util.bytecount(frawsize)))
            seen.add('f')
            progress[:] = [_('file revisions'), 0, frevcount]

        ui.progress(progress[0], progress[1], total=progress[2])

        ui.note(_('cloning %d revisions from %s\n') % (len(oldrl), unencoded))
        oldrl.clone(tr, newrl, addrevisioncb=oncopiedrevision,
                    deltareuse=deltareuse,
                    aggressivemergedeltas=aggressivemergedeltas)

        datasize = 0
        idx = newrl.index
        for rev in newrl:
            datasize += idx[rev][1]

        dstsize += datasize

        if isinstance(newrl, changelog.changelog):
            cdstsize += datasize
        elif isinstance(newrl, manifest.manifestrevlog):
            mdstsize += datasize
        else:
            fdstsize += datasize

    ui.progress(progress[0], None)

    ui.write(_('finished migrating %d changelog revisions; change in size: '
               '%s\n') % (crevcount, util.bytecount(cdstsize - csrcsize)))

    ui.write(_('finished migrating %d total revisions; total change in store '
               'size: %s\n') % (revcount, util.bytecount(dstsize - srcsize)))

def _filterstorefile(srcrepo, dstrepo, requirements, path, mode, st):
    """Determine whether to copy a store file during upgrade.

    This function is called when migrating store files from ``srcrepo`` to
    ``dstrepo`` as part of upgrading a repository.

    Args:
      srcrepo: repo we are copying from
      dstrepo: repo we are copying to
      requirements: set of requirements for ``dstrepo``
      path: store file being examined
      mode: the ``ST_MODE`` file type of ``path``
      st: ``stat`` data structure for ``path``

    Function should return ``True`` if the file is to be copied.
    """
    # Skip revlogs.
    if path.endswith(('.i', '.d')):
        return False
    # Skip transaction related files.
    if path.startswith('undo'):
        return False
    # Only copy regular files.
    if mode != stat.S_IFREG:
        return False
    # Skip other skipped files.
    if path in ('lock', 'fncache'):
        return False

    return True

def _finishdatamigration(ui, srcrepo, dstrepo, requirements):
    """Hook point for extensions to perform additional actions during upgrade.

    This function is called after revlogs and store files have been copied but
    before the new store is swapped into the original location.
    """

def _upgraderepo(ui, srcrepo, dstrepo, requirements, actions):
    """Do the low-level work of upgrading a repository.

    The upgrade is effectively performed as a copy between a source
    repository and a temporary destination repository.

    The source repository is unmodified for as long as possible so the
    upgrade can abort at any time without causing loss of service for
    readers and without corrupting the source repository.
    """
    assert srcrepo.currentwlock()
    assert dstrepo.currentwlock()

    ui.write(_('(it is safe to interrupt this process any time before '
               'data migration completes)\n'))

    if 'redeltaall' in actions:
        deltareuse = revlog.revlog.DELTAREUSENEVER
    elif 'redeltaparent' in actions:
        deltareuse = revlog.revlog.DELTAREUSESAMEREVS
    elif 'redeltamultibase' in actions:
        deltareuse = revlog.revlog.DELTAREUSESAMEREVS
    elif 'redeltafulladd' in actions:
        deltareuse = revlog.revlog.DELTAREUSEFULLADD
    else:
        deltareuse = revlog.revlog.DELTAREUSEALWAYS

    with dstrepo.transaction('upgrade') as tr:
        _copyrevlogs(ui, srcrepo, dstrepo, tr, deltareuse,
                     'redeltamultibase' in actions)

    # Now copy other files in the store directory.
    # The sorted() makes execution deterministic.
    for p, kind, st in sorted(srcrepo.store.vfs.readdir('', stat=True)):
        if not _filterstorefile(srcrepo, dstrepo, requirements,
                                       p, kind, st):
            continue

        srcrepo.ui.write(_('copying %s\n') % p)
        src = srcrepo.store.rawvfs.join(p)
        dst = dstrepo.store.rawvfs.join(p)
        util.copyfile(src, dst, copystat=True)

    _finishdatamigration(ui, srcrepo, dstrepo, requirements)

    ui.write(_('data fully migrated to temporary repository\n'))

    backuppath = tempfile.mkdtemp(prefix='upgradebackup.', dir=srcrepo.path)
    backupvfs = vfsmod.vfs(backuppath)

    # Make a backup of requires file first, as it is the first to be modified.
    util.copyfile(srcrepo.vfs.join('requires'), backupvfs.join('requires'))

    # We install an arbitrary requirement that clients must not support
    # as a mechanism to lock out new clients during the data swap. This is
    # better than allowing a client to continue while the repository is in
    # an inconsistent state.
    ui.write(_('marking source repository as being upgraded; clients will be '
               'unable to read from repository\n'))
    scmutil.writerequires(srcrepo.vfs,
                          srcrepo.requirements | {'upgradeinprogress'})

    ui.write(_('starting in-place swap of repository data\n'))
    ui.write(_('replaced files will be backed up at %s\n') %
             backuppath)

    # Now swap in the new store directory. Doing it as a rename should make
    # the operation nearly instantaneous and atomic (at least in well-behaved
    # environments).
    ui.write(_('replacing store...\n'))
    tstart = util.timer()
    util.rename(srcrepo.spath, backupvfs.join('store'))
    util.rename(dstrepo.spath, srcrepo.spath)
    elapsed = util.timer() - tstart
    ui.write(_('store replacement complete; repository was inconsistent for '
               '%0.1fs\n') % elapsed)

    # We first write the requirements file. Any new requirements will lock
    # out legacy clients.
    ui.write(_('finalizing requirements file and making repository readable '
               'again\n'))
    scmutil.writerequires(srcrepo.vfs, requirements)

    # The lock file from the old store won't be removed because nothing has a
    # reference to its new location. So clean it up manually. Alternatively, we
    # could update srcrepo.svfs and other variables to point to the new
    # location. This is simpler.
    backupvfs.unlink('store/lock')

    return backuppath

def upgraderepo(ui, repo, run=False, optimize=None):
    """Upgrade a repository in place."""
    optimize = set(optimize or [])
    repo = repo.unfiltered()

    # Ensure the repository can be upgraded.
    missingreqs = requiredsourcerequirements(repo) - repo.requirements
    if missingreqs:
        raise error.Abort(_('cannot upgrade repository; requirement '
                            'missing: %s') % _(', ').join(sorted(missingreqs)))

    blockedreqs = blocksourcerequirements(repo) & repo.requirements
    if blockedreqs:
        raise error.Abort(_('cannot upgrade repository; unsupported source '
                            'requirement: %s') %
                          _(', ').join(sorted(blockedreqs)))

    # FUTURE there is potentially a need to control the wanted requirements via
    # command arguments or via an extension hook point.
    newreqs = localrepo.newreporequirements(repo)
    newreqs.update(preservedrequirements(repo))

    noremovereqs = (repo.requirements - newreqs -
                   supportremovedrequirements(repo))
    if noremovereqs:
        raise error.Abort(_('cannot upgrade repository; requirement would be '
                            'removed: %s') % _(', ').join(sorted(noremovereqs)))

    noaddreqs = (newreqs - repo.requirements -
                 allowednewrequirements(repo))
    if noaddreqs:
        raise error.Abort(_('cannot upgrade repository; do not support adding '
                            'requirement: %s') %
                          _(', ').join(sorted(noaddreqs)))

    unsupportedreqs = newreqs - supporteddestrequirements(repo)
    if unsupportedreqs:
        raise error.Abort(_('cannot upgrade repository; do not support '
                            'destination requirement: %s') %
                          _(', ').join(sorted(unsupportedreqs)))

    # Find and validate all improvements that can be made.
    alloptimizations = findoptimizations(repo)

    # Apply and Validate arguments.
    optimizations = []
    for o in alloptimizations:
        if o.name in optimize:
            optimizations.append(o)
            optimize.discard(o.name)

    if optimize: # anything left is unknown
        raise error.Abort(_('unknown optimization action requested: %s') %
                          ', '.join(sorted(optimize)),
                          hint=_('run without arguments to see valid '
                                 'optimizations'))

    deficiencies = finddeficiencies(repo)
    actions = determineactions(repo, deficiencies, repo.requirements, newreqs)
    actions.extend(o for o in sorted(optimizations)
                   # determineactions could have added optimisation
                   if o not in actions)

    def printrequirements():
        ui.write(_('requirements\n'))
        ui.write(_('   preserved: %s\n') %
                 _(', ').join(sorted(newreqs & repo.requirements)))

        if repo.requirements - newreqs:
            ui.write(_('   removed: %s\n') %
                     _(', ').join(sorted(repo.requirements - newreqs)))

        if newreqs - repo.requirements:
            ui.write(_('   added: %s\n') %
                     _(', ').join(sorted(newreqs - repo.requirements)))

        ui.write('\n')

    def printupgradeactions():
        for a in actions:
            ui.write('%s\n   %s\n\n' % (a.name, a.upgrademessage))

    if not run:
        fromconfig = []
        onlydefault = []

        for d in deficiencies:
            if d.fromconfig(repo):
                fromconfig.append(d)
            elif d.default:
                onlydefault.append(d)

        if fromconfig or onlydefault:

            if fromconfig:
                ui.write(_('repository lacks features recommended by '
                           'current config options:\n\n'))
                for i in fromconfig:
                    ui.write('%s\n   %s\n\n' % (i.name, i.description))

            if onlydefault:
                ui.write(_('repository lacks features used by the default '
                           'config options:\n\n'))
                for i in onlydefault:
                    ui.write('%s\n   %s\n\n' % (i.name, i.description))

            ui.write('\n')
        else:
            ui.write(_('(no feature deficiencies found in existing '
                       'repository)\n'))

        ui.write(_('performing an upgrade with "--run" will make the following '
                   'changes:\n\n'))

        printrequirements()
        printupgradeactions()

        unusedoptimize = [i for i in alloptimizations if i not in actions]

        if unusedoptimize:
            ui.write(_('additional optimizations are available by specifying '
                     '"--optimize <name>":\n\n'))
            for i in unusedoptimize:
                ui.write(_('%s\n   %s\n\n') % (i.name, i.description))
        return

    # Else we're in the run=true case.
    ui.write(_('upgrade will perform the following actions:\n\n'))
    printrequirements()
    printupgradeactions()

    upgradeactions = [a.name for a in actions]

    ui.write(_('beginning upgrade...\n'))
    with repo.wlock(), repo.lock():
        ui.write(_('repository locked and read-only\n'))
        # Our strategy for upgrading the repository is to create a new,
        # temporary repository, write data to it, then do a swap of the
        # data. There are less heavyweight ways to do this, but it is easier
        # to create a new repo object than to instantiate all the components
        # (like the store) separately.
        tmppath = tempfile.mkdtemp(prefix='upgrade.', dir=repo.path)
        backuppath = None
        try:
            ui.write(_('creating temporary repository to stage migrated '
                       'data: %s\n') % tmppath)

            # clone ui without using ui.copy because repo.ui is protected
            repoui = repo.ui.__class__(repo.ui)
            dstrepo = hg.repository(repoui, path=tmppath, create=True)

            with dstrepo.wlock(), dstrepo.lock():
                backuppath = _upgraderepo(ui, repo, dstrepo, newreqs,
                                          upgradeactions)

        finally:
            ui.write(_('removing temporary repository %s\n') % tmppath)
            repo.vfs.rmtree(tmppath, forcibly=True)

            if backuppath:
                ui.warn(_('copy of old repository backed up at %s\n') %
                        backuppath)
                ui.warn(_('the old repository will not be deleted; remove '
                          'it to free up disk space once the upgraded '
                          'repository is verified\n'))