~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Rory Yorke
  • Date: 2010-10-20 14:38:53 UTC
  • mto: This revision was merged to the branch mainline in revision 5519.
  • Revision ID: rory.yorke@gmail.com-20101020143853-9kfd2ldcjfroh8jw
Show missing files in bzr status (bug 134168).

"bzr status" will now show missing files, that is, those added with "bzr
add" and then removed by non bzr means (e.g., rm).

Blackbox tests were added for this case, and tests were also added to
test_delta, since the implementation change is in bzrlib.delta.

Might also affect bug 189709.

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,
 
42
    pyutils,
37
43
    revision as _mod_revision,
38
44
    static_tuple,
 
45
    symbol_versioning,
 
46
    trace,
39
47
    tsort,
40
48
    versionedfile,
41
49
    )
42
50
from bzrlib.bundle import serializer
43
 
from bzrlib.recordcounter import RecordCounter
44
 
from bzrlib.revisiontree import InventoryRevisionTree
 
51
from bzrlib.revisiontree import RevisionTree
45
52
from bzrlib.store.versioned import VersionedFileStore
46
53
from bzrlib.testament import Testament
47
54
""")
48
55
 
 
56
import sys
49
57
from bzrlib import (
50
58
    errors,
51
59
    registry,
52
 
    symbol_versioning,
53
60
    ui,
54
61
    )
55
62
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
60
67
    ROOT_ID,
61
68
    entry_factory,
62
69
    )
 
70
from bzrlib.recordcounter import RecordCounter
63
71
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
64
72
from bzrlib.trace import (
65
73
    log_exception_quietly, note, mutter, mutter_callsite, warning)
88
96
    record_root_entry = True
89
97
    # the default CommitBuilder does not manage trees whose root is versioned.
90
98
    _versioned_root = False
91
 
    # this commit builder supports the record_entry_contents interface
92
 
    supports_record_entry_contents = True
93
99
 
94
100
    def __init__(self, repository, parents, config, timestamp=None,
95
101
                 timezone=None, committer=None, revprops=None,
96
 
                 revision_id=None, lossy=False):
 
102
                 revision_id=None):
97
103
        """Initiate a CommitBuilder.
98
104
 
99
105
        :param repository: Repository to commit to.
100
106
        :param parents: Revision ids of the parents of the new revision.
 
107
        :param config: Configuration to use.
101
108
        :param timestamp: Optional timestamp recorded for commit.
102
109
        :param timezone: Optional timezone for timestamp.
103
110
        :param committer: Optional committer to set for commit.
104
111
        :param revprops: Optional dictionary of revision properties.
105
112
        :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
113
        """
109
114
        self._config = config
110
 
        self._lossy = lossy
111
115
 
112
116
        if committer is None:
113
117
            self._committer = self._config.username()
114
 
        elif not isinstance(committer, unicode):
115
 
            self._committer = committer.decode() # throw if non-ascii
116
118
        else:
117
119
            self._committer = committer
118
120
 
172
174
            self._validate_unicode_text(value,
173
175
                                        'revision property (%s)' % (key,))
174
176
 
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
177
    def commit(self, message):
211
178
        """Make the actual commit.
212
179
 
224
191
        rev.parent_ids = self.parents
225
192
        self.repository.add_revision(self._new_revision_id, rev,
226
193
            self.new_inventory, self._config)
227
 
        self._ensure_fallback_inventories()
228
194
        self.repository.commit_write_group()
229
195
        return self._new_revision_id
230
196
 
236
202
    def revision_tree(self):
237
203
        """Return the tree that was just committed.
238
204
 
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
 
205
        After calling commit() this can be called to get a RevisionTree
 
206
        representing the newly committed tree. This is preferred to
 
207
        calling Repository.revision_tree() because that may require
 
208
        deserializing the inventory, while we already have a copy in
243
209
        memory.
244
210
        """
245
211
        if self.new_inventory is None:
246
212
            self.new_inventory = self.repository.get_inventory(
247
213
                self._new_revision_id)
248
 
        return InventoryRevisionTree(self.repository, self.new_inventory,
 
214
        return RevisionTree(self.repository, self.new_inventory,
249
215
            self._new_revision_id)
250
216
 
251
217
    def finish_inventory(self):
985
951
    # in a Repository class subclass rather than to override
986
952
    # get_commit_builder.
987
953
    _commit_builder_class = CommitBuilder
 
954
    # The search regex used by xml based repositories to determine what things
 
955
    # where changed in a single commit.
 
956
    _file_ids_altered_regex = lazy_regex.lazy_compile(
 
957
        r'file_id="(?P<file_id>[^"]+)"'
 
958
        r'.* revision="(?P<revision_id>[^"]+)"'
 
959
        )
988
960
 
989
961
    def abort_write_group(self, suppress_errors=False):
990
962
        """Commit the contents accrued within the current write group.
1163
1135
        if config is not None and config.signature_needed():
1164
1136
            if inv is None:
1165
1137
                inv = self.get_inventory(revision_id)
1166
 
            tree = InventoryRevisionTree(self, inv, revision_id)
1167
 
            testament = Testament(rev, tree)
1168
 
            plaintext = testament.as_short_text()
 
1138
            plaintext = Testament(rev, inv).as_short_text()
1169
1139
            self.store_revision_signature(
1170
1140
                gpg.GPGStrategy(config), plaintext, revision_id)
1171
1141
        # check inventory present
1589
1559
        return ret
1590
1560
 
1591
1561
    @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):
 
1562
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1595
1563
        """Return the revision ids that other has that this does not.
1596
1564
 
1597
1565
        These are returned in topological order.
1598
1566
 
1599
1567
        revision_id: only return revision ids included by revision_id.
1600
1568
        """
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
1569
        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)
 
1570
            revision_id, find_ghosts)
1614
1571
 
1615
1572
    @staticmethod
1616
1573
    def open(base):
1738
1695
    def _resume_write_group(self, tokens):
1739
1696
        raise errors.UnsuspendableWriteGroup(self)
1740
1697
 
1741
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
 
1698
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1742
1699
            fetch_spec=None):
1743
1700
        """Fetch the content required to construct revision_id from source.
1744
1701
 
1778
1735
                not _mod_revision.is_null(revision_id)):
1779
1736
                self.get_revision(revision_id)
