~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Andrew Bennetts
  • Date: 2011-01-06 06:26:23 UTC
  • mto: This revision was merged to the branch mainline in revision 5612.
  • Revision ID: andrew.bennetts@canonical.com-20110106062623-43tda58mqroybjui
Start of a developer doc describing how fetch works.

Show diffs side-by-side

added added

removed removed

Lines of Context:
25
25
    check,
26
26
    chk_map,
27
27
    config,
 
28
    controldir,
28
29
    debug,
29
 
    errors,
30
30
    fetch as _mod_fetch,
31
31
    fifo_cache,
32
32
    generate_ids,
39
39
    lockdir,
40
40
    lru_cache,
41
41
    osutils,
 
42
    pyutils,
42
43
    revision as _mod_revision,
43
44
    static_tuple,
44
 
    symbol_versioning,
45
45
    trace,
46
46
    tsort,
47
 
    ui,
48
47
    versionedfile,
49
48
    )
50
49
from bzrlib.bundle import serializer
53
52
from bzrlib.testament import Testament
54
53
""")
55
54
 
 
55
import sys
 
56
from bzrlib import (
 
57
    errors,
 
58
    registry,
 
59
    symbol_versioning,
 
60
    ui,
 
61
    )
56
62
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
57
63
from bzrlib.inter import InterObject
58
64
from bzrlib.inventory import (
61
67
    ROOT_ID,
62
68
    entry_factory,
63
69
    )
64
 
from bzrlib.lock import _RelockDebugMixin
65
 
from bzrlib import registry
 
70
from bzrlib.recordcounter import RecordCounter
 
71
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
66
72
from bzrlib.trace import (
67
73
    log_exception_quietly, note, mutter, mutter_callsite, warning)
68
74
 
71
77
_deprecation_warning_done = False
72
78
 
73
79
 
 
80
class IsInWriteGroupError(errors.InternalBzrError):
 
81
 
 
82
    _fmt = "May not refresh_data of repo %(repo)s while in a write group."
 
83
 
 
84
    def __init__(self, repo):
 
85
        errors.InternalBzrError.__init__(self, repo=repo)
 
86
 
 
87
 
74
88
class CommitBuilder(object):
75
89
    """Provides an interface to build up a commit.
76
90
 
101
115
 
102
116
        if committer is None:
103
117
            self._committer = self._config.username()
 
118
        elif not isinstance(committer, unicode):
 
119
            self._committer = committer.decode() # throw if non-ascii
104
120
        else:
105
121
            self._committer = committer
106
122
 
231
247
 
232
248
    def _gen_revision_id(self):
233
249
        """Return new revision-id."""
234
 
        return generate_ids.gen_revision_id(self._config.username(),
235
 
                                            self._timestamp)
 
250
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
236
251
 
237
252
    def _generate_revision_if_needed(self):
238
253
        """Create a revision id if None was supplied.
278
293
 
279
294
        :param tree: The tree which is being committed.
280
295
        """
281
 
        # NB: if there are no parents then this method is not called, so no
282
 
        # need to guard on parents having length.
 
296
        if len(self.parents) == 0:
 
297
            raise errors.RootMissing()
283
298
        entry = entry_factory['directory'](tree.path2id(''), '',
284
299
            None)
285
300
        entry.revision = self._new_revision_id
423
438
            else:
424
439
                # we don't need to commit this, because the caller already
425
440
                # determined that an existing revision of this file is
426
 
                # appropriate. If its not being considered for committing then
 
441
                # appropriate. If it's not being considered for committing then
427
442
                # it and all its parents to the root must be unaltered so
428
443
                # no-change against the basis.
429
444
                if ie.revision == self._new_revision_id:
745
760
                    # after iter_changes examines and decides it has changed,
746
761
                    # we will unconditionally record a new version even if some
747
762
                    # other process reverts it while commit is running (with
748
 
                    # the revert happening after iter_changes did it's
 
763
                    # the revert happening after iter_changes did its
749
764
                    # examination).
