~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Andrew Bennetts
  • Date: 2011-02-25 08:45:27 UTC
  • mto: This revision was merged to the branch mainline in revision 5695.
  • Revision ID: andrew.bennetts@canonical.com-20110225084527-0ucp7p00d00hoqon
Add another test.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
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
from bzrlib import (
 
56
    errors,
 
57
    registry,
 
58
    symbol_versioning,
 
59
    ui,
 
60
    )
56
61
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
57
62
from bzrlib.inter import InterObject
58
63
from bzrlib.inventory import (
61
66
    ROOT_ID,
62
67
    entry_factory,
63
68
    )
64
 
from bzrlib.lock import _RelockDebugMixin
65
 
from bzrlib import registry
 
69
from bzrlib.recordcounter import RecordCounter
 
70
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
66
71
from bzrlib.trace import (
67
72
    log_exception_quietly, note, mutter, mutter_callsite, warning)
68
73
 
71
76
_deprecation_warning_done = False
72
77
 
73
78
 
 
79
class IsInWriteGroupError(errors.InternalBzrError):
 
80
 
 
81
    _fmt = "May not refresh_data of repo %(repo)s while in a write group."
 
82
 
 
83
    def __init__(self, repo):
 
84
        errors.InternalBzrError.__init__(self, repo=repo)
 
85
 
 
86
 
74
87
class CommitBuilder(object):
75
88
    """Provides an interface to build up a commit.
76
89
 
101
114
 
102
115
        if committer is None:
103
116
            self._committer = self._config.username()
 
117
        elif not isinstance(committer, unicode):
 
118
            self._committer = committer.decode() # throw if non-ascii
104
119
        else:
105
120
            self._committer = committer
106
121
 
160
175
            self._validate_unicode_text(value,
161
176
                                        'revision property (%s)' % (key,))
162
177
 
 
178
    def _ensure_fallback_inventories(self):
 
179
        """Ensure that appropriate inventories are available.
 
180
 
 
181
        This only applies to repositories that are stacked, and is about
 
182
        enusring the stacking invariants. Namely, that for any revision that is
 
183
        present, we either have all of the file content, or we have the parent
 
184
        inventory and the delta file content.
 
185
        """
 
186
        if not self.repository._fallback_repositories:
 
187
            return
 
188
        if not self.repository._format.supports_chks:
 
189
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
190
                " in pre-2a formats. See "
 
191
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
192
        # This is a stacked repo, we need to make sure we have the parent
 
193
        # inventories for the parents.
 
194
        parent_keys = [(p,) for p in self.parents]
 
195
        parent_map = self.repository.inventories._index.get_parent_map(parent_keys)
 
196
        missing_parent_keys = set([pk for pk in parent_keys
 
197
                                       if pk not in parent_map])
 
198
        fallback_repos = list(reversed(self.repository._fallback_repositories))
 
199
        missing_keys = [('inventories', pk[0])
 
200
                        for pk in missing_parent_keys]
 
201
        resume_tokens = []
 
202
        while missing_keys and fallback_repos:
 
203
            fallback_repo = fallback_repos.pop()
 
204
            source = fallback_repo._get_source(self.repository._format)
 
205
            sink = self.repository._get_sink()
 
206
            stream = source.get_stream_for_missing_keys(missing_keys)
 
207
            missing_keys = sink.insert_stream_without_locking(stream,
 
208
                self.repository._format)
 
209
        if missing_keys:
 
210
            raise errors.BzrError('Unable to fill in parent inventories for a'
 
211
                                  ' stacked branch')
 
212
 
163
213
    def commit(self, message):
164
214
        """Make the actual commit.
165
215
 
177
227
        rev.parent_ids = self.parents
178
228
        self.repository.add_revision(self._new_revision_id, rev,
179
229
            self.new_inventory, self._config)
 
230
        self._ensure_fallback_inventories()
180
231
        self.repository.commit_write_group()
181
232
        return self._new_revision_id
182
233
 
231
282
 
232
283
    def _gen_revision_id(self):
233
284
        """Return new revision-id."""
234
 
        return generate_ids.gen_revision_id(self._config.username(),
235
 
                                            self._timestamp)
 
285
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
236
286
 
237
287
    def _generate_revision_if_needed(self):
238
288
        """Create a revision id if None was supplied.
278
328
 
279
329
        :param tree: The tree which is being committed.
280
330
        """
281
 
        # NB: if there are no parents then this method is not called, so no
282
 
        # need to guard on parents having length.
 
331
        if len(self.parents) == 0:
 
332
            raise errors.RootMissing()
283
333
        entry = entry_factory['directory'](tree.path2id(''), '',
284
334
            None)
285
335
        entry.revision = self._new_revision_id
423
473
            else:
424
474
                # we don't need to commit this, because the caller already
425
475
                # determined that an existing revision of this file is
426
 
                # appropriate. If its not being considered for committing then
 
476
                # appropriate. If it's not being considered for committing then
427
477
                # it and all its parents to the root must be unaltered so
428
478
                # no-change against the basis.
429
479
                if ie.revision == self._new_revision_id:
745
795
                    # after iter_changes examines and decides it has changed,
746
796
                    # we will unconditionally record a new version even if some
747
797
                    # other process reverts it while commit is running (with
748
 
                    # the revert happening after iter_changes did it's
 
798
                    # the revert happening after iter_changes did its
749
799
                    # examination).
750
800
                    if change[7][1]:
751
801
                        entry.executable = True
860
910
        # versioned roots do not change unless the tree found a change.
861
911
 
862
912
 
 
913
class RepositoryWriteLockResult(LogicalLockResult):
 
914
    """The result of write locking a repository.
 
915
 
 
916
    :ivar repository_token: The token obtained from the underlying lock, or
 
917
        None.
 
918
    :ivar unlock: A callable which will unlock the lock.
 
919
    """
 
