~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

merge trunk

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
34
34
    graph,
35
35
    inventory,
36
36
    inventory_delta,
37
 
    lazy_regex,
38
37
    lockable_files,
39
38
    lockdir,
40
39
    lru_cache,
41
40
    osutils,
 
41
    pyutils,
42
42
    revision as _mod_revision,
43
43
    static_tuple,
44
 
    symbol_versioning,
45
44
    trace,
46
45
    tsort,
47
46
    versionedfile,
48
47
    )
49
48
from bzrlib.bundle import serializer
 
49
from bzrlib.recordcounter import RecordCounter
50
50
from bzrlib.revisiontree import RevisionTree
51
51
from bzrlib.store.versioned import VersionedFileStore
52
52
from bzrlib.testament import Testament
55
55
from bzrlib import (
56
56
    errors,
57
57
    registry,
 
58
    symbol_versioning,
58
59
    ui,
59
60
    )
60
61
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
65
66
    ROOT_ID,
66
67
    entry_factory,
67
68
    )
68
 
from bzrlib.recordcounter import RecordCounter
69
69
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
70
70
from bzrlib.trace import (
71
71
    log_exception_quietly, note, mutter, mutter_callsite, warning)
94
94
    record_root_entry = True
95
95
    # the default CommitBuilder does not manage trees whose root is versioned.
96
96
    _versioned_root = False
 
97
    # this commit builder supports the record_entry_contents interface
 
98
    supports_record_entry_contents = True
97
99
 
98
100
    def __init__(self, repository, parents, config, timestamp=None,
99
101
                 timezone=None, committer=None, revprops=None,
113
115
 
114
116
        if committer is None:
115
117
            self._committer = self._config.username()
 
118
        elif not isinstance(committer, unicode):
 
119
            self._committer = committer.decode() # throw if non-ascii
116
120
        else:
117
121
            self._committer = committer
118
122
 
172
176
            self._validate_unicode_text(value,
173
177
                                        'revision property (%s)' % (key,))
174
178
 
 
179
    def _ensure_fallback_inventories(self):
 
180
        """Ensure that appropriate inventories are available.
 
181
 
 
182
        This only applies to repositories that are stacked, and is about
 
183
        enusring the stacking invariants. Namely, that for any revision that is
 
184
        present, we either have all of the file content, or we have the parent
 
185
        inventory and the delta file content.
 
186
        """
 
187
        if not self.repository._fallback_repositories:
 
188
            return
 
189
        if not self.repository._format.supports_chks:
 
190
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
191
                " in pre-2a formats. See "
 
192
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
193
        # This is a stacked repo, we need to make sure we have the parent
 
194
        # inventories for the parents.
 
195
        parent_keys = [(p,) for p in self.parents]
 
196
        parent_map = self.repository.inventories._index.get_parent_map(parent_keys)
 
197
        missing_parent_keys = set([pk for pk in parent_keys
 
198
                                       if pk not in parent_map])
 
199
        fallback_repos = list(reversed(self.repository._fallback_repositories))
 
200
        missing_keys = [('inventories', pk[0])
 
201
                        for pk in missing_parent_keys]
 
202
        resume_tokens = []
 
203
        while missing_keys and fallback_repos:
 
204
            fallback_repo = fallback_repos.pop()
 
205
            source = fallback_repo._get_source(self.repository._format)
 
206
            sink = self.repository._get_sink()
 
207
            stream = source.get_stream_for_missing_keys(missing_keys)
 
208
            missing_keys = sink.insert_stream_without_locking(stream,
 
209
                self.repository._format)
 
210
        if missing_keys:
 
211
            raise errors.BzrError('Unable to fill in parent inventories for a'
 
212
                                  ' stacked branch')
 
213
 
175
214
    def commit(self, message):
176
215
        """Make the actual commit.
177
216
 
189
228
        rev.parent_ids = self.parents
190
229
        self.repository.add_revision(self._new_revision_id, rev,
191
230
            self.new_inventory, self._config)
 
231
        self._ensure_fallback_inventories()
192
232
        self.repository.commit_write_group()
193
233
        return self._new_revision_id
194
234
 
434
474
            else:
435
475
                # we don't need to commit this, because the caller already
436
476
                # determined that an existing revision of this file is
437
 
                # appropriate. If its not being considered for committing then
 
477
                # appropriate. If it's not being considered for committing then
438
478
                # it and all its parents to the root must be unaltered so
439
479
                # no-change against the basis.
440
480
                if ie.revision == self._new_revision_id:
756
796
                    # after iter_changes examines and decides it has changed,
757
797
                    # we will unconditionally record a new version even if some
758
798
                    # other process reverts it while commit is running (with
759
 
                    # the revert happening after iter_changes did it's
 
799
                    # the revert happening after iter_changes did its
760
800
                    # examination).
761
801
                    if change[7][1]:
762
802
                        entry.executable = True
945
985
        pointing to .bzr/repository.
946
986
    """
947
987
 
948
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
988
    # What class to use for a CommitBuilder. Often it's simpler to change this
949
989
    # in a Repository class subclass rather than to override
950
990
    # get_commit_builder.
951
991
    _commit_builder_class = CommitBuilder
952
 
    # The search regex used by xml based repositories to determine what things
953
 
    # where changed in a single commit.
954
 
    _file_ids_altered_regex = lazy_regex.lazy_compile(
955
 
        r'file_id="(?P<file_id>[^"]+)"'
956
 
        r'.* revision="(?P<revision_id>[^"]+)"'
957
 
        )
958
992
 
959
993
    def abort_write_group(self, suppress_errors=False):
960
994
        """Commit the contents accrued within the current write group.
1557
1591
        return ret
1558
1592
 
1559
1593
    @needs_read_lock
1560
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1594
    def search_missing_revision_ids(self, other,
 
1595
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
1596
            find_ghosts=True, revision_ids=None, if_present_ids=None):
1561
1597
        """Return the revision ids that other has that this does not.
1562
1598
 
1563
1599
        These are returned in topological order.
1564
1600
 
1565
1601
        revision_id: only return revision ids included by revision_id.
1566
1602
        """
 
1603
        if symbol_versioning.deprecated_passed(revision_id):
 
1604
            symbol_versioning.warn(
 
1605
                'search_missing_revision_ids(revision_id=...) was '
 
1606
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
1607
                DeprecationWarning, stacklevel=3)
 
1608
            if revision_ids is not None:
 
1609
                raise AssertionError(
 
1610
                    'revision_ids is mutually exclusive with revision_id')
 
1611
            if revision_id is not None:
 
1612
                revision_ids = [revision_id]
1567
1613
        return InterRepository.get(other, self).search_missing_revision_ids(
1568
 
            revision_id, find_ghosts)
 
1614
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1615
            if_present_ids=if_present_ids)
1569
1616
 
1570
1617
    @staticmethod
1571
1618
    def open(base):
1693
1740
    def _resume_write_group(self, tokens):
1694
1741
        raise errors.UnsuspendableWriteGroup(self)
1695
1742
 
1696
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1743
    def fetch(self, source, revision_id=None, find_ghosts=False,
1697
1744
            fetch_spec=None):
1698
1745
        """Fetch the content required to construct revision_id from source.
1699
1746
 
1733
1780
                not _mod_revision.is_null(revision_id)):