750
765
                    if change[7][1]:
751
766
                        entry.executable = True
860
875
        # versioned roots do not change unless the tree found a change.
861
876
 
862
877
 
863
 
class RepositoryWriteLockResult(object):
 
878
class RepositoryWriteLockResult(LogicalLockResult):
864
879
    """The result of write locking a repository.
865
880
 
866
881
    :ivar repository_token: The token obtained from the underlying lock, or
869
884
    """
870
885
 
871
886
    def __init__(self, unlock, repository_token):
 
887
        LogicalLockResult.__init__(self, unlock)
872
888
        self.repository_token = repository_token
873
 
        self.unlock = unlock
874
889
 
875
 
    def __str__(self):
 
890
    def __repr__(self):
876
891
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
877
892
            self.unlock)
878
893
 
881
896
# Repositories
882
897
 
883
898
 
884
 
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
 
899
class Repository(_RelockDebugMixin, controldir.ControlComponent):
885
900
    """Repository holding history for one or more branches.
886
901
 
887
902
    The repository holds and retrieves historical information including
934
949
        pointing to .bzr/repository.
935
950
    """
936
951
 
937
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
952
    # What class to use for a CommitBuilder. Often it's simpler to change this
938
953
    # in a Repository class subclass rather than to override
939
954
    # get_commit_builder.
940
955
    _commit_builder_class = CommitBuilder
1035
1050
                " id and insertion revid (%r, %r)"
1036
1051
                % (inv.revision_id, revision_id))
1037
1052
        if inv.root is None:
1038
 
            raise AssertionError()
 
1053
            raise errors.RootMissing()
1039
1054
        return self._add_inventory_checked(revision_id, inv, parents)
1040
1055
 
1041
1056
    def _add_inventory_checked(self, revision_id, inv, parents):
1434
1449
            for repo in self._fallback_repositories:
1435
1450
                repo.lock_read()
1436
1451
            self._refresh_data()
1437
 
        return self
 
1452
        return LogicalLockResult(self.unlock)
1438
1453
 
1439
1454
    def get_physical_lock_status(self):
1440
1455
        return self.control_files.get_physical_lock_status()
1546
1561
        return ret
1547
1562
 
1548
1563
    @needs_read_lock
1549
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1564
    def search_missing_revision_ids(self, other,
 
1565
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
1566
            find_ghosts=True, revision_ids=None, if_present_ids=None):
1550
1567
        """Return the revision ids that other has that this does not.
1551
1568
 
1552
1569
        These are returned in topological order.
1553
1570
 
1554
1571
        revision_id: only return revision ids included by revision_id.
1555
1572
        """
 
1573
        if symbol_versioning.deprecated_passed(revision_id):
 
1574
            symbol_versioning.warn(
 
1575
                'search_missing_revision_ids(revision_id=...) was '
 
1576
                'deprecated in 2.3.  Use revision_ids=[...] instead.',
 
1577
                DeprecationWarning, stacklevel=3)
 
1578
            if revision_ids is not None:
 
1579
                raise AssertionError(
 
1580
                    'revision_ids is mutually exclusive with revision_id')
 
1581
            if revision_id is not None:
 
1582
                revision_ids = [revision_id]
1556
1583
        return InterRepository.get(other, self).search_missing_revision_ids(
1557
 
            revision_id, find_ghosts)
 
1584
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1585
            if_present_ids=if_present_ids)
1558
1586
 
1559
1587
    @staticmethod
1560
1588
    def open(base):
1658
1686
        return missing_keys
1659
1687
 
1660
1688
    def refresh_data(self):
1661
 
        """Re-read any data needed to to synchronise with disk.
 
1689
        """Re-read any data needed to synchronise with disk.
1662
1690
 
1663
1691
        This method is intended to be called after another repository instance
1664
1692
        (such as one used by a smart server) has inserted data into the
1665
 
        repository. It may not be called during a write group, but may be
1666
 
        called at any other time.
 
1693
        repository. On all repositories this will work outside of write groups.
 