920
 
 
921
    def __init__(self, unlock, repository_token):
 
922
        LogicalLockResult.__init__(self, unlock)
 
923
        self.repository_token = repository_token
 
924
 
 
925
    def __repr__(self):
 
926
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
 
927
            self.unlock)
 
928
 
 
929
 
863
930
######################################################################
864
931
# Repositories
865
932
 
866
933
 
867
 
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
 
934
class Repository(_RelockDebugMixin, controldir.ControlComponent):
868
935
    """Repository holding history for one or more branches.
869
936
 
870
937
    The repository holds and retrieves historical information including
917
984
        pointing to .bzr/repository.
918
985
    """
919
986
 
920
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
987
    # What class to use for a CommitBuilder. Often it's simpler to change this
921
988
    # in a Repository class subclass rather than to override
922
989
    # get_commit_builder.
923
990
    _commit_builder_class = CommitBuilder
1018
1085
                " id and insertion revid (%r, %r)"
1019
1086
                % (inv.revision_id, revision_id))
1020
1087
        if inv.root is None:
1021
 
            raise AssertionError()
 
1088
            raise errors.RootMissing()
1022
1089
        return self._add_inventory_checked(revision_id, inv, parents)
1023
1090
 
1024
1091
    def _add_inventory_checked(self, revision_id, inv, parents):
1376
1443
        data during reads, and allows a 'write_group' to be obtained. Write
1377
1444
        groups must be used for actual data insertion.
1378
1445
 
 
1446
        A token should be passed in if you know that you have locked the object
 
1447
        some other way, and need to synchronise this object's state with that
 
1448
        fact.
 
1449
 
 
1450
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1451
 
1379
1452
        :param token: if this is already locked, then lock_write will fail
1380
1453
            unless the token matches the existing lock.
1381
1454
        :returns: a token if this instance supports tokens, otherwise None.
1384
1457
        :raises MismatchedToken: if the specified token doesn't match the token
1385
1458
            of the existing lock.
1386
1459
        :seealso: start_write_group.
1387
 
 
1388
 
        A token should be passed in if you know that you have locked the object
1389
 
        some other way, and need to synchronise this object's state with that
1390
 
        fact.
1391
 
 
1392
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1460
        :return: A RepositoryWriteLockResult.
1393
1461
        """
1394
1462
        locked = self.is_locked()
1395
 
        result = self.control_files.lock_write(token=token)
 
1463
        token = self.control_files.lock_write(token=token)
1396
1464
        if not locked:
1397
1465
            self._warn_if_deprecated()
1398
1466
            self._note_lock('w')
1400
1468
                # Writes don't affect fallback repos
1401
1469
                repo.lock_read()
1402
1470
            self._refresh_data()
1403
 
        return result
 
1471
        return RepositoryWriteLockResult(self.unlock, token)
1404
1472
 
1405
1473
    def lock_read(self):
 
1474
        """Lock the repository for read operations.
 
1475
 
 
1476
        :return: An object with an unlock method which will release the lock
 
1477
            obtained.
 
1478
        """
1406
1479
        locked = self.is_locked()
1407
1480
        self.control_files.lock_read()
1408
1481
        if not locked:
1411
1484
            for repo in self._fallback_repositories:
1412
1485
                repo.lock_read()
1413
1486
            self._refresh_data()
 
1487
        return LogicalLockResult(self.unlock)
1414
1488
 
1415
1489
    def get_physical_lock_status(self):
1416
1490
        return self.control_files.get_physical_lock_status()
1522
1596
        return ret
1523
1597
 
1524
1598
    @needs_read_lock
1525
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1599
    def search_missing_revision_ids(self, other,
 
1600
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
1601
            find_ghosts=True, revision_ids=None, if_present_ids=None):
1526
1602
        """Return the revision ids that other has that this does not.
1527
1603
 
1528
1604
        These are returned in topological order.
1529
1605
 
1530
1606
        revision_id: only return revision ids included by revision_id.
1531
1607
        """
 
1608
        if symbol_versioning.deprecated_passed(revision_id):
 
1609
            symbol_versioning.warn(
 
1610
                'search_missing_revision_ids(revision_id=...) was '
 
1611
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
1612
                DeprecationWarning, stacklevel=3)
 
1613
            if revision_ids is not None:
 
1614
                raise AssertionError(
 
1615
                    'revision_ids is mutually exclusive with revision_id')
 
1616
            if revision_id is not None:
 
1617
                revision_ids = [revision_id]
1532
1618
        return InterRepository.get(other, self).search_missing_revision_ids(
1533
 
            revision_id, find_ghosts)
 
1619
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1620
            if_present_ids=if_present_ids)
1534
1621
 
1535
1622
    @staticmethod
1536
1623
    def open(base):
1634
1721
        return missing_keys
1635
1722
 
1636
1723
    def refresh_data(self):
1637
 
        """Re-read any data needed to to synchronise with disk.
 
1724
        """Re-read any data needed to synchronise with disk.
1638
1725
 
1639
1726
        This method is intended to be called after another repository instance
1640
1727
        (such as one used by a smart server) has inserted data into the
1641
 
        repository. It may not be called during a write group, but may be
1642
 
        called at any other time.
 
1728
        repository. On all repositories this will work outside of write groups.
 
1729
        Some repository formats (pack and newer for bzrlib native formats)
 
1730
        support refresh_data inside write groups. If called inside a write
 
1731
        group on a repository that does not support refreshing in a write group
 
1732
        IsInWriteGroupError will be raised.
1643
1733
        """
1644
 
        if self.is_in_write_group():
1645
 
            raise errors.InternalBzrError(
1646
 
                "May not refresh_data while in a write group.")
1647
1734
        self._refresh_data()
1648
1735
 
1649
1736
    def resume_write_group(self, tokens):
1658
1745
    def _resume_write_group(self, tokens):
