/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: mbp at sourcefrog
  • Date: 2011-04-11 01:23:58 UTC
  • mfrom: (5777 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5802.
  • Revision ID: mbp@sourcefrog.net-20110411012358-gl07rdtxydlq7fh1
merge news

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
16
16
 
17
17
from bzrlib.lazy_import import lazy_import
18
18
lazy_import(globals(), """
19
 
import cStringIO
20
 
import re
21
19
import time
22
20
 
23
21
from bzrlib import (
24
22
    bzrdir,
25
23
    check,
26
 
    chk_map,
27
24
    config,
 
25
    controldir,
28
26
    debug,
29
27
    fetch as _mod_fetch,
30
28
    fifo_cache,
31
29
    generate_ids,
32
30
    gpg,
33
31
    graph,
34
 
    inventory,
35
32
    inventory_delta,
36
 
    lazy_regex,
37
33
    lockable_files,
38
34
    lockdir,
39
35
    lru_cache,
40
36
    osutils,
41
37
    revision as _mod_revision,
42
38
    static_tuple,
43
 
    symbol_versioning,
44
 
    trace,
45
39
    tsort,
46
 
    ui,
47
40
    versionedfile,
48
41
    )
49
42
from bzrlib.bundle import serializer
 
43
from bzrlib.recordcounter import RecordCounter
50
44
from bzrlib.revisiontree import RevisionTree
51
45
from bzrlib.store.versioned import VersionedFileStore
52
46
from bzrlib.testament import Testament
55
49
from bzrlib import (
56
50
    errors,
57
51
    registry,
 
52
    symbol_versioning,
 
53
    ui,
58
54
    )
59
55
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
60
56
from bzrlib.inter import InterObject
92
88
    record_root_entry = True
93
89
    # the default CommitBuilder does not manage trees whose root is versioned.
94
90
    _versioned_root = False
 
91
    # this commit builder supports the record_entry_contents interface
 
92
    supports_record_entry_contents = True
95
93
 
96
94
    def __init__(self, repository, parents, config, timestamp=None,
97
95
                 timezone=None, committer=None, revprops=None,
100
98
 
101
99
        :param repository: Repository to commit to.
102
100
        :param parents: Revision ids of the parents of the new revision.
103
 
        :param config: Configuration to use.
104
101
        :param timestamp: Optional timestamp recorded for commit.
105
102
        :param timezone: Optional timezone for timestamp.
106
103
        :param committer: Optional committer to set for commit.
111
108
 
112
109
        if committer is None:
113
110
            self._committer = self._config.username()
 
111
        elif not isinstance(committer, unicode):
 
112
            self._committer = committer.decode() # throw if non-ascii
114
113
        else:
115
114
            self._committer = committer
116
115
 
170
169
            self._validate_unicode_text(value,
171
170
                                        'revision property (%s)' % (key,))
172
171
 
 
172
    def _ensure_fallback_inventories(self):
 
173
        """Ensure that appropriate inventories are available.
 
174
 
 
175
        This only applies to repositories that are stacked, and is about
 
176
        enusring the stacking invariants. Namely, that for any revision that is
 
177
        present, we either have all of the file content, or we have the parent
 
178
        inventory and the delta file content.
 
179
        """
 
180
        if not self.repository._fallback_repositories:
 
181
            return
 
182
        if not self.repository._format.supports_chks:
 
183
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
184
                " in pre-2a formats. See "
 
185
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
186
        # This is a stacked repo, we need to make sure we have the parent
 
187
        # inventories for the parents.
 
188
        parent_keys = [(p,) for p in self.parents]
 
189
        parent_map = self.repository.inventories._index.get_parent_map(parent_keys)
 
190
        missing_parent_keys = set([pk for pk in parent_keys
 
191
                                       if pk not in parent_map])
 
192
        fallback_repos = list(reversed(self.repository._fallback_repositories))
 
193
        missing_keys = [('inventories', pk[0])
 
194
                        for pk in missing_parent_keys]
 
195
        resume_tokens = []
 
196
        while missing_keys and fallback_repos:
 
197
            fallback_repo = fallback_repos.pop()
 
198
            source = fallback_repo._get_source(self.repository._format)
 
199
            sink = self.repository._get_sink()
 
200
            stream = source.get_stream_for_missing_keys(missing_keys)
 
201
            missing_keys = sink.insert_stream_without_locking(stream,
 
202
                self.repository._format)
 
203
        if missing_keys:
 
204
            raise errors.BzrError('Unable to fill in parent inventories for a'
 
205
                                  ' stacked branch')
 
206
 
173
207
    def commit(self, message):
174
208
        """Make the actual commit.
175
209
 
187
221
        rev.parent_ids = self.parents
188
222
        self.repository.add_revision(self._new_revision_id, rev,
189
223
            self.new_inventory, self._config)
 
224
        self._ensure_fallback_inventories()
190
225
        self.repository.commit_write_group()
191
226
        return self._new_revision_id
192
227
 
241
276
 
242
277
    def _gen_revision_id(self):
243
278
        """Return new revision-id."""
244
 
        return generate_ids.gen_revision_id(self._config.username(),
245
 
                                            self._timestamp)
 
279
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
246
280
 
247
281
    def _generate_revision_if_needed(self):
248
282
        """Create a revision id if None was supplied.
288
322
 
289
323
        :param tree: The tree which is being committed.
290
324
        """
291
 
        # NB: if there are no parents then this method is not called, so no
292
 
        # need to guard on parents having length.
 
325
        if len(self.parents) == 0:
 
326
            raise errors.RootMissing()
293
327
        entry = entry_factory['directory'](tree.path2id(''), '',
294
328
            None)
295
329
        entry.revision = self._new_revision_id
433
467
            else:
434
468
                # we don't need to commit this, because the caller already
435
469
                # determined that an existing revision of this file is
436
 
                # appropriate. If its not being considered for committing then
 
470
                # appropriate. If it's not being considered for committing then
437
471
                # it and all its parents to the root must be unaltered so
438
472
                # no-change against the basis.
439
473
                if ie.revision == self._new_revision_id:
755
789
                    # after iter_changes examines and decides it has changed,
756
790
                    # we will unconditionally record a new version even if some
757
791
                    # other process reverts it while commit is running (with
758
 
                    # the revert happening after iter_changes did it's
 
792
                    # the revert happening after iter_changes did its
759
793
                    # examination).
760
794
                    if change[7][1]:
761
795
                        entry.executable = True
891
925
# Repositories
892
926
 
893
927
 
894
 
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
 
928
class Repository(_RelockDebugMixin, controldir.ControlComponent):
895
929
    """Repository holding history for one or more branches.
896
930
 
897
931
    The repository holds and retrieves historical information including
944
978
        pointing to .bzr/repository.
945
979
    """
946
980
 
947
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
981
    # What class to use for a CommitBuilder. Often it's simpler to change this
948
982
    # in a Repository class subclass rather than to override
949
983
    # get_commit_builder.
950
984
    _commit_builder_class = CommitBuilder
951
 
    # The search regex used by xml based repositories to determine what things
952
 
    # where changed in a single commit.
953
 
    _file_ids_altered_regex = lazy_regex.lazy_compile(
954
 
        r'file_id="(?P<file_id>[^"]+)"'
955
 
        r'.* revision="(?P<revision_id>[^"]+)"'
956
 
        )
957
985
 
958
986
    def abort_write_group(self, suppress_errors=False):
959
987
        """Commit the contents accrued within the current write group.
1045
1073
                " id and insertion revid (%r, %r)"
1046
1074
                % (inv.revision_id, revision_id))
1047
1075
        if inv.root is None:
1048
 
            raise AssertionError()
 
1076
            raise errors.RootMissing()
1049
1077
        return self._add_inventory_checked(revision_id, inv, parents)
1050
1078
 
1051
1079
    def _add_inventory_checked(self, revision_id, inv, parents):
1556
1584
        return ret
1557
1585
 
1558
1586
    @needs_read_lock
1559
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1587
    def search_missing_revision_ids(self, other,
 
1588
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
1589
            find_ghosts=True, revision_ids=None, if_present_ids=None):
1560
1590
        """Return the revision ids that other has that this does not.
1561
1591
 
1562
1592
        These are returned in topological order.
1563
1593
 
1564
1594
        revision_id: only return revision ids included by revision_id.
1565
1595
        """
 
1596
        if symbol_versioning.deprecated_passed(revision_id):
 
1597
            symbol_versioning.warn(
 
1598
                'search_missing_revision_ids(revision_id=...) was '
 
1599
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
1600
                DeprecationWarning, stacklevel=3)
 
1601
            if revision_ids is not None:
 
1602
                raise AssertionError(
 
1603
                    'revision_ids is mutually exclusive with revision_id')
 
1604
            if revision_id is not None:
 
1605
                revision_ids = [revision_id]
1566
1606
        return InterRepository.get(other, self).search_missing_revision_ids(
1567
 
            revision_id, find_ghosts)
 
1607
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1608
            if_present_ids=if_present_ids)
1568
1609
 
1569
1610
    @staticmethod
1570
1611
    def open(base):
1692
1733
    def _resume_write_group(self, tokens):
1693
1734
        raise errors.UnsuspendableWriteGroup(self)
1694
1735
 
1695
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1736
    def fetch(self, source, revision_id=None, find_ghosts=False,
1696
1737
            fetch_spec=None):
1697
1738
        """Fetch the content required to construct revision_id from source.
1698
1739
 
1722
1763
                "May not fetch while in a write group.")
1723
1764
        # fast path same-url fetch operations
1724
1765
        # TODO: lift out to somewhere common with RemoteRepository
1725
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1766
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1726
1767
        if (self.has_same_location(source)
1727
1768
            and fetch_spec is None
1728
1769
            and self._has_same_fallbacks(source)):
1732
1773
                not _mod_revision.is_null(revision_id)):