1694
        Some repository formats (pack and newer for bzrlib native formats)
 
1695
        support refresh_data inside write groups. If called inside a write
 
1696
        group on a repository that does not support refreshing in a write group
 
1697
        IsInWriteGroupError will be raised.
1667
1698
        """
1668
 
        if self.is_in_write_group():
1669
 
            raise errors.InternalBzrError(
1670
 
                "May not refresh_data while in a write group.")
1671
1699
        self._refresh_data()
1672
1700
 
1673
1701
    def resume_write_group(self, tokens):
1712
1740
                "May not fetch while in a write group.")
1713
1741
        # fast path same-url fetch operations
1714
1742
        # TODO: lift out to somewhere common with RemoteRepository
1715
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1743
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1716
1744
        if (self.has_same_location(source)
1717
1745
            and fetch_spec is None
1718
1746
            and self._has_same_fallbacks(source)):
2500
2528
            ancestors will be traversed.
2501
2529
        """
2502
2530
        graph = self.get_graph()
2503
 
        next_id = revision_id
2504
 
        while True:
2505
 
            if next_id in (None, _mod_revision.NULL_REVISION):
2506
 
                return
2507
 
            try:
2508
 
                parents = graph.get_parent_map([next_id])[next_id]
2509
 
            except KeyError:
2510
 
                raise errors.RevisionNotPresent(next_id, self)
2511
 
            yield next_id
2512
 
            if len(parents) == 0:
2513
 
                return
2514
 
            else:
2515
 
                next_id = parents[0]
 
2531
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2532
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2516
2533
 
2517
2534
    def is_shared(self):
2518
2535
        """Return True if this repository is flagged as a shared repository."""
2619
2636
        types it should be a no-op that just returns.
2620
2637
 
2621
2638
        This stub method does not require a lock, but subclasses should use
2622
 
        @needs_write_lock as this is a long running call its reasonable to
 
2639
        @needs_write_lock as this is a long running call it's reasonable to
2623
2640
        implicitly lock for the user.
2624
2641
 
2625
2642
        :param hint: If not supplied, the whole repository is packed.
2825
2842
            % (name, from_module),
2826
2843
            DeprecationWarning,
2827
2844
            stacklevel=2)
2828
 
        m = __import__(from_module, globals(), locals(), [name])
2829
2845
        try:
2830
 
            return getattr(m, name)
 
2846
            return pyutils.get_named_object(from_module, name)
2831
2847
        except AttributeError:
2832
2848
            raise AttributeError('module %s has no name %s'
2833
 
                    % (m, name))
 
2849
                    % (sys.modules[from_module], name))
2834
2850
    globals()[name] = _deprecated_repository_forwarder
2835
2851
 
2836
2852
for _name in [
3349
3365
    'bzrlib.repofmt.pack_repo',
3350
3366
    'RepositoryFormatKnitPack6RichRoot',
3351
3367
    )
 
3368
format_registry.register_lazy(
 
3369
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3370
    'bzrlib.repofmt.groupcompress_repo',
 
3371
    'RepositoryFormat2a',
 
3372
    )
3352
3373
 
3353
3374
# Development formats.
3354
 
# Obsolete but kept pending a CHK based subtree format.
 
3375
# Check their docstrings to see if/when they are obsolete.
3355
3376
format_registry.register_lazy(
3356
3377
    ("Bazaar development format 2 with subtree support "
3357
3378
        "(needs bzr.dev from before 1.8)\n"),
3358
3379
    'bzrlib.repofmt.pack_repo',
3359
3380
    'RepositoryFormatPackDevelopment2Subtree',
3360
3381
    )
3361
 
 
3362
 
# 1.14->1.16 go below here
3363
 
format_registry.register_lazy(
3364
 
    'Bazaar development format - group compression and chk inventory'
3365
 
        ' (needs bzr.dev from 1.14)\n',
3366
 
    'bzrlib.repofmt.groupcompress_repo',
3367
 
    'RepositoryFormatCHK1',
3368
 
    )
3369
 
 
3370
 