1659
1746
        raise errors.UnsuspendableWriteGroup(self)
1660
1747
 
1661
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1748
    def fetch(self, source, revision_id=None, find_ghosts=False,
1662
1749
            fetch_spec=None):
1663
1750
        """Fetch the content required to construct revision_id from source.
1664
1751
 
1688
1775
                "May not fetch while in a write group.")
1689
1776
        # fast path same-url fetch operations
1690
1777
        # TODO: lift out to somewhere common with RemoteRepository
1691
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1778
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1692
1779
        if (self.has_same_location(source)
1693
1780
            and fetch_spec is None
1694
1781
            and self._has_same_fallbacks(source)):
1698
1785
                not _mod_revision.is_null(revision_id)):
1699
1786
                self.get_revision(revision_id)
1700
1787
            return 0, []
1701
 
        # if there is no specific appropriate InterRepository, this will get
1702
 
        # the InterRepository base class, which raises an
1703
 
        # IncompatibleRepositories when asked to fetch.
1704
1788
        inter = InterRepository.get(source, self)
1705
 
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1789
        return inter.fetch(revision_id=revision_id,
1706
1790
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1707
1791
 
1708
1792
    def create_bundle(self, target, base, fileobj, format=None):
1722
1806
        :param revprops: Optional dictionary of revision properties.
1723
1807
        :param revision_id: Optional revision id.
1724
1808
        """
1725
 
        if self._fallback_repositories:
1726
 
            raise errors.BzrError("Cannot commit from a lightweight checkout "
1727
 
                "to a stacked branch. See "
 
1809
        if self._fallback_repositories and not self._format.supports_chks:
 
1810
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1811
                " in pre-2a formats. See "
1728
1812
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1729
1813
        result = self._commit_builder_class(self, parents, config,
1730
1814
            timestamp, timezone, committer, revprops, revision_id)
2476
2560
            ancestors will be traversed.
2477
2561
        """
2478
2562
        graph = self.get_graph()
2479
 
        next_id = revision_id
2480
 
        while True:
2481
 
            if next_id in (None, _mod_revision.NULL_REVISION):
2482
 
                return
2483
 
            try:
2484
 
                parents = graph.get_parent_map([next_id])[next_id]
2485
 
            except KeyError:
2486
 
                raise errors.RevisionNotPresent(next_id, self)
2487
 
            yield next_id
2488
 
            if len(parents) == 0:
2489
 
                return
2490
 
            else:
2491
 
                next_id = parents[0]
 
2563
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2564
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2492
2565
 
2493
2566
    def is_shared(self):
2494
2567
        """Return True if this repository is flagged as a shared repository."""
2595
2668
        types it should be a no-op that just returns.
2596
2669
 
2597
2670
        This stub method does not require a lock, but subclasses should use
2598
 
        @needs_write_lock as this is a long running call its reasonable to
 
2671
        @needs_write_lock as this is a long running call it's reasonable to
2599
2672
        implicitly lock for the user.
2600
2673
 
2601
2674
        :param hint: If not supplied, the whole repository is packed.
2794
2867
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
2795
2868
 
2796
2869
 
2797
 
# remove these delegates a while after bzr 0.15
2798
 
def __make_delegated(name, from_module):
2799
 
    def _deprecated_repository_forwarder():
2800
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
2801
 
            % (name, from_module),
2802
 
            DeprecationWarning,
2803
 
            stacklevel=2)
2804
 
        m = __import__(from_module, globals(), locals(), [name])
2805
 
        try:
2806
 
            return getattr(m, name)
2807
 
        except AttributeError:
2808
 
            raise AttributeError('module %s has no name %s'
2809
 
                    % (m, name))
2810
 
    globals()[name] = _deprecated_repository_forwarder
2811
 
 
2812
 
for _name in [
2813
 
        'AllInOneRepository',
2814
 
        'WeaveMetaDirRepository',
2815
 
        'PreSplitOutRepositoryFormat',
2816
 
        'RepositoryFormat4',
2817
 
        'RepositoryFormat5',
2818
 
        'RepositoryFormat6',
2819
 
        'RepositoryFormat7',
2820
 
        ]:
2821
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
2822
 
 
2823
 
for _name in [
2824
 
        'KnitRepository',
2825
 
        'RepositoryFormatKnit',
2826
 
        'RepositoryFormatKnit1',
2827
 
        ]:
2828
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
2829
 
 
2830
 
 
2831
2870
def install_revision(repository, rev, revision_tree):
2832
2871
    """Install all revision data into a repository."""
2833
2872
    install_revisions(repository, [(rev, revision_tree, None)])
2965
3004
            control_files)
2966
3005
 
2967
3006
 
 
3007
class RepositoryFormatRegistry(registry.FormatRegistry):
 
3008
    """Repository format registry."""
 
3009
 
 
3010
    def __init__(self, other_registry=None):
 
3011
        super(RepositoryFormatRegistry, self).__init__(other_registry)
 
3012
        self._extra_formats = []
 
3013
 
 
3014
    def register(self, format):
 
3015
        """Register a new repository format."""
 
3016
        super(RepositoryFormatRegistry, self).register(
 
3017
            format.get_format_string(), format)
 
3018
 
 
3019
    def remove(self, format):
 
3020
        """Remove a registered repository format."""
 
3021
        super(RepositoryFormatRegistry, self).remove(
 
3022
            format.get_format_string())
 
3023
 
 
3024
    def register_extra(self, format):
 
3025
        """Register a repository format that can not be used in a metadir.
 
3026
 
 
3027
        This is mainly useful to allow custom repository formats, such as older
 
3028
        Bazaar formats and foreign formats, to be tested.
 
3029
        """
 
3030
        self._extra_formats.append(registry._ObjectGetter(format))
 
3031
 
 
3032
    def remove_extra(self, format):
 
3033
        """Remove an extra repository format.
 
3034
        """
 
3035
        self._extra_formats.remove(registry._ObjectGetter(format))
 
3036
 
 
3037
    def register_extra_lazy(self, module_name, member_name):
 
3038
        """Register a repository format lazily.
 
3039
        """
 
3040
        self._extra_formats.append(
 
3041
            registry._LazyObjectGetter(module_name, member_name))
 
3042
 
 
3043
    def get_default(self):
 
3044
        """Return the current default format."""
 
3045
        from bzrlib import bzrdir
 
3046
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
3047
 
 
3048
    def _get_extra(self):
 
3049
        result = []
 
3050
        for getter in self._extra_formats:
 
3051
            f = getter.get_obj()
 
3052
            if callable(f):
 
3053
                f = f()
 
3054
            result.append(f)
 
3055
        return result
 
3056
 
 
3057
    def _get_all(self):
 
3058
        """Return all repository formats, even those not usable in metadirs.
 
3059
        """
 
3060
        return [self.get(k) for k in self.keys()] + self._get_extra()
 
3061
 
 
3062
 
2968
3063
network_format_registry = registry.FormatRegistry()
2969
3064
"""Registry of formats indexed by their network name.
2970
3065
 
2974
3069
"""
2975
3070
 
2976
3071
 
2977
 
format_registry = registry.FormatRegistry(network_format_registry)
 
3072
format_registry = RepositoryFormatRegistry(network_format_registry)
2978
3073
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
2979
3074
 
2980
3075
This can contain either format instances themselves, or classes/factories that
3052
3147
    supports_tree_reference = None
3053
3148
    # Is the format experimental ?
3054
3149
    experimental = False
 
3150
    # Does this repository format escape funky characters, or does it create files with
 
3151
    # similar names as the versioned files in its contents on disk ?
 
3152
    supports_funky_characters = True
3055
3153
 
3056
3154
    def __repr__(self):
3057
3155
        return "%s()" % self.__class__.__name__
3082
3180
                                            kind='repository')
3083
3181
 
3084
3182
    @classmethod
 
3183
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3085
3184
    def register_format(klass, format):
3086
 
        format_registry.register(format.get_format_string(), format)
 
3185
        format_registry.register(format)
3087
3186
 
3088
3187
    @classmethod
 
3188
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3089
3189
    def unregister_format(klass, format):
3090
 
        format_registry.remove(format.get_format_string())
 
3190
        format_registry.remove(format)
3091
3191
 
3092
3192
    @classmethod
 
3193
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3093
3194
    def get_default_format(klass):
3094
3195
        """Return the current default format."""
3095
 
        from bzrlib import bzrdir
3096
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
3196
        return format_registry.get_default()
3097
3197
 
3098
3198
    def get_format_string(self):
3099
3199
        """Return the ASCII format string that identifies this format.
3252
3352
    'RepositoryFormat6',
3253
3353
)
3254
3354
 
 
3355
format_registry.register_extra_lazy(
 
3356
    'bzrlib.repofmt.weaverepo',
 
3357
    'RepositoryFormat4')
 
