/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/repository.py

  • Committer: Jelmer Vernooij
  • Date: 2017-06-08 23:30:31 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170608233031-3qavls2o7a1pqllj
Update imports.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
 
17
from __future__ import absolute_import
 
18
 
17
19
from .lazy_import import lazy_import
18
20
lazy_import(globals(), """
 
21
import itertools
19
22
import time
20
23
 
21
24
from breezy import (
22
25
    config,
23
26
    controldir,
24
27
    debug,
 
28
    generate_ids,
25
29
    graph,
26
30
    osutils,
27
31
    revision as _mod_revision,
 
32
    testament as _mod_testament,
28
33
    gpg,
29
34
    )
 
35
from breezy.bundle import serializer
30
36
from breezy.i18n import gettext
31
37
""")
32
38
 
35
41
    registry,
36
42
    ui,
37
43
    )
38
 
from .decorators import only_raises
 
44
from .decorators import needs_read_lock, needs_write_lock, only_raises
39
45
from .inter import InterObject
40
46
from .lock import _RelockDebugMixin, LogicalLockResult
 
47
from .sixish import (
 
48
    viewitems,
 
49
    viewvalues,
 
50
    )
41
51
from .trace import (
42
52
    log_exception_quietly, note, mutter, mutter_callsite, warning)
43
53
 
54
64
        errors.InternalBzrError.__init__(self, repo=repo)
55
65
 
56
66
 
57
 
class CannotSetRevisionId(errors.BzrError):
58
 
 
59
 
    _fmt = "Repository format does not support setting revision ids."
60
 
 
61
 
 
62
 
class FetchResult(object):
63
 
    """Result of a fetch operation.
64
 
 
65
 
    :ivar revidmap: For lossy fetches, map from source revid to target revid.
66
 
    :ivar total_fetched: Number of revisions fetched
67
 
    """
68
 
 
69
 
    def __init__(self, total_fetched=None, revidmap=None):
70
 
        self.total_fetched = total_fetched
71
 
        self.revidmap = revidmap
72
 
 
73
 
 
74
67
class CommitBuilder(object):
75
68
    """Provides an interface to build up a commit.
76
69
 
80
73
 
81
74
    # all clients should supply tree roots.
82
75
    record_root_entry = True
 
76
    # whether this commit builder supports the record_entry_contents interface
 
77
    supports_record_entry_contents = False
83
78
    # whether this commit builder will automatically update the branch that is
84
79
    # being committed to
85
80
    updates_branch = False
97
92
        :param revprops: Optional dictionary of revision properties.
98
93
        :param revision_id: Optional revision id.
99
94
        :param lossy: Whether to discard data that can not be natively
100
 
            represented, when pushing to a foreign VCS
 
95
            represented, when pushing to a foreign VCS 
101
96
        """
102
97
        self._config_stack = config_stack
103
98
        self._lossy = lossy
104
99
 
105
100
        if committer is None:
106
101
            self._committer = self._config_stack.get('email')
107
 
        elif not isinstance(committer, str):
108
 
            self._committer = committer.decode()  # throw if non-ascii
 
102
        elif not isinstance(committer, unicode):
 
103
            self._committer = committer.decode() # throw if non-ascii
109
104
        else:
110
105
            self._committer = committer
111
106
 
 
107
        self._new_revision_id = revision_id
112
108
        self.parents = parents
113
109
        self.repository = repository
114
110
 
127
123
        else:
128
124
            self._timezone = int(timezone)
129
125
 
130
 
        self._generate_revision_if_needed(revision_id)
 
126
        self._generate_revision_if_needed()
131
127
 
132
128
    def any_changes(self):
133
129
        """Return True if any entries were changed.
141
137
 
142
138
    def _validate_unicode_text(self, text, context):
143
139
        """Verify things like commit messages don't have bogus characters."""
144
 
        # TODO(jelmer): Make this repository-format specific
145
 
        if u'\r' in text:
 
140
        if '\r' in text:
146
141
            raise ValueError('Invalid value for %s: %r' % (context, text))
147
142
 
148
143
    def _validate_revprops(self, revprops):
149
 
        for key, value in revprops.items():
 
144
        for key, value in viewitems(revprops):
150
145
            # We know that the XML serializers do not round trip '\r'
151
146
            # correctly, so refuse to accept them
152
 
            if not isinstance(value, str):
 
147
            if not isinstance(value, basestring):
153
148
                raise ValueError('revision property (%s) is not a valid'
154
149
                                 ' (unicode) string: %r' % (key, value))
155
 
            # TODO(jelmer): Make this repository-format specific
156
150
            self._validate_unicode_text(value,
157
151
                                        'revision property (%s)' % (key,))
158
152
 
187
181
        """
188
182
        raise NotImplementedError(self.finish_inventory)
189
183
 
190
 
    def _generate_revision_if_needed(self, revision_id):
 
184
    def _gen_revision_id(self):
 
185
        """Return new revision-id."""
 
186
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
 
187
 
 
188
    def _generate_revision_if_needed(self):
191
189
        """Create a revision id if None was supplied.
192
190
 
193
191
        If the repository can not support user-specified revision ids
196
194
 
197
195
        :raises: CannotSetRevisionId
198
196
        """
199
 
        if not self.repository._format.supports_setting_revision_ids:
200
 
            if revision_id is not None:
201
 
                raise CannotSetRevisionId()
202
 
            return
203
 
        if revision_id is None:
 
197
        if self._new_revision_id is None:
204
198
            self._new_revision_id = self._gen_revision_id()
205
199
            self.random_revid = True
206
200
        else:
207
 
            self._new_revision_id = revision_id
208
201
            self.random_revid = False
209
202
 
 
203
    def will_record_deletes(self):
 
204
        """Tell the commit builder that deletes are being notified.
 
205
 
 
206
        This enables the accumulation of an inventory delta; for the resulting
 
207
        commit to be valid, deletes against the basis MUST be recorded via
 
208
        builder.record_delete().
 
209
        """
 
210
        raise NotImplementedError(self.will_record_deletes)
 
211
 
210
212
    def record_iter_changes(self, tree, basis_revision_id, iter_changes):
211
213
        """Record a new tree via iter_changes.
212
214
 