1733
1774
                self.get_revision(revision_id)
1734
1775
            return 0, []
1735
 
        # if there is no specific appropriate InterRepository, this will get
1736
 
        # the InterRepository base class, which raises an
1737
 
        # IncompatibleRepositories when asked to fetch.
1738
1776
        inter = InterRepository.get(source, self)
1739
 
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1777
        return inter.fetch(revision_id=revision_id,
1740
1778
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1741
1779
 
1742
1780
    def create_bundle(self, target, base, fileobj, format=None):
1756
1794
        :param revprops: Optional dictionary of revision properties.
1757
1795
        :param revision_id: Optional revision id.
1758
1796
        """
1759
 
        if self._fallback_repositories:
1760
 
            raise errors.BzrError("Cannot commit from a lightweight checkout "
1761
 
                "to a stacked branch. See "
 
1797
        if self._fallback_repositories and not self._format.supports_chks:
 
1798
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1799
                " in pre-2a formats. See "
1762
1800
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1763
1801
        result = self._commit_builder_class(self, parents, config,
1764
1802
            timestamp, timezone, committer, revprops, revision_id)
2013
2051
        w = self.inventories
2014
2052
        pb = ui.ui_factory.nested_progress_bar()
2015
2053
        try:
2016
 
            return self._find_text_key_references_from_xml_inventory_lines(
 
2054
            return self._serializer._find_text_key_references(
2017
2055
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
2018
2056
        finally:
2019
2057
            pb.finished()
2020
2058
 
2021
 
    def _find_text_key_references_from_xml_inventory_lines(self,
2022
 
        line_iterator):
2023
 
        """Core routine for extracting references to texts from inventories.
2024
 
 
2025
 
        This performs the translation of xml lines to revision ids.
2026
 
 
2027
 
        :param line_iterator: An iterator of lines, origin_version_id
2028
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
2029
 
            to whether they were referred to by the inventory of the
2030
 
            revision_id that they contain. Note that if that revision_id was
2031
 
            not part of the line_iterator's output then False will be given -
2032
 
            even though it may actually refer to that key.
2033
 
        """
2034
 
        if not self._serializer.support_altered_by_hack:
2035
 
            raise AssertionError(
2036
 
                "_find_text_key_references_from_xml_inventory_lines only "
2037
 
                "supported for branches which store inventory as unnested xml"
2038
 
                ", not on %r" % self)
2039
 
        result = {}
2040
 
 
2041
 
        # this code needs to read every new line in every inventory for the
2042
 
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
2043
 
        # not present in one of those inventories is unnecessary but not
2044
 
        # harmful because we are filtering by the revision id marker in the
2045
 
        # inventory lines : we only select file ids altered in one of those
2046
 
        # revisions. We don't need to see all lines in the inventory because
2047
 
        # only those added in an inventory in rev X can contain a revision=X
2048
 
        # line.
2049
 
        unescape_revid_cache = {}
2050
 
        unescape_fileid_cache = {}
2051
 
 
2052
 
        # jam 20061218 In a big fetch, this handles hundreds of thousands
2053
 
        # of lines, so it has had a lot of inlining and optimizing done.
2054
 
        # Sorry that it is a little bit messy.
2055
 
        # Move several functions to be local variables, since this is a long
2056
 
        # running loop.
2057
 
        search = self._file_ids_altered_regex.search
2058
 
        unescape = _unescape_xml
2059
 
        setdefault = result.setdefault
2060
 
        for line, line_key in line_iterator:
2061
 
            match = search(line)
2062
 
            if match is None:
2063
 
                continue
2064
 
            # One call to match.group() returning multiple items is quite a
2065
 
            # bit faster than 2 calls to match.group() each returning 1
2066
 
            file_id, revision_id = match.group('file_id', 'revision_id')
2067
 
 
2068
 
            # Inlining the cache lookups helps a lot when you make 170,000
2069
 
            # lines and 350k ids, versus 8.4 unique ids.
2070
 
            # Using a cache helps in 2 ways:
2071
 
            #   1) Avoids unnecessary decoding calls
2072
 
            #   2) Re-uses cached strings, which helps in future set and
2073
 
            #      equality checks.
2074
 
            # (2) is enough that removing encoding entirely along with
2075
 
            # the cache (so we are using plain strings) results in no
2076
 
            # performance improvement.
2077
 
            try:
2078
 
                revision_id = unescape_revid_cache[revision_id]
2079
 
            except KeyError:
2080
 
                unescaped = unescape(revision_id)
2081
 
                unescape_revid_cache[revision_id] = unescaped
2082
 
                revision_id = unescaped
2083
 
 
2084
 
            # Note that unconditionally unescaping means that we deserialise
2085
 
            # every fileid, which for general 'pull' is not great, but we don't
2086
 
            # really want to have some many fulltexts that this matters anyway.
2087
 
            # RBC 20071114.
2088
 
            try:
2089
 
                file_id = unescape_fileid_cache[file_id]
2090
 
            except KeyError:
2091
 
                unescaped = unescape(file_id)
2092
 
                unescape_fileid_cache[file_id] = unescaped
2093
 
                file_id = unescaped
2094
 
 
2095
 
            key = (file_id, revision_id)
2096
 
            setdefault(key, False)
2097
 
            if revision_id == line_key[-1]:
2098
 
                result[key] = True
2099
 
        return result
2100
 
 
2101
2059
    def _inventory_xml_lines_for_keys(self, keys):
2102
2060
        """Get a line iterator of the sort needed for findind references.
2103
2061
 
2133
2091
        revision_ids. Each altered file-ids has the exact revision_ids that
2134
2092
        altered it listed explicitly.
2135
2093
        """
2136
 
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2094
        seen = set(self._serializer._find_text_key_references(
2137
2095
                line_iterator).iterkeys())
2138
2096
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
2139
 
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2097
        parent_seen = set(self._serializer._find_text_key_references(
2140
2098
            self._inventory_xml_lines_for_keys(parent_keys)))
2141
2099
        new_keys = seen - parent_seen
2142
2100
        result = {}
2510
2468
            ancestors will be traversed.
2511
2469
        """
2512
2470
        graph = self.get_graph()
2513
 
        next_id = revision_id
2514
 
        while True:
2515
 
            if next_id in (None, _mod_revision.NULL_REVISION):
2516
 
                return
2517
 
            try:
2518
 
                parents = graph.get_parent_map([next_id])[next_id]
2519
 
            except KeyError:
2520
 
                raise errors.RevisionNotPresent(next_id, self)
2521
 
            yield next_id
2522
 
            if len(parents) == 0:
2523
 
                return
2524
 
            else:
2525
 
                next_id = parents[0]
 
2471
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2472
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2526
2473
 
2527
2474
    def is_shared(self):
2528
2475
        """Return True if this repository is flagged as a shared repository."""
2629
2576
        types it should be a no-op that just returns.
2630
2577
 
2631
2578
        This stub method does not require a lock, but subclasses should use
2632
 
        @needs_write_lock as this is a long running call its reasonable to
 
2579
        @needs_write_lock as this is a long running call it's reasonable to
2633
2580
        implicitly lock for the user.
2634
2581
 
2635
2582
        :param hint: If not supplied, the whole repository is packed.
2781
2728
        return result
2782
2729
 
2783
2730
    def _warn_if_deprecated(self, branch=None):
 
2731
        if not self._format.is_deprecated():
 
2732
            return
2784
2733
        global _deprecation_warning_done
2785
2734
        if _deprecation_warning_done:
2786
2735
            return
2816
2765
                except UnicodeDecodeError:
2817
2766
                    raise errors.NonAsciiRevisionId(method, self)
2818
2767
 
2819
 
    def revision_graph_can_have_wrong_parents(self):
2820
 
        """Is it possible for this repository to have a revision graph with
2821
 
        incorrect parents?
2822
 
 
2823
 
        If True, then this repository must also implement
2824
 
        _find_inconsistent_revision_parents so that check and reconcile can
2825
 
        check for inconsistencies before proceeding with other checks that may
2826
 
        depend on the revision index being consistent.
2827
 
        """
2828
 
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
2829
 
 
2830
 
 
2831
 
# remove these delegates a while after bzr 0.15
2832
 
def __make_delegated(name, from_module):
2833
 
    def _deprecated_repository_forwarder():
2834
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
2835
 
            % (name, from_module),
2836
 
            DeprecationWarning,
2837
 
            stacklevel=2)