3358
format_registry.register_extra_lazy(
 
3359
    'bzrlib.repofmt.weaverepo',
 
3360
    'RepositoryFormat5')
 
3361
format_registry.register_extra_lazy(
 
3362
    'bzrlib.repofmt.weaverepo',
 
3363
    'RepositoryFormat6')
 
3364
 
3255
3365
# formats which have no format string are not discoverable or independently
3256
3366
# creatable on disk, so are not registered in format_registry.  They're
3257
3367
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
3325
3435
    'bzrlib.repofmt.pack_repo',
3326
3436
    'RepositoryFormatKnitPack6RichRoot',
3327
3437
    )
 
3438
format_registry.register_lazy(
 
3439
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3440
    'bzrlib.repofmt.groupcompress_repo',
 
3441
    'RepositoryFormat2a',
 
3442
    )
3328
3443
 
3329
3444
# Development formats.
3330
 
# Obsolete but kept pending a CHK based subtree format.
 
3445
# Check their docstrings to see if/when they are obsolete.
3331
3446
format_registry.register_lazy(
3332
3447
    ("Bazaar development format 2 with subtree support "
3333
3448
        "(needs bzr.dev from before 1.8)\n"),
3334
3449
    'bzrlib.repofmt.pack_repo',
3335
3450
    'RepositoryFormatPackDevelopment2Subtree',
3336
3451
    )
3337
 
 
3338
 
# 1.14->1.16 go below here
3339
 
format_registry.register_lazy(
3340
 
    'Bazaar development format - group compression and chk inventory'
3341
 
        ' (needs bzr.dev from 1.14)\n',
3342
 
    'bzrlib.repofmt.groupcompress_repo',
3343
 
    'RepositoryFormatCHK1',
3344
 
    )
3345
 
 
3346
 
format_registry.register_lazy(
3347
 
    'Bazaar development format - chk repository with bencode revision '
3348
 
        'serialization (needs bzr.dev from 1.16)\n',
3349
 
    'bzrlib.repofmt.groupcompress_repo',
3350
 
    'RepositoryFormatCHK2',
3351
 
    )
3352
 
format_registry.register_lazy(
3353
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3354
 
    'bzrlib.repofmt.groupcompress_repo',
3355
 
    'RepositoryFormat2a',
 
3452
format_registry.register_lazy(
 
3453
    'Bazaar development format 8\n',
 
3454
    'bzrlib.repofmt.groupcompress_repo',
 
3455
    'RepositoryFormat2aSubtree',
3356
3456
    )
3357
3457
 
3358
3458
 
3389
3489
        self.target.fetch(self.source, revision_id=revision_id)
3390
3490
 
3391
3491
    @needs_write_lock
3392
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3492
    def fetch(self, revision_id=None, find_ghosts=False,
3393
3493
            fetch_spec=None):
3394
3494
        """Fetch the content required to construct revision_id.
3395
3495
 
3397
3497
 
3398
3498
        :param revision_id: if None all content is copied, if NULL_REVISION no
3399
3499
                            content is copied.
3400
 
        :param pb: ignored.
3401
3500
        :return: None.
3402
3501
        """
3403
3502
        ui.ui_factory.warn_experimental_format_fetch(self)
3413
3512
                               fetch_spec=fetch_spec,
3414
3513
                               find_ghosts=find_ghosts)