1734
1781
                self.get_revision(revision_id)
1735
1782
            return 0, []
1736
 
        # if there is no specific appropriate InterRepository, this will get
1737
 
        # the InterRepository base class, which raises an
1738
 
        # IncompatibleRepositories when asked to fetch.
1739
1783
        inter = InterRepository.get(source, self)
1740
 
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1784
        return inter.fetch(revision_id=revision_id,
1741
1785
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1742
1786
 
1743
1787
    def create_bundle(self, target, base, fileobj, format=None):
1757
1801
        :param revprops: Optional dictionary of revision properties.
1758
1802
        :param revision_id: Optional revision id.
1759
1803
        """
1760
 
        if self._fallback_repositories:
1761
 
            raise errors.BzrError("Cannot commit from a lightweight checkout "
1762
 
                "to a stacked branch. See "
 
1804
        if self._fallback_repositories and not self._format.supports_chks:
 
1805
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1806
                " in pre-2a formats. See "
1763
1807
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1764
1808
        result = self._commit_builder_class(self, parents, config,
1765
1809
            timestamp, timezone, committer, revprops, revision_id)
2014
2058
        w = self.inventories
2015
2059
        pb = ui.ui_factory.nested_progress_bar()
2016
2060
        try:
2017
 
            return self._find_text_key_references_from_xml_inventory_lines(
 
2061
            return self._serializer._find_text_key_references(
2018
2062
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
2019
2063
        finally:
2020
2064
            pb.finished()
2021
2065
 
2022
 
    def _find_text_key_references_from_xml_inventory_lines(self,
2023
 
        line_iterator):
2024
 
        """Core routine for extracting references to texts from inventories.
2025
 
 
2026
 
        This performs the translation of xml lines to revision ids.
2027
 
 
2028
 
        :param line_iterator: An iterator of lines, origin_version_id
2029
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
2030
 
            to whether they were referred to by the inventory of the
2031
 
            revision_id that they contain. Note that if that revision_id was
2032
 
            not part of the line_iterator's output then False will be given -
2033
 
            even though it may actually refer to that key.
2034
 
        """
2035
 
        if not self._serializer.support_altered_by_hack:
2036
 
            raise AssertionError(
2037
 
                "_find_text_key_references_from_xml_inventory_lines only "
2038
 
                "supported for branches which store inventory as unnested xml"
2039
 
                ", not on %r" % self)
2040
 
        result = {}
2041
 
 
2042
 
        # this code needs to read every new line in every inventory for the
2043
 
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
2044
 
        # not present in one of those inventories is unnecessary but not
2045
 
        # harmful because we are filtering by the revision id marker in the
2046
 
        # inventory lines : we only select file ids altered in one of those
2047
 
        # revisions. We don't need to see all lines in the inventory because
2048
 
        # only those added in an inventory in rev X can contain a revision=X
2049
 
        # line.
2050
 
        unescape_revid_cache = {}
2051
 
        unescape_fileid_cache = {}
2052
 
 
2053
 
        # jam 20061218 In a big fetch, this handles hundreds of thousands
2054
 
        # of lines, so it has had a lot of inlining and optimizing done.
2055
 
        # Sorry that it is a little bit messy.
2056
 
        # Move several functions to be local variables, since this is a long
2057
 
        # running loop.
2058
 
        search = self._file_ids_altered_regex.search
2059
 
        unescape = _unescape_xml
2060
 
        setdefault = result.setdefault
2061
 
        for line, line_key in line_iterator:
2062
 
            match = search(line)
2063
 
            if match is None:
2064
 
                continue
2065
 
            # One call to match.group() returning multiple items is quite a
2066
 
            # bit faster than 2 calls to match.group() each returning 1
2067
 
            file_id, revision_id = match.group('file_id', 'revision_id')
2068
 
 
2069
 
            # Inlining the cache lookups helps a lot when you make 170,000
2070
 
            # lines and 350k ids, versus 8.4 unique ids.
2071
 
            # Using a cache helps in 2 ways:
2072
 
            #   1) Avoids unnecessary decoding calls
2073
 
            #   2) Re-uses cached strings, which helps in future set and
2074
 
            #      equality checks.
2075
 
            # (2) is enough that removing encoding entirely along with
2076
 
            # the cache (so we are using plain strings) results in no
2077
 
            # performance improvement.
2078
 
            try:
2079
 
                revision_id = unescape_revid_cache[revision_id]
2080
 
            except KeyError:
2081
 
                unescaped = unescape(revision_id)
2082
 
                unescape_revid_cache[revision_id] = unescaped
2083
 
                revision_id = unescaped
2084
 
 
2085
 
            # Note that unconditionally unescaping means that we deserialise
2086
 
            # every fileid, which for general 'pull' is not great, but we don't
2087
 
            # really want to have some many fulltexts that this matters anyway.
2088
 
            # RBC 20071114.
2089
 
            try:
2090
 
                file_id = unescape_fileid_cache[file_id]
2091
 
            except KeyError:
2092
 
                unescaped = unescape(file_id)
2093
 
                unescape_fileid_cache[file_id] = unescaped
2094
 
                file_id = unescaped
2095
 
 
2096
 
            key = (file_id, revision_id)
2097
 
            setdefault(key, False)
2098
 
            if revision_id == line_key[-1]:
2099
 
                result[key] = True
2100
 
        return result
2101
 
 
2102
2066
    def _inventory_xml_lines_for_keys(self, keys):
2103
2067
        """Get a line iterator of the sort needed for findind references.
2104
2068
 
2134
2098
        revision_ids. Each altered file-ids has the exact revision_ids that
2135
2099
        altered it listed explicitly.
2136
2100
        """
2137
 
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2101
        seen = set(self._serializer._find_text_key_references(
2138
2102
                line_iterator).iterkeys())
2139
2103
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
2140
 
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2104
        parent_seen = set(self._serializer._find_text_key_references(
2141
2105
            self._inventory_xml_lines_for_keys(parent_keys)))
2142
2106
        new_keys = seen - parent_seen
2143
2107
        result = {}
2511
2475
            ancestors will be traversed.
2512
2476
        """
2513
2477
        graph = self.get_graph()
2514
 
        next_id = revision_id
2515
 
        while True:
2516
 
            if next_id in (None, _mod_revision.NULL_REVISION):
2517
 
                return
2518
 
            try:
2519
 
                parents = graph.get_parent_map([next_id])[next_id]
2520
 
            except KeyError:
2521
 
                raise errors.RevisionNotPresent(next_id, self)
2522
 
            yield next_id
2523
 
            if len(parents) == 0:
2524
 
                return
2525
 
            else:
2526
 
                next_id = parents[0]
 
2478
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2479
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2527
2480
 
2528
2481
    def is_shared(self):
2529
2482
        """Return True if this repository is flagged as a shared repository."""
2630
2583
        types it should be a no-op that just returns.
2631
2584
 
2632
2585
        This stub method does not require a lock, but subclasses should use
2633
 
        @needs_write_lock as this is a long running call its reasonable to
 
2586
        @needs_write_lock as this is a long running call it's reasonable to
2634
2587
        implicitly lock for the user.
2635
2588
 
2636
2589
        :param hint: If not supplied, the whole repository is packed.
2782
2735
        return result
2783
2736
 
2784
2737
    def _warn_if_deprecated(self, branch=None):
 
2738
        if not self._format.is_deprecated():
 
2739
            return
2785
2740
        global _deprecation_warning_done
2786
2741
        if _deprecation_warning_done:
2787
2742
            return
2829
2784
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
2830
2785
 
2831
2786
 
2832
 
# remove these delegates a while after bzr 0.15
2833
 
def __make_delegated(name, from_module):
2834
 
    def _deprecated_repository_forwarder():
2835
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
2836
 
            % (name, from_module),
2837
 
            DeprecationWarning,
2838
 
            stacklevel=2)