2838
 
        m = __import__(from_module, globals(), locals(), [name])
2839
 
        try:
2840
 
            return getattr(m, name)
2841
 
        except AttributeError:
2842
 
            raise AttributeError('module %s has no name %s'
2843
 
                    % (m, name))
2844
 
    globals()[name] = _deprecated_repository_forwarder
2845
 
 
2846
 
for _name in [
2847
 
        'AllInOneRepository',
2848
 
        'WeaveMetaDirRepository',
2849
 
        'PreSplitOutRepositoryFormat',
2850
 
        'RepositoryFormat4',
2851
 
        'RepositoryFormat5',
2852
 
        'RepositoryFormat6',
2853
 
        'RepositoryFormat7',
2854
 
        ]:
2855
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
2856
 
 
2857
 
for _name in [
2858
 
        'KnitRepository',
2859
 
        'RepositoryFormatKnit',
2860
 
        'RepositoryFormatKnit1',
2861
 
        ]:
2862
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
2863
 
 
2864
2768
 
2865
2769
def install_revision(repository, rev, revision_tree):
2866
2770
    """Install all revision data into a repository."""
2999
2903
            control_files)
3000
2904
 
3001
2905
 
 
2906
class RepositoryFormatRegistry(controldir.ControlComponentFormatRegistry):
 
2907
    """Repository format registry."""
 
2908
 
 
2909
    def get_default(self):
 
2910
        """Return the current default format."""
 
2911
        from bzrlib import bzrdir
 
2912
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
2913
 
 
2914
 
3002
2915
network_format_registry = registry.FormatRegistry()
3003
2916
"""Registry of formats indexed by their network name.
3004
2917
 
3008
2921
"""
3009
2922
 
3010
2923
 
3011
 
format_registry = registry.FormatRegistry(network_format_registry)
 
2924
format_registry = RepositoryFormatRegistry(network_format_registry)
3012
2925
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
3013
2926
 
3014
2927
This can contain either format instances themselves, or classes/factories that
3019
2932
#####################################################################
3020
2933
# Repository Formats
3021
2934
 
3022
 
class RepositoryFormat(object):
 