format_registry.register_lazy(
3371
 
    'Bazaar development format - chk repository with bencode revision '
3372
 
        'serialization (needs bzr.dev from 1.16)\n',
3373
 
    'bzrlib.repofmt.groupcompress_repo',
3374
 
    'RepositoryFormatCHK2',
3375
 
    )
3376
 
format_registry.register_lazy(
3377
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3378
 
    'bzrlib.repofmt.groupcompress_repo',
3379
 
    'RepositoryFormat2a',
 
3382
format_registry.register_lazy(
 
3383
    'Bazaar development format 8\n',
 
3384
    'bzrlib.repofmt.groupcompress_repo',
 
3385
    'RepositoryFormat2aSubtree',
3380
3386
    )
3381
3387
 
3382
3388
 
3437
3443
                               fetch_spec=fetch_spec,
3438
3444
                               find_ghosts=find_ghosts)
3439
3445
 
3440
 
    def _walk_to_common_revisions(self, revision_ids):
 
3446
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
3441
3447
        """Walk out from revision_ids in source to revisions target has.
3442
3448
 
3443
3449
        :param revision_ids: The start point for the search.
3445
3451
        """
3446
3452
        target_graph = self.target.get_graph()
3447
3453
        revision_ids = frozenset(revision_ids)
 
3454
        if if_present_ids:
 
3455
            all_wanted_revs = revision_ids.union(if_present_ids)
 
3456
        else:
 
3457
            all_wanted_revs = revision_ids
3448
3458
        missing_revs = set()
3449
3459
        source_graph = self.source.get_graph()
3450
3460
        # ensure we don't pay silly lookup costs.
3451
 
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
 
3461
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
3452
3462
        null_set = frozenset([_mod_revision.NULL_REVISION])
3453
3463
        searcher_exhausted = False
3454
3464
        while True:
3490
3500
        return searcher.get_result()
3491
3501
 
3492
3502
    @needs_read_lock
3493
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3503
    def search_missing_revision_ids(self,
 
3504
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
3505
            find_ghosts=True, revision_ids=None, if_present_ids=None):
3494
3506
        """Return the revision ids that source has that target does not.
3495
3507
 
3496
3508
        :param revision_id: only return revision ids included by this
3497
 
                            revision_id.
 
3509
            revision_id.
 
3510
        :param revision_ids: return revision ids included by these
 
3511
            revision_ids.  NoSuchRevision will be raised if any of these
 
3512
            revisions are not present.
 
3513
        :param if_present_ids: like revision_ids, but will not cause
 
3514
            NoSuchRevision if any of these are absent, instead they will simply
 
3515
            not be in the result.  This is useful for e.g. finding revisions
 
3516
            to fetch for tags, which may reference absent revisions.
3498
3517
        :param find_ghosts: If True find missing revisions in deep history
3499
3518
            rather than just finding the surface difference.
3500
3519
        :return: A bzrlib.graph.SearchResult.
3501
3520
        """
 
3521
        if symbol_versioning.deprecated_passed(revision_id):
 
3522
            symbol_versioning.warn(
 
3523
                'search_missing_revision_ids(revision_id=...) was '
 
3524
                'deprecated in 2.3.  Use revision_ids=[...] instead.',
 
3525
                DeprecationWarning, stacklevel=2)
 
3526
            if revision_ids is not None:
 
3527
                raise AssertionError(
 
3528
                    'revision_ids is mutually exclusive with revision_id')
 
3529
            if revision_id is not None:
 
3530
                revision_ids = [revision_id]
 
3531
        del revision_id
3502
3532
        # stop searching at found target revisions.
3503
 
        if not find_ghosts and revision_id is not None:
3504
 
            return self._walk_to_common_revisions([revision_id])
 
3533
        if not find_ghosts and (revision_ids is not None or if_present_ids is
 
3534
                not None):
 
3535
            return self._walk_to_common_revisions(revision_ids,
 
3536
                    if_present_ids=if_present_ids)