2839
 
        m = __import__(from_module, globals(), locals(), [name])
2840
 
        try:
2841
 
            return getattr(m, name)
2842
 
        except AttributeError:
2843
 
            raise AttributeError('module %s has no name %s'
2844
 
                    % (m, name))
2845
 
    globals()[name] = _deprecated_repository_forwarder
2846
 
 
2847
 
for _name in [
2848
 
        'AllInOneRepository',
2849
 
        'WeaveMetaDirRepository',
2850
 
        'PreSplitOutRepositoryFormat',
2851
 
        'RepositoryFormat4',
2852
 
        'RepositoryFormat5',
2853
 
        'RepositoryFormat6',
2854
 
        'RepositoryFormat7',
2855
 
        ]:
2856
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
2857
 
 
2858
 
for _name in [
2859
 
        'KnitRepository',
2860
 
        'RepositoryFormatKnit',
2861
 
        'RepositoryFormatKnit1',
2862
 
        ]:
2863
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
2864
 
 
2865
 
 
2866
2787
def install_revision(repository, rev, revision_tree):
2867
2788
    """Install all revision data into a repository."""
2868
2789
    install_revisions(repository, [(rev, revision_tree, None)])
3000
2921
            control_files)
3001
2922
 
3002
2923
 
 
2924
class RepositoryFormatRegistry(controldir.ControlComponentFormatRegistry):
 
2925
    """Repository format registry."""
 
2926
 
 
2927
    def get_default(self):
 
2928
        """Return the current default format."""
 
2929
        from bzrlib import bzrdir
 
2930
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
2931
 
 
2932
 
3003
2933
network_format_registry = registry.FormatRegistry()
3004
2934
"""Registry of formats indexed by their network name.
3005
2935
 
3009
2939
"""
3010
2940
 
3011
2941
 
3012
 
format_registry = registry.FormatRegistry(network_format_registry)
 
2942
format_registry = RepositoryFormatRegistry(network_format_registry)
3013
2943
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
3014
2944
 
3015
2945
This can contain either format instances themselves, or classes/factories that
3020
2950
#####################################################################
3021
2951
# Repository Formats
3022
2952
 
3023
 
class RepositoryFormat(object):
 