218
220
            to basis_revision_id. The iterator must not include any items with
219
221
            a current kind of None - missing items must be either filtered out
220
222
            or errored-on beefore record_iter_changes sees the item.
221
 
        :return: A generator of (relpath, fs_hash) tuples for use with
 
223
        :return: A generator of (file_id, relpath, fs_hash) tuples for use with
222
224
            tree._observed_sha1.
223
225
        """
224
226
        raise NotImplementedError(self.record_iter_changes)
238
240
 
239
241
    def __repr__(self):
240
242
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
241
 
                                                      self.unlock)
242
 
 
243
 
 
244
 
class WriteGroup(object):
245
 
    """Context manager that manages a write group.
246
 
 
247
 
    Raising an exception will result in the write group being aborted.
248
 
    """
249
 
 
250
 
    def __init__(self, repository, suppress_errors=False):
251
 
        self.repository = repository
252
 
        self._suppress_errors = suppress_errors
253
 
 
254
 
    def __enter__(self):
255
 
        self.repository.start_write_group()
256
 
        return self
257
 
 
258
 
    def __exit__(self, exc_type, exc_val, exc_tb):
259
 
        if exc_type:
260
 
            self.repository.abort_write_group(self._suppress_errors)
261
 
            return False
262
 
        else:
263
 
            self.repository.commit_write_group()
 
243
            self.unlock)
264
244
 
265
245
 
266
246
######################################################################
278
258
    base class for most Bazaar repositories.
279
259
    """
280
260
 
281
 
    # Does this repository implementation support random access to
282
 
    # items in the tree, or just bulk fetching/pushing of data?
283
 
    supports_random_access = True
284
 
 
285
261
    def abort_write_group(self, suppress_errors=False):
286
262
        """Commit the contents accrued within the current write group.
287
263
 
295
271
            # has an unlock or relock occured ?
296
272
            if suppress_errors:
297
273
                mutter(
298
 
                    '(suppressed) mismatched lock context and write group. %r, %r',
299
 
                    self._write_group, self.get_transaction())
 
274
                '(suppressed) mismatched lock context and write group. %r, %r',
 
275
                self._write_group, self.get_transaction())
300
276
                return
301
277
            raise errors.BzrError(
302
278
                'mismatched lock context and write group. %r, %r' %
309
285
                raise
310
286
            mutter('abort_write_group failed')
311
287
            log_exception_quietly()
312
 
            note(gettext('brz: ERROR (ignored): %s'), exc)
 
288
            note(gettext('bzr: ERROR (ignored): %s'), exc)
313
289
        self._write_group = None
314
290
 
315
291
    def _abort_write_group(self):
387
363
        super(Repository, self).__init__()
388
364
        self._format = _format
389
365
        # the following are part of the public API for Repository:
390
 
        self.controldir = controldir
 
366
        self.bzrdir = controldir
391
367
        self.control_files = control_files
392
368
        # for tests
393
369
        self._write_group = None
396
372
 
397
373
    @property
398
374
    def user_transport(self):
399
 
        return self.controldir.user_transport
 
375
        return self.bzrdir.user_transport
400
376
 
401
377
    @property
402
378
    def control_transport(self):
517
493
        """
518
494
        self.control_files.dont_leave_in_place()
519
495
 
 
496
    @needs_read_lock
520
497
    def gather_stats(self, revid=None, committers=None):
521
498
        """Gather statistics from a revision id.
522
499
 
533
510
            revisions: The total revision count in the repository.
534
511
            size: An estimate disk size of the repository in bytes.
535
512
        """
536
 
        with self.lock_read():
537
 
            result = {}
538
 
            if revid and committers:
539
 
                result['committers'] = 0
540
 
            if revid and revid != _mod_revision.NULL_REVISION:
541
 
                graph = self.get_graph()
542
 
                if committers:
543
 
                    all_committers = set()
544
 
                revisions = [r for (r, p) in graph.iter_ancestry([revid])
545
 
                             if r != _mod_revision.NULL_REVISION]
546
 
                last_revision = None
547
 
                if not committers:
548
 
                    # ignore the revisions in the middle - just grab first and last
549
 
                    revisions = revisions[0], revisions[-1]
550
 
                for revision in self.get_revisions(revisions):
551
 
                    if not last_revision:
552
 
                        last_revision = revision
553
 
                    if committers:
554
 
                        all_committers.add(revision.committer)
555
 
                first_revision = revision
556
 
                if committers:
557
 
                    result['committers'] = len(all_committers)
558
 
                result['firstrev'] = (first_revision.timestamp,
559
 
                                      first_revision.timezone)
560
 
                result['latestrev'] = (last_revision.timestamp,
561
 
                                       last_revision.timezone)
562
 
            return result
 
513
        result = {}
 
514
        if revid and committers:
 
515
            result['committers'] = 0
 
516
        if revid and revid != _mod_revision.NULL_REVISION:
 
517
            graph = self.get_graph()
 
518
            if committers:
 
519
                all_committers = set()
 
520
            revisions = [r for (r, p) in graph.iter_ancestry([revid])
 
521
                        if r != _mod_revision.NULL_REVISION]
 
522
            last_revision = None
 
523
            if not committers:
 
524
                # ignore the revisions in the middle - just grab first and last
 
525
                revisions = revisions[0], revisions[-1]
 
526
            for revision in self.get_revisions(revisions):
 
527
                if not last_revision:
 
528
                    last_revision = revision
 
529
                if committers:
 
530
                    all_committers.add(revision.committer)
 
531
            first_revision = revision
 
532
            if committers:
 
533
                result['committers'] = len(all_committers)
 
534
            result['firstrev'] = (first_revision.timestamp,
 
535
                first_revision.timezone)
 
536
            result['latestrev'] = (last_revision.timestamp,
 
537
                last_revision.timezone)
 
538
        return result
563
539
 
564
540
    def find_branches(self, using=False):
565
541
        """Find branches underneath this repository.
569
545
        :param using: If True, list only branches using this repository.
570
546
        """
571
547
        if using and not self.is_shared():
572
 
            for branch in self.controldir.list_branches():
573
 
                yield branch
574
 
            return
575
 
 
 
548
            return self.bzrdir.list_branches()