3415
3514
 
3416
 
    def _walk_to_common_revisions(self, revision_ids):
 
3515
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
3417
3516
        """Walk out from revision_ids in source to revisions target has.
3418
3517
 
3419
3518
        :param revision_ids: The start point for the search.
3421
3520
        """
3422
3521
        target_graph = self.target.get_graph()
3423
3522
        revision_ids = frozenset(revision_ids)
 
3523
        if if_present_ids:
 
3524
            all_wanted_revs = revision_ids.union(if_present_ids)
 
3525
        else:
 
3526
            all_wanted_revs = revision_ids
3424
3527
        missing_revs = set()
3425
3528
        source_graph = self.source.get_graph()
3426
3529
        # ensure we don't pay silly lookup costs.
3427
 
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
 
3530
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
3428
3531
        null_set = frozenset([_mod_revision.NULL_REVISION])
3429
3532
        searcher_exhausted = False
3430
3533
        while True:
3466
3569
        return searcher.get_result()
3467
3570
 
3468
3571
    @needs_read_lock
3469
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3572
    def search_missing_revision_ids(self,
 
3573
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
3574
            find_ghosts=True, revision_ids=None, if_present_ids=None):
3470
3575
        """Return the revision ids that source has that target does not.
3471
3576
 
3472
3577
        :param revision_id: only return revision ids included by this
3473
 
                            revision_id.
 
3578
            revision_id.
 
3579
        :param revision_ids: return revision ids included by these
 
3580
            revision_ids.  NoSuchRevision will be raised if any of these
 
3581
            revisions are not present.
 
3582
        :param if_present_ids: like revision_ids, but will not cause
 
3583
            NoSuchRevision if any of these are absent, instead they will simply
 
3584
            not be in the result.  This is useful for e.g. finding revisions
 
3585
            to fetch for tags, which may reference absent revisions.
3474
3586
        :param find_ghosts: If True find missing revisions in deep history
3475
3587
            rather than just finding the surface difference.
3476
3588
        :return: A bzrlib.graph.SearchResult.
3477
3589
        """
 
3590
        if symbol_versioning.deprecated_passed(revision_id):
 
3591
            symbol_versioning.warn(
 
3592
                'search_missing_revision_ids(revision_id=...) was '
 
3593
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
3594
                DeprecationWarning, stacklevel=2)
 
3595
            if revision_ids is not None:
 
3596
                raise AssertionError(
 
3597
                    'revision_ids is mutually exclusive with revision_id')
 
3598
            if revision_id is not None:
 
3599
                revision_ids = [revision_id]
 
3600
        del revision_id
3478
3601
        # stop searching at found target revisions.
3479
 
        if not find_ghosts and revision_id is not None:
3480
 
            return self._walk_to_common_revisions([revision_id])
 
3602
        if not find_ghosts and (revision_ids is not None or if_present_ids is
 
3603
                not None):
 
3604
            return self._walk_to_common_revisions(revision_ids,
 
3605
                    if_present_ids=if_present_ids)
3481
3606
        # generic, possibly worst case, slow code path.
3482
3607
        target_ids = set(self.target.all_revision_ids())
3483
 
        if revision_id is not None:
3484
 
            source_ids = self.source.get_ancestry(revision_id)
3485
 
            if source_ids[0] is not None:
3486
 
                raise AssertionError()
3487
 
            source_ids.pop(0)
3488
 
        else:
3489
 
            source_ids = self.source.all_revision_ids()
 
3608
        source_ids = self._present_source_revisions_for(
 
3609
            revision_ids, if_present_ids)
3490
3610
        result_set = set(source_ids).difference(target_ids)
3491
3611
        return self.source.revision_ids_to_search_result(result_set)
3492
3612
 
 
3613
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
 
3614
        """Returns set of all revisions in ancestry of revision_ids present in
 
3615
        the source repo.
 
3616
 
 
3617
        :param revision_ids: if None, all revisions in source are returned.
 
3618
        :param if_present_ids: like revision_ids, but if any/all of these are
 
3619
            absent no error is raised.
 
3620
        """
 
3621
        if revision_ids is not None or if_present_ids is not None:
 
3622
            # First, ensure all specified revisions exist.  Callers expect
 
3623
            # NoSuchRevision when they pass absent revision_ids here.
 
3624
            if revision_ids is None:
 
3625
                revision_ids = set()
 
3626
            if if_present_ids is None:
 
3627
                if_present_ids = set()
 
3628
            revision_ids = set(revision_ids)
 
3629
            if_present_ids = set(if_present_ids)
 
3630
            all_wanted_ids = revision_ids.union(if_present_ids)
 
3631
            graph = self.source.get_graph()
 
3632
            present_revs = set(graph.get_parent_map(all_wanted_ids))
 
3633
            missing = revision_ids.difference(present_revs)
 
3634
            if missing:
 
3635
                raise errors.NoSuchRevision(self.source, missing.pop())
 
3636
            found_ids = all_wanted_ids.intersection(present_revs)
 
3637
            source_ids = [rev_id for (rev_id, parents) in
 
3638
                          graph.iter_ancestry(found_ids)
 
3639
                          if rev_id != _mod_revision.NULL_REVISION
 
3640
                          and parents is not None]
 
3641
        else:
 
3642
            source_ids = self.source.all_revision_ids()
 