2953
class RepositoryFormat(controldir.ControlComponentFormat):
3024
2954
    """A repository format.
3025
2955
 
3026
2956
    Formats provide four things:
3087
3017
    supports_tree_reference = None
3088
3018
    # Is the format experimental ?
3089
3019
    experimental = False
 
3020
    # Does this repository format escape funky characters, or does it create files with
 
3021
    # similar names as the versioned files in its contents on disk ?
 
3022
    supports_funky_characters = None
 
3023
    # Does this repository format support leaving locks?
 
3024
    supports_leaving_lock = None
 
3025
    # Does this format support the full VersionedFiles interface?
 
3026
    supports_full_versioned_files = None
3090
3027
 
3091
3028
    def __repr__(self):
3092
3029
        return "%s()" % self.__class__.__name__
3117
3054
                                            kind='repository')
3118
3055
 
3119
3056
    @classmethod
 
3057
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3120
3058
    def register_format(klass, format):
3121
 
        format_registry.register(format.get_format_string(), format)
 
3059
        format_registry.register(format)
3122
3060
 
3123
3061
    @classmethod
 
3062
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3124
3063
    def unregister_format(klass, format):
3125
 
        format_registry.remove(format.get_format_string())
 
3064
        format_registry.remove(format)
3126
3065
 
3127
3066
    @classmethod
 
3067
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3128
3068
    def get_default_format(klass):
3129
3069
        """Return the current default format."""
3130
 
        from bzrlib import bzrdir
3131
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
3070
        return format_registry.get_default()
3132
3071
 
3133
3072
    def get_format_string(self):
3134
3073
        """Return the ASCII format string that identifies this format.
3185
3124
        """
3186
3125
        return True
3187
3126
 
 
3127
    def is_deprecated(self):
 
3128
        """Is this format deprecated?
 
3129
 
 
3130
        Deprecated formats may trigger a user-visible warning recommending
 
3131
        the user to upgrade. They are still fully supported.
 
3132
        """
 
3133
        return False
 
3134
 
3188
3135
    def network_name(self):
3189
3136
        """A simple byte string uniquely identifying this format for RPC calls.
3190
3137
 
3229
3176
    rich_root_data = False
3230
3177
    supports_tree_reference = False
3231
3178
    supports_external_lookups = False
 
3179
    supports_leaving_lock = True
3232
3180
 
3233
3181
    @property
3234
3182
    def _matchingbzrdir(self):
3272
3220
        return self.get_format_string()
3273
3221
 
3274
3222
 
3275
 
# Pre-0.8 formats that don't have a disk format string (because they are
3276
 
# versioned by the matching control directory). We use the control directories
3277
 
# disk format string as a key for the network_name because they meet the
3278
 
# constraints (simple string, unique, immutable).
3279
 
network_format_registry.register_lazy(
3280
 
    "Bazaar-NG branch, format 5\n",
3281
 
    'bzrlib.repofmt.weaverepo',
3282
 
    'RepositoryFormat5',
3283
 
)
3284
 
network_format_registry.register_lazy(
3285
 
    "Bazaar-NG branch, format 6\n",
3286
 
    'bzrlib.repofmt.weaverepo',
3287
 
    'RepositoryFormat6',
3288
 
)
3289
 
 
3290
3223
# formats which have no format string are not discoverable or independently
3291
3224
# creatable on disk, so are not registered in format_registry.  They're
3292
 
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
 
3225
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
3293
3226
# needed, it's constructed directly by the BzrDir.  Non-native formats where
3294
3227
# the repository is not separately opened are similar.
3295
3228
 
3296
3229
format_registry.register_lazy(
3297
 
    'Bazaar-NG Repository format 7',
3298
 
    'bzrlib.repofmt.weaverepo',
3299
 
    'RepositoryFormat7'
3300
 
    )
3301
 
 
3302
 
format_registry.register_lazy(
3303
3230
    'Bazaar-NG Knit Repository Format 1',
3304
3231
    'bzrlib.repofmt.knitrepo',
3305
3232
    'RepositoryFormatKnit1',
3360
3287
    'bzrlib.repofmt.pack_repo',
3361
3288
    'RepositoryFormatKnitPack6RichRoot',
3362
3289
    )
 
3290
format_registry.register_lazy(
 
3291
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3292
    'bzrlib.repofmt.groupcompress_repo',
 
3293
    'RepositoryFormat2a',
 
3294
    )
3363
3295
 
3364
3296
# Development formats.
3365
 
# Obsolete but kept pending a CHK based subtree format.
 
3297
# Check their docstrings to see if/when they are obsolete.
3366
3298
format_registry.register_lazy(
3367
3299
    ("Bazaar development format 2 with subtree support "
3368
3300
        "(needs bzr.dev from before 1.8)\n"),
3369
3301
    'bzrlib.repofmt.pack_repo',
3370
3302
    'RepositoryFormatPackDevelopment2Subtree',
3371
3303
    )
3372
 
 
3373
 
# 1.14->1.16 go below here
3374
 
format_registry.register_lazy(
3375
 
    'Bazaar development format - group compression and chk inventory'
3376
 
        ' (needs bzr.dev from 1.14)\n',
3377
 
    'bzrlib.repofmt.groupcompress_repo',
3378
 
    'RepositoryFormatCHK1',
3379
 
    )
3380
 
 
3381
 
format_registry.register_lazy(
3382
 
    'Bazaar development format - chk repository with bencode revision '
3383
 
        'serialization (needs bzr.dev from 1.16)\n',
3384
 
    'bzrlib.repofmt.groupcompress_repo',
3385
 
    'RepositoryFormatCHK2',
3386
 
    )
3387
 
format_registry.register_lazy(
3388
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3389
 
    'bzrlib.repofmt.groupcompress_repo',
3390
 
    'RepositoryFormat2a',
3391
 
    )
3392
3304
format_registry.register_lazy(
3393
3305
    'Bazaar development format 8\n',
3394
3306
    'bzrlib.repofmt.groupcompress_repo',
3429
3341
        self.target.fetch(self.source, revision_id=revision_id)
3430
3342
 
3431
3343
    @needs_write_lock
3432
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3344
    def fetch(self, revision_id=None, find_ghosts=False,
3433
3345
            fetch_spec=None):
3434
3346
        """Fetch the content required to construct revision_id.
3435
3347
 
3437
3349
 
3438
3350
        :param revision_id: if None all content is copied, if NULL_REVISION no
3439
3351
                            content is copied.
3440
 
        :param pb: ignored.
3441
3352
        :return: None.
3442
3353
        """
3443
3354
        ui.ui_factory.warn_experimental_format_fetch(self)
3453
3364
                               fetch_spec=fetch_spec,
3454
3365
                               find_ghosts=find_ghosts)
3455
3366
 
