~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-04-18 04:55:00 UTC
  • mfrom: (5784.2.1 754188-apport-test)
  • Revision ID: pqm@pqm.ubuntu.com-20110418045500-ce6lkgyiq7f47q43
(mbp) Rewrite test_report_bug_legacy away from using doctest (see bug
 764188) (Martin Pool)

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,
28
25
    controldir,
29
26
    debug,
32
29
    generate_ids,
33
30
    gpg,
34
31
    graph,
35
 
    inventory,
36
32
    inventory_delta,
37
 
    lazy_regex,
38
33
    lockable_files,
39
34
    lockdir,
40
35
    lru_cache,
41
36
    osutils,
42
 
    pyutils,
43
37
    revision as _mod_revision,
44
38
    static_tuple,
45
 
    symbol_versioning,
46
 
    trace,
47
39
    tsort,
48
40
    versionedfile,
49
41
    )
50
42
from bzrlib.bundle import serializer
51
 
from bzrlib.revisiontree import RevisionTree
 
43
from bzrlib.recordcounter import RecordCounter
 
44
from bzrlib.revisiontree import InventoryRevisionTree
52
45
from bzrlib.store.versioned import VersionedFileStore
53
46
from bzrlib.testament import Testament
54
47
""")
55
48
 
56
 
import sys
57
49
from bzrlib import (
58
50
    errors,
59
51
    registry,
 
52
    symbol_versioning,
60
53
    ui,
61
54
    )
62
55
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
67
60
    ROOT_ID,
68
61
    entry_factory,
69
62
    )
70
 
from bzrlib.recordcounter import RecordCounter
71
63
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
72
64
from bzrlib.trace import (
73
65
    log_exception_quietly, note, mutter, mutter_callsite, warning)
96
88
    record_root_entry = True
97
89
    # the default CommitBuilder does not manage trees whose root is versioned.
98
90
    _versioned_root = False
 
91
    # this commit builder supports the record_entry_contents interface
 
92
    supports_record_entry_contents = True
99
93
 
100
94
    def __init__(self, repository, parents, config, timestamp=None,
101
95
                 timezone=None, committer=None, revprops=None,
102
 
                 revision_id=None):
 
96
                 revision_id=None, lossy=False):
103
97
        """Initiate a CommitBuilder.
104
98
 
105
99
        :param repository: Repository to commit to.
106
100
        :param parents: Revision ids of the parents of the new revision.
107
 
        :param config: Configuration to use.
108
101
        :param timestamp: Optional timestamp recorded for commit.
109
102
        :param timezone: Optional timezone for timestamp.
110
103
        :param committer: Optional committer to set for commit.
111
104
        :param revprops: Optional dictionary of revision properties.
112
105
        :param revision_id: Optional revision id.
 
106
        :param lossy: Whether to discard data that can not be natively
 
107
            represented, when pushing to a foreign VCS 
113
108
        """
114
109
        self._config = config
 
110
        self._lossy = lossy
115
111
 
116
112
        if committer is None:
117
113
            self._committer = self._config.username()
176
172
            self._validate_unicode_text(value,
177
173
                                        'revision property (%s)' % (key,))
178
174
 
 
175
    def _ensure_fallback_inventories(self):
 
176
        """Ensure that appropriate inventories are available.
 
177
 
 
178
        This only applies to repositories that are stacked, and is about
 
179
        enusring the stacking invariants. Namely, that for any revision that is
 
180
        present, we either have all of the file content, or we have the parent
 
181
        inventory and the delta file content.
 
182
        """
 
183
        if not self.repository._fallback_repositories:
 
184
            return
 
185
        if not self.repository._format.supports_chks:
 
186
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
187
                " in pre-2a formats. See "
 
188
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
189
        # This is a stacked repo, we need to make sure we have the parent
 
190
        # inventories for the parents.
 
191
        parent_keys = [(p,) for p in self.parents]
 
192
        parent_map = self.repository.inventories._index.get_parent_map(parent_keys)
 
193
        missing_parent_keys = set([pk for pk in parent_keys
 
194
                                       if pk not in parent_map])
 
195
        fallback_repos = list(reversed(self.repository._fallback_repositories))
 
196
        missing_keys = [('inventories', pk[0])
 
197
                        for pk in missing_parent_keys]
 
198
        resume_tokens = []
 
199
        while missing_keys and fallback_repos:
 
200
            fallback_repo = fallback_repos.pop()
 
201
            source = fallback_repo._get_source(self.repository._format)
 
202
            sink = self.repository._get_sink()
 
203
            stream = source.get_stream_for_missing_keys(missing_keys)
 
204
            missing_keys = sink.insert_stream_without_locking(stream,
 
205
                self.repository._format)
 
206
        if missing_keys:
 
207
            raise errors.BzrError('Unable to fill in parent inventories for a'
 
208
                                  ' stacked branch')
 
209
 
179
210
    def commit(self, message):
180
211
        """Make the actual commit.
181
212
 
193
224
        rev.parent_ids = self.parents
194
225
        self.repository.add_revision(self._new_revision_id, rev,
195
226
            self.new_inventory, self._config)
 
227
        self._ensure_fallback_inventories()
196
228
        self.repository.commit_write_group()
197
229
        return self._new_revision_id
198
230
 
204
236
    def revision_tree(self):
205
237
        """Return the tree that was just committed.
206
238
 
207
 
        After calling commit() this can be called to get a RevisionTree
208
 
        representing the newly committed tree. This is preferred to
209
 
        calling Repository.revision_tree() because that may require
210
 
        deserializing the inventory, while we already have a copy in
 
239
        After calling commit() this can be called to get a
 
240
        InventoryRevisionTree representing the newly committed tree. This is
 
241
        preferred to calling Repository.revision_tree() because that may
 
242
        require deserializing the inventory, while we already have a copy in
211
243
        memory.
212
244
        """
213
245
        if self.new_inventory is None:
214
246
            self.new_inventory = self.repository.get_inventory(
215
247
                self._new_revision_id)