576
549
        class Evaluator(object):
577
550
 
578
551
            def __init__(self):
592
565
                value = (controldir.list_branches(), None)
593
566
                return True, value
594
567
 
595
 
        for branches, repository in controldir.ControlDir.find_controldirs(
 
568
        ret = []
 
569
        for branches, repository in controldir.ControlDir.find_bzrdirs(
596
570
                self.user_transport, evaluate=Evaluator()):
597
571
            if branches is not None:
598
 
                for branch in branches:
599
 
                    yield branch
 
572
                ret.extend(branches)
600
573
            if not using and repository is not None:
601
 
                for branch in repository.find_branches():
602
 
                    yield branch
 
574
                ret.extend(repository.find_branches())
 
575
        return ret
603
576
 
 
577
    @needs_read_lock
604
578
    def search_missing_revision_ids(self, other,
605
 
                                    find_ghosts=True, revision_ids=None, if_present_ids=None,
606
 
                                    limit=None):
 
579
            find_ghosts=True, revision_ids=None, if_present_ids=None,
 
580
            limit=None):
607
581
        """Return the revision ids that other has that this does not.
608
582
 
609
583
        These are returned in topological order.
610
584
 
611
585
        revision_ids: only return revision ids included by revision_id.
612
586
        """
613
 
        with self.lock_read():
614
 
            return InterRepository.get(other, self).search_missing_revision_ids(
615
 
                find_ghosts=find_ghosts, revision_ids=revision_ids,
616
 
                if_present_ids=if_present_ids, limit=limit)
 
587
        return InterRepository.get(other, self).search_missing_revision_ids(
 
588
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
589
            if_present_ids=if_present_ids, limit=limit)
617
590
 
618
591
    @staticmethod
619
592
    def open(base):
637
610
        """Commit the contents accrued within the current write group.
638
611
 
639
612
        :seealso: start_write_group.
640
 
 
 
613
        
641
614
        :return: it may return an opaque hint that can be passed to 'pack'.
642
615
        """
643
616
        if self._write_group is not self.get_transaction():
644
617
            # has an unlock or relock occured ?
645
618
            raise errors.BzrError('mismatched lock context %r and '
646
 
                                  'write group %r.' %
647
 
                                  (self.get_transaction(), self._write_group))
 
619
                'write group %r.' %
 
620
                (self.get_transaction(), self._write_group))
648
621
        result = self._commit_write_group()
649
622
        self._write_group = None
650
623
        return result
692
665
    def _resume_write_group(self, tokens):
693
666
        raise errors.UnsuspendableWriteGroup(self)
694
667
 
695
 
    def fetch(self, source, revision_id=None, find_ghosts=False, lossy=False):
 
668
    def fetch(self, source, revision_id=None, find_ghosts=False):
696
669
        """Fetch the content required to construct revision_id from source.
697
670
 
698
671
        If revision_id is None, then all content is copied.
707
680
        :param revision_id: If specified, all the content needed for this
708
681
            revision ID will be copied to the target.  Fetch will determine for
709
682
            itself which content needs to be copied.
710
 
        :return: A FetchResult object
711
683
        """
712
684
        if self.is_in_write_group():
713
685
            raise errors.InternalBzrError(
716
688
        # TODO: lift out to somewhere common with RemoteRepository
717
689
        # <https://bugs.launchpad.net/bzr/+bug/401646>
718
690
        if (self.has_same_location(source)
719
 
                and self._has_same_fallbacks(source)):
 
691
            and self._has_same_fallbacks(source)):
720
692
            # check that last_revision is in 'from' and then return a
721
693
            # no-operation.
722
694
            if (revision_id is not None and
723
 
                    not _mod_revision.is_null(revision_id)):
 
695
                not _mod_revision.is_null(revision_id)):
724
696
                self.get_revision(revision_id)
725
697
            return 0, []
726
698
        inter = InterRepository.get(source, self)
727
 
        return inter.fetch(
728
 
            revision_id=revision_id, find_ghosts=find_ghosts, lossy=lossy)
 
699
        return inter.fetch(revision_id=revision_id, find_ghosts=find_ghosts)
 
700
 
 
701
    def create_bundle(self, target, base, fileobj, format=None):
 
702
        return serializer.write_bundle(self, target, base, fileobj, format)
729
703
 
730
704
    def get_commit_builder(self, branch, parents, config_stack, timestamp=None,
731
705
                           timezone=None, committer=None, revprops=None,
748
722
    @only_raises(errors.LockNotHeld, errors.LockBroken)
749
723
    def unlock(self):
750
724
        if (self.control_files._lock_count == 1 and
751
 
                self.control_files._lock_mode == 'w'):
 
725
            self.control_files._lock_mode == 'w'):
752
726
            if self._write_group is not None:
753
727
                self.abort_write_group()
754
728
                self.control_files.unlock()
759
733
            for repo in self._fallback_repositories:
760
734
                repo.unlock()
761
735
 
 
736
    @needs_read_lock
762
737
    def clone(self, controldir, revision_id=None):
763
738
        """Clone this repository into controldir using the current format.
764
739
 
767
742
 
768
743
        :return: The newly created destination repository.
769
744
        """
770
 
        with self.lock_read():
771
 
            # TODO: deprecate after 0.16; cloning this with all its settings is
772
 
            # probably not very useful -- mbp 20070423
773
 
            dest_repo = self._create_sprouting_repo(
774
 
                controldir, shared=self.is_shared())
775
 
            self.copy_content_into(dest_repo, revision_id)
776
 
            return dest_repo
 
745
        # TODO: deprecate after 0.16; cloning this with all its settings is
 
746
        # probably not very useful -- mbp 20070423
 
747
        dest_repo = self._create_sprouting_repo(
 
748
            controldir, shared=self.is_shared())
 
749
        self.copy_content_into(dest_repo, revision_id)
 
750
        return dest_repo
777
751
 
778
752
    def start_write_group(self):
779
753
        """Start a write group in the repository.
782
756
        between file ids and backend store to manage the insertion of data from
783
757
        both fetch and commit operations.
784
758
 
785
 
        A write lock is required around the
786
 
        start_write_group/commit_write_group for the support of lock-requiring
787
 
        repository formats.
 
759
        A write lock is required around the start_write_group/commit_write_group
 
760
        for the support of lock-requiring repository formats.
788
761
 
789
762
        One can only insert data into a repository inside a write group.
790
763
 
805
778
        entered.
806
779
        """