3505
3537
        # generic, possibly worst case, slow code path.
3506
3538
        target_ids = set(self.target.all_revision_ids())
3507
 
        if revision_id is not None:
3508
 
            source_ids = self.source.get_ancestry(revision_id)
3509
 
            if source_ids[0] is not None:
3510
 
                raise AssertionError()
3511
 
            source_ids.pop(0)
3512
 
        else:
3513
 
            source_ids = self.source.all_revision_ids()
 
3539
        source_ids = self._present_source_revisions_for(
 
3540
            revision_ids, if_present_ids)
3514
3541
        result_set = set(source_ids).difference(target_ids)
3515
3542
        return self.source.revision_ids_to_search_result(result_set)
3516
3543
 
 
3544
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
 
3545
        """Returns set of all revisions in ancestry of revision_ids present in
 
3546
        the source repo.
 
3547
 
 
3548
        :param revision_ids: if None, all revisions in source are returned.
 
3549
        :param if_present_ids: like revision_ids, but if any/all of these are
 
3550
            absent no error is raised.
 
3551
        """
 
3552
        if revision_ids is not None or if_present_ids is not None:
 
3553
            # First, ensure all specified revisions exist.  Callers expect
 
3554
            # NoSuchRevision when they pass absent revision_ids here.
 
3555
            if revision_ids is None:
 
3556
                revision_ids = set()
 
3557
            if if_present_ids is None:
 
3558
                if_present_ids = set()
 
3559
            revision_ids = set(revision_ids)
 
3560
            if_present_ids = set(if_present_ids)
 
3561
            all_wanted_ids = revision_ids.union(if_present_ids)
 
3562
            graph = self.source.get_graph()
 
3563
            present_revs = set(graph.get_parent_map(all_wanted_ids))
 
3564
            missing = revision_ids.difference(present_revs)
 
3565
            if missing:
 
3566
                raise errors.NoSuchRevision(self.source, missing.pop())
 
3567
            found_ids = all_wanted_ids.intersection(present_revs)
 
3568
            source_ids = [rev_id for (rev_id, parents) in
 
3569
                          graph.iter_ancestry(found_ids)
 
3570
                          if rev_id != _mod_revision.NULL_REVISION
 
3571
                          and parents is not None]
 
3572
        else:
 
3573
            source_ids = self.source.all_revision_ids()
 
3574
        return set(source_ids)
 
3575
 
3517
3576
    @staticmethod
3518
3577
    def _same_model(source, target):
3519
3578
        """True if source and target have the same data representation.
3560
3619
        return InterRepository._same_model(source, target)
3561
3620
 
3562
3621
 
3563
 
class InterWeaveRepo(InterSameDataRepository):
3564
 
    """Optimised code paths between Weave based repositories.
3565
 
 
3566
 
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
3567
 
    implemented lazy inter-object optimisation.
3568
 
    """
3569
 
 
3570
 
    @classmethod
3571
 
    def _get_repo_format_to_test(self):
3572
 
        from bzrlib.repofmt import weaverepo
3573
 
        return weaverepo.RepositoryFormat7()
3574
 
 
3575
 
    @staticmethod
3576
 
    def is_compatible(source, target):
3577
 
        """Be compatible with known Weave formats.
3578
 
 
3579
 
        We don't test for the stores being of specific types because that
3580
 
        could lead to confusing results, and there is no need to be
3581
 
        overly general.