3643
        return set(source_ids)
 
3644
 
3493
3645
    @staticmethod
3494
3646
    def _same_model(source, target):
3495
3647
        """True if source and target have the same data representation.
3536
3688
        return InterRepository._same_model(source, target)
3537
3689
 
3538
3690
 
3539
 
class InterWeaveRepo(InterSameDataRepository):
3540
 
    """Optimised code paths between Weave based repositories.
3541
 
 
3542
 
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
3543
 
    implemented lazy inter-object optimisation.
3544
 
    """
3545
 
 
3546
 
    @classmethod
3547
 
    def _get_repo_format_to_test(self):
3548
 
        from bzrlib.repofmt import weaverepo
3549
 
        return weaverepo.RepositoryFormat7()
3550
 
 
3551
 
    @staticmethod
3552
 
    def is_compatible(source, target):
3553
 
        """Be compatible with known Weave formats.
3554
 
 
3555
 
        We don't test for the stores being of specific types because that
3556
 
        could lead to confusing results, and there is no need to be
3557
 
        overly general.
3558
 
        """
3559
 
        from bzrlib.repofmt.weaverepo import (
3560
 
                RepositoryFormat5,
3561
 
                RepositoryFormat6,
3562
 
                RepositoryFormat7,
3563
 
                )
3564
 
        try:
3565
 
            return (isinstance(source._format, (RepositoryFormat5,
3566
 
                                                RepositoryFormat6,
3567
 
                                                RepositoryFormat7)) and
3568
 
                    isinstance(target._format, (RepositoryFormat5,
3569
 
                                                RepositoryFormat6,
3570
 
                                                RepositoryFormat7)))
3571
 
        except AttributeError:
3572
 
            return False
3573
 
 
3574
 
    @needs_write_lock
3575
 
    def copy_content(self, revision_id=None):
3576
 
        """See InterRepository.copy_content()."""
3577
 
        # weave specific optimised path:
3578
 
        try:
3579
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3580
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
3581
 
            pass
3582
 
        # FIXME do not peek!
3583
 
        if self.source._transport.listable():
3584
 
            pb = ui.ui_factory.nested_progress_bar()
3585
 
            try:
3586
 
                self.target.texts.insert_record_stream(
3587
 
                    self.source.texts.get_record_stream(
3588
 
                        self.source.texts.keys(), 'topological', False))
3589
 
                pb.update('Copying inventory', 0, 1)
3590
 
                self.target.inventories.insert_record_stream(
3591
 
                    self.source.inventories.get_record_stream(
3592
 
                        self.source.inventories.keys(), 'topological', False))
3593
 
                self.target.signatures.insert_record_stream(
3594
 
                    self.source.signatures.get_record_stream(
3595
 
                        self.source.signatures.keys(),
3596
 
                        'unordered', True))
3597
 
                self.target.revisions.insert_record_stream(
3598
 
                    self.source.revisions.get_record_stream(
3599
 
                        self.source.revisions.keys(),
3600
 
                        'topological', True))
3601
 
            finally:
3602
 
                pb.finished()
3603
 
        else:
3604
 
            self.target.fetch(self.source, revision_id=revision_id)
3605
 
 
3606
 
    @needs_read_lock
3607
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3608
 
        """See InterRepository.missing_revision_ids()."""
3609
 
        # we want all revisions to satisfy revision_id in source.
3610
 
        # but we don't want to stat every file here and there.
3611
 
        # we want then, all revisions other needs to satisfy revision_id
3612
 
        # checked, but not those that we have locally.
3613
 
        # so the first thing is to get a subset of the revisions to
3614
 
        # satisfy revision_id in source, and then eliminate those that
3615
 
        # we do already have.
3616
 
        # this is slow on high latency connection to self, but as this
3617
 
        # disk format scales terribly for push anyway due to rewriting
3618
 
        # inventory.weave, this is considered acceptable.
3619
 
        # - RBC 20060209
3620
 
        if revision_id is not None:
3621
 
            source_ids = self.source.get_ancestry(revision_id)
3622
 
            if source_ids[0] is not None:
3623
 
                raise AssertionError()
3624
 
            source_ids.pop(0)
3625
 
        else:
3626
 
            source_ids = self.source._all_possible_ids()
3627
 
        source_ids_set = set(source_ids)
3628
 
        # source_ids is the worst possible case we may need to pull.
3629
 
        # now we want to filter source_ids against what we actually
3630
 
        # have in target, but don't try to check for existence where we know
3631
 
        # we do not have a revision as that would be pointless.
3632
 
        target_ids = set(self.target._all_possible_ids())
3633
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3634
 
        actually_present_revisions = set(
3635
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3636
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3637
 
        if revision_id is not None:
3638
 
            # we used get_ancestry to determine source_ids then we are assured all
3639
 
            # revisions referenced are present as they are installed in topological order.
3640
 
            # and the tip revision was validated by get_ancestry.
3641
 
            result_set = required_revisions
3642
 
        else:
3643
 
            # if we just grabbed the possibly available ids, then
3644
 
            # we only have an estimate of whats available and need to validate
3645
 
            # that against the revision records.
3646
 
            result_set = set(
3647
 
                self.source._eliminate_revisions_not_present(required_revisions))
3648
 
        return self.source.revision_ids_to_search_result(result_set)
3649
 
 
3650
 
 
3651
 
class InterKnitRepo(InterSameDataRepository):
3652
 
    """Optimised code paths between Knit based repositories."""
3653
 
 
3654
 
    @classmethod
3655
 
    def _get_repo_format_to_test(self):
3656
 
        from bzrlib.repofmt import knitrepo
3657
 
        return knitrepo.RepositoryFormatKnit1()
3658
 
 
3659
 
    @staticmethod
3660
 
    def is_compatible(source, target):
3661
 
        """Be compatible with known Knit formats.