3456
 
    def _walk_to_common_revisions(self, revision_ids):
 
3367
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
3457
3368
        """Walk out from revision_ids in source to revisions target has.
3458
3369
 
3459
3370
        :param revision_ids: The start point for the search.
3461
3372
        """
3462
3373
        target_graph = self.target.get_graph()
3463
3374
        revision_ids = frozenset(revision_ids)
 
3375
        if if_present_ids:
 
3376
            all_wanted_revs = revision_ids.union(if_present_ids)
 
3377
        else:
 
3378
            all_wanted_revs = revision_ids
3464
3379
        missing_revs = set()
3465
3380
        source_graph = self.source.get_graph()
3466
3381
        # ensure we don't pay silly lookup costs.
3467
 
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
 
3382
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
3468
3383
        null_set = frozenset([_mod_revision.NULL_REVISION])
3469
3384
        searcher_exhausted = False
3470
3385
        while True:
3506
3421
        return searcher.get_result()
3507
3422
 
3508
3423
    @needs_read_lock
3509
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3424
    def search_missing_revision_ids(self,
 
3425
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
3426
            find_ghosts=True, revision_ids=None, if_present_ids=None):
3510
3427
        """Return the revision ids that source has that target does not.
3511
3428
 
3512
3429
        :param revision_id: only return revision ids included by this
3513
 
                            revision_id.
 
3430
            revision_id.
 
3431
        :param revision_ids: return revision ids included by these
 
3432
            revision_ids.  NoSuchRevision will be raised if any of these
 
3433
            revisions are not present.
 
3434
        :param if_present_ids: like revision_ids, but will not cause
 
3435
            NoSuchRevision if any of these are absent, instead they will simply
 
3436
            not be in the result.  This is useful for e.g. finding revisions
 
3437
            to fetch for tags, which may reference absent revisions.
3514
3438
        :param find_ghosts: If True find missing revisions in deep history
3515
3439
            rather than just finding the surface difference.
3516
3440
        :return: A bzrlib.graph.SearchResult.
3517
3441
        """
 
3442
        if symbol_versioning.deprecated_passed(revision_id):
 
3443
            symbol_versioning.warn(
 
3444
                'search_missing_revision_ids(revision_id=...) was '
 
3445
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
3446
                DeprecationWarning, stacklevel=2)
 
3447
            if revision_ids is not None:
 
3448
                raise AssertionError(
 
3449
                    'revision_ids is mutually exclusive with revision_id')
 
3450
            if revision_id is not None:
 
3451
                revision_ids = [revision_id]
 
3452
        del revision_id
3518
3453
        # stop searching at found target revisions.
3519
 
        if not find_ghosts and revision_id is not None:
3520
 
            return self._walk_to_common_revisions([revision_id])
 
3454
        if not find_ghosts and (revision_ids is not None or if_present_ids is
 
3455
                not None):
 
3456
            return self._walk_to_common_revisions(revision_ids,
 
3457
                    if_present_ids=if_present_ids)
3521
3458
        # generic, possibly worst case, slow code path.
3522
3459
        target_ids = set(self.target.all_revision_ids())
3523
 
        if revision_id is not None:
3524
 
            source_ids = self.source.get_ancestry(revision_id)
3525
 
            if source_ids[0] is not None:
3526
 
                raise AssertionError()
3527
 
            source_ids.pop(0)
3528
 
        else:
3529
 
            source_ids = self.source.all_revision_ids()
 
3460
        source_ids = self._present_source_revisions_for(
 
3461
            revision_ids, if_present_ids)
3530
3462
        result_set = set(source_ids).difference(target_ids)
3531
3463
        return self.source.revision_ids_to_search_result(result_set)
3532
3464
 
 
3465
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
 
3466
        """Returns set of all revisions in ancestry of revision_ids present in
 
3467
        the source repo.
 
3468
 
 
3469
        :param revision_ids: if None, all revisions in source are returned.
 
3470
        :param if_present_ids: like revision_ids, but if any/all of these are
 
3471
            absent no error is raised.
 
3472
        """
 
3473
        if revision_ids is not None or if_present_ids is not None:
 
3474
            # First, ensure all specified revisions exist.  Callers expect
 
3475
            # NoSuchRevision when they pass absent revision_ids here.
 
3476
            if revision_ids is None:
 
3477
                revision_ids = set()
 
3478
            if if_present_ids is None:
 
3479
                if_present_ids = set()
 
3480
            revision_ids = set(revision_ids)
 
3481
            if_present_ids = set(if_present_ids)
 
3482
            all_wanted_ids = revision_ids.union(if_present_ids)
 
3483
            graph = self.source.get_graph()
 
3484
            present_revs = set(graph.get_parent_map(all_wanted_ids))
 
3485
            missing = revision_ids.difference(present_revs)
 
3486
            if missing:
 
3487
                raise errors.NoSuchRevision(self.source, missing.pop())
 
3488
            found_ids = all_wanted_ids.intersection(present_revs)
 
3489
            source_ids = [rev_id for (rev_id, parents) in
 
3490
                          graph.iter_ancestry(found_ids)
 
3491
                          if rev_id != _mod_revision.NULL_REVISION
 
3492
                          and parents is not None]
 
3493
        else:
 
3494
            source_ids = self.source.all_revision_ids()
 
3495
        return set(source_ids)
 
3496
 
3533
3497
    @staticmethod
3534
3498
    def _same_model(source, target):
3535
3499
        """True if source and target have the same data representation.
3576
3540
        return InterRepository._same_model(source, target)
3577
3541
 
3578
3542
 
3579
 
class InterWeaveRepo(InterSameDataRepository):
3580
 
    """Optimised code paths between Weave based repositories.
3581
 
 
3582
 
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
3583
 
    implemented lazy inter-object optimisation.
3584
 
    """
3585
 
 
3586
 
    @classmethod
3587
 
    def _get_repo_format_to_test(self):
3588
 
        from bzrlib.repofmt import weaverepo
3589
 
        return weaverepo.RepositoryFormat7()
3590
 
 
3591
 
    @staticmethod
3592
 
    def is_compatible(source, target):
3593
 
        """Be compatible with known Weave formats.