807
780
 
 
781
    @needs_read_lock
808
782
    def sprout(self, to_bzrdir, revision_id=None):
809
783
        """Create a descendent repository for new development.
810
784
 
811
785
        Unlike clone, this does not copy the settings of the repository.
812
786
        """
813
 
        with self.lock_read():
814
 
            dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
815
 
            dest_repo.fetch(self, revision_id=revision_id)
816
 
            return dest_repo
 
787
        dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
 
788
        dest_repo.fetch(self, revision_id=revision_id)
 
789
        return dest_repo
817
790
 
818
 
    def _create_sprouting_repo(self, a_controldir, shared):
819
 
        if not isinstance(
820
 
                a_controldir._format, self.controldir._format.__class__):
 
791
    def _create_sprouting_repo(self, a_bzrdir, shared):
 
792
        if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
821
793
            # use target default format.
822
 
            dest_repo = a_controldir.create_repository()
 
794
            dest_repo = a_bzrdir.create_repository()
823
795
        else:
824
796
            # Most control formats need the repository to be specifically
825
797
            # created, but on some old all-in-one formats it's not needed
826
798
            try:
827
 
                dest_repo = self._format.initialize(
828
 
                    a_controldir, shared=shared)
 
799
                dest_repo = self._format.initialize(a_bzrdir, shared=shared)
829
800
            except errors.UninitializableFormat:
830
 
                dest_repo = a_controldir.open_repository()
 
801
                dest_repo = a_bzrdir.open_repository()
831
802
        return dest_repo
832
803
 
 
804
    @needs_read_lock
833
805
    def has_revision(self, revision_id):
834
806
        """True if this repository has a copy of the revision."""
835
 
        with self.lock_read():
836
 
            return revision_id in self.has_revisions((revision_id,))
 
807
        return revision_id in self.has_revisions((revision_id,))
837
808
 
 
809
    @needs_read_lock
838
810
    def has_revisions(self, revision_ids):
839
811
        """Probe to find out the presence of multiple revisions.
840
812
 
843
815
        """
844
816
        raise NotImplementedError(self.has_revisions)
845
817
 
 
818
    @needs_read_lock
846
819
    def get_revision(self, revision_id):
847
820
        """Return the Revision object for a named revision."""
848
 
        with self.lock_read():
849
 
            return self.get_revisions([revision_id])[0]
 
821
        return self.get_revisions([revision_id])[0]
850
822
 
851
823
    def get_revision_reconcile(self, revision_id):
852
824
        """'reconcile' helper routine that allows access to a revision always.
860
832
 
861
833
    def get_revisions(self, revision_ids):
862
834
        """Get many revisions at once.
863
 
 
864
 
        Repositories that need to check data on every revision read should
 
835
        
 
836
        Repositories that need to check data on every revision read should 
865
837
        subclass this method.
866
838
        """
867
 
        revs = {}
868
 
        for revid, rev in self.iter_revisions(revision_ids):
869
 
            if rev is None:
870
 
                raise errors.NoSuchRevision(self, revid)
871
 
            revs[revid] = rev
872
 
        return [revs[revid] for revid in revision_ids]
873
 
 
874
 
    def iter_revisions(self, revision_ids):
875
 
        """Iterate over revision objects.
876
 
 
877
 
        :param revision_ids: An iterable of revisions to examine. None may be
878
 
            passed to request all revisions known to the repository. Note that
879
 
            not all repositories can find unreferenced revisions; for those
880
 
            repositories only referenced ones will be returned.
881
 
        :return: An iterator of (revid, revision) tuples. Absent revisions (
882
 
            those asked for but not available) are returned as (revid, None).
883
 
            N.B.: Revisions are not necessarily yielded in order.
884
 
        """
885
 
        raise NotImplementedError(self.iter_revisions)
886
 
 
887
 
    def get_revision_delta(self, revision_id):
888
 
        """Return the delta for one revision.
889
 
 
890
 
        The delta is relative to the left-hand predecessor of the
891
 
        revision.
892
 
        """
893
 
        with self.lock_read():
894
 
            r = self.get_revision(revision_id)
895
 
            return list(self.get_revision_deltas([r]))[0]
896
 
 
897
 
    def get_revision_deltas(self, revisions, specific_files=None):
 
839
        raise NotImplementedError(self.get_revisions)
 
840
 
 
841
    def get_deltas_for_revisions(self, revisions, specific_fileids=None):
898
842
        """Produce a generator of revision deltas.
899
843
 
900
 
        Note that the input is a sequence of REVISIONS, not revision ids.
 
844
        Note that the input is a sequence of REVISIONS, not revision_ids.
901
845
        Trees will be held in memory until the generator exits.
902
846
        Each delta is relative to the revision's lefthand predecessor.
903
847
 
904
 
        specific_files should exist in the first revision.
905
 
 
906
 
        :param specific_files: if not None, the result is filtered
907
 
          so that only those files, their parents and their
 
848
        :param specific_fileids: if not None, the result is filtered
 
849
          so that only those file-ids, their parents and their
908
850
          children are included.