2935
class RepositoryFormat(controldir.ControlComponentFormat):
3023
2936
    """A repository format.
3024
2937
 
3025
2938
    Formats provide four things:
3086
2999
    supports_tree_reference = None
3087
3000
    # Is the format experimental ?
3088
3001
    experimental = False
 
3002
    # Does this repository format escape funky characters, or does it create files with
 
3003
    # similar names as the versioned files in its contents on disk ?
 
3004
    supports_funky_characters = None
 
3005
    # Does this repository format support leaving locks?
 
3006
    supports_leaving_lock = None
 
3007
    # Does this format support the full VersionedFiles interface?
 
3008
    supports_full_versioned_files = None
 
3009
    # Does this format support signing revision signatures?
 
3010
    supports_revision_signatures = True
 
3011
    # Can the revision graph have incorrect parents?
 
3012
    revision_graph_can_have_wrong_parents = None
3089
3013
 
3090
3014
    def __repr__(self):
3091
3015
        return "%s()" % self.__class__.__name__
3116
3040
                                            kind='repository')
3117
3041
 
3118
3042
    @classmethod
 
3043
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3119
3044
    def register_format(klass, format):
3120
 
        format_registry.register(format.get_format_string(), format)
 
3045
        format_registry.register(format)
3121
3046
 
3122
3047
    @classmethod
 
3048
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3123
3049
    def unregister_format(klass, format):
3124
 
        format_registry.remove(format.get_format_string())
 
3050
        format_registry.remove(format)
3125
3051
 
3126
3052
    @classmethod
 
3053
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3127
3054
    def get_default_format(klass):
3128
3055
        """Return the current default format."""
3129
 
        from bzrlib import bzrdir
3130
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
3056
        return format_registry.get_default()
3131
3057
 
3132
3058
    def get_format_string(self):
3133
3059
        """Return the ASCII format string that identifies this format.
3184
3110
        """
3185
3111
        return True
3186
3112
 
 
3113
    def is_deprecated(self):
 
3114
        """Is this format deprecated?
 
3115
 
 
3116
        Deprecated formats may trigger a user-visible warning recommending
 
3117
        the user to upgrade. They are still fully supported.
 
3118
        """
 
3119
        return False
 
3120
 
3187
3121
    def network_name(self):
3188
3122
        """A simple byte string uniquely identifying this format for RPC calls.
3189
3123
 
3228
3162
    rich_root_data = False
3229
3163
    supports_tree_reference = False
3230
3164
    supports_external_lookups = False
 
3165
    supports_leaving_lock = True
3231
3166
 
3232
3167
    @property
3233
3168
    def _matchingbzrdir(self):
3271
3206
        return self.get_format_string()
3272
3207
 
3273
3208
 
3274
 
# Pre-0.8 formats that don't have a disk format string (because they are
3275
 
# versioned by the matching control directory). We use the control directories
3276
 
# disk format string as a key for the network_name because they meet the
3277
 
# constraints (simple string, unique, immutable).
3278
 
network_format_registry.register_lazy(
3279
 
    "Bazaar-NG branch, format 5\n",
3280
 
    'bzrlib.repofmt.weaverepo',
3281
 
    'RepositoryFormat5',
3282
 
)
3283
 
network_format_registry.register_lazy(
3284
 
    "Bazaar-NG branch, format 6\n",
3285
 
    'bzrlib.repofmt.weaverepo',
3286
 
    'RepositoryFormat6',
3287
 
)
3288
 
 
3289
3209
# formats which have no format string are not discoverable or independently
3290
3210
# creatable on disk, so are not registered in format_registry.  They're
3291
 
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
 
3211
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
3292
3212
# needed, it's constructed directly by the BzrDir.  Non-native formats where
3293
3213
# the repository is not separately opened are similar.
3294
3214
 
3295
3215
format_registry.register_lazy(
3296
 
    'Bazaar-NG Repository format 7',
3297
 
    'bzrlib.repofmt.weaverepo',
3298
 
    'RepositoryFormat7'
3299
 
    )
3300
 
 
3301
 