3594
 
 
3595
 
        We don't test for the stores being of specific types because that
3596
 
        could lead to confusing results, and there is no need to be
3597
 
        overly general.
3598
 
        """
3599
 
        from bzrlib.repofmt.weaverepo import (
3600
 
                RepositoryFormat5,
3601
 
                RepositoryFormat6,
3602
 
                RepositoryFormat7,
3603
 
                )
3604
 
        try:
3605
 
            return (isinstance(source._format, (RepositoryFormat5,
3606
 
                                                RepositoryFormat6,
3607
 
                                                RepositoryFormat7)) and
3608
 
                    isinstance(target._format, (RepositoryFormat5,
3609
 
                                                RepositoryFormat6,
3610
 
                                                RepositoryFormat7)))
3611
 
        except AttributeError:
3612
 
            return False
3613
 
 
3614
 
    @needs_write_lock
3615
 
    def copy_content(self, revision_id=None):
3616
 
        """See InterRepository.copy_content()."""
3617
 
        # weave specific optimised path:
3618
 
        try:
3619
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3620
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
3621
 
            pass
3622
 
        # FIXME do not peek!
3623
 
        if self.source._transport.listable():
3624
 
            pb = ui.ui_factory.nested_progress_bar()
3625
 
            try:
3626
 
                self.target.texts.insert_record_stream(
3627
 
                    self.source.texts.get_record_stream(
3628
 
                        self.source.texts.keys(), 'topological', False))
3629
 
                pb.update('Copying inventory', 0, 1)
3630
 
                self.target.inventories.insert_record_stream(
3631
 
                    self.source.inventories.get_record_stream(
3632
 
                        self.source.inventories.keys(), 'topological', False))
3633
 
                self.target.signatures.insert_record_stream(
3634
 
                    self.source.signatures.get_record_stream(
3635
 
                        self.source.signatures.keys(),
3636
 
                        'unordered', True))
3637
 
                self.target.revisions.insert_record_stream(
3638
 
                    self.source.revisions.get_record_stream(
3639
 
                        self.source.revisions.keys(),
3640
 
                        'topological', True))
3641
 
            finally:
3642
 
                pb.finished()
3643
 
        else:
3644
 
            self.target.fetch(self.source, revision_id=revision_id)
3645
 
 
3646
 
    @needs_read_lock
3647
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3648
 
        """See InterRepository.missing_revision_ids()."""
3649
 
        # we want all revisions to satisfy revision_id in source.
3650
 
        # but we don't want to stat every file here and there.
3651
 
        # we want then, all revisions other needs to satisfy revision_id
3652
 
        # checked, but not those that we have locally.
3653
 
        # so the first thing is to get a subset of the revisions to
3654
 
        # satisfy revision_id in source, and then eliminate those that
3655
 
        # we do already have.
3656
 
        # this is slow on high latency connection to self, but as this
3657
 
        # disk format scales terribly for push anyway due to rewriting
3658
 
        # inventory.weave, this is considered acceptable.
3659
 
        # - RBC 20060209
3660
 
        if revision_id is not None:
3661
 
            source_ids = self.source.get_ancestry(revision_id)
3662
 
            if source_ids[0] is not None:
3663
 
                raise AssertionError()
3664
 
            source_ids.pop(0)
3665
 
        else:
3666
 
            source_ids = self.source._all_possible_ids()
3667
 
        source_ids_set = set(source_ids)
3668
 
        # source_ids is the worst possible case we may need to pull.
3669
 
        # now we want to filter source_ids against what we actually
3670
 
        # have in target, but don't try to check for existence where we know
3671
 
        # we do not have a revision as that would be pointless.
3672
 
        target_ids = set(self.target._all_possible_ids())
3673
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3674
 
        actually_present_revisions = set(
3675
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3676
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3677
 
        if revision_id is not None:
3678
 
            # we used get_ancestry to determine source_ids then we are assured all
3679
 
            # revisions referenced are present as they are installed in topological order.
3680
 
            # and the tip revision was validated by get_ancestry.
3681
 
            result_set = required_revisions
3682
 
        else:
3683
 
            # if we just grabbed the possibly available ids, then
3684
 
            # we only have an estimate of whats available and need to validate
3685
 
            # that against the revision records.
3686
 
            result_set = set(
3687
 
                self.source._eliminate_revisions_not_present(required_revisions))
3688
 
        return self.source.revision_ids_to_search_result(result_set)
3689
 
 
3690
 
 
3691
 
class InterKnitRepo(InterSameDataRepository):
3692
 
    """Optimised code paths between Knit based repositories."""
3693
 
 
3694
 
    @classmethod
3695
 
    def _get_repo_format_to_test(self):
3696
 
        from bzrlib.repofmt import knitrepo
3697
 
        return knitrepo.RepositoryFormatKnit1()
3698
 
 
3699
 
    @staticmethod
3700
 
    def is_compatible(source, target):
3701
 
        """Be compatible with known Knit formats.
3702
 
 
3703
 
        We don't test for the stores being of specific types because that
3704
 
        could lead to confusing results, and there is no need to be
3705
 
        overly general.