1780
1737
            return 0, []
 
1738
        # if there is no specific appropriate InterRepository, this will get
 
1739
        # the InterRepository base class, which raises an
 
1740
        # IncompatibleRepositories when asked to fetch.
1781
1741
        inter = InterRepository.get(source, self)
1782
 
        return inter.fetch(revision_id=revision_id,
 
1742
        return inter.fetch(revision_id=revision_id, pb=pb,
1783
1743
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1784
1744
 
1785
1745
    def create_bundle(self, target, base, fileobj, format=None):
1787
1747
 
1788
1748
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1789
1749
                           timezone=None, committer=None, revprops=None,
1790
 
                           revision_id=None, lossy=False):
 
1750
                           revision_id=None):
1791
1751
        """Obtain a CommitBuilder for this repository.
1792
1752
 
1793
1753
        :param branch: Branch to commit to.
1798
1758
        :param committer: Optional committer to set for commit.
1799
1759
        :param revprops: Optional dictionary of revision properties.
1800
1760
        :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
1761
        """
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 "
 
1762
        if self._fallback_repositories:
 
1763
            raise errors.BzrError("Cannot commit from a lightweight checkout "
 
1764
                "to a stacked branch. See "
1807
1765
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1808
1766
        result = self._commit_builder_class(self, parents, config,
1809
 
            timestamp, timezone, committer, revprops, revision_id,
1810
 
            lossy)
 
1767
            timestamp, timezone, committer, revprops, revision_id)
1811
1768
        self.start_write_group()
1812
1769
        return result
1813
1770
 
2059
2016
        w = self.inventories
2060
2017
        pb = ui.ui_factory.nested_progress_bar()
2061
2018
        try:
2062
 
            return self._serializer._find_text_key_references(
 
2019
            return self._find_text_key_references_from_xml_inventory_lines(
2063
2020
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
2064
2021
        finally:
2065
2022
            pb.finished()
2066
2023
 
 
2024
    def _find_text_key_references_from_xml_inventory_lines(self,
 
2025
        line_iterator):
 
2026
        """Core routine for extracting references to texts from inventories.
 
2027
 
 
2028
        This performs the translation of xml lines to revision ids.
 
2029
 
 
2030
        :param line_iterator: An iterator of lines, origin_version_id
 
2031
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
 
2032
            to whether they were referred to by the inventory of the
 
2033
            revision_id that they contain. Note that if that revision_id was
 
2034
            not part of the line_iterator's output then False will be given -
 
2035
            even though it may actually refer to that key.
 
2036
        """
 
2037
        if not self._serializer.support_altered_by_hack:
 
2038
            raise AssertionError(
 
2039
                "_find_text_key_references_from_xml_inventory_lines only "
 
2040
                "supported for branches which store inventory as unnested xml"
 
2041
                ", not on %r" % self)
 
2042
        result = {}
 
2043
 
 
2044
        # this code needs to read every new line in every inventory for the
 
2045
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
 
2046
        # not present in one of those inventories is unnecessary but not
 
2047
        # harmful because we are filtering by the revision id marker in the
 
2048
        # inventory lines : we only select file ids altered in one of those
 
2049
        # revisions. We don't need to see all lines in the inventory because
 
2050
        # only those added in an inventory in rev X can contain a revision=X
 
2051
        # line.
 
2052
        unescape_revid_cache = {}
 
2053
        unescape_fileid_cache = {}
 
2054
 
 
2055
        # jam 20061218 In a big fetch, this handles hundreds of thousands
 
2056
        # of lines, so it has had a lot of inlining and optimizing done.
 
2057
        # Sorry that it is a little bit messy.
 
2058
        # Move several functions to be local variables, since this is a long
 
2059
        # running loop.
 
2060
        search = self._file_ids_altered_regex.search
 
2061
        unescape = _unescape_xml
 
2062
        setdefault = result.setdefault
 
2063
        for line, line_key in line_iterator:
 
2064
            match = search(line)
 
2065
            if match is None:
 
2066
                continue
 
2067
            # One call to match.group() returning multiple items is quite a
 
2068
            # bit faster than 2 calls to match.group() each returning 1
 
2069
            file_id, revision_id = match.group('file_id', 'revision_id')
 
2070
 
 
2071
            # Inlining the cache lookups helps a lot when you make 170,000
 
2072
            # lines and 350k ids, versus 8.4 unique ids.
 
2073
            # Using a cache helps in 2 ways:
 
2074
            #   1) Avoids unnecessary decoding calls
 
2075
            #   2) Re-uses cached strings, which helps in future set and
 
2076
            #      equality checks.
 
2077
            # (2) is enough that removing encoding entirely along with
 
2078
            # the cache (so we are using plain strings) results in no
 
2079
            # performance improvement.
 
2080
            try:
 
2081
                revision_id = unescape_revid_cache[revision_id]
 
2082
            except KeyError:
 
2083
                unescaped = unescape(revision_id)
 
2084
                unescape_revid_cache[revision_id] = unescaped
 
2085
                revision_id = unescaped
 
2086
 
 
2087
            # Note that unconditionally unescaping means that we deserialise
 
2088
            # every fileid, which for general 'pull' is not great, but we don't
 
2089
            # really want to have some many fulltexts that this matters anyway.
 
2090
            # RBC 20071114.
 
2091
            try:
 
2092
                file_id = unescape_fileid_cache[file_id]
 
2093
            except KeyError:
 
2094
                unescaped = unescape(file_id)
 
2095
                unescape_fileid_cache[file_id] = unescaped
 
2096
                file_id = unescaped
 
2097
 
 
2098
            key = (file_id, revision_id)
 
2099
            setdefault(key, False)
 
2100
            if revision_id == line_key[-1]:
 
2101
                result[key] = True
 
2102
        return result
 
2103
 
2067
2104
    def _inventory_xml_lines_for_keys(self, keys):
2068
2105
        """Get a line iterator of the sort needed for findind references.
2069
2106
 
2099
2136
        revision_ids. Each altered file-ids has the exact revision_ids that
2100
2137
        altered it listed explicitly.
2101
2138
        """
2102
 
        seen = set(self._serializer._find_text_key_references(
 
2139
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
2103
2140
                line_iterator).iterkeys())
2104
2141
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
2105
 
        parent_seen = set(self._serializer._find_text_key_references(
 
2142
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
2106
2143
            self._inventory_xml_lines_for_keys(parent_keys)))
2107
2144
        new_keys = seen - parent_seen
2108
2145
        result = {}
2515
2552
        # TODO: refactor this to use an existing revision object
2516
2553
        # so we don't need to read it in twice.
2517
2554
        if revision_id == _mod_revision.NULL_REVISION:
2518
 
            return InventoryRevisionTree(self,
2519
 
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
 
2555
            return RevisionTree(self, Inventory(root_id=None),
 
2556
                                _mod_revision.NULL_REVISION)
2520
2557
        else:
2521
2558
            inv = self.get_inventory(revision_id)
2522
 
            return InventoryRevisionTree(self, inv, revision_id)
 
2559
            return RevisionTree(self, inv, revision_id)
2523
2560
 
2524
2561
    def revision_trees(self, revision_ids):
2525
2562
        """Return Trees for revisions in this repository.
2529
2566
        """
2530
2567
        inventories = self.iter_inventories(revision_ids)
2531
2568
        for inv in inventories:
2532
 
            yield InventoryRevisionTree(self, inv, inv.revision_id)
 
2569
            yield RevisionTree(self, inv, inv.revision_id)
2533
2570
 
2534
2571
    def _filtered_revision_trees(self, revision_ids, file_ids):
2535
2572
        """Return Tree for a revision on this branch with only some files.
2545
2582
            # Should we introduce a FilteredRevisionTree class rather
2546
2583
            # than pre-filter the inventory here?
2547
2584
            filtered_inv = inv.filter(file_ids)
2548
 
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2585
            yield RevisionTree(self, filtered_inv, filtered_inv.revision_id)
2549
2586
 
2550
2587
    @needs_read_lock
2551
2588
    def get_ancestry(self, revision_id, topo_sorted=True):
2736
2773
        return result
2737
2774
 
2738
2775
    def _warn_if_deprecated(self, branch=None):
2739
 
        if not self._format.is_deprecated():
2740
 
            return
2741
2776
        global _deprecation_warning_done
2742
2777
        if _deprecation_warning_done:
2743
2778
            return
2773
2808
                except UnicodeDecodeError:
2774
2809
                    raise errors.NonAsciiRevisionId(method, self)
2775
2810
 
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.
 
2811
    def revision_graph_can_have_wrong_parents(self):
 
2812
        """Is it possible for this repository to have a revision graph with
 
2813
        incorrect parents?
2779
2814
 
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).
 
2815
        If True, then this repository must also implement
 
2816
        _find_inconsistent_revision_parents so that check and reconcile can
 
2817
        check for inconsistencies before proceeding with other checks that may
 
2818
        depend on the revision index being consistent.
2784
2819
        """
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.")
 
2820
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
 
2821
 
 
2822
 
 
2823
# remove these delegates a while after bzr 0.15
 
2824
def __make_delegated(name, from_module):
 
2825
    def _deprecated_repository_forwarder():
 
2826
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
 
2827
            % (name, from_module),
 
2828
            DeprecationWarning,
 
2829
            stacklevel=2)
 
2830
        try:
 
2831
            return pyutils.get_named_object(from_module, name)
 
2832
        except AttributeError:
 
2833
            raise AttributeError('module %s has no name %s'
 
2834
                    % (sys.modules[from_module], name))
 
2835
    globals()[name] = _deprecated_repository_forwarder
 
2836
 
 
2837
for _name in [
 
2838
        'AllInOneRepository',
 
2839
        'WeaveMetaDirRepository',
 
2840
        'PreSplitOutRepositoryFormat',
 
2841
        'RepositoryFormat4',
 
2842
        'RepositoryFormat5',
 
2843
        'RepositoryFormat6',
 
2844
        'RepositoryFormat7',
 
2845
        ]:
 
2846
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
 
2847
 
 
2848
for _name in [
 
2849
        'KnitRepository',
 
2850
        'RepositoryFormatKnit',
 
2851
        'RepositoryFormatKnit1',
 
2852
        ]:
 
2853
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
2806
2854
 
2807
2855
 
2808
2856
def install_revision(repository, rev, revision_tree):
2867
2915
        for revision, tree in parent_trees.iteritems():
2868
2916
            if ie.file_id not in tree:
2869
2917
                continue
2870
 
            parent_id = tree.get_file_revision(ie.file_id)
 
2918
            parent_id = tree.inventory[ie.file_id].revision
2871
2919
            if parent_id in text_parents:
2872
2920
                continue
2873
2921
            text_parents.append((ie.file_id, parent_id))
2942
2990
            control_files)
2943
2991
 
2944
2992
 
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
2993
network_format_registry = registry.FormatRegistry()
2955
2994
"""Registry of formats indexed by their network name.
2956
2995
 
2960
2999
"""
2961
3000
 
2962
3001
 
2963
 
format_registry = RepositoryFormatRegistry(network_format_registry)
 
3002
format_registry = registry.FormatRegistry(network_format_registry)
2964
3003
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
2965
3004
 
2966
3005
This can contain either format instances themselves, or classes/factories that
2971
3010
#####################################################################
2972
3011
# Repository Formats
2973
3012
 
2974
 
class RepositoryFormat(controldir.ControlComponentFormat):
 
3013
class RepositoryFormat(object):
2975
3014
    """A repository format.
2976
3015
 
2977
3016
    Formats provide four things:
3038
3077
    supports_tree_reference = None
3039
3078
    # Is the format experimental ?
3040
3079
    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
3080
 
3053
3081
    def __repr__(self):
3054
3082
        return "%s()" % self.__class__.__name__
3079
3107
                                            kind='repository')
3080
3108
 
3081
3109
    @classmethod
3082
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3083
3110
    def register_format(klass, format):
3084
 
        format_registry.register(format)
 
3111
        format_registry.register(format.get_format_string(), format)
3085
3112
 
3086
3113
    @classmethod
3087
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3088
3114
    def unregister_format(klass, format):
3089
 
        format_registry.remove(format)
 
3115
        format_registry.remove(format.get_format_string())
3090
3116
 
3091
3117
    @classmethod
3092
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3093
3118
    def get_default_format(klass):
3094
3119
        """Return the current default format."""
3095
 
        return format_registry.get_default()
 
3120
        from bzrlib import bzrdir
 
3121
        return bzrdir.format_registry.make_bzrdir('default').repository_format
3096
3122
 
3097
3123
    def get_format_string(self):
3098
3124
        """Return the ASCII format string that identifies this format.
3106
3132
        """Return the short description for this format."""
3107
3133
        raise NotImplementedError(self.get_format_description)
3108
3134
 
 
3135
    # TODO: this shouldn't be in the base class, it's specific to things that
 
3136
    # use weaves or knits -- mbp 20070207
 
3137
    def _get_versioned_file_store(self,
 
3138
                                  name,
 
3139
                                  transport,
 
3140
                                  control_files,
 
3141
                                  prefixed=True,
 
3142
                                  versionedfile_class=None,
 
3143
                                  versionedfile_kwargs={},
 
3144
                                  escaped=False):
 
3145
        if versionedfile_class is None:
 
3146
            versionedfile_class = self._versionedfile_class
 
3147
        weave_transport = control_files._transport.clone(name)
 
3148
        dir_mode = control_files._dir_mode
 
3149
        file_mode = control_files._file_mode
 
3150
        return VersionedFileStore(weave_transport, prefixed=prefixed,
 
3151
                                  dir_mode=dir_mode,
 
3152
                                  file_mode=file_mode,
 
3153
                                  versionedfile_class=versionedfile_class,
 
3154
                                  versionedfile_kwargs=versionedfile_kwargs,
 
3155
                                  escaped=escaped)
 
3156
 
3109
3157
    def initialize(self, a_bzrdir, shared=False):
3110
3158
        """Initialize a repository of this format in a_bzrdir.
3111
3159
 
3127
3175
        """
3128
3176
        return True
3129
3177
 
3130
 
    def is_deprecated(self):
3131
 
        """Is this format deprecated?
3132
 
 
3133
 
        Deprecated formats may trigger a user-visible warning recommending
3134
 
        the user to upgrade. They are still fully supported.
3135
 
        """
3136
 
        return False
3137
 
 
3138
3178
    def network_name(self):
3139
3179
        """A simple byte string uniquely identifying this format for RPC calls.
3140
3180
 
3179
3219
    rich_root_data = False
3180
3220
    supports_tree_reference = False
3181
3221
    supports_external_lookups = False
3182
 
    supports_leaving_lock = True
3183
3222
 
3184
3223
    @property
3185
3224
    def _matchingbzrdir(self):
3223
3262
        return self.get_format_string()
3224
3263
 
3225
3264
 
 
3265
# Pre-0.8 formats that don't have a disk format string (because they are
 
3266
# versioned by the matching control directory). We use the control directories
 
3267
# disk format string as a key for the network_name because they meet the
 
3268
# constraints (simple string, unique, immutable).
 
3269
network_format_registry.register_lazy(
 
3270
    "Bazaar-NG branch, format 5\n",
 
3271
    'bzrlib.repofmt.weaverepo',
 
3272
    'RepositoryFormat5',
 
3273
)
 
3274
network_format_registry.register_lazy(
 
3275
    "Bazaar-NG branch, format 6\n",
 
3276
    'bzrlib.repofmt.weaverepo',
 
3277
    'RepositoryFormat6',
 
3278
)
 
3279
 
3226
3280
# formats which have no format string are not discoverable or independently
3227
3281
# creatable on disk, so are not registered in format_registry.  They're
3228
 
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
 
3282
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
3229
3283
# needed, it's constructed directly by the BzrDir.  Non-native formats where
3230
3284
# the repository is not separately opened are similar.
3231
3285
 
3232
3286
format_registry.register_lazy(
 
3287
    'Bazaar-NG Repository format 7',
 
3288
    'bzrlib.repofmt.weaverepo',
 
3289
    'RepositoryFormat7'
 
3290
    )
 
3291
 
 
3292
format_registry.register_lazy(
3233
3293
    'Bazaar-NG Knit Repository Format 1',
3234
3294
    'bzrlib.repofmt.knitrepo',
3235
3295
    'RepositoryFormatKnit1',
3252
3312
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
3253
3313
format_registry.register_lazy(
3254
3314
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
3255
 
    'bzrlib.repofmt.knitpack_repo',
 
3315
    'bzrlib.repofmt.pack_repo',
3256
3316
    'RepositoryFormatKnitPack1',
3257
3317
    )
3258
3318
format_registry.register_lazy(
3259
3319
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
3260
 
    'bzrlib.repofmt.knitpack_repo',
 
3320
    'bzrlib.repofmt.pack_repo',
3261
3321
    'RepositoryFormatKnitPack3',
3262
3322
    )
3263
3323
format_registry.register_lazy(
3264
3324
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
3265
 
    'bzrlib.repofmt.knitpack_repo',
 
3325
    'bzrlib.repofmt.pack_repo',
3266
3326
    'RepositoryFormatKnitPack4',
3267
3327
    )
3268
3328
format_registry.register_lazy(
3269
3329
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
3270
 
    'bzrlib.repofmt.knitpack_repo',
 
3330
    'bzrlib.repofmt.pack_repo',
3271
3331
    'RepositoryFormatKnitPack5',
3272
3332
    )
3273
3333
format_registry.register_lazy(
3274
3334
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
3275
 
    'bzrlib.repofmt.knitpack_repo',
 
3335
    'bzrlib.repofmt.pack_repo',
3276
3336
    'RepositoryFormatKnitPack5RichRoot',
3277
3337
    )
3278
3338
format_registry.register_lazy(
3279
3339
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
3280
 
    'bzrlib.repofmt.knitpack_repo',
 
3340
    'bzrlib.repofmt.pack_repo',
3281
3341
    'RepositoryFormatKnitPack5RichRootBroken',
3282
3342
    )
3283
3343
format_registry.register_lazy(
3284
3344
    'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
3285
 
    'bzrlib.repofmt.knitpack_repo',
 
3345
    'bzrlib.repofmt.pack_repo',
3286
3346
    'RepositoryFormatKnitPack6',
3287
3347
    )
3288
3348
format_registry.register_lazy(
3289
3349
    'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
3290
 
    'bzrlib.repofmt.knitpack_repo',
 
3350
    'bzrlib.repofmt.pack_repo',
3291
3351
    'RepositoryFormatKnitPack6RichRoot',
3292
3352
    )
3293
 
format_registry.register_lazy(
3294
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3295
 
    'bzrlib.repofmt.groupcompress_repo',
3296
 
    'RepositoryFormat2a',
3297
 
    )
3298
3353
 
3299
3354
# Development formats.
3300
 
# Check their docstrings to see if/when they are obsolete.
 
3355
# Obsolete but kept pending a CHK based subtree format.
3301
3356
format_registry.register_lazy(
3302
3357
    ("Bazaar development format 2 with subtree support "
3303
3358
        "(needs bzr.dev from before 1.8)\n"),
3304
 
    'bzrlib.repofmt.knitpack_repo',
 
3359
    'bzrlib.repofmt.pack_repo',
3305
3360
    'RepositoryFormatPackDevelopment2Subtree',
3306
3361
    )
 
3362
 
 
3363
# 1.14->1.16 go below here
 
3364
format_registry.register_lazy(
 
3365
    'Bazaar development format - group compression and chk inventory'
 
3366
        ' (needs bzr.dev from 1.14)\n',
 
3367
    'bzrlib.repofmt.groupcompress_repo',
 
3368
    'RepositoryFormatCHK1',
 
3369
    )
 
3370
 
 
3371
format_registry.register_lazy(
 
3372
    'Bazaar development format - chk repository with bencode revision '
 
3373
        'serialization (needs bzr.dev from 1.16)\n',
 
3374
    'bzrlib.repofmt.groupcompress_repo',
 
3375
    'RepositoryFormatCHK2',
 
3376
    )
 
3377
format_registry.register_lazy(
 
3378
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3379
    'bzrlib.repofmt.groupcompress_repo',
 
3380
    'RepositoryFormat2a',
 
3381
    )
3307
3382
format_registry.register_lazy(
3308
3383
    'Bazaar development format 8\n',
3309
3384
    'bzrlib.repofmt.groupcompress_repo',
3344
3419
        self.target.fetch(self.source, revision_id=revision_id)
3345
3420
 
3346
3421
    @needs_write_lock
3347
 
    def fetch(self, revision_id=None, find_ghosts=False,
 
3422
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3348
3423
            fetch_spec=None):
3349
3424
        """Fetch the content required to construct revision_id.
3350
3425
 
3352
3427
 
3353
3428
        :param revision_id: if None all content is copied, if NULL_REVISION no
3354
3429
                            content is copied.
 
3430
        :param pb: ignored.
3355
3431
        :return: None.
3356
3432
        """
3357
3433
        ui.ui_factory.warn_experimental_format_fetch(self)
3367
3443
                               fetch_spec=fetch_spec,
3368
3444
                               find_ghosts=find_ghosts)
3369
3445
 
3370
 
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
 
3446
    def _walk_to_common_revisions(self, revision_ids):
3371
3447
        """Walk out from revision_ids in source to revisions target has.
3372
3448
 
3373
3449
        :param revision_ids: The start point for the search.
3375
3451
        """
3376
3452
        target_graph = self.target.get_graph()
3377
3453
        revision_ids = frozenset(revision_ids)
3378
 
        if if_present_ids:
3379
 
            all_wanted_revs = revision_ids.union(if_present_ids)
3380
 
        else:
3381
 
            all_wanted_revs = revision_ids
3382
3454
        missing_revs = set()
3383
3455
        source_graph = self.source.get_graph()
3384
3456
        # ensure we don't pay silly lookup costs.
3385
 
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
 
3457
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
3386
3458
        null_set = frozenset([_mod_revision.NULL_REVISION])
3387
3459
        searcher_exhausted = False
3388
3460
        while True:
3424
3496
        return searcher.get_result()
3425
3497
 
3426
3498
    @needs_read_lock
3427
 
    def search_missing_revision_ids(self,
3428
 
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
3429
 
            find_ghosts=True, revision_ids=None, if_present_ids=None):
 
3499
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3430
3500
        """Return the revision ids that source has that target does not.
3431
3501
 
3432
3502
        :param revision_id: only return revision ids included by this
3433
 
            revision_id.
3434
 
        :param revision_ids: return revision ids included by these
3435
 
            revision_ids.  NoSuchRevision will be raised if any of these
3436
 
            revisions are not present.
3437
 
        :param if_present_ids: like revision_ids, but will not cause
3438
 
            NoSuchRevision if any of these are absent, instead they will simply
3439
 
            not be in the result.  This is useful for e.g. finding revisions
3440
 
            to fetch for tags, which may reference absent revisions.
 
3503
                            revision_id.
3441
3504
        :param find_ghosts: If True find missing revisions in deep history
3442
3505
            rather than just finding the surface difference.
3443
3506
        :return: A bzrlib.graph.SearchResult.
3444
3507
        """
3445
 
        if symbol_versioning.deprecated_passed(revision_id):
3446
 
            symbol_versioning.warn(
3447
 
                'search_missing_revision_ids(revision_id=...) was '
3448
 
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
3449
 
                DeprecationWarning, stacklevel=2)
3450
 
            if revision_ids is not None:
3451
 
                raise AssertionError(
3452
 
                    'revision_ids is mutually exclusive with revision_id')
3453
 
            if revision_id is not None:
3454
 
                revision_ids = [revision_id]
3455
 
        del revision_id
3456
3508
        # stop searching at found target revisions.
3457
 
        if not find_ghosts and (revision_ids is not None or if_present_ids is
3458
 
                not None):
3459
 
            return self._walk_to_common_revisions(revision_ids,
3460
 
                    if_present_ids=if_present_ids)
 
3509
        if not find_ghosts and revision_id is not None:
 
3510
            return self._walk_to_common_revisions([revision_id])
3461
3511
        # generic, possibly worst case, slow code path.
3462
3512
        target_ids = set(self.target.all_revision_ids())
3463
 
        source_ids = self._present_source_revisions_for(
3464
 
            revision_ids, if_present_ids)
 
3513
        if revision_id is not None:
 
3514
            source_ids = self.source.get_ancestry(revision_id)
 
3515
            if source_ids[0] is not None:
 
3516
                raise AssertionError()
 
3517
            source_ids.pop(0)
 
3518
        else:
 
3519
            source_ids = self.source.all_revision_ids()
3465
3520
        result_set = set(source_ids).difference(target_ids)
3466
3521
        return self.source.revision_ids_to_search_result(result_set)
3467
3522
 
3468
 
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
3469
 
        """Returns set of all revisions in ancestry of revision_ids present in
3470
 
        the source repo.
3471
 
 
3472
 
        :param revision_ids: if None, all revisions in source are returned.
3473
 
        :param if_present_ids: like revision_ids, but if any/all of these are
3474
 
            absent no error is raised.
3475
 
        """
3476
 
        if revision_ids is not None or if_present_ids is not None:
3477
 
            # First, ensure all specified revisions exist.  Callers expect
3478
 
            # NoSuchRevision when they pass absent revision_ids here.
3479
 
            if revision_ids is None:
3480
 
                revision_ids = set()
3481
 
            if if_present_ids is None:
3482
 
                if_present_ids = set()
3483
 
            revision_ids = set(revision_ids)
3484
 
            if_present_ids = set(if_present_ids)
3485
 
            all_wanted_ids = revision_ids.union(if_present_ids)
3486
 
            graph = self.source.get_graph()
3487
 
            present_revs = set(graph.get_parent_map(all_wanted_ids))
3488
 
            missing = revision_ids.difference(present_revs)
3489
 
            if missing:
3490
 
                raise errors.NoSuchRevision(self.source, missing.pop())
3491
 
            found_ids = all_wanted_ids.intersection(present_revs)
3492
 
            source_ids = [rev_id for (rev_id, parents) in
3493
 
                          graph.iter_ancestry(found_ids)
3494
 
                          if rev_id != _mod_revision.NULL_REVISION
3495
 
                          and parents is not None]
3496
 
        else:
3497
 
            source_ids = self.source.all_revision_ids()
3498
 
        return set(source_ids)
3499
 
 
3500
3523
    @staticmethod
3501
3524
    def _same_model(source, target):
3502
3525
        """True if source and target have the same data representation.
3543
3566
        return InterRepository._same_model(source, target)
3544
3567
 
3545
3568
 
 
3569
class InterWeaveRepo(InterSameDataRepository):
 
3570
    """Optimised code paths between Weave based repositories.
 
3571
 
 
3572
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
 
3573
    implemented lazy inter-object optimisation.
 
3574
    """
 
3575
 
 
3576
    @classmethod
 
3577
    def _get_repo_format_to_test(self):
 
3578
        from bzrlib.repofmt import weaverepo
 
3579
        return weaverepo.RepositoryFormat7()
 
3580
 
 
3581
    @staticmethod
 
3582
    def is_compatible(source, target):
 
3583
        """Be compatible with known Weave formats.
 
3584
 
 
3585
        We don't test for the stores being of specific types because that
 
3586
        could lead to confusing results, and there is no need to be
 
3587
        overly general.
 
3588
        """
 
3589
        from bzrlib.repofmt.weaverepo import (
 
3590
                RepositoryFormat5,
 
3591
                RepositoryFormat6,
 
3592
                RepositoryFormat7,
 
3593
                )
 
3594
        try:
 
3595
            return (isinstance(source._format, (RepositoryFormat5,
 
3596
                                                RepositoryFormat6,
 
3597
                                                RepositoryFormat7)) and
 
3598
                    isinstance(target._format, (RepositoryFormat5,
 
3599
                                                RepositoryFormat6,
 
3600
                                                RepositoryFormat7)))
 
3601
        except AttributeError:
 
3602
            return False
 
3603
 
 
3604
    @needs_write_lock
 
3605
    def copy_content(self, revision_id=None):
 
3606
        """See InterRepository.copy_content()."""
 
3607
        # weave specific optimised path:
 
3608
        try:
 
3609
            self.target.set_make_working_trees(self.source.make_working_trees())
 
3610
        except (errors.RepositoryUpgradeRequired, NotImplemented):
 
3611
            pass
 
3612
        # FIXME do not peek!
 
3613
        if self.source._transport.listable():
 
3614
            pb = ui.ui_factory.nested_progress_bar()
 
3615
            try:
 
3616
                self.target.texts.insert_record_stream(
 
3617
                    self.source.texts.get_record_stream(
 
3618
                        self.source.texts.keys(), 'topological', False))
 
3619
                pb.update('Copying inventory', 0, 1)
 
3620
                self.target.inventories.insert_record_stream(
 
3621
                    self.source.inventories.get_record_stream(
 
3622
                        self.source.inventories.keys(), 'topological', False))
 
3623
                self.target.signatures.insert_record_stream(
 
3624
                    self.source.signatures.get_record_stream(
 
3625
                        self.source.signatures.keys(),
 
3626
                        'unordered', True))
 
3627
                self.target.revisions.insert_record_stream(
 
3628
                    self.source.revisions.get_record_stream(
 
3629
                        self.source.revisions.keys(),
 
3630
                        'topological', True))
 
3631
            finally:
 
3632
                pb.finished()
 
3633
        else:
 
3634
            self.target.fetch(self.source, revision_id=revision_id)
 
3635
 
 
3636
    @needs_read_lock
 
3637
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3638
        """See InterRepository.missing_revision_ids()."""
 
3639
        # we want all revisions to satisfy revision_id in source.
 
3640
        # but we don't want to stat every file here and there.
 
3641
        # we want then, all revisions other needs to satisfy revision_id
 
3642
        # checked, but not those that we have locally.
 
3643
        # so the first thing is to get a subset of the revisions to
 
3644
        # satisfy revision_id in source, and then eliminate those that
 
3645
        # we do already have.
 
3646
        # this is slow on high latency connection to self, but as this
 
3647
        # disk format scales terribly for push anyway due to rewriting
 
3648
        # inventory.weave, this is considered acceptable.
 
3649
        # - RBC 20060209
 
3650
        if revision_id is not None:
 
3651
            source_ids = self.source.get_ancestry(revision_id)
 
3652
            if source_ids[0] is not None:
 
3653
                raise AssertionError()
 
3654
            source_ids.pop(0)
 
3655
        else:
 
3656
            source_ids = self.source._all_possible_ids()
 
3657
        source_ids_set = set(source_ids)
 
3658
        # source_ids is the worst possible case we may need to pull.
 
3659
        # now we want to filter source_ids against what we actually
 
3660
        # have in target, but don't try to check for existence where we know
 
3661
        # we do not have a revision as that would be pointless.
 
3662
        target_ids = set(self.target._all_possible_ids())
 
3663
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
3664
        actually_present_revisions = set(
 
3665
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
3666
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
3667
        if revision_id is not None:
 
3668
            # we used get_ancestry to determine source_ids then we are assured all
 
3669
            # revisions referenced are present as they are installed in topological order.
 
3670
            # and the tip revision was validated by get_ancestry.
 
3671
            result_set = required_revisions
 
3672
        else:
 
3673
            # if we just grabbed the possibly available ids, then
 
3674
            # we only have an estimate of whats available and need to validate
 
3675
            # that against the revision records.
 
3676
            result_set = set(
 
3677
                self.source._eliminate_revisions_not_present(required_revisions))
 
3678
        return self.source.revision_ids_to_search_result(result_set)
 
3679
 
 
3680
 
 
3681
class InterKnitRepo(InterSameDataRepository):
 
3682
    """Optimised code paths between Knit based repositories."""
 
3683
 
 
3684
    @classmethod
 
3685
    def _get_repo_format_to_test(self):
 
3686
        from bzrlib.repofmt import knitrepo
 
3687
        return knitrepo.RepositoryFormatKnit1()
 
3688
 
 
3689
    @staticmethod
 
3690
    def is_compatible(source, target):
 
3691
        """Be compatible with known Knit formats.
 
3692
 
 
3693
        We don't test for the stores being of specific types because that
 
3694
        could lead to confusing results, and there is no need to be
 
3695
        overly general.
 
3696
        """
 
3697
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
 
3698
        try:
 
3699
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
 
3700
                isinstance(target._format, RepositoryFormatKnit))
 
3701
        except AttributeError:
 
3702
            return False
 
3703
        return are_knits and InterRepository._same_model(source, target)
 
3704
 
 
3705
    @needs_read_lock
 
3706
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3707
        """See InterRepository.missing_revision_ids()."""
 
3708
        if revision_id is not None:
 
3709
            source_ids = self.source.get_ancestry(revision_id)
 
3710
            if source_ids[0] is not None:
 
3711
                raise AssertionError()
 
3712
            source_ids.pop(0)
 
3713
        else:
 
3714
            source_ids = self.source.all_revision_ids()
 
3715
        source_ids_set = set(source_ids)
 
3716
        # source_ids is the worst possible case we may need to pull.
 
3717
        # now we want to filter source_ids against what we actually
 
3718
        # have in target, but don't try to check for existence where we know
 
3719
        # we do not have a revision as that would be pointless.
 
3720
        target_ids = set(self.target.all_revision_ids())
 
3721
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
3722
        actually_present_revisions = set(
 
3723
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
3724
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
3725
        if revision_id is not None:
 
3726
            # we used get_ancestry to determine source_ids then we are assured all
 
3727
            # revisions referenced are present as they are installed in topological order.
 
3728
            # and the tip revision was validated by get_ancestry.
 
3729
            result_set = required_revisions
 
3730
        else:
 
3731
            # if we just grabbed the possibly available ids, then
 
3732
            # we only have an estimate of whats available and need to validate
 
3733
            # that against the revision records.
 
3734
            result_set = set(
 
3735
                self.source._eliminate_revisions_not_present(required_revisions))
 
3736
        return self.source.revision_ids_to_search_result(result_set)
 
3737
 
 
3738
 
3546
3739
class InterDifferingSerializer(InterRepository):
3547
3740
 
3548
3741
    @classmethod
3816
4009
                  len(revision_ids))
3817
4010
 
3818
4011
    @needs_write_lock
3819
 
    def fetch(self, revision_id=None, find_ghosts=False,
 
4012
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3820
4013
            fetch_spec=None):
3821
4014
        """See InterRepository.fetch()."""
3822
4015
        if fetch_spec is not None:
3823
 
            revision_ids = fetch_spec.get_keys()
3824
 
        else:
3825
 
            revision_ids = None
 
4016
            raise AssertionError("Not implemented yet...")
3826
4017
        ui.ui_factory.warn_experimental_format_fetch(self)
3827
4018
        if (not self.source.supports_rich_root()
3828
4019
            and self.target.supports_rich_root()):
3835
4026
            ui.ui_factory.show_user_warning('cross_format_fetch',
3836
4027
                from_format=self.source._format,
3837
4028
                to_format=self.target._format)
3838
 
        if revision_ids is None:
3839
 
            if revision_id:
3840
 
                search_revision_ids = [revision_id]
3841
 
            else:
3842
 
                search_revision_ids = None
3843
 
            revision_ids = self.target.search_missing_revision_ids(self.source,
3844
 
                revision_ids=search_revision_ids,
3845
 
                find_ghosts=find_ghosts).get_keys()
 
4029
        revision_ids = self.target.search_missing_revision_ids(self.source,
 
4030
            revision_id, find_ghosts=find_ghosts).get_keys()
3846
4031
        if not revision_ids:
3847
4032
            return 0, 0
3848
4033
        revision_ids = tsort.topo_sort(
3852
4037
        # Walk though all revisions; get inventory deltas, copy referenced
3853
4038
        # texts that delta references, insert the delta, revision and
3854
4039
        # signature.
3855
 
        pb = ui.ui_factory.nested_progress_bar()
 
4040
        if pb is None:
 
4041
            my_pb = ui.ui_factory.nested_progress_bar()
 
4042
            pb = my_pb
 
4043
        else:
 
4044
            symbol_versioning.warn(
 
4045
                symbol_versioning.deprecated_in((1, 14, 0))
 
4046
                % "pb parameter to fetch()")
 
4047
            my_pb = None
3856
4048
        try:
3857
4049
            self._fetch_all_revisions(revision_ids, pb)
3858
4050
        finally:
3859
 
            pb.finished()
 
4051
            if my_pb is not None:
 
4052
                my_pb.finished()
3860
4053
        return len(revision_ids), 0
3861
4054
 
3862
4055
    def _get_basis(self, first_revision_id):
3884
4077
 
3885
4078
InterRepository.register_optimiser(InterDifferingSerializer)
3886
4079
InterRepository.register_optimiser(InterSameDataRepository)
 
4080
InterRepository.register_optimiser(InterWeaveRepo)
 
4081
InterRepository.register_optimiser(InterKnitRepo)
3887
4082
 
3888
4083
 
3889
4084
class CopyConverter(object):
3934
4129
        pb.finished()
3935
4130
 
3936
4131
 
 
4132
_unescape_map = {
 
4133
    'apos':"'",
 
4134
    'quot':'"',
 
4135
    'amp':'&',
 
4136
    'lt':'<',
 
4137
    'gt':'>'
 
4138
}
 
4139
 
 
4140
 
 
4141
def _unescaper(match, _map=_unescape_map):
 
4142
    code = match.group(1)
 
4143
    try:
 
4144
        return _map[code]
 
4145
    except KeyError:
 
4146
        if not code.startswith('#'):
 
4147
            raise
 
4148
        return unichr(int(code[1:])).encode('utf8')
 
4149
 
 
4150
 
 
4151
_unescape_re = None
 
4152
 
 
4153
 
 
4154
def _unescape_xml(data):
 
4155
    """Unescape predefined XML entities in a string of data."""
 
4156
    global _unescape_re
 
4157
    if _unescape_re is None:
 
4158
        _unescape_re = re.compile('\&([^;]*);')
 
4159
    return _unescape_re.sub(_unescaper, data)
 
4160
 
 
4161
 
3937
4162
class _VersionedFileChecker(object):
3938
4163
 
3939
4164
    def __init__(self, repository, text_key_references=None, ancestors=None):
3998
4223
        return wrong_parents, unused_keys
3999
4224
 
4000
4225
 
 
4226
def _old_get_graph(repository, revision_id):
 
4227
    """DO NOT USE. That is all. I'm serious."""
 
4228
    graph = repository.get_graph()
 
4229
    revision_graph = dict(((key, value) for key, value in
 
4230
        graph.iter_ancestry([revision_id]) if value is not None))
 
4231
    return _strip_NULL_ghosts(revision_graph)
 
4232
 
 
4233
 
4001
4234
def _strip_NULL_ghosts(revision_graph):
4002
4235
    """Also don't use this. more compatibility code for unmigrated clients."""
4003
4236
    # Filter ghosts, and null:
4039
4272
                is_resume = False
4040
4273
            try:
4041
4274
                # locked_insert_stream performs a commit|suspend.
4042
 
                missing_keys = self.insert_stream_without_locking(stream,
4043
 
                                    src_format, is_resume)
4044
 
                if missing_keys:
4045
 
                    # suspend the write group and tell the caller what we is
4046
 
                    # missing. We know we can suspend or else we would not have
4047
 
                    # entered this code path. (All repositories that can handle
4048
 
                    # missing keys can handle suspending a write group).
4049
 
                    write_group_tokens = self.target_repo.suspend_write_group()
4050
 
                    return write_group_tokens, missing_keys
4051
 
                hint = self.target_repo.commit_write_group()
4052
 
                to_serializer = self.target_repo._format._serializer
4053
 
                src_serializer = src_format._serializer
4054
 
                if (to_serializer != src_serializer and
4055
 
                    self.target_repo._format.pack_compresses):
4056
 
                    self.target_repo.pack(hint=hint)
4057
 
                return [], set()
 
4275
                return self._locked_insert_stream(stream, src_format,
 
4276
                    is_resume)
4058
4277
            except:
4059
4278
                self.target_repo.abort_write_group(suppress_errors=True)
4060
4279
                raise
4061
4280
        finally:
4062
4281
            self.target_repo.unlock()
4063
4282
 
4064
 
    def insert_stream_without_locking(self, stream, src_format,
4065
 
                                      is_resume=False):
4066
 
        """Insert a stream's content into the target repository.
4067
 
 
4068
 
        This assumes that you already have a locked repository and an active
4069
 
        write group.
4070
 
 
4071
 
        :param src_format: a bzr repository format.
4072
 
        :param is_resume: Passed down to get_missing_parent_inventories to
4073
 
            indicate if we should be checking for missing texts at the same
4074
 
            time.
4075
 
 
4076
 
        :return: A set of keys that are missing.
4077
 
        """
4078
 
        if not self.target_repo.is_write_locked():
4079
 
            raise errors.ObjectNotLocked(self)
4080
 
        if not self.target_repo.is_in_write_group():
4081
 
            raise errors.BzrError('you must already be in a write group')
 
4283
    def _locked_insert_stream(self, stream, src_format, is_resume):
4082
4284
        to_serializer = self.target_repo._format._serializer
4083
4285
        src_serializer = src_format._serializer
4084
4286
        new_pack = None
4163
4365
            # cannot even attempt suspending, and missing would have failed
4164
4366
            # during stream insertion.
4165
4367
            missing_keys = set()
4166
 
        return missing_keys
 
4368
        else:
 
4369
            if missing_keys:
 
4370
                # suspend the write group and tell the caller what we is
 
4371
                # missing. We know we can suspend or else we would not have
 
4372
                # entered this code path. (All repositories that can handle
 
4373
                # missing keys can handle suspending a write group).
 
4374
                write_group_tokens = self.target_repo.suspend_write_group()
 
4375
                return write_group_tokens, missing_keys
 
4376
        hint = self.target_repo.commit_write_group()
 
4377
        if (to_serializer != src_serializer and
 
4378
            self.target_repo._format.pack_compresses):
 
4379
            self.target_repo.pack(hint=hint)
 
4380
        return [], set()
4167
4381
 
4168
4382
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4169
4383
        target_rich_root = self.target_repo._format.rich_root_data
4176
4390
                parse_result = deserialiser.parse_text_bytes(
4177
4391
                    inventory_delta_bytes)
4178
4392
            except inventory_delta.IncompatibleInventoryDelta, err:
4179
 
                mutter("Incompatible delta: %s", err.msg)
 
4393
                trace.mutter("Incompatible delta: %s", err.msg)
4180
4394
                raise errors.IncompatibleRevision(self.target_repo._format)
4181
4395
            basis_id, new_id, rich_root, tree_refs, inv_delta = parse_result
4182
4396
            revision_id = new_id
4517
4731
    except StopIteration:
4518
4732
        # No more history
4519
4733
        return
 
4734