format_registry.register_lazy(
3302
3216
    'Bazaar-NG Knit Repository Format 1',
3303
3217
    'bzrlib.repofmt.knitrepo',
3304
3218
    'RepositoryFormatKnit1',
3321
3235
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
3322
3236
format_registry.register_lazy(
3323
3237
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
3324
 
    'bzrlib.repofmt.pack_repo',
 
3238
    'bzrlib.repofmt.knitpack_repo',
3325
3239
    'RepositoryFormatKnitPack1',
3326
3240
    )
3327
3241
format_registry.register_lazy(
3328
3242
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
3329
 
    'bzrlib.repofmt.pack_repo',
 
3243
    'bzrlib.repofmt.knitpack_repo',
3330
3244
    'RepositoryFormatKnitPack3',
3331
3245
    )
3332
3246
format_registry.register_lazy(
3333
3247
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
3334
 
    'bzrlib.repofmt.pack_repo',
 
3248
    'bzrlib.repofmt.knitpack_repo',
3335
3249
    'RepositoryFormatKnitPack4',
3336
3250
    )
3337
3251
format_registry.register_lazy(
3338
3252
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
3339
 
    'bzrlib.repofmt.pack_repo',
 
3253
    'bzrlib.repofmt.knitpack_repo',
3340
3254
    'RepositoryFormatKnitPack5',
3341
3255
    )
3342
3256
format_registry.register_lazy(
3343
3257
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
3344
 
    'bzrlib.repofmt.pack_repo',
 
3258
    'bzrlib.repofmt.knitpack_repo',
3345
3259
    'RepositoryFormatKnitPack5RichRoot',
3346
3260
    )
3347
3261
format_registry.register_lazy(
3348
3262
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
3349
 
    'bzrlib.repofmt.pack_repo',
 
3263
    'bzrlib.repofmt.knitpack_repo',
3350
3264
    'RepositoryFormatKnitPack5RichRootBroken',
3351
3265
    )
3352
3266
format_registry.register_lazy(
3353
3267
    'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
3354
 
    'bzrlib.repofmt.pack_repo',
 
3268
    'bzrlib.repofmt.knitpack_repo',
3355
3269
    'RepositoryFormatKnitPack6',
3356
3270
    )
3357
3271
format_registry.register_lazy(
3358
3272
    'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
3359
 
    'bzrlib.repofmt.pack_repo',
 
3273
    'bzrlib.repofmt.knitpack_repo',
3360
3274
    'RepositoryFormatKnitPack6RichRoot',
3361
3275
    )
 
3276
format_registry.register_lazy(
 
3277
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3278
    'bzrlib.repofmt.groupcompress_repo',
 
3279
    'RepositoryFormat2a',
 
3280
    )
3362
3281
 
3363
3282
# Development formats.
3364
 
# Obsolete but kept pending a CHK based subtree format.
 
3283
# Check their docstrings to see if/when they are obsolete.
3365
3284
format_registry.register_lazy(
3366
3285
    ("Bazaar development format 2 with subtree support "
3367
3286
        "(needs bzr.dev from before 1.8)\n"),
3368
 
    'bzrlib.repofmt.pack_repo',
 
3287
    'bzrlib.repofmt.knitpack_repo',
3369
3288
    'RepositoryFormatPackDevelopment2Subtree',
3370
3289
    )
3371
 
 
3372
 
# 1.14->1.16 go below here
3373
 
format_registry.register_lazy(
3374
 
    'Bazaar development format - group compression and chk inventory'
3375
 
        ' (needs bzr.dev from 1.14)\n',
3376
 
    'bzrlib.repofmt.groupcompress_repo',
3377
 
    'RepositoryFormatCHK1',
3378
 
    )
3379
 
 
3380
 
format_registry.register_lazy(
3381
 
    'Bazaar development format - chk repository with bencode revision '
3382
 
        'serialization (needs bzr.dev from 1.16)\n',
3383
 
    'bzrlib.repofmt.groupcompress_repo',
3384
 
    'RepositoryFormatCHK2',
3385
 
    )
3386
 
format_registry.register_lazy(
3387
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3388
 
    'bzrlib.repofmt.groupcompress_repo',
3389
 
    'RepositoryFormat2a',
 
3290
format_registry.register_lazy(
 
3291
    'Bazaar development format 8\n',
 
3292
    'bzrlib.repofmt.groupcompress_repo',
 
3293
    'RepositoryFormat2aSubtree',
3390
3294
    )
3391
3295
 
3392
3296
 
3423
3327
        self.target.fetch(self.source, revision_id=revision_id)
3424
3328
 
3425
3329
    @needs_write_lock
3426
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3330
    def fetch(self, revision_id=None, find_ghosts=False,
3427
3331
            fetch_spec=None):
3428
3332
        """Fetch the content required to construct revision_id.
3429
3333
 
3431
3335
 
3432
3336
        :param revision_id: if None all content is copied, if NULL_REVISION no
3433
3337
                            content is copied.
3434
 
        :param pb: ignored.
3435
3338
        :return: None.
3436
3339
        """
3437
3340
        ui.ui_factory.warn_experimental_format_fetch(self)
3447
3350
                               fetch_spec=fetch_spec,
3448
3351
                               find_ghosts=find_ghosts)
3449
3352
 
3450
 
    def _walk_to_common_revisions(self, revision_ids):
 
3353
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
3451
3354
        """Walk out from revision_ids in source to revisions target has.
3452
3355
 
3453
3356
        :param revision_ids: The start point for the search.
3455
3358
        """
3456
3359
        target_graph = self.target.get_graph()
3457
3360
        revision_ids = frozenset(revision_ids)
 
3361
        if if_present_ids:
 
3362
            all_wanted_revs = revision_ids.union(if_present_ids)
 
3363
        else:
 
3364
            all_wanted_revs = revision_ids
3458
3365
        missing_revs = set()
3459
3366
        source_graph = self.source.get_graph()
3460
3367
        # ensure we don't pay silly lookup costs.
3461
 
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
 
3368
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
3462
3369
        null_set = frozenset([_mod_revision.NULL_REVISION])
3463
3370
        searcher_exhausted = False
3464
3371
        while True:
3500
3407
        return searcher.get_result()
3501
3408
 
3502
3409
    @needs_read_lock
3503
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3410
    def search_missing_revision_ids(self,
 
3411
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
3412
            find_ghosts=True, revision_ids=None, if_present_ids=None):
3504
3413
        """Return the revision ids that source has that target does not.
3505
3414
 
3506
3415
        :param revision_id: only return revision ids included by this
3507
 
                            revision_id.
 
3416
            revision_id.
 
3417
        :param revision_ids: return revision ids included by these
 
3418
            revision_ids.  NoSuchRevision will be raised if any of these
 
3419
            revisions are not present.
 
3420
        :param if_present_ids: like revision_ids, but will not cause
 
3421
            NoSuchRevision if any of these are absent, instead they will simply
 
3422
            not be in the result.  This is useful for e.g. finding revisions
 
3423
            to fetch for tags, which may reference absent revisions.
3508
3424
        :param find_ghosts: If True find missing revisions in deep history
3509
3425
            rather than just finding the surface difference.
3510
3426
        :return: A bzrlib.graph.SearchResult.
3511
3427
        """
 
3428
        if symbol_versioning.deprecated_passed(revision_id):
 
3429
            symbol_versioning.warn(
 
3430
                'search_missing_revision_ids(revision_id=...) was '
 
3431
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
3432
                DeprecationWarning, stacklevel=2)
 
3433
            if revision_ids is not None:
 
3434
                raise AssertionError(
 
3435
                    'revision_ids is mutually exclusive with revision_id')
 
3436
            if revision_id is not None:
 
3437
                revision_ids = [revision_id]
 
3438
        del revision_id
3512
3439
        # stop searching at found target revisions.
3513
 
        if not find_ghosts and revision_id is not None:
3514
 
            return self._walk_to_common_revisions([revision_id])
 
3440
        if not find_ghosts and (revision_ids is not None or if_present_ids is
 
3441
                not None):
 
3442
            return self._walk_to_common_revisions(revision_ids,
 
3443
                    if_present_ids=if_present_ids)
3515
3444
        # generic, possibly worst case, slow code path.
3516
3445
        target_ids = set(self.target.all_revision_ids())
3517
 
        if revision_id is not None:
3518
 
            source_ids = self.source.get_ancestry(revision_id)
3519
 
            if source_ids[0] is not None:
3520
 
                raise AssertionError()
3521
 
            source_ids.pop(0)
3522
 
        else:
3523
 
            source_ids = self.source.all_revision_ids()
 
3446
        source_ids = self._present_source_revisions_for(
 
3447
            revision_ids, if_present_ids)
3524
3448
        result_set = set(source_ids).difference(target_ids)
3525
3449
        return self.source.revision_ids_to_search_result(result_set)
3526
3450
 
 
3451
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
 
3452
        """Returns set of all revisions in ancestry of revision_ids present in
 
3453
        the source repo.
 
3454
 
 
3455
        :param revision_ids: if None, all revisions in source are returned.
 
3456
        :param if_present_ids: like revision_ids, but if any/all of these are
 
3457
            absent no error is raised.
 
3458
        """
 
3459
        if revision_ids is not None or if_present_ids is not None:
 
3460
            # First, ensure all specified revisions exist.  Callers expect
 
3461
            # NoSuchRevision when they pass absent revision_ids here.
 
3462
            if revision_ids is None:
 
3463
                revision_ids = set()
 
3464
            if if_present_ids is None:
 
3465
                if_present_ids = set()
 
3466
            revision_ids = set(revision_ids)
 
3467
            if_present_ids = set(if_present_ids)
 
3468
            all_wanted_ids = revision_ids.union(if_present_ids)
 
3469
            graph = self.source.get_graph()
 
3470
            present_revs = set(graph.get_parent_map(all_wanted_ids))
 
3471
            missing = revision_ids.difference(present_revs)
 
3472
            if missing:
 
3473
                raise errors.NoSuchRevision(self.source, missing.pop())
 
3474
            found_ids = all_wanted_ids.intersection(present_revs)
 
3475
            source_ids = [rev_id for (rev_id, parents) in
 
3476
                          graph.iter_ancestry(found_ids)
 
3477
                          if rev_id != _mod_revision.NULL_REVISION
 
3478
                          and parents is not None]
 
3479
        else:
 
3480
            source_ids = self.source.all_revision_ids()
 
3481
        return set(source_ids)
 
3482
 
3527
3483
    @staticmethod
3528
3484
    def _same_model(source, target):
3529
3485
        """True if source and target have the same data representation.
3570
3526
        return InterRepository._same_model(source, target)
3571
3527
 
3572
3528
 
3573
 
class InterWeaveRepo(InterSameDataRepository):
3574
 
    """Optimised code paths between Weave based repositories.
3575
 
 
3576
 
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
3577
 
    implemented lazy inter-object optimisation.
3578
 
    """
3579
 
 
3580
 
    @classmethod
3581
 
    def _get_repo_format_to_test(self):
3582
 
        from bzrlib.repofmt import weaverepo
3583
 
        return weaverepo.RepositoryFormat7()
3584
 
 
3585
 
    @staticmethod
3586
 
    def is_compatible(source, target):
3587
 
        """Be compatible with known Weave formats.
3588
 
 
3589
 
        We don't test for the stores being of specific types because that
3590
 
        could lead to confusing results, and there is no need to be
3591
 
        overly general.
3592
 
        """
3593
 
        from bzrlib.repofmt.weaverepo import (
3594
 
                RepositoryFormat5,
3595
 
                RepositoryFormat6,
3596
 
                RepositoryFormat7,
3597
 
                )
3598
 
        try:
3599
 
            return (isinstance(source._format, (RepositoryFormat5,
3600
 
                                                RepositoryFormat6,
3601
 
                                                RepositoryFormat7)) and
3602
 
                    isinstance(target._format, (RepositoryFormat5,
3603
 
                                                RepositoryFormat6,
3604
 
                                                RepositoryFormat7)))
3605
 
        except AttributeError:
3606
 
            return False
3607
 
 
3608
 
    @needs_write_lock
3609
 
    def copy_content(self, revision_id=None):
3610
 
        """See InterRepository.copy_content()."""
3611
 
        # weave specific optimised path:
3612
 
        try:
3613
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3614
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
3615
 
            pass
3616
 
        # FIXME do not peek!
3617
 
        if self.source._transport.listable():
3618
 
            pb = ui.ui_factory.nested_progress_bar()
3619
 
            try:
3620
 
                self.target.texts.insert_record_stream(
3621
 
                    self.source.texts.get_record_stream(
3622
 
                        self.source.texts.keys(), 'topological', False))
3623
 
                pb.update('Copying inventory', 0, 1)
3624
 
                self.target.inventories.insert_record_stream(
3625
 
                    self.source.inventories.get_record_stream(
3626
 
                        self.source.inventories.keys(), 'topological', False))
3627
 
                self.target.signatures.insert_record_stream(
3628
 
                    self.source.signatures.get_record_stream(
3629
 
                        self.source.signatures.keys(),
3630
 
                        'unordered', True))
3631
 
                self.target.revisions.insert_record_stream(
3632
 
                    self.source.revisions.get_record_stream(
3633
 
                        self.source.revisions.keys(),
3634
 
                        'topological', True))
3635
 
            finally:
3636
 
                pb.finished()
3637
 
        else:
3638
 
            self.target.fetch(self.source, revision_id=revision_id)
3639
 
 
3640
 
    @needs_read_lock
3641
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3642
 
        """See InterRepository.missing_revision_ids()."""
3643
 
        # we want all revisions to satisfy revision_id in source.
3644
 
        # but we don't want to stat every file here and there.
3645
 
        # we want then, all revisions other needs to satisfy revision_id
3646
 
        # checked, but not those that we have locally.
3647
 
        # so the first thing is to get a subset of the revisions to
3648
 
        # satisfy revision_id in source, and then eliminate those that
3649
 
        # we do already have.
3650
 
        # this is slow on high latency connection to self, but as this
3651
 
        # disk format scales terribly for push anyway due to rewriting
3652
 
        # inventory.weave, this is considered acceptable.
3653
 
        # - RBC 20060209
3654
 
        if revision_id is not None:
3655
 
            source_ids = self.source.get_ancestry(revision_id)
3656
 
            if source_ids[0] is not None:
3657
 
                raise AssertionError()
3658
 
            source_ids.pop(0)
3659
 
        else:
3660
 
            source_ids = self.source._all_possible_ids()
3661
 
        source_ids_set = set(source_ids)
3662
 
        # source_ids is the worst possible case we may need to pull.
3663
 
        # now we want to filter source_ids against what we actually
3664
 
        # have in target, but don't try to check for existence where we know
3665
 
        # we do not have a revision as that would be pointless.
3666
 
        target_ids = set(self.target._all_possible_ids())
3667
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3668
 
        actually_present_revisions = set(
3669
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3670
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3671
 
        if revision_id is not None:
3672
 
            # we used get_ancestry to determine source_ids then we are assured all
3673
 
            # revisions referenced are present as they are installed in topological order.
3674
 
            # and the tip revision was validated by get_ancestry.
3675
 
            result_set = required_revisions
3676
 
        else:
3677
 
            # if we just grabbed the possibly available ids, then
3678
 
            # we only have an estimate of whats available and need to validate
3679
 
            # that against the revision records.
3680
 
            result_set = set(
3681
 
                self.source._eliminate_revisions_not_present(required_revisions))
3682
 
        return self.source.revision_ids_to_search_result(result_set)
3683
 
 
3684
 
 
3685
 
class InterKnitRepo(InterSameDataRepository):
3686
 
    """Optimised code paths between Knit based repositories."""
3687
 
 
3688
 
    @classmethod
3689
 
    def _get_repo_format_to_test(self):
3690
 
        from bzrlib.repofmt import knitrepo
3691
 
        return knitrepo.RepositoryFormatKnit1()
3692
 
 
3693
 
    @staticmethod
3694
 
    def is_compatible(source, target):
3695
 
        """Be compatible with known Knit formats.
3696
 
 
3697
 
        We don't test for the stores being of specific types because that
3698
 
        could lead to confusing results, and there is no need to be
3699
 
        overly general.
3700
 
        """
3701
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
3702
 
        try:
3703
 
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
3704
 
                isinstance(target._format, RepositoryFormatKnit))
3705
 
        except AttributeError:
3706
 
            return False
3707
 
        return are_knits and InterRepository._same_model(source, target)
3708
 
 
3709
 
    @needs_read_lock
3710
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3711
 
        """See InterRepository.missing_revision_ids()."""
3712
 
        if revision_id is not None:
3713
 
            source_ids = self.source.get_ancestry(revision_id)
3714
 
            if source_ids[0] is not None:
3715
 
                raise AssertionError()
3716
 
            source_ids.pop(0)
3717
 
        else:
3718
 
            source_ids = self.source.all_revision_ids()
3719
 
        source_ids_set = set(source_ids)
3720
 
        # source_ids is the worst possible case we may need to pull.
3721
 
        # now we want to filter source_ids against what we actually
3722
 
        # have in target, but don't try to check for existence where we know
3723
 
        # we do not have a revision as that would be pointless.
3724
 
        target_ids = set(self.target.all_revision_ids())
3725
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3726
 
        actually_present_revisions = set(
3727
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3728
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3729
 
        if revision_id is not None:
3730
 
            # we used get_ancestry to determine source_ids then we are assured all
3731
 
            # revisions referenced are present as they are installed in topological order.
3732
 
            # and the tip revision was validated by get_ancestry.
3733
 
            result_set = required_revisions
3734
 
        else:
3735
 
            # if we just grabbed the possibly available ids, then
3736
 
            # we only have an estimate of whats available and need to validate
3737
 
            # that against the revision records.
3738
 
            result_set = set(
3739
 
                self.source._eliminate_revisions_not_present(required_revisions))
3740
 
        return self.source.revision_ids_to_search_result(result_set)
3741
 
 
3742
 
 
3743
3529
class InterDifferingSerializer(InterRepository):
3744
3530
 
3745
3531
    @classmethod
3847
3633
                basis_id, delta, current_revision_id, parents_parents)
3848
3634
            cache[current_revision_id] = parent_tree
3849
3635
 
3850
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3636
    def _fetch_batch(self, revision_ids, basis_id, cache):
3851
3637
        """Fetch across a few revisions.
3852
3638
 
3853
3639
        :param revision_ids: The revisions to copy
3854
3640
        :param basis_id: The revision_id of a tree that must be in cache, used
3855
3641
            as a basis for delta when no other base is available
3856
3642
        :param cache: A cache of RevisionTrees that we can use.
3857
 
        :param a_graph: A Graph object to determine the heads() of the
3858
 
            rich-root data stream.
3859
3643
        :return: The revision_id of the last converted tree. The RevisionTree
3860
3644
            for it will be in cache
3861
3645
        """
3929
3713
        if root_keys_to_create:
3930
3714
            root_stream = _mod_fetch._new_root_data_stream(
3931
3715
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3932
 
                self.source, graph=a_graph)
 
3716
                self.source)
3933
3717
            to_texts.insert_record_stream(root_stream)
3934
3718
        to_texts.insert_record_stream(from_texts.get_record_stream(
3935
3719
            text_keys, self.target._format._fetch_order,
3992
3776
        cache[basis_id] = basis_tree
3993
3777
        del basis_tree # We don't want to hang on to it here
3994
3778
        hints = []
3995
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3996
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3997
 
                                                            revision_ids)
3998
 
        else:
3999
 
            a_graph = None
 
3779
        a_graph = None
4000
3780
 
4001
3781
        for offset in range(0, len(revision_ids), batch_size):
4002
3782
            self.target.start_write_group()
4004
3784
                pb.update('Transferring revisions', offset,
4005
3785
                          len(revision_ids))
4006
3786
                batch = revision_ids[offset:offset+batch_size]
4007
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
4008
 
                                             a_graph=a_graph)
 
3787
                basis_id = self._fetch_batch(batch, basis_id, cache)
4009
3788
            except:
4010
3789
                self.source._safe_to_return_from_cache = False
4011
3790
                self.target.abort_write_group()
4020
3799
                  len(revision_ids))
4021
3800
 
4022
3801
    @needs_write_lock
4023
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3802
    def fetch(self, revision_id=None, find_ghosts=False,
4024
3803
            fetch_spec=None):
4025
3804
        """See InterRepository.fetch()."""
4026
3805
        if fetch_spec is not None:
4027
 
            raise AssertionError("Not implemented yet...")
 
3806
            revision_ids = fetch_spec.get_keys()
 
3807
        else:
 
3808
            revision_ids = None
4028
3809
        ui.ui_factory.warn_experimental_format_fetch(self)
4029
3810
        if (not self.source.supports_rich_root()
4030
3811
            and self.target.supports_rich_root()):
4037
3818
            ui.ui_factory.show_user_warning('cross_format_fetch',
4038
3819
                from_format=self.source._format,
4039
3820
                to_format=self.target._format)
4040
 
        revision_ids = self.target.search_missing_revision_ids(self.source,
4041
 
            revision_id, find_ghosts=find_ghosts).get_keys()
 
3821
        if revision_ids is None:
 
3822
            if revision_id:
 
3823
                search_revision_ids = [revision_id]
 
3824
            else:
 
3825
                search_revision_ids = None
 
3826
            revision_ids = self.target.search_missing_revision_ids(self.source,
 
3827
                revision_ids=search_revision_ids,
 
3828
                find_ghosts=find_ghosts).get_keys()
4042
3829
        if not revision_ids:
4043
3830
            return 0, 0
4044
3831
        revision_ids = tsort.topo_sort(
4048
3835
        # Walk though all revisions; get inventory deltas, copy referenced
4049
3836
        # texts that delta references, insert the delta, revision and
4050
3837
        # signature.
4051
 
        if pb is None:
4052
 
            my_pb = ui.ui_factory.nested_progress_bar()
4053
 
            pb = my_pb
4054
 
        else:
4055
 
            symbol_versioning.warn(
4056
 
                symbol_versioning.deprecated_in((1, 14, 0))
4057
 
                % "pb parameter to fetch()")
4058
 
            my_pb = None
 
3838
        pb = ui.ui_factory.nested_progress_bar()
4059
3839
        try:
4060
3840
            self._fetch_all_revisions(revision_ids, pb)
4061
3841
        finally:
4062
 
            if my_pb is not None:
4063
 
                my_pb.finished()
 
3842
            pb.finished()
4064
3843
        return len(revision_ids), 0
4065
3844
 
4066
3845
    def _get_basis(self, first_revision_id):
4077
3856
            basis_id = first_rev.parent_ids[0]
4078
3857
            # only valid as a basis if the target has it
4079
3858
            self.target.get_revision(basis_id)
4080
 
            # Try to get a basis tree - if its a ghost it will hit the
 
3859
            # Try to get a basis tree - if it's a ghost it will hit the
4081
3860
            # NoSuchRevision case.
4082
3861
            basis_tree = self.source.revision_tree(basis_id)
4083
3862
        except (IndexError, errors.NoSuchRevision):
4088
3867
 
4089
3868
InterRepository.register_optimiser(InterDifferingSerializer)
4090
3869
InterRepository.register_optimiser(InterSameDataRepository)
4091
 
InterRepository.register_optimiser(InterWeaveRepo)
4092
 
InterRepository.register_optimiser(InterKnitRepo)
4093
3870
 
4094
3871
 
4095
3872
class CopyConverter(object):
4140
3917
        pb.finished()
4141
3918
 
4142
3919
 
4143
 
_unescape_map = {
4144
 
    'apos':"'",
4145
 
    'quot':'"',
4146
 
    'amp':'&',
4147
 
    'lt':'<',
4148
 
    'gt':'>'
4149
 
}
4150
 
 
4151
 
 
4152
 
def _unescaper(match, _map=_unescape_map):
4153
 
    code = match.group(1)
4154
 
    try:
4155
 
        return _map[code]
4156
 
    except KeyError:
4157
 
        if not code.startswith('#'):
4158
 
            raise
4159
 
        return unichr(int(code[1:])).encode('utf8')
4160
 
 
4161
 
 
4162
 
_unescape_re = None
4163
 
 
4164
 
 
4165
 
def _unescape_xml(data):
4166
 
    """Unescape predefined XML entities in a string of data."""
4167
 
    global _unescape_re
4168
 
    if _unescape_re is None:
4169
 
        _unescape_re = re.compile('\&([^;]*);')
4170
 
    return _unescape_re.sub(_unescaper, data)
4171
 
 
4172
 
 
4173
3920
class _VersionedFileChecker(object):
4174
3921
 
4175
3922
    def __init__(self, repository, text_key_references=None, ancestors=None):
4234
3981
        return wrong_parents, unused_keys
4235
3982
 
4236
3983
 
4237
 
def _old_get_graph(repository, revision_id):
4238
 
    """DO NOT USE. That is all. I'm serious."""
4239
 
    graph = repository.get_graph()
4240
 
    revision_graph = dict(((key, value) for key, value in
4241
 
        graph.iter_ancestry([revision_id]) if value is not None))
4242
 
    return _strip_NULL_ghosts(revision_graph)
4243
 
 
4244
 
 
4245
3984
def _strip_NULL_ghosts(revision_graph):
4246
3985
    """Also don't use this. more compatibility code for unmigrated clients."""
4247
3986
    # Filter ghosts, and null:
4283
4022
                is_resume = False
4284
4023
            try:
4285
4024
                # locked_insert_stream performs a commit|suspend.
4286
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4025
                missing_keys = self.insert_stream_without_locking(stream,
 
4026
                                    src_format, is_resume)
 
4027
                if missing_keys:
 
4028
                    # suspend the write group and tell the caller what we is
 
4029
                    # missing. We know we can suspend or else we would not have
 
4030
                    # entered this code path. (All repositories that can handle
 
4031
                    # missing keys can handle suspending a write group).
 
4032
                    write_group_tokens = self.target_repo.suspend_write_group()
 
4033
                    return write_group_tokens, missing_keys
 
4034
                hint = self.target_repo.commit_write_group()
 
4035
                to_serializer = self.target_repo._format._serializer
 
4036
                src_serializer = src_format._serializer
 
4037
                if (to_serializer != src_serializer and
 
4038
                    self.target_repo._format.pack_compresses):
 
4039
                    self.target_repo.pack(hint=hint)
 
4040
                return [], set()
4287
4041
            except:
4288
4042
                self.target_repo.abort_write_group(suppress_errors=True)
4289
4043
                raise
4290
4044
        finally:
4291
4045
            self.target_repo.unlock()
4292
4046
 
4293
 
    def _locked_insert_stream(self, stream, src_format, is_resume):
 
4047
    def insert_stream_without_locking(self, stream, src_format,
 
4048
                                      is_resume=False):
 
4049
        """Insert a stream's content into the target repository.
 
4050
 
 
4051
        This assumes that you already have a locked repository and an active
 
4052
        write group.
 
4053
 
 
4054
        :param src_format: a bzr repository format.
 
4055
        :param is_resume: Passed down to get_missing_parent_inventories to
 
4056
            indicate if we should be checking for missing texts at the same
 
4057
            time.
 
4058
 
 
4059
        :return: A set of keys that are missing.
 
4060
        """
 
4061
        if not self.target_repo.is_write_locked():
 
4062
            raise errors.ObjectNotLocked(self)
 
4063
        if not self.target_repo.is_in_write_group():
 
4064
            raise errors.BzrError('you must already be in a write group')
4294
4065
        to_serializer = self.target_repo._format._serializer
4295
4066
        src_serializer = src_format._serializer
4296
4067
        new_pack = None
4336
4107
                # required if the serializers are different only in terms of
4337
4108
                # the inventory.
4338
4109
                if src_serializer == to_serializer:
4339
 
                    self.target_repo.revisions.insert_record_stream(
4340
 
                        substream)
 
4110
                    self.target_repo.revisions.insert_record_stream(substream)
4341
4111
                else:
4342
4112
                    self._extract_and_insert_revisions(substream,
4343
4113
                        src_serializer)
4376
4146
            # cannot even attempt suspending, and missing would have failed
4377
4147
            # during stream insertion.
4378
4148
            missing_keys = set()
4379
 
        else:
4380
 
            if missing_keys:
4381
 
                # suspend the write group and tell the caller what we is
4382
 
                # missing. We know we can suspend or else we would not have
4383
 
                # entered this code path. (All repositories that can handle
4384
 
                # missing keys can handle suspending a write group).
4385
 
                write_group_tokens = self.target_repo.suspend_write_group()
4386
 
                return write_group_tokens, missing_keys
4387
 
        hint = self.target_repo.commit_write_group()
4388
 
        if (to_serializer != src_serializer and
4389
 
            self.target_repo._format.pack_compresses):
4390
 
            self.target_repo.pack(hint=hint)
4391
 
        return [], set()
 
4149
        return missing_keys
4392
4150
 
4393
4151
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4394
4152
        target_rich_root = self.target_repo._format.rich_root_data
4401
4159
                parse_result = deserialiser.parse_text_bytes(
4402
4160
                    inventory_delta_bytes)
4403
4161
            except inventory_delta.IncompatibleInventoryDelta, err:
4404
 
                trace.mutter("Incompatible delta: %s", err.msg)
 
4162
                mutter("Incompatible delta: %s", err.msg)
4405
4163
                raise errors.IncompatibleRevision(self.target_repo._format)
4406
4164
            basis_id, new_id, rich_root, tree_refs, inv_delta = parse_result
4407
4165
            revision_id = new_id
4451
4209
        """Create a StreamSource streaming from from_repository."""
4452
4210
        self.from_repository = from_repository
4453
4211
        self.to_format = to_format
 
4212
        self._record_counter = RecordCounter()
4454
4213
 
4455
4214
    def delta_on_metadata(self):
4456
4215
        """Return True if delta's are permitted on metadata streams.
4741
4500
    except StopIteration:
4742
4501
        # No more history
4743
4502
        return
4744