3706
 
        """
3707
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
3708
 
        try:
3709
 
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
3710
 
                isinstance(target._format, RepositoryFormatKnit))
3711
 
        except AttributeError:
3712
 
            return False
3713
 
        return are_knits and InterRepository._same_model(source, target)
3714
 
 
3715
 
    @needs_read_lock
3716
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3717
 
        """See InterRepository.missing_revision_ids()."""
3718
 
        if revision_id is not None:
3719
 
            source_ids = self.source.get_ancestry(revision_id)
3720
 
            if source_ids[0] is not None:
3721
 
                raise AssertionError()
3722
 
            source_ids.pop(0)
3723
 
        else:
3724
 
            source_ids = self.source.all_revision_ids()
3725
 
        source_ids_set = set(source_ids)
3726
 
        # source_ids is the worst possible case we may need to pull.
3727
 
        # now we want to filter source_ids against what we actually
3728
 
        # have in target, but don't try to check for existence where we know
3729
 
        # we do not have a revision as that would be pointless.
3730
 
        target_ids = set(self.target.all_revision_ids())
3731
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3732
 
        actually_present_revisions = set(
3733
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3734
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3735
 
        if revision_id is not None:
3736
 
            # we used get_ancestry to determine source_ids then we are assured all
3737
 
            # revisions referenced are present as they are installed in topological order.
3738
 
            # and the tip revision was validated by get_ancestry.
3739
 
            result_set = required_revisions
3740
 
        else:
3741
 
            # if we just grabbed the possibly available ids, then
3742
 
            # we only have an estimate of whats available and need to validate
3743
 
            # that against the revision records.
3744
 
            result_set = set(
3745
 
                self.source._eliminate_revisions_not_present(required_revisions))
3746
 
        return self.source.revision_ids_to_search_result(result_set)
3747
 
 
3748
 
 
3749
3543
class InterDifferingSerializer(InterRepository):
3750
3544
 
3751
3545
    @classmethod
3853
3647
                basis_id, delta, current_revision_id, parents_parents)
3854
3648
            cache[current_revision_id] = parent_tree
3855
3649
 
3856
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3650
    def _fetch_batch(self, revision_ids, basis_id, cache):
3857
3651
        """Fetch across a few revisions.
3858
3652
 
3859
3653
        :param revision_ids: The revisions to copy
3860
3654
        :param basis_id: The revision_id of a tree that must be in cache, used
3861
3655
            as a basis for delta when no other base is available
3862
3656
        :param cache: A cache of RevisionTrees that we can use.
3863
 
        :param a_graph: A Graph object to determine the heads() of the
3864
 
            rich-root data stream.
3865
3657
        :return: The revision_id of the last converted tree. The RevisionTree
3866
3658
            for it will be in cache
3867
3659
        """
3935
3727
        if root_keys_to_create:
3936
3728
            root_stream = _mod_fetch._new_root_data_stream(
3937
3729
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3938
 
                self.source, graph=a_graph)
 
3730
                self.source)
3939
3731
            to_texts.insert_record_stream(root_stream)
3940
3732
        to_texts.insert_record_stream(from_texts.get_record_stream(
3941
3733
            text_keys, self.target._format._fetch_order,
3998
3790
        cache[basis_id] = basis_tree
3999
3791
        del basis_tree # We don't want to hang on to it here
4000
3792
        hints = []
4001
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
4002
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
4003
 
                                                            revision_ids)
4004
 
        else:
4005
 
            a_graph = None
 
3793
        a_graph = None
4006
3794
 
4007
3795
        for offset in range(0, len(revision_ids), batch_size):
4008
3796
            self.target.start_write_group()
4010
3798
                pb.update('Transferring revisions', offset,
4011
3799
                          len(revision_ids))
4012
3800
                batch = revision_ids[offset:offset+batch_size]
4013
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
4014
 
                                             a_graph=a_graph)
 
3801
                basis_id = self._fetch_batch(batch, basis_id, cache)
4015
3802
            except:
4016
3803
                self.source._safe_to_return_from_cache = False
4017
3804
                self.target.abort_write_group()
4026
3813
                  len(revision_ids))
4027
3814
 
4028
3815
    @needs_write_lock
4029
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3816
    def fetch(self, revision_id=None, find_ghosts=False,
4030
3817
            fetch_spec=None):
4031
3818
        """See InterRepository.fetch()."""
4032
3819
        if fetch_spec is not None:
4033
 
            raise AssertionError("Not implemented yet...")
 
3820
            revision_ids = fetch_spec.get_keys()
 
3821
        else:
 
3822
            revision_ids = None
4034
3823
        ui.ui_factory.warn_experimental_format_fetch(self)
4035
3824
        if (not self.source.supports_rich_root()
4036
3825
            and self.target.supports_rich_root()):
4043
3832
            ui.ui_factory.show_user_warning('cross_format_fetch',
4044
3833
                from_format=self.source._format,
4045
3834
                to_format=self.target._format)
4046
 
        revision_ids = self.target.search_missing_revision_ids(self.source,
4047
 
            revision_id, find_ghosts=find_ghosts).get_keys()
 
3835
        if revision_ids is None:
 
3836
            if revision_id:
 
3837
                search_revision_ids = [revision_id]
 
3838
            else:
 
3839
                search_revision_ids = None
 
3840
            revision_ids = self.target.search_missing_revision_ids(self.source,
 
3841
                revision_ids=search_revision_ids,
 
3842
                find_ghosts=find_ghosts).get_keys()
4048
3843
        if not revision_ids:
4049
3844
            return 0, 0
4050
3845
        revision_ids = tsort.topo_sort(
4054
3849
        # Walk though all revisions; get inventory deltas, copy referenced
4055
3850
        # texts that delta references, insert the delta, revision and
4056
3851
        # signature.
4057
 
        if pb is None:
4058
 
            my_pb = ui.ui_factory.nested_progress_bar()
4059
 
            pb = my_pb
4060
 
        else:
4061
 
            symbol_versioning.warn(
4062
 
                symbol_versioning.deprecated_in((1, 14, 0))
4063
 
                % "pb parameter to fetch()")
4064
 
            my_pb = None
 
3852
        pb = ui.ui_factory.nested_progress_bar()
4065
3853
        try:
4066
3854
            self._fetch_all_revisions(revision_ids, pb)
4067
3855
        finally:
4068
 
            if my_pb is not None:
4069
 
                my_pb.finished()
 
3856
            pb.finished()
4070
3857
        return len(revision_ids), 0
4071
3858
 
4072
3859
    def _get_basis(self, first_revision_id):
4083
3870
            basis_id = first_rev.parent_ids[0]
4084
3871
            # only valid as a basis if the target has it
4085
3872
            self.target.get_revision(basis_id)
4086
 
            # Try to get a basis tree - if its a ghost it will hit the
 
3873
            # Try to get a basis tree - if it's a ghost it will hit the
4087
3874
            # NoSuchRevision case.
4088
3875
            basis_tree = self.source.revision_tree(basis_id)
4089
3876
        except (IndexError, errors.NoSuchRevision):
4094
3881
 
4095
3882
InterRepository.register_optimiser(InterDifferingSerializer)
4096
3883
InterRepository.register_optimiser(InterSameDataRepository)
4097
 
InterRepository.register_optimiser(InterWeaveRepo)
4098
 
InterRepository.register_optimiser(InterKnitRepo)
4099
3884
 
4100
3885
 
4101
3886
class CopyConverter(object):
4146
3931
        pb.finished()
4147
3932
 
4148
3933
 
4149
 
_unescape_map = {
4150
 
    'apos':"'",
4151
 
    'quot':'"',
4152
 
    'amp':'&',
4153
 
    'lt':'<',
4154
 
    'gt':'>'
4155
 
}
4156
 
 
4157
 
 
4158
 
def _unescaper(match, _map=_unescape_map):
4159
 
    code = match.group(1)
4160
 
    try:
4161
 
        return _map[code]
4162
 
    except KeyError:
4163
 
        if not code.startswith('#'):
4164
 
            raise
4165
 
        return unichr(int(code[1:])).encode('utf8')
4166
 
 
4167
 
 
4168
 
_unescape_re = None
4169
 
 
4170
 
 
4171
 
def _unescape_xml(data):
4172
 
    """Unescape predefined XML entities in a string of data."""
4173
 
    global _unescape_re
4174
 
    if _unescape_re is None:
4175
 
        _unescape_re = re.compile('\&([^;]*);')
4176
 
    return _unescape_re.sub(_unescaper, data)
4177
 
 
4178
 
 
4179
3934
class _VersionedFileChecker(object):
4180
3935
 
4181
3936
    def __init__(self, repository, text_key_references=None, ancestors=None):
4240
3995
        return wrong_parents, unused_keys
4241
3996
 
4242
3997
 
4243
 
def _old_get_graph(repository, revision_id):
4244
 
    """DO NOT USE. That is all. I'm serious."""