909
851
        """
910
 
        from .tree import InterTree
911
852
        # Get the revision-ids of interest
912
853
        required_trees = set()
913
854
        for revision in revisions:
914
855
            required_trees.add(revision.revision_id)
915
856
            required_trees.update(revision.parent_ids[:1])
916
857
 
917
 
        trees = {
918
 
            t.get_revision_id(): t
919
 
            for t in self.revision_trees(required_trees)}
 
858
        # Get the matching filtered trees. Note that it's more
 
859
        # efficient to pass filtered trees to changes_from() rather
 
860
        # than doing the filtering afterwards. changes_from() could
 
861
        # arguably do the filtering itself but it's path-based, not
 
862
        # file-id based, so filtering before or afterwards is
 
863
        # currently easier.
 
864
        if specific_fileids is None:
 
865
            trees = dict((t.get_revision_id(), t) for
 
866
                t in self.revision_trees(required_trees))
 
867
        else:
 
868
            trees = dict((t.get_revision_id(), t) for
 
869
                t in self._filtered_revision_trees(required_trees,
 
870
                specific_fileids))
920
871
 
921
872
        # Calculate the deltas
922
873
        for revision in revisions:
924
875
                old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
925
876
            else:
926
877
                old_tree = trees[revision.parent_ids[0]]
927
 
            intertree = InterTree.get(old_tree, trees[revision.revision_id])
928
 
            yield intertree.compare(specific_files=specific_files)
929
 
            if specific_files is not None:
930
 
                specific_files = [
931
 
                    p for p in intertree.find_source_paths(
932
 
                        specific_files).values()
933
 
                    if p is not None]
934
 
 
 
878
            yield trees[revision.revision_id].changes_from(old_tree)
 
879
 
 
880
    @needs_read_lock
 
881
    def get_revision_delta(self, revision_id, specific_fileids=None):
 
882
        """Return the delta for one revision.
 
883
 
 
884
        The delta is relative to the left-hand predecessor of the
 
885
        revision.
 
886
 
 
887
        :param specific_fileids: if not None, the result is filtered
 
888
          so that only those file-ids, their parents and their
 
889
          children are included.
 
890
        """
 
891
        r = self.get_revision(revision_id)
 
892
        return list(self.get_deltas_for_revisions([r],
 
893
            specific_fileids=specific_fileids))[0]
 
894
 
 
895
    @needs_write_lock
935
896
    def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
936
 
        raise NotImplementedError(self.store_revision_signature)
 
897
        signature = gpg_strategy.sign(plaintext)
 
898
        self.add_signature_text(revision_id, signature)
937
899
 
938
900
    def add_signature_text(self, revision_id, signature):
939
901
        """Store a signature text for a revision.
971
933
        partial_history = [known_revid]
972
934
        distance_from_known = known_revno - revno
973
935
        if distance_from_known < 0:
974
 
            raise errors.RevnoOutOfBounds(revno, (0, known_revno))
 
936
            raise ValueError(
 
937
                'requested revno (%d) is later than given known revno (%d)'
 
938
                % (revno, known_revno))
975
939
        try:
976
940
            _iter_for_revno(
977
941
                self, partial_history, stop_index=distance_from_known)
978
942
        except errors.RevisionNotPresent as err:
979
943
            if err.revision_id == known_revid:
980
944
                # The start revision (known_revid) wasn't found.
981
 
                raise errors.NoSuchRevision(self, known_revid)
 
945
                raise
982
946
            # This is a stacked repository with no fallbacks, or a there's a
983
947
            # left-hand ghost.  Either way, even though the revision named in
984
948
            # the error isn't in this repo, we know it's the next step in this
996
960
        """Return True if this repository is flagged as a shared repository."""
997
961
        raise NotImplementedError(self.is_shared)
998
962
 
 
963
    @needs_write_lock
999
964
    def reconcile(self, other=None, thorough=False):
1000
965
        """Reconcile this repository."""
1001
 
        raise NotImplementedError(self.reconcile)
 
966
        from .reconcile import RepoReconciler
 
967
        reconciler = RepoReconciler(self, thorough=thorough)
 
968
        reconciler.reconcile()
 
969
        return reconciler
1002
970
 
1003
971
    def _refresh_data(self):
1004
972
        """Helper called from lock_* to ensure coherency with disk.
1014
982
        repository.
1015
983
        """
1016
984
 
 
985
    @needs_read_lock
1017
986
    def revision_tree(self, revision_id):
1018
987
        """Return Tree for a revision on this branch.
1019
988
 
1025
994
        """Return Trees for revisions in this repository.
1026
995
 
1027
996
        :param revision_ids: a sequence of revision-ids;
1028
 
          a revision-id may not be None or b'null:'
 
997
          a revision-id may not be None or 'null:'
1029
998
        """
1030
999
        raise NotImplementedError(self.revision_trees)
1031
1000
 
1036
1005
        types it should be a no-op that just returns.
1037
1006
 
1038
1007
        This stub method does not require a lock, but subclasses should use
1039
 
        self.write_lock as this is a long running call it's reasonable to
 
1008
        @needs_write_lock as this is a long running call it's reasonable to
1040
1009
        implicitly lock for the user.
1041
1010
 
1042
1011
        :param hint: If not supplied, the whole repository is packed.
1069
1038
            elif revision_id is None:
1070
1039
                raise ValueError('get_parent_map(None) is not valid')
1071
1040
            else:
1072
 
                query_keys.append((revision_id,))
 
1041
                query_keys.append((revision_id ,))
1073
1042
        vf = self.revisions.without_fallbacks()
1074
 
        for (revision_id,), parent_keys in (
1075
 
                vf.get_parent_map(query_keys).items()):
 
1043
        for (revision_id,), parent_keys in viewitems(
 
1044
                vf.get_parent_map(query_keys)):
1076
1045
            if parent_keys:
1077
1046
                result[revision_id] = tuple([parent_revid
1078
 
                                             for (parent_revid,) in parent_keys])
 
1047
                    for (parent_revid,) in parent_keys])
1079
1048
            else:
1080
1049
                result[revision_id] = (_mod_revision.NULL_REVISION,)
1081
1050
        return result
1091
1060
        return graph.CallableToParentsProviderAdapter(
1092
1061
            self._get_parent_map_no_fallbacks)
1093
1062
 
 
1063
    @needs_read_lock
1094
1064
    def get_known_graph_ancestry(self, revision_ids):
1095
1065
        """Return the known graph for a set of revision ids and their ancestors.
1096
1066
        """
1104
1074
        """Return the graph walker for this repository format"""
1105
1075
        parents_provider = self._make_parents_provider()
1106
1076
        if (other_repository is not None and
1107
 
                not self.has_same_location(other_repository)):
 
1077
            not self.has_same_location(other_repository)):
1108
1078
            parents_provider = graph.StackedParentsProvider(
1109
1079
                [parents_provider, other_repository._make_parents_provider()])
1110
1080
        return graph.Graph(parents_provider)
1111
1081
 
 
1082
    @needs_write_lock
1112
1083
    def set_make_working_trees(self, new_value):
1113
1084
        """Set the policy flag for making working trees when creating branches.