3662
 
 
3663
 
        We don't test for the stores being of specific types because that
3664
 
        could lead to confusing results, and there is no need to be
3665
 
        overly general.
3666
 
        """
3667
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
3668
 
        try:
3669
 
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
3670
 
                isinstance(target._format, RepositoryFormatKnit))
3671
 
        except AttributeError:
3672
 
            return False
3673
 
        return are_knits and InterRepository._same_model(source, target)
3674
 
 
3675
 
    @needs_read_lock
3676
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3677
 
        """See InterRepository.missing_revision_ids()."""
3678
 
        if revision_id is not None:
3679
 
            source_ids = self.source.get_ancestry(revision_id)
3680
 
            if source_ids[0] is not None:
3681
 
                raise AssertionError()
3682
 
            source_ids.pop(0)
3683
 
        else:
3684
 
            source_ids = self.source.all_revision_ids()
3685
 
        source_ids_set = set(source_ids)
3686
 
        # source_ids is the worst possible case we may need to pull.
3687
 
        # now we want to filter source_ids against what we actually
3688
 
        # have in target, but don't try to check for existence where we know
3689
 
        # we do not have a revision as that would be pointless.
3690
 
        target_ids = set(self.target.all_revision_ids())
3691
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3692
 
        actually_present_revisions = set(
3693
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3694
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3695
 
        if revision_id is not None:
3696
 
            # we used get_ancestry to determine source_ids then we are assured all
3697
 
            # revisions referenced are present as they are installed in topological order.
3698
 
            # and the tip revision was validated by get_ancestry.
3699
 
            result_set = required_revisions
3700
 
        else:
3701
 
            # if we just grabbed the possibly available ids, then
3702
 
            # we only have an estimate of whats available and need to validate
3703
 
            # that against the revision records.
3704
 
            result_set = set(
3705
 
                self.source._eliminate_revisions_not_present(required_revisions))
3706
 
        return self.source.revision_ids_to_search_result(result_set)
3707
 
 
3708
 
 
3709
3691
class InterDifferingSerializer(InterRepository):
3710
3692
 
3711
3693
    @classmethod
3813
3795
                basis_id, delta, current_revision_id, parents_parents)
3814
3796
            cache[current_revision_id] = parent_tree
3815
3797
 
3816
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3798
    def _fetch_batch(self, revision_ids, basis_id, cache):
3817
3799
        """Fetch across a few revisions.
3818
3800
 
3819
3801
        :param revision_ids: The revisions to copy
3820
3802
        :param basis_id: The revision_id of a tree that must be in cache, used
3821
3803
            as a basis for delta when no other base is available
3822
3804
        :param cache: A cache of RevisionTrees that we can use.
3823
 
        :param a_graph: A Graph object to determine the heads() of the
3824
 
            rich-root data stream.
3825
3805
        :return: The revision_id of the last converted tree. The RevisionTree
3826
3806
            for it will be in cache