4245
 
    graph = repository.get_graph()
4246
 
    revision_graph = dict(((key, value) for key, value in
4247
 
        graph.iter_ancestry([revision_id]) if value is not None))
4248
 
    return _strip_NULL_ghosts(revision_graph)
4249
 
 
4250
 
 
4251
3998
def _strip_NULL_ghosts(revision_graph):
4252
3999
    """Also don't use this. more compatibility code for unmigrated clients."""
4253
4000
    # Filter ghosts, and null:
4289
4036
                is_resume = False
4290
4037
            try:
4291
4038
                # locked_insert_stream performs a commit|suspend.
4292
 
                return self._locked_insert_stream(stream, src_format,
4293
 
                    is_resume)
 
4039
                missing_keys = self.insert_stream_without_locking(stream,
 
4040
                                    src_format, is_resume)
 
4041
                if missing_keys:
 
4042
                    # suspend the write group and tell the caller what we is
 
4043
                    # missing. We know we can suspend or else we would not have
 
4044
                    # entered this code path. (All repositories that can handle
 
4045
                    # missing keys can handle suspending a write group).
 
4046
                    write_group_tokens = self.target_repo.suspend_write_group()
 
4047
                    return write_group_tokens, missing_keys
 
4048
                hint = self.target_repo.commit_write_group()
 
4049
                to_serializer = self.target_repo._format._serializer
 
4050
                src_serializer = src_format._serializer
 
4051
                if (to_serializer != src_serializer and
 
4052
                    self.target_repo._format.pack_compresses):
 
4053
                    self.target_repo.pack(hint=hint)
 
4054
                return [], set()
4294
4055
            except:
4295
4056
                self.target_repo.abort_write_group(suppress_errors=True)
4296
4057
                raise
4297
4058
        finally:
4298
4059
            self.target_repo.unlock()
4299
4060
 
4300
 
    def _locked_insert_stream(self, stream, src_format, is_resume):
 
4061
    def insert_stream_without_locking(self, stream, src_format,
 
4062
                                      is_resume=False):
 
4063
        """Insert a stream's content into the target repository.
 
4064
 
 
4065
        This assumes that you already have a locked repository and an active
 
4066
        write group.
 
4067
 
 
4068
        :param src_format: a bzr repository format.
 
4069
        :param is_resume: Passed down to get_missing_parent_inventories to
 
4070
            indicate if we should be checking for missing texts at the same
 
4071
            time.
 
4072
 
 
4073
        :return: A set of keys that are missing.
 
4074
        """
 
4075
        if not self.target_repo.is_write_locked():
 
4076
            raise errors.ObjectNotLocked(self)
 
4077
        if not self.target_repo.is_in_write_group():
 
4078
            raise errors.BzrError('you must already be in a write group')
4301
4079
        to_serializer = self.target_repo._format._serializer
4302
4080
        src_serializer = src_format._serializer
4303
4081
        new_pack = None
4382
4160
            # cannot even attempt suspending, and missing would have failed
4383
4161
            # during stream insertion.
4384
4162
            missing_keys = set()
4385
 
        else:
4386
 
            if missing_keys:
4387
 
                # suspend the write group and tell the caller what we is
4388
 
                # missing. We know we can suspend or else we would not have
4389
 
                # entered this code path. (All repositories that can handle
4390
 
                # missing keys can handle suspending a write group).
4391
 
                write_group_tokens = self.target_repo.suspend_write_group()
4392
 
                return write_group_tokens, missing_keys
4393
 
        hint = self.target_repo.commit_write_group()
4394
 
        if (to_serializer != src_serializer and
4395
 
            self.target_repo._format.pack_compresses):
4396
 
            self.target_repo.pack(hint=hint)
4397
 
        return [], set()
 
4163
        return missing_keys
4398
4164
 
4399
4165
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4400
4166
        target_rich_root = self.target_repo._format.rich_root_data
4748
4514
    except StopIteration:
4749
4515
        # No more history
4750
4516
        return
4751