~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Karl Bielefeldt
  • Date: 2010-09-29 19:57:28 UTC
  • mto: (5483.1.1 integration)
  • mto: This revision was merged to the branch mainline in revision 5484.
  • Revision ID: 7mq3cbbd9q@snkmail.com-20100929195728-nvuqlepsrwcxbziw
Use meliae to dump memory to a file upon MemoryError.

Show diffs side-by-side

added added

removed removed

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