3582
 
        """
3583
 
        from bzrlib.repofmt.weaverepo import (
3584
 
                RepositoryFormat5,
3585
 
                RepositoryFormat6,
3586
 
                RepositoryFormat7,
3587
 
                )
3588
 
        try:
3589
 
            return (isinstance(source._format, (RepositoryFormat5,
3590
 
                                                RepositoryFormat6,
3591
 
                                                RepositoryFormat7)) and
3592
 
                    isinstance(target._format, (RepositoryFormat5,
3593
 
                                                RepositoryFormat6,
3594
 
                                                RepositoryFormat7)))
3595
 
        except AttributeError:
3596
 
            return False
3597
 
 
3598
 
    @needs_write_lock
3599
 
    def copy_content(self, revision_id=None):
3600
 
        """See InterRepository.copy_content()."""
3601
 
        # weave specific optimised path:
3602
 
        try:
3603
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3604
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
3605
 
            pass
3606
 
        # FIXME do not peek!
3607
 
        if self.source._transport.listable():
3608
 
            pb = ui.ui_factory.nested_progress_bar()
3609
 
            try:
3610
 
                self.target.texts.insert_record_stream(
3611
 
                    self.source.texts.get_record_stream(
3612
 
                        self.source.texts.keys(), 'topological', False))
3613
 
                pb.update('Copying inventory', 0, 1)
3614
 
                self.target.inventories.insert_record_stream(
3615
 
                    self.source.inventories.get_record_stream(
3616
 
                        self.source.inventories.keys(), 'topological', False))
3617
 
                self.target.signatures.insert_record_stream(
3618
 
                    self.source.signatures.get_record_stream(
3619
 
                        self.source.signatures.keys(),
3620
 
                        'unordered', True))
3621
 
                self.target.revisions.insert_record_stream(
3622
 
                    self.source.revisions.get_record_stream(
3623
 
                        self.source.revisions.keys(),
3624
 
                        'topological', True))
3625
 
            finally:
3626
 
                pb.finished()
3627
 
        else:
3628
 
            self.target.fetch(self.source, revision_id=revision_id)
3629
 
 
3630
 
    @needs_read_lock
3631
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3632
 
        """See InterRepository.missing_revision_ids()."""
3633
 
        # we want all revisions to satisfy revision_id in source.
3634
 
        # but we don't want to stat every file here and there.
3635
 
        # we want then, all revisions other needs to satisfy revision_id
3636
 
        # checked, but not those that we have locally.
3637
 
        # so the first thing is to get a subset of the revisions to
3638
 
        # satisfy revision_id in source, and then eliminate those that
3639
 
        # we do already have.
3640
 
        # this is slow on high latency connection to self, but as this
3641
 
        # disk format scales terribly for push anyway due to rewriting
3642
 
        # inventory.weave, this is considered acceptable.
3643
 
        # - RBC 20060209
3644
 
        if revision_id is not None:
3645
 
            source_ids = self.source.get_ancestry(revision_id)
3646
 
            if source_ids[0] is not None:
3647
 
                raise AssertionError()
3648
 
            source_ids.pop(0)
3649
 
        else:
3650
 
            source_ids = self.source._all_possible_ids()
3651
 
        source_ids_set = set(source_ids)
3652
 
        # source_ids is the worst possible case we may need to pull.
3653
 
        # now we want to filter source_ids against what we actually
3654
 
        # have in target, but don't try to check for existence where we know
3655
 
        # we do not have a revision as that would be pointless.
3656
 
        target_ids = set(self.target._all_possible_ids())
3657
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3658
 
        actually_present_revisions = set(
3659
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3660
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3661
 
        if revision_id is not None:
3662
 
            # we used get_ancestry to determine source_ids then we are assured all
3663
 
            # revisions referenced are present as they are installed in topological order.
3664
 
            # and the tip revision was validated by get_ancestry.
3665
 
            result_set = required_revisions
3666
 
        else:
3667
 
            # if we just grabbed the possibly available ids, then
3668
 
            # we only have an estimate of whats available and need to validate
3669
 
            # that against the revision records.
3670
 
            result_set = set(
3671
 
                self.source._eliminate_revisions_not_present(required_revisions))
3672
 
        return self.source.revision_ids_to_search_result(result_set)
3673
 
 
3674
 
 
3675
 
class InterKnitRepo(InterSameDataRepository):
3676
 
    """Optimised code paths between Knit based repositories."""
3677
 
 
3678
 
    @classmethod
3679
 
    def _get_repo_format_to_test(self):
3680
 
        from bzrlib.repofmt import knitrepo
3681
 
        return knitrepo.RepositoryFormatKnit1()
3682
 
 
3683
 
    @staticmethod
3684
 
    def is_compatible(source, target):
3685
 
        """Be compatible with known Knit formats.