1114
1085
 
1124
1095
        """Returns the policy for making working trees on new branches."""
1125
1096
        raise NotImplementedError(self.make_working_trees)
1126
1097
 
 
1098
    @needs_write_lock
1127
1099
    def sign_revision(self, revision_id, gpg_strategy):
1128
 
        raise NotImplementedError(self.sign_revision)
 
1100
        testament = _mod_testament.Testament.from_revision(self, revision_id)
 
1101
        plaintext = testament.as_short_text()
 
1102
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1129
1103
 
 
1104
    @needs_read_lock
1130
1105
    def verify_revision_signature(self, revision_id, gpg_strategy):
1131
1106
        """Verify the signature on a revision.
1132
1107
 
1135
1110
 
1136
1111
        :return: gpg.SIGNATURE_VALID or a failed SIGNATURE_ value
1137
1112
        """
1138
 
        raise NotImplementedError(self.verify_revision_signature)
1139
 
 
 
1113
        if not self.has_signature_for_revision_id(revision_id):
 
1114
            return gpg.SIGNATURE_NOT_SIGNED, None
 
1115
        signature = self.get_signature_text(revision_id)
 
1116
 
 
1117
        testament = _mod_testament.Testament.from_revision(self, revision_id)
 
1118
        plaintext = testament.as_short_text()
 
1119
 
 
1120
        return gpg_strategy.verify(signature, plaintext)
 
1121
 
 
1122
    @needs_read_lock
1140
1123
    def verify_revision_signatures(self, revision_ids, gpg_strategy):
1141
1124
        """Verify revision signatures for a number of revisions.
1142
1125
 
1144
1127
        :gpg_strategy: the GPGStrategy object to used
1145
1128
        :return: Iterator over tuples with revision id, result and keys
1146
1129
        """
1147
 
        with self.lock_read():
1148
 
            for revid in revision_ids:
1149
 
                (result, key) = self.verify_revision_signature(revid, gpg_strategy)
1150
 
                yield revid, result, key
 
1130
        for revid in revision_ids:
 
1131
            (result, key) = self.verify_revision_signature(revid, gpg_strategy)
 
1132
            yield revid, result, key
1151
1133
 
1152
1134
    def has_signature_for_revision_id(self, revision_id):
1153
1135
        """Query for a revision signature for revision_id in the repository."""
1167
1149
        :param callback_refs: A dict of check-refs to resolve and callback
1168
1150
            the check/_check method on the items listed as wanting the ref.
1169
1151
            see breezy.check.
1170
 
        :param check_repo: If False do not check the repository contents, just
 
1152
        :param check_repo: If False do not check the repository contents, just 
1171
1153
            calculate the data callback_refs requires and call them back.