3827
3807
        """
3895
3875
        if root_keys_to_create:
3896
3876
            root_stream = _mod_fetch._new_root_data_stream(
3897
3877
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3898
 
                self.source, graph=a_graph)
 
3878
                self.source)
3899
3879
            to_texts.insert_record_stream(root_stream)
3900
3880
        to_texts.insert_record_stream(from_texts.get_record_stream(
3901
3881
            text_keys, self.target._format._fetch_order,
3958
3938
        cache[basis_id] = basis_tree
3959
3939
        del basis_tree # We don't want to hang on to it here
3960
3940
        hints = []
3961
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3962
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3963
 
                                                            revision_ids)
3964
 
        else:
3965
 
            a_graph = None
 
3941
        a_graph = None
3966
3942
 
3967
3943
        for offset in range(0, len(revision_ids), batch_size):
3968
3944
            self.target.start_write_group()
3970
3946
                pb.update('Transferring revisions', offset,
3971
3947
                          len(revision_ids))
3972
3948
                batch = revision_ids[offset:offset+batch_size]
3973
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
3974
 
                                             a_graph=a_graph)
 
3949
                basis_id = self._fetch_batch(batch, basis_id, cache)
3975
3950
            except:
3976
3951
                self.source._safe_to_return_from_cache = False
3977
3952
                self.target.abort_write_group()
3986
3961
                  len(revision_ids))
3987
3962
 
3988
3963
    @needs_write_lock
3989
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3964
    def fetch(self, revision_id=None, find_ghosts=False,
3990
3965
            fetch_spec=None):
3991
3966
        """See InterRepository.fetch()."""
3992
3967
        if fetch_spec is not None:
3993
 
            raise AssertionError("Not implemented yet...")
 
3968
            revision_ids = fetch_spec.get_keys()
 
3969
        else:
 
3970
            revision_ids = None
3994
3971
        ui.ui_factory.warn_experimental_format_fetch(self)
3995
3972
        if (not self.source.supports_rich_root()
3996
3973
            and self.target.supports_rich_root()):
4003
3980
            ui.ui_factory.show_user_warning('cross_format_fetch',
4004
3981
                from_format=self.source._format,
4005
3982
                to_format=self.target._format)
4006
 
        revision_ids = self.target.search_missing_revision_ids(self.source,
4007
 
            revision_id, find_ghosts=find_ghosts).get_keys()
 
3983
        if revision_ids is None:
 
3984
            if revision_id:
 
3985
                search_revision_ids = [revision_id]
 
3986
            else:
 
3987
                search_revision_ids = None
 
3988
            revision_ids = self.target.search_missing_revision_ids(self.source,
 
3989
                revision_ids=search_revision_ids,
 
3990
                find_ghosts=find_ghosts).get_keys()
4008
3991
        if not revision_ids:
4009
3992
            return 0, 0
4010
3993
        revision_ids = tsort.topo_sort(
4014
3997
        # Walk though all revisions; get inventory deltas, copy referenced
4015
3998
        # texts that delta references, insert the delta, revision and
4016
3999
        # signature.
4017
 
        if pb is None:
4018
 
            my_pb = ui.ui_factory.nested_progress_bar()
4019
 
            pb = my_pb
4020
 
        else:
4021
 
            symbol_versioning.warn(
4022
 
                symbol_versioning.deprecated_in((1, 14, 0))
4023
 
                % "pb parameter to fetch()")
4024
 
            my_pb = None
 
4000
        pb = ui.ui_factory.nested_progress_bar()
4025
4001
        try:
4026
4002
            self._fetch_all_revisions(revision_ids, pb)
4027
4003
        finally:
4028
 
            if my_pb is not None:
4029
 
                my_pb.finished()
 
4004
            pb.finished()
4030
4005
        return len(revision_ids), 0
4031
4006
 
4032
4007
    def _get_basis(self, first_revision_id):
4043
4018
            basis_id = first_rev.parent_ids[0]
4044
4019
            # only valid as a basis if the target has it
4045
4020
            self.target.get_revision(basis_id)
4046
 
            # Try to get a basis tree - if its a ghost it will hit the
 
4021
            # Try to get a basis tree - if it's a ghost it will hit the
4047
4022
            # NoSuchRevision case.
4048
4023
            basis_tree = self.source.revision_tree(basis_id)
4049
4024
        except (IndexError, errors.NoSuchRevision):
4054
4029
 
4055
4030
InterRepository.register_optimiser(InterDifferingSerializer)
4056
4031
InterRepository.register_optimiser(InterSameDataRepository)
4057
 
InterRepository.register_optimiser(InterWeaveRepo)
4058
 
InterRepository.register_optimiser(InterKnitRepo)
4059
4032
 
4060
4033
 
4061
4034
class CopyConverter(object):
4249
4222
                is_resume = False
4250
4223
            try:
4251
4224
                # locked_insert_stream performs a commit|suspend.
4252
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4225
                missing_keys = self.insert_stream_without_locking(stream,
 
4226
                                    src_format, is_resume)
 
4227
                if missing_keys:
 
4228
                    # suspend the write group and tell the caller what we is
 
4229
                    # missing. We know we can suspend or else we would not have
 
4230
                    # entered this code path. (All repositories that can handle
 
4231
                    # missing keys can handle suspending a write group).
 
4232
                    write_group_tokens = self.target_repo.suspend_write_group()
 
4233
                    return write_group_tokens, missing_keys
 
4234
                hint = self.target_repo.commit_write_group()
 
4235
                to_serializer = self.target_repo._format._serializer
 
4236
                src_serializer = src_format._serializer
 
4237
                if (to_serializer != src_serializer and
 
4238
                    self.target_repo._format.pack_compresses):
 
4239
                    self.target_repo.pack(hint=hint)
 
4240
                return [], set()
4253
4241
            except:
4254
4242
                self.target_repo.abort_write_group(suppress_errors=True)
4255
4243
                raise
4256
4244
        finally:
4257
4245
            self.target_repo.unlock()
4258
4246
 
4259
 
    def _locked_insert_stream(self, stream, src_format, is_resume):
 
4247
    def insert_stream_without_locking(self, stream, src_format,
 
4248
                                      is_resume=False):
 
4249
        """Insert a stream's content into the target repository.
 
4250
 
 
4251
        This assumes that you already have a locked repository and an active
 
4252
        write group.
 
4253
 
 
4254
        :param src_format: a bzr repository format.
 
4255
        :param is_resume: Passed down to get_missing_parent_inventories to
 
4256
            indicate if we should be checking for missing texts at the same
 
4257
            time.
 
4258
 
 
4259
        :return: A set of keys that are missing.
 
4260
        """
 
4261
        if not self.target_repo.is_write_locked():
 
4262
            raise errors.ObjectNotLocked(self)
 
4263
        if not self.target_repo.is_in_write_group():
 
4264
            raise errors.BzrError('you must already be in a write group')
4260
4265
        to_serializer = self.target_repo._format._serializer
4261
4266
        src_serializer = src_format._serializer
4262
4267
        new_pack = None
4302
4307
                # required if the serializers are different only in terms of
4303
4308
                # the inventory.
4304
4309
                if src_serializer == to_serializer:
4305
 
                    self.target_repo.revisions.insert_record_stream(
4306
 
                        substream)
 
4310
                    self.target_repo.revisions.insert_record_stream(substream)
4307
4311
                else:
4308
4312
                    self._extract_and_insert_revisions(substream,
4309
4313
                        src_serializer)
4342
4346
            # cannot even attempt suspending, and missing would have failed
4343
4347
            # during stream insertion.
4344
4348
            missing_keys = set()
4345
 
        else:
4346
 
            if missing_keys:
4347
 
                # suspend the write group and tell the caller what we is
4348
 
                # missing. We know we can suspend or else we would not have
4349
 
                # entered this code path. (All repositories that can handle
4350
 
                # missing keys can handle suspending a write group).
4351
 
                write_group_tokens = self.target_repo.suspend_write_group()
4352
 
                return write_group_tokens, missing_keys
4353
 
        hint = self.target_repo.commit_write_group()
4354
 
        if (to_serializer != src_serializer and
4355
 
            self.target_repo._format.pack_compresses):
4356
 
            self.target_repo.pack(hint=hint)
4357
 
        return [], set()
 
4349
        return missing_keys
4358
4350
 
4359
4351
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4360
4352
        target_rich_root = self.target_repo._format.rich_root_data
4417
4409
        """Create a StreamSource streaming from from_repository."""
4418
4410
        self.from_repository = from_repository
4419
4411
        self.to_format = to_format
 
4412
        self._record_counter = RecordCounter()
4420
4413
 
4421
4414
    def delta_on_metadata(self):
4422
4415
        """Return True if delta's are permitted on metadata streams.