3686
 
 
3687
 
        We don't test for the stores being of specific types because that
3688
 
        could lead to confusing results, and there is no need to be
3689
 
        overly general.
3690
 
        """
3691
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
3692
 
        try:
3693
 
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
3694
 
                isinstance(target._format, RepositoryFormatKnit))
3695
 
        except AttributeError:
3696
 
            return False
3697
 
        return are_knits and InterRepository._same_model(source, target)
3698
 
 
3699
 
    @needs_read_lock
3700
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3701
 
        """See InterRepository.missing_revision_ids()."""
3702
 
        if revision_id is not None:
3703
 
            source_ids = self.source.get_ancestry(revision_id)
3704
 
            if source_ids[0] is not None:
3705
 
                raise AssertionError()
3706
 
            source_ids.pop(0)
3707
 
        else:
3708
 
            source_ids = self.source.all_revision_ids()
3709
 
        source_ids_set = set(source_ids)
3710
 
        # source_ids is the worst possible case we may need to pull.
3711
 
        # now we want to filter source_ids against what we actually
3712
 
        # have in target, but don't try to check for existence where we know
3713
 
        # we do not have a revision as that would be pointless.
3714
 
        target_ids = set(self.target.all_revision_ids())
3715
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3716
 
        actually_present_revisions = set(
3717
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3718
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3719
 
        if revision_id is not None:
3720
 
            # we used get_ancestry to determine source_ids then we are assured all
3721
 
            # revisions referenced are present as they are installed in topological order.
3722
 
            # and the tip revision was validated by get_ancestry.
3723
 
            result_set = required_revisions
3724
 
        else:
3725
 
            # if we just grabbed the possibly available ids, then
3726
 
            # we only have an estimate of whats available and need to validate
3727
 
            # that against the revision records.
3728
 
            result_set = set(
3729
 
                self.source._eliminate_revisions_not_present(required_revisions))
3730
 
        return self.source.revision_ids_to_search_result(result_set)
3731
 
 
3732
 
 
3733
3622
class InterDifferingSerializer(InterRepository):
3734
3623
 
3735
3624
    @classmethod
3837
3726
                basis_id, delta, current_revision_id, parents_parents)
3838
3727
            cache[current_revision_id] = parent_tree
3839
3728
 
3840
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3729
    def _fetch_batch(self, revision_ids, basis_id, cache):
3841
3730
        """Fetch across a few revisions.
3842
3731
 
3843
3732
        :param revision_ids: The revisions to copy
3844
3733
        :param basis_id: The revision_id of a tree that must be in cache, used
3845
3734
            as a basis for delta when no other base is available
3846
3735
        :param cache: A cache of RevisionTrees that we can use.
3847
 
        :param a_graph: A Graph object to determine the heads() of the
3848
 
            rich-root data stream.
3849
3736
        :return: The revision_id of the last converted tree. The RevisionTree
3850
3737
            for it will be in cache