1172
1154
        """
1173
1155
        return self._check(revision_ids=revision_ids, callback_refs=callback_refs,
1174
 
                           check_repo=check_repo)
 
1156
            check_repo=check_repo)
1175
1157
 
1176
1158
    def _check(self, revision_ids=None, callback_refs=None, check_repo=True):
1177
1159
        raise NotImplementedError(self.check)
1191
1173
                return
1192
1174
            warning("Format %s for %s is deprecated -"
1193
1175
                    " please use 'brz upgrade' to get better performance"
1194
 
                    % (self._format, self.controldir.transport.base))
 
1176
                    % (self._format, self.bzrdir.transport.base))
1195
1177
        finally:
1196
1178
            _deprecation_warning_done = True
1197
1179
 
1203
1185
        # weave repositories refuse to store revisionids that are non-ascii.
1204
1186
        if revision_id is not None:
1205
1187
            # weaves require ascii revision ids.
1206
 
            if isinstance(revision_id, str):
 
1188
            if isinstance(revision_id, unicode):
1207
1189
                try:
1208
1190
                    revision_id.encode('ascii')
1209
1191
                except UnicodeEncodeError:
1220
1202
 
1221
1203
    def get_default(self):
1222
1204
        """Return the current default format."""
1223
 
        return controldir.format_registry.make_controldir('default').repository_format
 
1205
        return controldir.format_registry.make_bzrdir('default').repository_format
1224
1206
 
1225
1207
 
1226
1208
network_format_registry = registry.FormatRegistry()
1269
1251
    created.
1270
1252
 
1271
1253
    Common instance attributes:
1272
 
    _matchingcontroldir - the controldir format that the repository format was
 
1254
    _matchingbzrdir - the controldir format that the repository format was
1273
1255
    originally written to work with. This can be used if manually
1274
1256
    constructing a bzrdir and repository, or more commonly for test suite
1275
1257
    parameterization.
1311
1293
    supports_revision_signatures = True
1312
1294
    # Can the revision graph have incorrect parents?
1313
1295
    revision_graph_can_have_wrong_parents = None
1314
 
    # Does this format support setting revision ids?
1315
 
    supports_setting_revision_ids = True
1316
1296
    # Does this format support rich root data?
1317
1297
    rich_root_data = None
1318
1298
    # Does this format support explicitly versioned directories?
1322
1302
    # Is it possible for revisions to be present without being referenced
1323
1303
    # somewhere ?
1324
1304
    supports_unreferenced_revisions = None
1325
 
    # Does this format store the current Branch.nick in a revision when
1326
 
    # creating commits?
1327
 
    supports_storing_branch_nick = True
1328
 
    # Does the format support overriding the transport to use
1329
 
    supports_overriding_transport = True
1330
 
    # Does the format support setting custom revision properties?
1331
 
    supports_custom_revision_properties = True
1332
 
    # Does the format record per-file revision metadata?
1333
 
    records_per_file_revision = True
1334
1305
 
1335
1306
    def __repr__(self):
1336
1307
        return "%s()" % self.__class__.__name__
1390
1361
            raise errors.BadConversionTarget(
1391
1362
                'Does not support rich root data.', target_format,
1392
1363
                from_format=self)
1393
 
        if (self.supports_tree_reference
1394
 
                and not getattr(target_format, 'supports_tree_reference', False)):
 
1364
        if (self.supports_tree_reference and 
 
1365
            not getattr(target_format, 'supports_tree_reference', False)):
1395
1366
            raise errors.BadConversionTarget(
1396
1367
                'Does not support nested trees', target_format,
1397
1368
                from_format=self)
1415
1386
 
1416
1387
# formats which have no format string are not discoverable or independently
1417
1388
# creatable on disk, so are not registered in format_registry.  They're
1418
 
# all in breezy.bzr.knitreponow.  When an instance of one of these is
 
1389
# all in breezy.repofmt.knitreponow.  When an instance of one of these is
1419
1390
# needed, it's constructed directly by the ControlDir.  Non-native formats where
1420
1391
# the repository is not separately opened are similar.
1421
1392
 
1422
1393
format_registry.register_lazy(
1423
 
    b'Bazaar-NG Knit Repository Format 1',
1424
 
    'breezy.bzr.knitrepo',
 
1394
    'Bazaar-NG Knit Repository Format 1',
 
1395
    'breezy.repofmt.knitrepo',
1425
1396
    'RepositoryFormatKnit1',
1426
1397
    )
1427
1398
 
1428
1399
format_registry.register_lazy(
1429
 
    b'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
1430
 
    'breezy.bzr.knitrepo',
 
1400
    'Bazaar Knit Repository Format 3 (bzr 0.15)\n',
 
1401
    'breezy.repofmt.knitrepo',
1431
1402
    'RepositoryFormatKnit3',
1432
1403
    )
1433
1404
 
1434
1405
format_registry.register_lazy(
1435
 
    b'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
1436
 
    'breezy.bzr.knitrepo',
 
1406
    'Bazaar Knit Repository Format 4 (bzr 1.0)\n',
 
1407
    'breezy.repofmt.knitrepo',
1437
1408
    'RepositoryFormatKnit4',
1438
1409
    )
1439
1410
 
1441
1412
# post-subtrees to allow ease of testing.
1442
1413
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
1443
1414
format_registry.register_lazy(
1444
 
    b'Bazaar pack repository format 1 (needs bzr 0.92)\n',
1445
 
    'breezy.bzr.knitpack_repo',
 
1415
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
 
1416
    'breezy.repofmt.knitpack_repo',
1446
1417
    'RepositoryFormatKnitPack1',
1447
1418
    )
1448
1419
format_registry.register_lazy(
1449
 
    b'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
1450
 
    'breezy.bzr.knitpack_repo',
 
1420
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
 
1421
    'breezy.repofmt.knitpack_repo',
1451
1422
    'RepositoryFormatKnitPack3',
1452
1423
    )
1453
1424
format_registry.register_lazy(
1454
 
    b'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
1455
 
    'breezy.bzr.knitpack_repo',
 
1425
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
 
1426
    'breezy.repofmt.knitpack_repo',
1456
1427
    'RepositoryFormatKnitPack4',
1457
1428
    )
1458
1429
format_registry.register_lazy(
1459
 
    b'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
1460
 
    'breezy.bzr.knitpack_repo',
 
1430
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
 
1431
    'breezy.repofmt.knitpack_repo',
1461
1432
    'RepositoryFormatKnitPack5',
1462
1433
    )
1463
1434
format_registry.register_lazy(
1464
 
    b'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
1465
 
    'breezy.bzr.knitpack_repo',
 
1435
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
 
1436
    'breezy.repofmt.knitpack_repo',
1466
1437
    'RepositoryFormatKnitPack5RichRoot',
1467
1438
    )
1468
1439
format_registry.register_lazy(
1469
 
    b'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
1470
 
    'breezy.bzr.knitpack_repo',
 
1440
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
 
1441
    'breezy.repofmt.knitpack_repo',
1471
1442
    'RepositoryFormatKnitPack5RichRootBroken',
1472
1443
    )
1473
1444
format_registry.register_lazy(
1474
 
    b'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
1475
 
    'breezy.bzr.knitpack_repo',
 
1445
    'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
 
1446
    'breezy.repofmt.knitpack_repo',
1476
1447
    'RepositoryFormatKnitPack6',
1477
1448
    )
1478
1449
format_registry.register_lazy(
1479
 
    b'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
1480
 
    'breezy.bzr.knitpack_repo',
 
1450
    'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
 
1451
    'breezy.repofmt.knitpack_repo',
1481
1452
    'RepositoryFormatKnitPack6RichRoot',
1482
1453
    )
1483
1454
format_registry.register_lazy(
1484
 
    b'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
1485
 
    'breezy.bzr.groupcompress_repo',
 
1455
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
1456
    'breezy.repofmt.groupcompress_repo',
1486
1457
    'RepositoryFormat2a',
1487
1458
    )
1488
1459
 
1489
1460
# Development formats.
1490
1461
# Check their docstrings to see if/when they are obsolete.
1491
1462
format_registry.register_lazy(
1492
 
    (b"Bazaar development format 2 with subtree support "
1493
 
        b"(needs bzr.dev from before 1.8)\n"),
1494
 
    'breezy.bzr.knitpack_repo',
 
1463
    ("Bazaar development format 2 with subtree support "
 
1464
        "(needs bzr.dev from before 1.8)\n"),
 
1465
    'breezy.repofmt.knitpack_repo',
1495
1466
    'RepositoryFormatPackDevelopment2Subtree',
1496
1467
    )
1497
1468
format_registry.register_lazy(
1498
 
    b'Bazaar development format 8\n',
1499
 
    'breezy.bzr.groupcompress_repo',
 
1469
    'Bazaar development format 8\n',
 
1470
    'breezy.repofmt.groupcompress_repo',
1500
1471
    'RepositoryFormat2aSubtree',
1501
1472
    )
1502
1473
 
1516
1487
    _optimisers = []
1517
1488
    """The available optimised InterRepository types."""
1518
1489
 
 
1490
    @needs_write_lock
1519
1491
    def copy_content(self, revision_id=None):
1520
1492
        """Make a complete copy of the content in self into destination.
1521
1493
 
1525
1497
        :param revision_id: Only copy the content needed to construct
1526
1498
                            revision_id and its parents.