216
 
        return RevisionTree(self.repository, self.new_inventory,
 
248
        return InventoryRevisionTree(self.repository, self.new_inventory,
217
249
            self._new_revision_id)
218
250
 
219
251
    def finish_inventory(self):
953
985
    # in a Repository class subclass rather than to override
954
986
    # get_commit_builder.
955
987
    _commit_builder_class = CommitBuilder
956
 
    # The search regex used by xml based repositories to determine what things
957
 
    # where changed in a single commit.
958
 
    _file_ids_altered_regex = lazy_regex.lazy_compile(
959
 
        r'file_id="(?P<file_id>[^"]+)"'
960
 
        r'.* revision="(?P<revision_id>[^"]+)"'
961
 
        )
962
988
 
963
989
    def abort_write_group(self, suppress_errors=False):
964
990
        """Commit the contents accrued within the current write group.
1561
1587
        return ret
1562
1588
 
1563
1589
    @needs_read_lock
1564
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1590
    def search_missing_revision_ids(self, other,
 
1591
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
1592
            find_ghosts=True, revision_ids=None, if_present_ids=None):
1565
1593
        """Return the revision ids that other has that this does not.
1566
1594
 
1567
1595
        These are returned in topological order.
1568
1596
 
1569
1597
        revision_id: only return revision ids included by revision_id.
1570
1598
        """
 
1599
        if symbol_versioning.deprecated_passed(revision_id):
 
1600
            symbol_versioning.warn(
 
1601
                'search_missing_revision_ids(revision_id=...) was '
 
1602
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
1603
                DeprecationWarning, stacklevel=3)
 
1604
            if revision_ids is not None:
 
1605
                raise AssertionError(
 
1606
                    'revision_ids is mutually exclusive with revision_id')
 
1607
            if revision_id is not None:
 
1608
                revision_ids = [revision_id]
1571
1609
        return InterRepository.get(other, self).search_missing_revision_ids(
1572
 
            revision_id, find_ghosts)
 
1610
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1611
            if_present_ids=if_present_ids)
1573
1612
 
1574
1613
    @staticmethod
1575
1614
    def open(base):
1697
1736
    def _resume_write_group(self, tokens):
1698
1737
        raise errors.UnsuspendableWriteGroup(self)
1699
1738
 
1700
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1739
    def fetch(self, source, revision_id=None, find_ghosts=False,
1701
1740
            fetch_spec=None):
1702
1741
        """Fetch the content required to construct revision_id from source.
1703
1742
 
1737
1776
                not _mod_revision.is_null(revision_id)):
1738
1777
                self.get_revision(revision_id)
1739
1778
            return 0, []
1740
 
        # if there is no specific appropriate InterRepository, this will get
1741
 
        # the InterRepository base class, which raises an
1742
 
        # IncompatibleRepositories when asked to fetch.
1743
1779
        inter = InterRepository.get(source, self)
1744
 
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1780
        return inter.fetch(revision_id=revision_id,
1745
1781
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1746
1782
 
1747
1783
    def create_bundle(self, target, base, fileobj, format=None):
1749
1785
 
1750
1786
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1751
1787
                           timezone=None, committer=None, revprops=None,
1752
 
                           revision_id=None):
 
1788
                           revision_id=None, lossy=False):
1753
1789
        """Obtain a CommitBuilder for this repository.
1754
1790
 
1755
1791
        :param branch: Branch to commit to.
1760
1796
        :param committer: Optional committer to set for commit.
1761
1797
        :param revprops: Optional dictionary of revision properties.
1762
1798
        :param revision_id: Optional revision id.
 
1799
        :param lossy: Whether to discard data that can not be natively
 
1800
            represented, when pushing to a foreign VCS
1763
1801
        """
1764
 
        if self._fallback_repositories:
1765
 
            raise errors.BzrError("Cannot commit from a lightweight checkout "
1766
 
                "to a stacked branch. See "
 
1802
        if self._fallback_repositories and not self._format.supports_chks:
 
1803
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1804
                " in pre-2a formats. See "
1767
1805
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1768
1806
        result = self._commit_builder_class(self, parents, config,
1769
 
            timestamp, timezone, committer, revprops, revision_id)
 
1807
            timestamp, timezone, committer, revprops, revision_id,
 
1808
            lossy)
1770
1809
        self.start_write_group()
1771
1810
        return result
1772
1811
 
2018
2057
        w = self.inventories
2019
2058
        pb = ui.ui_factory.nested_progress_bar()
2020
2059
        try:
2021
 
            return self._find_text_key_references_from_xml_inventory_lines(
 
2060
            return self._serializer._find_text_key_references(
2022
2061
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
2023
2062
        finally:
2024
2063
            pb.finished()
2025
2064
 
2026
 
    def _find_text_key_references_from_xml_inventory_lines(self,
2027
 
        line_iterator):
2028
 
        """Core routine for extracting references to texts from inventories.
2029
 
 
2030
 
        This performs the translation of xml lines to revision ids.
2031
 
 
2032
 
        :param line_iterator: An iterator of lines, origin_version_id
2033
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
2034
 
            to whether they were referred to by the inventory of the
2035
 
            revision_id that they contain. Note that if that revision_id was
2036
 
            not part of the line_iterator's output then False will be given -
2037
 
            even though it may actually refer to that key.
2038
 
        """
2039
 
        if not self._serializer.support_altered_by_hack:
2040
 
            raise AssertionError(
2041
 
                "_find_text_key_references_from_xml_inventory_lines only "
2042
 
                "supported for branches which store inventory as unnested xml"
2043
 
                ", not on %r" % self)
2044
 
        result = {}
2045
 
 
2046
 
        # this code needs to read every new line in every inventory for the
2047
 
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
2048
 
        # not present in one of those inventories is unnecessary but not
2049
 
        # harmful because we are filtering by the revision id marker in the
2050
 
        # inventory lines : we only select file ids altered in one of those
2051
 
        # revisions. We don't need to see all lines in the inventory because
2052
 
        # only those added in an inventory in rev X can contain a revision=X
2053
 
        # line.
2054
 
        unescape_revid_cache = {}
2055
 
        unescape_fileid_cache = {}
2056
 
 
2057
 
        # jam 20061218 In a big fetch, this handles hundreds of thousands
2058
 
        # of lines, so it has had a lot of inlining and optimizing done.
2059
 
        # Sorry that it is a little bit messy.
2060
 
        # Move several functions to be local variables, since this is a long
2061
 
        # running loop.
2062
 
        search = self._file_ids_altered_regex.search
2063
 
        unescape = _unescape_xml
2064
 
        setdefault = result.setdefault
2065
 
        for line, line_key in line_iterator:
2066
 
            match = search(line)
2067
 
            if match is None:
2068
 
                continue
2069
 
            # One call to match.group() returning multiple items is quite a
2070
 
            # bit faster than 2 calls to match.group() each returning 1
2071
 
            file_id, revision_id = match.group('file_id', 'revision_id')
2072
 
 
2073
 
            # Inlining the cache lookups helps a lot when you make 170,000
2074
 
            # lines and 350k ids, versus 8.4 unique ids.
2075
 
            # Using a cache helps in 2 ways:
2076
 
            #   1) Avoids unnecessary decoding calls
2077
 
            #   2) Re-uses cached strings, which helps in future set and
2078
 
            #      equality checks.
2079
 
            # (2) is enough that removing encoding entirely along with
2080
 
            # the cache (so we are using plain strings) results in no
2081
 
            # performance improvement.
2082
 
            try:
2083
 
                revision_id = unescape_revid_cache[revision_id]
2084
 
            except KeyError:
2085
 
                unescaped = unescape(revision_id)
2086
 
                unescape_revid_cache[revision_id] = unescaped
2087
 
                revision_id = unescaped
2088
 
 
2089
 
            # Note that unconditionally unescaping means that we deserialise
2090
 
            # every fileid, which for general 'pull' is not great, but we don't
2091
 
            # really want to have some many fulltexts that this matters anyway.
2092
 
            # RBC 20071114.
2093
 
            try:
2094
 
                file_id = unescape_fileid_cache[file_id]
2095
 
            except KeyError:
2096
 
                unescaped = unescape(file_id)
2097
 
                unescape_fileid_cache[file_id] = unescaped
2098
 
                file_id = unescaped
2099
 
 
2100
 
            key = (file_id, revision_id)
2101
 
            setdefault(key, False)
2102
 
            if revision_id == line_key[-1]:
2103
 
                result[key] = True
2104
 
        return result
2105
 
 
2106
2065
    def _inventory_xml_lines_for_keys(self, keys):
2107
2066
        """Get a line iterator of the sort needed for findind references.
2108
2067
 
2138
2097
        revision_ids. Each altered file-ids has the exact revision_ids that
2139
2098
        altered it listed explicitly.
2140
2099
        """
2141
 
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2100
        seen = set(self._serializer._find_text_key_references(
2142
2101
                line_iterator).iterkeys())
2143
2102
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
2144
 
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2103
        parent_seen = set(self._serializer._find_text_key_references(
2145
2104
            self._inventory_xml_lines_for_keys(parent_keys)))
2146
2105
        new_keys = seen - parent_seen
2147
2106
        result = {}
2554
2513
        # TODO: refactor this to use an existing revision object
2555
2514
        # so we don't need to read it in twice.
2556
2515
        if revision_id == _mod_revision.NULL_REVISION:
2557
 
            return RevisionTree(self, Inventory(root_id=None),
2558
 
                                _mod_revision.NULL_REVISION)
 
2516
            return InventoryRevisionTree(self,
 
2517
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
2559
2518
        else:
2560
2519
            inv = self.get_inventory(revision_id)
2561
 
            return RevisionTree(self, inv, revision_id)
 
2520
            return InventoryRevisionTree(self, inv, revision_id)
2562
2521
 
2563
2522
    def revision_trees(self, revision_ids):
2564
2523
        """Return Trees for revisions in this repository.
2568
2527
        """
2569
2528
        inventories = self.iter_inventories(revision_ids)
2570
2529
        for inv in inventories:
2571
 
            yield RevisionTree(self, inv, inv.revision_id)
 
2530
            yield InventoryRevisionTree(self, inv, inv.revision_id)
2572
2531
 
2573
2532
    def _filtered_revision_trees(self, revision_ids, file_ids):
2574
2533
        """Return Tree for a revision on this branch with only some files.
2584
2543
            # Should we introduce a FilteredRevisionTree class rather
2585
2544
            # than pre-filter the inventory here?
2586
2545
            filtered_inv = inv.filter(file_ids)
2587
 
            yield RevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2546
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2588
2547
 
2589
2548
    @needs_read_lock
2590
2549
    def get_ancestry(self, revision_id, topo_sorted=True):
2775
2734
        return result
2776
2735
 
2777
2736
    def _warn_if_deprecated(self, branch=None):
 
2737
        if not self._format.is_deprecated():
 
2738
            return
2778
2739
        global _deprecation_warning_done
2779
2740
        if _deprecation_warning_done:
2780
2741
            return
2810
2771
                except UnicodeDecodeError:
2811
2772
                    raise errors.NonAsciiRevisionId(method, self)
2812
2773
 
2813
 
    def revision_graph_can_have_wrong_parents(self):
2814
 
        """Is it possible for this repository to have a revision graph with
2815
 
        incorrect parents?
 
2774
    def _find_inconsistent_revision_parents(self, revisions_iterator=None):
 
2775
        """Find revisions with different parent lists in the revision object
 
2776
        and in the index graph.
2816
2777
 
2817
 
        If True, then this repository must also implement
2818
 
        _find_inconsistent_revision_parents so that check and reconcile can
2819
 
        check for inconsistencies before proceeding with other checks that may
2820
 
        depend on the revision index being consistent.
 
2778
        :param revisions_iterator: None, or an iterator of (revid,
 
2779
            Revision-or-None). This iterator controls the revisions checked.
 
2780
        :returns: an iterator yielding tuples of (revison-id, parents-in-index,
 
2781
            parents-in-revision).
2821
2782
        """
2822
 
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
2823
 
 
2824
 
 
2825
 
# remove these delegates a while after bzr 0.15
2826
 
def __make_delegated(name, from_module):
2827
 
    def _deprecated_repository_forwarder():
2828
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
2829
 
            % (name, from_module),
2830
 
            DeprecationWarning,
2831
 
            stacklevel=2)
2832
 
        try:
2833
 
            return pyutils.get_named_object(from_module, name)
2834
 
        except AttributeError:
2835
 
            raise AttributeError('module %s has no name %s'
2836
 
                    % (sys.modules[from_module], name))
2837
 
    globals()[name] = _deprecated_repository_forwarder
2838
 
 
2839
 
for _name in [
2840
 
        'AllInOneRepository',
2841
 
        'WeaveMetaDirRepository',
2842
 
        'PreSplitOutRepositoryFormat',
2843
 
        'RepositoryFormat4',
2844
 
        'RepositoryFormat5',
2845
 
        'RepositoryFormat6',
2846
 
        'RepositoryFormat7',
2847
 
        ]:
2848
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
2849
 
 
2850
 
for _name in [
2851
 
        'KnitRepository',
2852
 
        'RepositoryFormatKnit',
2853
 
        'RepositoryFormatKnit1',
2854
 
        ]:
2855
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
 
2783
        if not self.is_locked():
 
2784
            raise AssertionError()
 
2785
        vf = self.revisions
 
2786
        if revisions_iterator is None:
 
2787
            revisions_iterator = self._iter_revisions(None)
 
2788
        for revid, revision in revisions_iterator:
 
2789
            if revision is None:
 
2790
                pass
 
2791
            parent_map = vf.get_parent_map([(revid,)])
 
2792
            parents_according_to_index = tuple(parent[-1] for parent in
 
2793
                parent_map[(revid,)])
 
2794
            parents_according_to_revision = tuple(revision.parent_ids)
 
2795
            if parents_according_to_index != parents_according_to_revision:
 
2796
                yield (revid, parents_according_to_index,
 
2797
                    parents_according_to_revision)
 
2798
 
 
2799
    def _check_for_inconsistent_revision_parents(self):
 
2800
        inconsistencies = list(self._find_inconsistent_revision_parents())
 
2801
        if inconsistencies:
 
2802
            raise errors.BzrCheckError(
 
2803
                "Revision knit has inconsistent parents.")
2856
2804
 
2857
2805
 
2858
2806
def install_revision(repository, rev, revision_tree):
2992
2940
            control_files)
2993
2941
 
2994
2942
 
 
2943
class RepositoryFormatRegistry(controldir.ControlComponentFormatRegistry):
 
2944
    """Repository format registry."""
 
2945
 
 
2946
    def get_default(self):
 
2947
        """Return the current default format."""
 
2948
        from bzrlib import bzrdir
 
2949
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
2950
 
 
2951
 
2995
2952
network_format_registry = registry.FormatRegistry()
2996
2953
"""Registry of formats indexed by their network name.
2997
2954
 
3001
2958
"""
3002
2959
 
3003
2960
 
3004
 
format_registry = registry.FormatRegistry(network_format_registry)
 
2961
format_registry = RepositoryFormatRegistry(network_format_registry)
3005
2962
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
3006
2963
 
3007
2964
This can contain either format instances themselves, or classes/factories that
3012
2969
#####################################################################
3013
2970
# Repository Formats
3014
2971
 
3015
 
class RepositoryFormat(object):
 
2972
class RepositoryFormat(controldir.ControlComponentFormat):
3016
2973
    """A repository format.
3017
2974
 
3018
2975
    Formats provide four things:
3079
3036
    supports_tree_reference = None
3080
3037
    # Is the format experimental ?
3081
3038
    experimental = False
 
3039
    # Does this repository format escape funky characters, or does it create files with
 
3040
    # similar names as the versioned files in its contents on disk ?
 
3041
    supports_funky_characters = None
 
3042
    # Does this repository format support leaving locks?
 
3043
    supports_leaving_lock = None
 
3044
    # Does this format support the full VersionedFiles interface?
 
3045
    supports_full_versioned_files = None
 
3046
    # Does this format support signing revision signatures?
 
3047
    supports_revision_signatures = True
 
3048
    # Can the revision graph have incorrect parents?
 
3049
    revision_graph_can_have_wrong_parents = None
3082
3050
 
3083
3051
    def __repr__(self):
3084
3052
        return "%s()" % self.__class__.__name__
3109
3077
                                            kind='repository')
3110
3078
 
3111
3079
    @classmethod
 
3080
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3112
3081
    def register_format(klass, format):
3113
 
        format_registry.register(format.get_format_string(), format)
 
3082
        format_registry.register(format)
3114
3083
 
3115
3084
    @classmethod
 
3085
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3116
3086
    def unregister_format(klass, format):
3117
 
        format_registry.remove(format.get_format_string())
 
3087
        format_registry.remove(format)
3118
3088
 
3119
3089
    @classmethod
 
3090
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3120
3091
    def get_default_format(klass):
3121
3092
        """Return the current default format."""
3122
 
        from bzrlib import bzrdir
3123
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
3093
        return format_registry.get_default()
3124
3094
 
3125
3095
    def get_format_string(self):
3126
3096
        """Return the ASCII format string that identifies this format.
3177
3147
        """
3178
3148
        return True
3179
3149
 
 
3150
    def is_deprecated(self):
 
3151
        """Is this format deprecated?
 
3152
 
 
3153
        Deprecated formats may trigger a user-visible warning recommending
 
3154
        the user to upgrade. They are still fully supported.
 
3155
        """
 
3156
        return False
 
3157
 
3180
3158
    def network_name(self):
3181
3159
        """A simple byte string uniquely identifying this format for RPC calls.
3182
3160
 
3221
3199
    rich_root_data = False
3222
3200
    supports_tree_reference = False
3223
3201
    supports_external_lookups = False
 
3202
    supports_leaving_lock = True
3224
3203
 
3225
3204
    @property
3226
3205
    def _matchingbzrdir(self):
3264
3243
        return self.get_format_string()
3265
3244
 
3266
3245
 
3267
 
# Pre-0.8 formats that don't have a disk format string (because they are
3268
 
# versioned by the matching control directory). We use the control directories
3269
 
# disk format string as a key for the network_name because they meet the
3270
 
# constraints (simple string, unique, immutable).
3271
 
network_format_registry.register_lazy(
3272
 
    "Bazaar-NG branch, format 5\n",
3273
 
    'bzrlib.repofmt.weaverepo',
3274
 
    'RepositoryFormat5',
3275
 
)
3276
 
network_format_registry.register_lazy(
3277
 
    "Bazaar-NG branch, format 6\n",
3278
 
    'bzrlib.repofmt.weaverepo',
3279
 
    'RepositoryFormat6',
3280
 
)
3281
 
 
3282
3246
# formats which have no format string are not discoverable or independently
3283
3247
# creatable on disk, so are not registered in format_registry.  They're
3284
 
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
 
3248
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
3285
3249
# needed, it's constructed directly by the BzrDir.  Non-native formats where
3286
3250
# the repository is not separately opened are similar.
3287
3251
 
3288
3252
format_registry.register_lazy(
3289
 
    'Bazaar-NG Repository format 7',
3290
 
    'bzrlib.repofmt.weaverepo',
3291
 
    'RepositoryFormat7'
3292
 
    )
3293
 
 
3294
 
format_registry.register_lazy(
3295
3253
    'Bazaar-NG Knit Repository Format 1',
3296
3254
    'bzrlib.repofmt.knitrepo',
3297
3255
    'RepositoryFormatKnit1',
3314
3272
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
3315
3273
format_registry.register_lazy(
3316
3274
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
3317
 
    'bzrlib.repofmt.pack_repo',
 
3275
    'bzrlib.repofmt.knitpack_repo',
3318
3276
    'RepositoryFormatKnitPack1',
3319
3277
    )
3320
3278
format_registry.register_lazy(
3321
3279
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
3322
 
    'bzrlib.repofmt.pack_repo',
 
3280
    'bzrlib.repofmt.knitpack_repo',
3323
3281
    'RepositoryFormatKnitPack3',
3324
3282
    )
3325
3283
format_registry.register_lazy(
3326
3284
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
3327
 
    'bzrlib.repofmt.pack_repo',
 
3285
    'bzrlib.repofmt.knitpack_repo',
3328
3286
    'RepositoryFormatKnitPack4',
3329
3287
    )
3330
3288
format_registry.register_lazy(
3331
3289
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
3332
 
    'bzrlib.repofmt.pack_repo',
 
3290
    'bzrlib.repofmt.knitpack_repo',
3333
3291
    'RepositoryFormatKnitPack5',
3334
3292
    )
3335
3293
format_registry.register_lazy(
3336
3294
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
3337
 
    'bzrlib.repofmt.pack_repo',
 
3295
    'bzrlib.repofmt.knitpack_repo',
3338
3296
    'RepositoryFormatKnitPack5RichRoot',
3339
3297
    )
3340
3298
format_registry.register_lazy(
3341
3299
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
3342
 
    'bzrlib.repofmt.pack_repo',
 
3300
    'bzrlib.repofmt.knitpack_repo',
3343
3301
    'RepositoryFormatKnitPack5RichRootBroken',
3344
3302
    )
3345
3303
format_registry.register_lazy(
3346
3304
    'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
3347
 
    'bzrlib.repofmt.pack_repo',
 
3305
    'bzrlib.repofmt.knitpack_repo',
3348
3306
    'RepositoryFormatKnitPack6',
3349
3307
    )
3350
3308
format_registry.register_lazy(
3351
3309
    'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
3352
 
    'bzrlib.repofmt.pack_repo',
 
3310
    'bzrlib.repofmt.knitpack_repo',
3353
3311
    'RepositoryFormatKnitPack6RichRoot',
3354
3312
    )
 
3313
format_registry.register_lazy(
 
3314
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3315
    'bzrlib.repofmt.groupcompress_repo',
 
3316
    'RepositoryFormat2a',
 
3317
    )
3355
3318
 
3356
3319
# Development formats.
3357
 
# Obsolete but kept pending a CHK based subtree format.
 
3320
# Check their docstrings to see if/when they are obsolete.
3358
3321
format_registry.register_lazy(
3359
3322
    ("Bazaar development format 2 with subtree support "
3360
3323
        "(needs bzr.dev from before 1.8)\n"),
3361
 
    'bzrlib.repofmt.pack_repo',
 
3324
    'bzrlib.repofmt.knitpack_repo',
3362
3325
    'RepositoryFormatPackDevelopment2Subtree',
3363
3326
    )
3364
 
 
3365
 
# 1.14->1.16 go below here
3366
 
format_registry.register_lazy(
3367
 
    'Bazaar development format - group compression and chk inventory'
3368
 
        ' (needs bzr.dev from 1.14)\n',
3369
 
    'bzrlib.repofmt.groupcompress_repo',
3370
 
    'RepositoryFormatCHK1',
3371
 
    )
3372
 
 
3373
 
format_registry.register_lazy(
3374
 
    'Bazaar development format - chk repository with bencode revision '
3375
 
        'serialization (needs bzr.dev from 1.16)\n',
3376
 
    'bzrlib.repofmt.groupcompress_repo',
3377
 
    'RepositoryFormatCHK2',
3378
 
    )
3379
 
format_registry.register_lazy(
3380
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3381
 
    'bzrlib.repofmt.groupcompress_repo',
3382
 
    'RepositoryFormat2a',
3383
 
    )
3384
3327
format_registry.register_lazy(
3385
3328
    'Bazaar development format 8\n',
3386
3329
    'bzrlib.repofmt.groupcompress_repo',
3421
3364
        self.target.fetch(self.source, revision_id=revision_id)
3422
3365
 
3423
3366
    @needs_write_lock
3424
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3367
    def fetch(self, revision_id=None, find_ghosts=False,
3425
3368
            fetch_spec=None):
3426
3369
        """Fetch the content required to construct revision_id.
3427
3370
 
3429
3372
 
3430
3373
        :param revision_id: if None all content is copied, if NULL_REVISION no
3431
3374
                            content is copied.
3432
 
        :param pb: ignored.
3433
3375
        :return: None.
3434
3376
        """
3435
3377
        ui.ui_factory.warn_experimental_format_fetch(self)
3445
3387
                               fetch_spec=fetch_spec,
3446
3388
                               find_ghosts=find_ghosts)
3447
3389
 
3448
 
    def _walk_to_common_revisions(self, revision_ids):
 
3390
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
3449
3391
        """Walk out from revision_ids in source to revisions target has.
3450
3392
 
3451
3393
        :param revision_ids: The start point for the search.
3453
3395
        """
3454
3396
        target_graph = self.target.get_graph()
3455
3397
        revision_ids = frozenset(revision_ids)
 
3398
        if if_present_ids:
 
3399
            all_wanted_revs = revision_ids.union(if_present_ids)
 
3400
        else:
 
3401
            all_wanted_revs = revision_ids
3456
3402
        missing_revs = set()
3457
3403
        source_graph = self.source.get_graph()
3458
3404
        # ensure we don't pay silly lookup costs.
3459
 
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
 
3405
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
3460
3406
        null_set = frozenset([_mod_revision.NULL_REVISION])
3461
3407
        searcher_exhausted = False
3462
3408
        while True:
3498
3444
        return searcher.get_result()
3499
3445
 
3500
3446
    @needs_read_lock
3501
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3447
    def search_missing_revision_ids(self,
 
3448
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
3449
            find_ghosts=True, revision_ids=None, if_present_ids=None):
3502
3450
        """Return the revision ids that source has that target does not.
3503
3451
 
3504
3452
        :param revision_id: only return revision ids included by this
3505
 
                            revision_id.
 
3453
            revision_id.
 
3454
        :param revision_ids: return revision ids included by these
 
3455
            revision_ids.  NoSuchRevision will be raised if any of these
 
3456
            revisions are not present.
 
3457
        :param if_present_ids: like revision_ids, but will not cause
 
3458
            NoSuchRevision if any of these are absent, instead they will simply
 
3459
            not be in the result.  This is useful for e.g. finding revisions
 
3460
            to fetch for tags, which may reference absent revisions.
3506
3461
        :param find_ghosts: If True find missing revisions in deep history
3507
3462
            rather than just finding the surface difference.
3508
3463
        :return: A bzrlib.graph.SearchResult.
3509
3464
        """
 
3465
        if symbol_versioning.deprecated_passed(revision_id):
 
3466
            symbol_versioning.warn(
 
3467
                'search_missing_revision_ids(revision_id=...) was '
 
3468
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
3469
                DeprecationWarning, stacklevel=2)
 
3470
            if revision_ids is not None:
 
3471
                raise AssertionError(
 
3472
                    'revision_ids is mutually exclusive with revision_id')
 
3473
            if revision_id is not None:
 
3474
                revision_ids = [revision_id]
 
3475
        del revision_id
3510
3476
        # stop searching at found target revisions.
3511
 
        if not find_ghosts and revision_id is not None:
3512
 
            return self._walk_to_common_revisions([revision_id])
 
3477
        if not find_ghosts and (revision_ids is not None or if_present_ids is
 
3478
                not None):
 
3479
            return self._walk_to_common_revisions(revision_ids,
 
3480
                    if_present_ids=if_present_ids)
3513
3481
        # generic, possibly worst case, slow code path.
3514
3482
        target_ids = set(self.target.all_revision_ids())
3515
 
        if revision_id is not None:
3516
 
            source_ids = self.source.get_ancestry(revision_id)
3517
 
            if source_ids[0] is not None:
3518
 
                raise AssertionError()
3519
 
            source_ids.pop(0)
3520
 
        else:
3521
 
            source_ids = self.source.all_revision_ids()
 
3483
        source_ids = self._present_source_revisions_for(
 
3484
            revision_ids, if_present_ids)
3522
3485
        result_set = set(source_ids).difference(target_ids)
3523
3486
        return self.source.revision_ids_to_search_result(result_set)
3524
3487
 
 
3488
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
 
3489
        """Returns set of all revisions in ancestry of revision_ids present in
 
3490
        the source repo.
 
3491
 
 
3492
        :param revision_ids: if None, all revisions in source are returned.
 
3493
        :param if_present_ids: like revision_ids, but if any/all of these are
 
3494
            absent no error is raised.
 
3495
        """
 
3496
        if revision_ids is not None or if_present_ids is not None:
 
3497
            # First, ensure all specified revisions exist.  Callers expect
 
3498
            # NoSuchRevision when they pass absent revision_ids here.
 
3499
            if revision_ids is None:
 
3500
                revision_ids = set()
 
3501
            if if_present_ids is None:
 
3502
                if_present_ids = set()
 
3503
            revision_ids = set(revision_ids)
 
3504
            if_present_ids = set(if_present_ids)
 
3505
            all_wanted_ids = revision_ids.union(if_present_ids)
 
3506
            graph = self.source.get_graph()
 
3507
            present_revs = set(graph.get_parent_map(all_wanted_ids))
 
3508
            missing = revision_ids.difference(present_revs)
 
3509
            if missing:
 
3510
                raise errors.NoSuchRevision(self.source, missing.pop())
 
3511
            found_ids = all_wanted_ids.intersection(present_revs)
 
3512
            source_ids = [rev_id for (rev_id, parents) in
 
3513
                          graph.iter_ancestry(found_ids)
 
3514
                          if rev_id != _mod_revision.NULL_REVISION
 
3515
                          and parents is not None]
 
3516
        else:
 
3517
            source_ids = self.source.all_revision_ids()
 
3518
        return set(source_ids)
 
3519
 
3525
3520
    @staticmethod
3526
3521
    def _same_model(source, target):
3527
3522
        """True if source and target have the same data representation.
3568
3563
        return InterRepository._same_model(source, target)
3569
3564
 
3570
3565
 
3571
 
class InterWeaveRepo(InterSameDataRepository):
3572
 
    """Optimised code paths between Weave based repositories.
3573
 
 
3574
 
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
3575
 
    implemented lazy inter-object optimisation.
3576
 
    """
3577
 
 
3578
 
    @classmethod
3579
 
    def _get_repo_format_to_test(self):
3580
 
        from bzrlib.repofmt import weaverepo
3581
 
        return weaverepo.RepositoryFormat7()
3582
 
 
3583
 
    @staticmethod
3584
 
    def is_compatible(source, target):
3585
 
        """Be compatible with known Weave formats.
3586
 
 
3587
 
        We don't test for the stores being of specific types because that
3588
 
        could lead to confusing results, and there is no need to be
3589
 
        overly general.
3590
 
        """
3591
 
        from bzrlib.repofmt.weaverepo import (
3592
 
                RepositoryFormat5,
3593
 
                RepositoryFormat6,
3594
 
                RepositoryFormat7,
3595
 
                )
3596
 
        try:
3597
 
            return (isinstance(source._format, (RepositoryFormat5,
3598
 
                                                RepositoryFormat6,
3599
 
                                                RepositoryFormat7)) and
3600
 
                    isinstance(target._format, (RepositoryFormat5,
3601
 
                                                RepositoryFormat6,
3602
 
                                                RepositoryFormat7)))
3603
 
        except AttributeError:
3604
 
            return False
3605
 
 
3606
 
    @needs_write_lock
3607
 
    def copy_content(self, revision_id=None):
3608
 
        """See InterRepository.copy_content()."""
3609
 
        # weave specific optimised path:
3610
 
        try:
3611
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3612
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
3613
 
            pass
3614
 
        # FIXME do not peek!
3615
 
        if self.source._transport.listable():
3616
 
            pb = ui.ui_factory.nested_progress_bar()
3617
 
            try:
3618
 
                self.target.texts.insert_record_stream(
3619
 
                    self.source.texts.get_record_stream(
3620
 
                        self.source.texts.keys(), 'topological', False))
3621
 
                pb.update('Copying inventory', 0, 1)
3622
 
                self.target.inventories.insert_record_stream(
3623
 
                    self.source.inventories.get_record_stream(
3624
 
                        self.source.inventories.keys(), 'topological', False))
3625
 
                self.target.signatures.insert_record_stream(
3626
 
                    self.source.signatures.get_record_stream(
3627
 
                        self.source.signatures.keys(),
3628
 
                        'unordered', True))
3629
 
                self.target.revisions.insert_record_stream(
3630
 
                    self.source.revisions.get_record_stream(
3631
 
                        self.source.revisions.keys(),
3632
 
                        'topological', True))
3633
 
            finally:
3634
 
                pb.finished()
3635
 
        else:
3636
 
            self.target.fetch(self.source, revision_id=revision_id)
3637
 
 
3638
 
    @needs_read_lock
3639
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3640
 
        """See InterRepository.missing_revision_ids()."""
3641
 
        # we want all revisions to satisfy revision_id in source.
3642
 
        # but we don't want to stat every file here and there.
3643
 
        # we want then, all revisions other needs to satisfy revision_id
3644
 
        # checked, but not those that we have locally.
3645
 
        # so the first thing is to get a subset of the revisions to
3646
 
        # satisfy revision_id in source, and then eliminate those that
3647
 
        # we do already have.
3648
 
        # this is slow on high latency connection to self, but as this
3649
 
        # disk format scales terribly for push anyway due to rewriting
3650
 
        # inventory.weave, this is considered acceptable.
3651
 
        # - RBC 20060209
3652
 
        if revision_id is not None:
3653
 
            source_ids = self.source.get_ancestry(revision_id)
3654
 
            if source_ids[0] is not None:
3655
 
                raise AssertionError()
3656
 
            source_ids.pop(0)
3657
 
        else:
3658
 
            source_ids = self.source._all_possible_ids()
3659
 
        source_ids_set = set(source_ids)
3660
 
        # source_ids is the worst possible case we may need to pull.
3661
 
        # now we want to filter source_ids against what we actually
3662
 
        # have in target, but don't try to check for existence where we know
3663
 
        # we do not have a revision as that would be pointless.
3664
 
        target_ids = set(self.target._all_possible_ids())
3665
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3666
 
        actually_present_revisions = set(
3667
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3668
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3669
 
        if revision_id is not None:
3670
 
            # we used get_ancestry to determine source_ids then we are assured all
3671
 
            # revisions referenced are present as they are installed in topological order.
3672
 
            # and the tip revision was validated by get_ancestry.
3673
 
            result_set = required_revisions
3674
 
        else:
3675
 
            # if we just grabbed the possibly available ids, then
3676
 
            # we only have an estimate of whats available and need to validate
3677
 
            # that against the revision records.
3678
 
            result_set = set(
3679
 
                self.source._eliminate_revisions_not_present(required_revisions))
3680
 
        return self.source.revision_ids_to_search_result(result_set)
3681
 
 
3682
 
 
3683
 
class InterKnitRepo(InterSameDataRepository):
3684
 
    """Optimised code paths between Knit based repositories."""
3685
 
 
3686
 
    @classmethod
3687
 
    def _get_repo_format_to_test(self):
3688
 
        from bzrlib.repofmt import knitrepo
3689
 
        return knitrepo.RepositoryFormatKnit1()
3690
 
 
3691
 
    @staticmethod
3692
 
    def is_compatible(source, target):
3693
 
        """Be compatible with known Knit formats.
3694
 
 
3695
 
        We don't test for the stores being of specific types because that
3696
 
        could lead to confusing results, and there is no need to be
3697
 
        overly general.
3698
 
        """
3699
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
3700
 
        try:
3701
 
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
3702
 
                isinstance(target._format, RepositoryFormatKnit))
3703
 
        except AttributeError:
3704
 
            return False
3705
 
        return are_knits and InterRepository._same_model(source, target)
3706
 
 
3707
 
    @needs_read_lock
3708
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3709
 
        """See InterRepository.missing_revision_ids()."""
3710
 
        if revision_id is not None:
3711
 
            source_ids = self.source.get_ancestry(revision_id)
3712
 
            if source_ids[0] is not None:
3713
 
                raise AssertionError()
3714
 
            source_ids.pop(0)
3715
 
        else:
3716
 
            source_ids = self.source.all_revision_ids()
3717
 
        source_ids_set = set(source_ids)
3718
 
        # source_ids is the worst possible case we may need to pull.
3719
 
        # now we want to filter source_ids against what we actually
3720
 
        # have in target, but don't try to check for existence where we know
3721
 
        # we do not have a revision as that would be pointless.
3722
 
        target_ids = set(self.target.all_revision_ids())
3723
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3724
 
        actually_present_revisions = set(
3725
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3726
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3727
 
        if revision_id is not None:
3728
 
            # we used get_ancestry to determine source_ids then we are assured all
3729
 
            # revisions referenced are present as they are installed in topological order.
3730
 
            # and the tip revision was validated by get_ancestry.
3731
 
            result_set = required_revisions
3732
 
        else:
3733
 
            # if we just grabbed the possibly available ids, then
3734
 
            # we only have an estimate of whats available and need to validate
3735
 
            # that against the revision records.
3736
 
            result_set = set(
3737
 
                self.source._eliminate_revisions_not_present(required_revisions))
3738
 
        return self.source.revision_ids_to_search_result(result_set)
3739
 
 
3740
 
 
3741
3566
class InterDifferingSerializer(InterRepository):
3742
3567
 
3743
3568
    @classmethod
4011
3836
                  len(revision_ids))
4012
3837
 
4013
3838
    @needs_write_lock
4014
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3839
    def fetch(self, revision_id=None, find_ghosts=False,
4015
3840
            fetch_spec=None):
4016
3841
        """See InterRepository.fetch()."""
4017
3842
        if fetch_spec is not None:
4018
 
            raise AssertionError("Not implemented yet...")
 
3843
            revision_ids = fetch_spec.get_keys()
 
3844
        else:
 
3845
            revision_ids = None
4019
3846
        ui.ui_factory.warn_experimental_format_fetch(self)
4020
3847
        if (not self.source.supports_rich_root()
4021
3848
            and self.target.supports_rich_root()):
4028
3855
            ui.ui_factory.show_user_warning('cross_format_fetch',
4029
3856
                from_format=self.source._format,
4030
3857
                to_format=self.target._format)
4031
 
        revision_ids = self.target.search_missing_revision_ids(self.source,
4032
 
            revision_id, find_ghosts=find_ghosts).get_keys()
 
3858
        if revision_ids is None:
 
3859
            if revision_id:
 
3860
                search_revision_ids = [revision_id]
 
3861
            else:
 
3862
                search_revision_ids = None
 
3863
            revision_ids = self.target.search_missing_revision_ids(self.source,
 
3864
                revision_ids=search_revision_ids,
 
3865
                find_ghosts=find_ghosts).get_keys()
4033
3866
        if not revision_ids:
4034
3867
            return 0, 0
4035
3868
        revision_ids = tsort.topo_sort(
4039
3872
        # Walk though all revisions; get inventory deltas, copy referenced
4040
3873
        # texts that delta references, insert the delta, revision and
4041
3874
        # signature.
4042
 
        if pb is None:
4043
 
            my_pb = ui.ui_factory.nested_progress_bar()
4044
 
            pb = my_pb
4045
 
        else:
4046
 
            symbol_versioning.warn(
4047
 
                symbol_versioning.deprecated_in((1, 14, 0))
4048
 
                % "pb parameter to fetch()")
4049
 
            my_pb = None
 
3875
        pb = ui.ui_factory.nested_progress_bar()
4050
3876
        try:
4051
3877
            self._fetch_all_revisions(revision_ids, pb)
4052
3878
        finally:
4053
 
            if my_pb is not None:
4054
 
                my_pb.finished()
 
3879
            pb.finished()
4055
3880
        return len(revision_ids), 0
4056
3881
 
4057
3882
    def _get_basis(self, first_revision_id):
4079
3904
 
4080
3905
InterRepository.register_optimiser(InterDifferingSerializer)
4081
3906
InterRepository.register_optimiser(InterSameDataRepository)
4082
 
InterRepository.register_optimiser(InterWeaveRepo)
4083
 
InterRepository.register_optimiser(InterKnitRepo)
4084
3907
 
4085
3908
 
4086
3909
class CopyConverter(object):
4131
3954
        pb.finished()
4132
3955
 
4133
3956
 
4134
 
_unescape_map = {
4135
 
    'apos':"'",
4136
 
    'quot':'"',
4137
 
    'amp':'&',
4138
 
    'lt':'<',
4139
 
    'gt':'>'
4140
 
}
4141
 
 
4142
 
 
4143
 
def _unescaper(match, _map=_unescape_map):
4144
 
    code = match.group(1)
4145
 
    try:
4146
 
        return _map[code]
4147
 
    except KeyError:
4148
 
        if not code.startswith('#'):
4149
 
            raise
4150
 
        return unichr(int(code[1:])).encode('utf8')
4151
 
 
4152
 
 
4153
 
_unescape_re = None
4154
 
 
4155
 
 
4156
 
def _unescape_xml(data):
4157
 
    """Unescape predefined XML entities in a string of data."""
4158
 
    global _unescape_re
4159
 
    if _unescape_re is None:
4160
 
        _unescape_re = re.compile('\&([^;]*);')
4161
 
    return _unescape_re.sub(_unescaper, data)
4162
 
 
4163
 
 
4164
3957
class _VersionedFileChecker(object):
4165
3958
 
4166
3959
    def __init__(self, repository, text_key_references=None, ancestors=None):
4225
4018
        return wrong_parents, unused_keys
4226
4019
 
4227
4020
 
4228
 
def _old_get_graph(repository, revision_id):
4229
 
    """DO NOT USE. That is all. I'm serious."""
4230
 
    graph = repository.get_graph()
4231
 
    revision_graph = dict(((key, value) for key, value in
4232
 
        graph.iter_ancestry([revision_id]) if value is not None))
4233
 
    return _strip_NULL_ghosts(revision_graph)
4234
 
 
4235
 
 
4236
4021
def _strip_NULL_ghosts(revision_graph):
4237
4022
    """Also don't use this. more compatibility code for unmigrated clients."""
4238
4023
    # Filter ghosts, and null:
4274
4059
                is_resume = False
4275
4060
            try:
4276
4061
                # locked_insert_stream performs a commit|suspend.
4277
 
                return self._locked_insert_stream(stream, src_format,
4278
 
                    is_resume)
 
4062
                missing_keys = self.insert_stream_without_locking(stream,
 
4063
                                    src_format, is_resume)
 
4064
                if missing_keys:
 
4065
                    # suspend the write group and tell the caller what we is
 
4066
                    # missing. We know we can suspend or else we would not have
 
4067
                    # entered this code path. (All repositories that can handle
 
4068
                    # missing keys can handle suspending a write group).
 
4069
                    write_group_tokens = self.target_repo.suspend_write_group()
 
4070
                    return write_group_tokens, missing_keys
 
4071
                hint = self.target_repo.commit_write_group()
 
4072
                to_serializer = self.target_repo._format._serializer
 
4073
                src_serializer = src_format._serializer
 
4074
                if (to_serializer != src_serializer and
 
4075
                    self.target_repo._format.pack_compresses):
 
4076
                    self.target_repo.pack(hint=hint)
 
4077
                return [], set()
4279
4078
            except:
4280
4079
                self.target_repo.abort_write_group(suppress_errors=True)
4281
4080
                raise
4282
4081
        finally:
4283
4082
            self.target_repo.unlock()
4284
4083
 
4285
 
    def _locked_insert_stream(self, stream, src_format, is_resume):
 
4084
    def insert_stream_without_locking(self, stream, src_format,
 
4085
                                      is_resume=False):
 
4086
        """Insert a stream's content into the target repository.
 
4087
 
 
4088
        This assumes that you already have a locked repository and an active
 
4089
        write group.
 
4090
 
 
4091
        :param src_format: a bzr repository format.
 
4092
        :param is_resume: Passed down to get_missing_parent_inventories to
 
4093
            indicate if we should be checking for missing texts at the same
 
4094
            time.
 
4095
 
 
4096
        :return: A set of keys that are missing.
 
4097
        """
 
4098
        if not self.target_repo.is_write_locked():
 
4099
            raise errors.ObjectNotLocked(self)
 
4100
        if not self.target_repo.is_in_write_group():
 
4101
            raise errors.BzrError('you must already be in a write group')
4286
4102
        to_serializer = self.target_repo._format._serializer
4287
4103
        src_serializer = src_format._serializer
4288
4104
        new_pack = None
4367
4183
            # cannot even attempt suspending, and missing would have failed
4368
4184
            # during stream insertion.
4369
4185
            missing_keys = set()
4370
 
        else:
4371
 
            if missing_keys:
4372
 
                # suspend the write group and tell the caller what we is
4373
 
                # missing. We know we can suspend or else we would not have
4374
 
                # entered this code path. (All repositories that can handle
4375
 
                # missing keys can handle suspending a write group).
4376
 
                write_group_tokens = self.target_repo.suspend_write_group()
4377
 
                return write_group_tokens, missing_keys
4378
 
        hint = self.target_repo.commit_write_group()
4379
 
        if (to_serializer != src_serializer and
4380
 
            self.target_repo._format.pack_compresses):
4381
 
            self.target_repo.pack(hint=hint)
4382
 
        return [], set()
 
4186
        return missing_keys
4383
4187
 
4384
4188
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4385
4189
        target_rich_root = self.target_repo._format.rich_root_data
4392
4196
                parse_result = deserialiser.parse_text_bytes(
4393
4197
                    inventory_delta_bytes)
4394
4198
            except inventory_delta.IncompatibleInventoryDelta, err:
4395
 
                trace.mutter("Incompatible delta: %s", err.msg)
 
4199
                mutter("Incompatible delta: %s", err.msg)
4396
4200
                raise errors.IncompatibleRevision(self.target_repo._format)
4397
4201
            basis_id, new_id, rich_root, tree_refs, inv_delta = parse_result
4398
4202
            revision_id = new_id
4733
4537
    except StopIteration:
4734
4538
        # No more history
4735
4539
        return
4736