3851
3738
        """
3919
3806
        if root_keys_to_create:
3920
3807
            root_stream = _mod_fetch._new_root_data_stream(
3921
3808
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3922
 
                self.source, graph=a_graph)
 
3809
                self.source)
3923
3810
            to_texts.insert_record_stream(root_stream)
3924
3811
        to_texts.insert_record_stream(from_texts.get_record_stream(
3925
3812
            text_keys, self.target._format._fetch_order,
3982
3869
        cache[basis_id] = basis_tree
3983
3870
        del basis_tree # We don't want to hang on to it here
3984
3871
        hints = []
3985
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3986
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3987
 
                                                            revision_ids)
3988
 
        else:
3989
 
            a_graph = None
 
3872
        a_graph = None
3990
3873
 
3991
3874
        for offset in range(0, len(revision_ids), batch_size):
3992
3875
            self.target.start_write_group()
3994
3877
                pb.update('Transferring revisions', offset,
3995
3878
                          len(revision_ids))
3996
3879
                batch = revision_ids[offset:offset+batch_size]
3997
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
3998
 
                                             a_graph=a_graph)
 
3880
                basis_id = self._fetch_batch(batch, basis_id, cache)
3999
3881
            except:
4000
3882
                self.source._safe_to_return_from_cache = False
4001
3883
                self.target.abort_write_group()
4014
3896
            fetch_spec=None):
4015
3897
        """See InterRepository.fetch()."""
4016
3898
        if fetch_spec is not None:
4017
 
            raise AssertionError("Not implemented yet...")
 
3899
            if (isinstance(fetch_spec, graph.NotInOtherForRevs) and
 
3900
                    len(fetch_spec.required_ids) == 1 and not
 
3901
                    fetch_spec.if_present_ids):
 
3902
                revision_id = list(fetch_spec.required_ids)[0]
 
3903
                del fetch_spec
 
3904
            else:
 
3905
                raise AssertionError("Not implemented yet...")
4018
3906
        ui.ui_factory.warn_experimental_format_fetch(self)
4019
3907
        if (not self.source.supports_rich_root()
4020
3908
            and self.target.supports_rich_root()):
4027
3915
            ui.ui_factory.show_user_warning('cross_format_fetch',
4028
3916
                from_format=self.source._format,
4029
3917
                to_format=self.target._format)
 
3918
        if revision_id:
 
3919
            search_revision_ids = [revision_id]
 
3920
        else:
 
3921
            search_revision_ids = None
4030
3922
        revision_ids = self.target.search_missing_revision_ids(self.source,
4031
 
            revision_id, find_ghosts=find_ghosts).get_keys()
 
3923
            revision_ids=search_revision_ids, find_ghosts=find_ghosts).get_keys()
4032
3924
        if not revision_ids:
4033
3925
            return 0, 0
4034
3926
        revision_ids = tsort.topo_sort(
4067
3959
            basis_id = first_rev.parent_ids[0]
4068
3960
            # only valid as a basis if the target has it
4069
3961
            self.target.get_revision(basis_id)
4070
 
            # Try to get a basis tree - if its a ghost it will hit the
 
3962
            # Try to get a basis tree - if it's a ghost it will hit the
4071
3963
            # NoSuchRevision case.
4072
3964
            basis_tree = self.source.revision_tree(basis_id)
4073
3965
        except (IndexError, errors.NoSuchRevision):
4078
3970
 
4079
3971
InterRepository.register_optimiser(InterDifferingSerializer)
4080
3972
InterRepository.register_optimiser(InterSameDataRepository)
4081
 
InterRepository.register_optimiser(InterWeaveRepo)
4082
 
InterRepository.register_optimiser(InterKnitRepo)
4083
3973
 
4084
3974
 
4085
3975
class CopyConverter(object):
4273
4163
                is_resume = False
4274
4164
            try:
4275
4165
                # locked_insert_stream performs a commit|suspend.
4276
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4166
                return self._locked_insert_stream(stream, src_format,
 
4167
                    is_resume)
4277
4168
            except:
4278
4169
                self.target_repo.abort_write_group(suppress_errors=True)
4279
4170
                raise
4326
4217
                # required if the serializers are different only in terms of
4327
4218
                # the inventory.
4328
4219
                if src_serializer == to_serializer:
4329
 
                    self.target_repo.revisions.insert_record_stream(
4330
 
                        substream)
 
4220
                    self.target_repo.revisions.insert_record_stream(substream)
4331
4221
                else:
4332
4222
                    self._extract_and_insert_revisions(substream,
4333
4223
                        src_serializer)
4441
4331
        """Create a StreamSource streaming from from_repository."""
4442
4332
        self.from_repository = from_repository
4443
4333
        self.to_format = to_format
 
4334
        self._record_counter = RecordCounter()
4444
4335
 
4445
4336
    def delta_on_metadata(self):
4446
4337
        """Return True if delta's are permitted on metadata streams.