1527
1499
        """
1528
 
        with self.lock_write():
1529
 
            try:
1530
 
                self.target.set_make_working_trees(
1531
 
                    self.source.make_working_trees())
1532
 
            except (NotImplementedError, errors.RepositoryUpgradeRequired):
1533
 
                pass
1534
 
            self.target.fetch(self.source, revision_id=revision_id)
 
1500
        try:
 
1501
            self.target.set_make_working_trees(
 
1502
                self.source.make_working_trees())
 
1503
        except NotImplementedError:
 
1504
            pass
 
1505
        self.target.fetch(self.source, revision_id=revision_id)
1535
1506
 
1536
 
    def fetch(self, revision_id=None, find_ghosts=False, lossy=False):
 
1507
    @needs_write_lock
 
1508
    def fetch(self, revision_id=None, find_ghosts=False):
1537
1509
        """Fetch the content required to construct revision_id.
1538
1510
 
1539
1511
        The content is copied from self.source to self.target.
1540
1512
 
1541
1513
        :param revision_id: if None all content is copied, if NULL_REVISION no
1542
1514
                            content is copied.
1543
 
        :return: FetchResult
 
1515
        :return: None.
1544
1516
        """
1545
1517
        raise NotImplementedError(self.fetch)
1546
1518
 
 
1519
    @needs_read_lock
1547
1520
    def search_missing_revision_ids(
1548
1521
            self, find_ghosts=True, revision_ids=None, if_present_ids=None,
1549
1522
            limit=None):
1583
1556
        """
1584
1557
        if source.supports_rich_root() != target.supports_rich_root():
1585
1558
            raise errors.IncompatibleRepositories(source, target,
1586
 
                                                  "different rich-root support")
 
1559
                "different rich-root support")
1587
1560
        if source._serializer != target._serializer:
1588
1561
            raise errors.IncompatibleRepositories(source, target,
1589
 
                                                  "different serializers")
 
1562
                "different serializers")
1590
1563
 
1591
1564
 
1592
1565
class CopyConverter(object):
1608
1581
        :param to_convert: The disk object to convert.
1609
1582
        :param pb: a progress bar to use for progress information.
1610
1583
        """
1611
 
        with ui.ui_factory.nested_progress_bar() as pb:
1612
 
            self.count = 0
1613
 
            self.total = 4
1614
 
            # this is only useful with metadir layouts - separated repo content.
1615
 
            # trigger an assertion if not such
1616
 
            repo._format.get_format_string()
1617
 
            self.repo_dir = repo.controldir
1618
 
            pb.update(gettext('Moving repository to repository.backup'))
1619
 
            self.repo_dir.transport.move('repository', 'repository.backup')
1620
 
            backup_transport = self.repo_dir.transport.clone(
1621
 
                'repository.backup')
1622
 
            repo._format.check_conversion_target(self.target_format)
1623
 
            self.source_repo = repo._format.open(self.repo_dir,
1624
 
                                                 _found=True,
1625
 
                                                 _override_transport=backup_transport)
1626
 
            pb.update(gettext('Creating new repository'))
1627
 
            converted = self.target_format.initialize(self.repo_dir,
1628
 
                                                      self.source_repo.is_shared())
1629
 
            with converted.lock_write():
1630
 
                pb.update(gettext('Copying content'))
1631
 
                self.source_repo.copy_content_into(converted)
1632
 
            pb.update(gettext('Deleting old repository content'))
1633
 
            self.repo_dir.transport.delete_tree('repository.backup')
1634
 
            ui.ui_factory.note(gettext('repository converted'))
 
1584
        pb = ui.ui_factory.nested_progress_bar()
 
1585
        self.count = 0
 
1586
        self.total = 4
 
1587
        # this is only useful with metadir layouts - separated repo content.
 
1588
        # trigger an assertion if not such
 
1589
        repo._format.get_format_string()
 
1590
        self.repo_dir = repo.bzrdir
 
1591
        pb.update(gettext('Moving repository to repository.backup'))
 
1592
        self.repo_dir.transport.move('repository', 'repository.backup')
 
1593
        backup_transport =  self.repo_dir.transport.clone('repository.backup')
 
1594
        repo._format.check_conversion_target(self.target_format)
 
1595
        self.source_repo = repo._format.open(self.repo_dir,
 
1596
            _found=True,
 
1597
            _override_transport=backup_transport)
 
1598
        pb.update(gettext('Creating new repository'))
 
1599
        converted = self.target_format.initialize(self.repo_dir,
 
1600
                                                  self.source_repo.is_shared())
 
1601
        converted.lock_write()
 
1602
        try:
 
1603
            pb.update(gettext('Copying content'))
 
1604
            self.source_repo.copy_content_into(converted)
 
1605
        finally:
 
1606
            converted.unlock()
 
1607
        pb.update(gettext('Deleting old repository content'))
 
1608
        self.repo_dir.transport.delete_tree('repository.backup')
 
1609
        ui.ui_factory.note(gettext('repository converted'))
 
1610
        pb.finished()
1635
1611
 
1636
1612
 
1637
1613
def _strip_NULL_ghosts(revision_graph):
1639
1615
    # Filter ghosts, and null:
1640
1616
    if _mod_revision.NULL_REVISION in revision_graph:
1641
1617
        del revision_graph[_mod_revision.NULL_REVISION]
1642
 
    for key, parents in revision_graph.items():
 
1618
    for key, parents in viewitems(revision_graph):
1643
1619
        revision_graph[key] = tuple(parent for parent in parents if parent
1644
 
                                    in revision_graph)
 
1620
            in revision_graph)
1645
1621
    return revision_graph
1646
1622
 
1647
1623
 
1662
1638
    start_revision = partial_history_cache[-1]
1663
1639
    graph = repo.get_graph()
1664
1640
    iterator = graph.iter_lefthand_ancestry(start_revision,
1665
 
                                            (_mod_revision.NULL_REVISION,))
 
1641
        (_mod_revision.NULL_REVISION,))
1666
1642
    try:
1667
1643
        # skip the last revision in the list
1668
1644
        next(iterator)
1669
1645
        while True:
1670
1646
            if (stop_index is not None and
1671
 
                    len(partial_history_cache) > stop_index):
 
1647
                len(partial_history_cache) > stop_index):
1672
1648
                break
1673
1649
            if partial_history_cache[-1] == stop_revision:
1674
1650
                break