~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Jelmer Vernooij
  • Date: 2010-08-28 11:19:49 UTC
  • mto: This revision was merged to the branch mainline in revision 5418.
  • Revision ID: jelmer@samba.org-20100828111949-6ke9opiop2oomr4f
Move get_config to ControlDir.

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 = {}
2476
2511
            ancestors will be traversed.
2477
2512
        """
2478
2513
        graph = self.get_graph()
2479
 
        stop_revisions = (None, _mod_revision.NULL_REVISION)
2480
 
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
 
2514
        next_id = revision_id
 
2515
        while True:
 
2516
            if next_id in (None, _mod_revision.NULL_REVISION):
 
2517
                return
 
2518
            try:
 
2519
                parents = graph.get_parent_map([next_id])[next_id]
 
2520
            except KeyError:
 
2521
                raise errors.RevisionNotPresent(next_id, self)
 
2522
            yield next_id
 
2523
            if len(parents) == 0:
 
2524
                return
 
2525
            else:
 
2526
                next_id = parents[0]
2481
2527
 
2482
2528
    def is_shared(self):
2483
2529
        """Return True if this repository is flagged as a shared repository."""
2515
2561
        # TODO: refactor this to use an existing revision object
2516
2562
        # so we don't need to read it in twice.
2517
2563
        if revision_id == _mod_revision.NULL_REVISION:
2518
 
            return InventoryRevisionTree(self,
2519
 
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
 
2564
            return RevisionTree(self, Inventory(root_id=None),
 
2565
                                _mod_revision.NULL_REVISION)
2520
2566
        else:
2521
2567
            inv = self.get_inventory(revision_id)
2522
 
            return InventoryRevisionTree(self, inv, revision_id)
 
2568
            return RevisionTree(self, inv, revision_id)
2523
2569
 
2524
2570
    def revision_trees(self, revision_ids):
2525
2571
        """Return Trees for revisions in this repository.
2529
2575
        """
2530
2576
        inventories = self.iter_inventories(revision_ids)
2531
2577
        for inv in inventories:
2532
 
            yield InventoryRevisionTree(self, inv, inv.revision_id)
 
2578
            yield RevisionTree(self, inv, inv.revision_id)
2533
2579
 
2534
2580
    def _filtered_revision_trees(self, revision_ids, file_ids):
2535
2581
        """Return Tree for a revision on this branch with only some files.
2545
2591
            # Should we introduce a FilteredRevisionTree class rather
2546
2592
            # than pre-filter the inventory here?
2547
2593
            filtered_inv = inv.filter(file_ids)
2548
 
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2594
            yield RevisionTree(self, filtered_inv, filtered_inv.revision_id)
2549
2595
 
2550
2596
    @needs_read_lock
2551
2597
    def get_ancestry(self, revision_id, topo_sorted=True):
2584
2630
        types it should be a no-op that just returns.
2585
2631
 
2586
2632
        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
 
2633
        @needs_write_lock as this is a long running call its reasonable to
2588
2634
        implicitly lock for the user.
2589
2635
 
2590
2636
        :param hint: If not supplied, the whole repository is packed.
2736
2782
        return result
2737
2783
 
2738
2784
    def _warn_if_deprecated(self, branch=None):
2739
 
        if not self._format.is_deprecated():
2740
 
            return
2741
2785
        global _deprecation_warning_done
2742
2786
        if _deprecation_warning_done:
2743
2787
            return
2773
2817
                except UnicodeDecodeError:
2774
2818
                    raise errors.NonAsciiRevisionId(method, self)
2775
2819
 
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.
 
2820
    def revision_graph_can_have_wrong_parents(self):
 
2821
        """Is it possible for this repository to have a revision graph with
 
2822
        incorrect parents?
2779
2823
 
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).
 
2824
        If True, then this repository must also implement
 
2825
        _find_inconsistent_revision_parents so that check and reconcile can
 
2826
        check for inconsistencies before proceeding with other checks that may
 
2827
        depend on the revision index being consistent.
2784
2828
        """
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.")
 
2829
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
 
2830
 
 
2831
 
 
2832
# remove these delegates a while after bzr 0.15
 
2833
def __make_delegated(name, from_module):
 
2834
    def _deprecated_repository_forwarder():
 
2835
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
 
2836
            % (name, from_module),
 
2837
            DeprecationWarning,
 
2838
            stacklevel=2)
 
2839
        m = __import__(from_module, globals(), locals(), [name])
 
2840
        try:
 
2841
            return getattr(m, name)
 
2842
        except AttributeError:
 
2843
            raise AttributeError('module %s has no name %s'
 
2844
                    % (m, name))
 
2845
    globals()[name] = _deprecated_repository_forwarder
 
2846
 
 
2847
for _name in [
 
2848
        'AllInOneRepository',
 
2849
        'WeaveMetaDirRepository',
 
2850
        'PreSplitOutRepositoryFormat',
 
2851
        'RepositoryFormat4',
 
2852
        'RepositoryFormat5',
 
2853
        'RepositoryFormat6',
 
2854
        'RepositoryFormat7',
 
2855
        ]:
 
2856
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
 
2857
 
 
2858
for _name in [
 
2859
        'KnitRepository',
 
2860
        'RepositoryFormatKnit',
 
2861
        'RepositoryFormatKnit1',
 
2862
        ]:
 
2863
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
2806
2864
 
2807
2865
 
2808
2866
def install_revision(repository, rev, revision_tree):
2867
2925
        for revision, tree in parent_trees.iteritems():
2868
2926
            if ie.file_id not in tree:
2869
2927
                continue
2870
 
            parent_id = tree.get_file_revision(ie.file_id)
 
2928
            parent_id = tree.inventory[ie.file_id].revision
2871
2929
            if parent_id in text_parents:
2872
2930
                continue
2873
2931
            text_parents.append((ie.file_id, parent_id))
2942
3000
            control_files)
2943
3001
 
2944
3002
 
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
3003
network_format_registry = registry.FormatRegistry()
2955
3004
"""Registry of formats indexed by their network name.
2956
3005
 
2960
3009
"""
2961
3010
 
2962
3011
 
2963
 
format_registry = RepositoryFormatRegistry(network_format_registry)
 
3012
format_registry = registry.FormatRegistry(network_format_registry)
2964
3013
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
2965
3014
 
2966
3015
This can contain either format instances themselves, or classes/factories that
2971
3020
#####################################################################
2972
3021
# Repository Formats
2973
3022
 
2974
 
class RepositoryFormat(controldir.ControlComponentFormat):
 
3023
class RepositoryFormat(object):
2975
3024
    """A repository format.
2976
3025
 
2977
3026
    Formats provide four things:
3038
3087
    supports_tree_reference = None
3039
3088
    # Is the format experimental ?
3040
3089
    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
3090
 
3053
3091
    def __repr__(self):
3054
3092
        return "%s()" % self.__class__.__name__
3079
3117
                                            kind='repository')
3080
3118
 
3081
3119
    @classmethod
3082
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3083
3120
    def register_format(klass, format):
3084
 
        format_registry.register(format)
 
3121
        format_registry.register(format.get_format_string(), format)
3085
3122
 
3086
3123
    @classmethod
3087
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3088
3124
    def unregister_format(klass, format):
3089
 
        format_registry.remove(format)
 
3125
        format_registry.remove(format.get_format_string())
3090
3126
 
3091
3127
    @classmethod
3092
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3093
3128
    def get_default_format(klass):
3094
3129
        """Return the current default format."""
3095
 
        return format_registry.get_default()
 
3130
        from bzrlib import bzrdir
 
3131
        return bzrdir.format_registry.make_bzrdir('default').repository_format
3096
3132
 
3097
3133
    def get_format_string(self):
3098
3134
        """Return the ASCII format string that identifies this format.
3149
3185
        """
3150
3186
        return True
3151
3187
 
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
3188
    def network_name(self):
3161
3189
        """A simple byte string uniquely identifying this format for RPC calls.
3162
3190
 
3201
3229
    rich_root_data = False
3202
3230
    supports_tree_reference = False
3203
3231
    supports_external_lookups = False
3204
 
    supports_leaving_lock = True
3205
3232
 
3206
3233
    @property
3207
3234
    def _matchingbzrdir(self):
3245
3272
        return self.get_format_string()
3246
3273
 
3247
3274
 
 
3275
# Pre-0.8 formats that don't have a disk format string (because they are
 
3276
# versioned by the matching control directory). We use the control directories
 
3277
# disk format string as a key for the network_name because they meet the
 
3278
# constraints (simple string, unique, immutable).
 
3279
network_format_registry.register_lazy(
 
3280
    "Bazaar-NG branch, format 5\n",
 
3281
    'bzrlib.repofmt.weaverepo',
 
3282
    'RepositoryFormat5',
 
3283
)
 
3284
network_format_registry.register_lazy(
 
3285
    "Bazaar-NG branch, format 6\n",
 
3286
    'bzrlib.repofmt.weaverepo',
 
3287
    'RepositoryFormat6',
 
3288
)
 
3289
 
3248
3290
# formats which have no format string are not discoverable or independently
3249
3291
# 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
 
3292
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
3251
3293
# needed, it's constructed directly by the BzrDir.  Non-native formats where
3252
3294
# the repository is not separately opened are similar.
3253
3295
 
3254
3296
format_registry.register_lazy(
 
3297
    'Bazaar-NG Repository format 7',
 
3298
    'bzrlib.repofmt.weaverepo',
 
3299
    'RepositoryFormat7'
 
3300
    )
 
3301
 
 
3302
format_registry.register_lazy(
3255
3303
    'Bazaar-NG Knit Repository Format 1',
3256
3304
    'bzrlib.repofmt.knitrepo',
3257
3305
    'RepositoryFormatKnit1',
3274
3322
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
3275
3323
format_registry.register_lazy(
3276
3324
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
3277
 
    'bzrlib.repofmt.knitpack_repo',
 
3325
    'bzrlib.repofmt.pack_repo',
3278
3326
    'RepositoryFormatKnitPack1',
3279
3327
    )
3280
3328
format_registry.register_lazy(
3281
3329
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
3282
 
    'bzrlib.repofmt.knitpack_repo',
 
3330
    'bzrlib.repofmt.pack_repo',
3283
3331
    'RepositoryFormatKnitPack3',
3284
3332
    )
3285
3333
format_registry.register_lazy(
3286
3334
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
3287
 
    'bzrlib.repofmt.knitpack_repo',
 
3335
    'bzrlib.repofmt.pack_repo',
3288
3336
    'RepositoryFormatKnitPack4',
3289
3337
    )
3290
3338
format_registry.register_lazy(
3291
3339
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
3292
 
    'bzrlib.repofmt.knitpack_repo',
 
3340
    'bzrlib.repofmt.pack_repo',
3293
3341
    'RepositoryFormatKnitPack5',
3294
3342
    )
3295
3343
format_registry.register_lazy(
3296
3344
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
3297
 
    'bzrlib.repofmt.knitpack_repo',
 
3345
    'bzrlib.repofmt.pack_repo',
3298
3346
    'RepositoryFormatKnitPack5RichRoot',
3299
3347
    )
3300
3348
format_registry.register_lazy(
3301
3349
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
3302
 
    'bzrlib.repofmt.knitpack_repo',
 
3350
    'bzrlib.repofmt.pack_repo',
3303
3351
    'RepositoryFormatKnitPack5RichRootBroken',
3304
3352
    )
3305
3353
format_registry.register_lazy(
3306
3354
    'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
3307
 
    'bzrlib.repofmt.knitpack_repo',
 
3355
    'bzrlib.repofmt.pack_repo',
3308
3356
    'RepositoryFormatKnitPack6',
3309
3357
    )
3310
3358
format_registry.register_lazy(
3311
3359
    'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
3312
 
    'bzrlib.repofmt.knitpack_repo',
 
3360
    'bzrlib.repofmt.pack_repo',
3313
3361
    'RepositoryFormatKnitPack6RichRoot',
3314
3362
    )
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
3363
 
3321
3364
# Development formats.
3322
 
# Check their docstrings to see if/when they are obsolete.
 
3365
# Obsolete but kept pending a CHK based subtree format.
3323
3366
format_registry.register_lazy(
3324
3367
    ("Bazaar development format 2 with subtree support "
3325
3368
        "(needs bzr.dev from before 1.8)\n"),
3326
 
    'bzrlib.repofmt.knitpack_repo',
 
3369
    'bzrlib.repofmt.pack_repo',
3327
3370
    'RepositoryFormatPackDevelopment2Subtree',
3328
3371
    )
3329
 
format_registry.register_lazy(
3330
 
    'Bazaar development format 8\n',
3331
 
    'bzrlib.repofmt.groupcompress_repo',
3332
 
    'RepositoryFormat2aSubtree',
 
3372
 
 
3373
# 1.14->1.16 go below here
 
3374
format_registry.register_lazy(
 
3375
    'Bazaar development format - group compression and chk inventory'
 
3376
        ' (needs bzr.dev from 1.14)\n',
 
3377
    'bzrlib.repofmt.groupcompress_repo',
 
3378
    'RepositoryFormatCHK1',
 
3379
    )
 
3380
 
 
3381
format_registry.register_lazy(
 
3382
    'Bazaar development format - chk repository with bencode revision '
 
3383
        'serialization (needs bzr.dev from 1.16)\n',
 
3384
    'bzrlib.repofmt.groupcompress_repo',
 
3385
    'RepositoryFormatCHK2',
 
3386
    )
 
3387
format_registry.register_lazy(
 
3388
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3389
    'bzrlib.repofmt.groupcompress_repo',
 
3390
    'RepositoryFormat2a',
3333
3391
    )
3334
3392
 
3335
3393
 
3366
3424
        self.target.fetch(self.source, revision_id=revision_id)
3367
3425
 
3368
3426
    @needs_write_lock
3369
 
    def fetch(self, revision_id=None, find_ghosts=False,
 
3427
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3370
3428
            fetch_spec=None):
3371
3429
        """Fetch the content required to construct revision_id.
3372
3430
 
3374
3432
 
3375
3433
        :param revision_id: if None all content is copied, if NULL_REVISION no
3376
3434
                            content is copied.
 
3435
        :param pb: ignored.
3377
3436
        :return: None.
3378
3437
        """
3379
3438
        ui.ui_factory.warn_experimental_format_fetch(self)
3389
3448
                               fetch_spec=fetch_spec,
3390
3449
                               find_ghosts=find_ghosts)
3391
3450
 
3392
 
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
 
3451
    def _walk_to_common_revisions(self, revision_ids):
3393
3452
        """Walk out from revision_ids in source to revisions target has.
3394
3453
 
3395
3454
        :param revision_ids: The start point for the search.
3397
3456
        """
3398
3457
        target_graph = self.target.get_graph()
3399
3458
        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
3459
        missing_revs = set()
3405
3460
        source_graph = self.source.get_graph()
3406
3461
        # ensure we don't pay silly lookup costs.
3407
 
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
 
3462
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
3408
3463
        null_set = frozenset([_mod_revision.NULL_REVISION])
3409
3464
        searcher_exhausted = False
3410
3465
        while True:
3446
3501
        return searcher.get_result()
3447
3502
 
3448
3503
    @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):
 
3504
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3452
3505
        """Return the revision ids that source has that target does not.
3453
3506
 
3454
3507
        :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.
 
3508
                            revision_id.
3463
3509
        :param find_ghosts: If True find missing revisions in deep history
3464
3510
            rather than just finding the surface difference.
3465
3511
        :return: A bzrlib.graph.SearchResult.
3466
3512
        """
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
3513
        # 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)
 
3514
        if not find_ghosts and revision_id is not None:
 
3515
            return self._walk_to_common_revisions([revision_id])
3483
3516
        # generic, possibly worst case, slow code path.
3484
3517
        target_ids = set(self.target.all_revision_ids())
3485
 
        source_ids = self._present_source_revisions_for(
3486
 
            revision_ids, if_present_ids)
 
3518
        if revision_id is not None:
 
3519
            source_ids = self.source.get_ancestry(revision_id)
 
3520
            if source_ids[0] is not None:
 
3521
                raise AssertionError()
 
3522
            source_ids.pop(0)
 
3523
        else:
 
3524
            source_ids = self.source.all_revision_ids()
3487
3525
        result_set = set(source_ids).difference(target_ids)
3488
3526
        return self.source.revision_ids_to_search_result(result_set)
3489
3527
 
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
3528
    @staticmethod
3523
3529
    def _same_model(source, target):
3524
3530
        """True if source and target have the same data representation.
3565
3571
        return InterRepository._same_model(source, target)
3566
3572
 
3567
3573
 
 
3574
class InterWeaveRepo(InterSameDataRepository):
 
3575
    """Optimised code paths between Weave based repositories.
 
3576
 
 
3577
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
 
3578
    implemented lazy inter-object optimisation.
 
3579
    """
 
3580
 
 
3581
    @classmethod
 
3582
    def _get_repo_format_to_test(self):
 
3583
        from bzrlib.repofmt import weaverepo
 
3584
        return weaverepo.RepositoryFormat7()
 
3585
 
 
3586
    @staticmethod
 
3587
    def is_compatible(source, target):
 
3588
        """Be compatible with known Weave formats.
 
3589
 
 
3590
        We don't test for the stores being of specific types because that
 
3591
        could lead to confusing results, and there is no need to be
 
3592
        overly general.
 
3593
        """
 
3594
        from bzrlib.repofmt.weaverepo import (
 
3595
                RepositoryFormat5,
 
3596
                RepositoryFormat6,
 
3597
                RepositoryFormat7,
 
3598
                )
 
3599
        try:
 
3600
            return (isinstance(source._format, (RepositoryFormat5,
 
3601
                                                RepositoryFormat6,
 
3602
                                                RepositoryFormat7)) and
 
3603
                    isinstance(target._format, (RepositoryFormat5,
 
3604
                                                RepositoryFormat6,
 
3605
                                                RepositoryFormat7)))
 
3606
        except AttributeError:
 
3607
            return False
 
3608
 
 
3609
    @needs_write_lock
 
3610
    def copy_content(self, revision_id=None):
 
3611
        """See InterRepository.copy_content()."""
 
3612
        # weave specific optimised path:
 
3613
        try:
 
3614
            self.target.set_make_working_trees(self.source.make_working_trees())
 
3615
        except (errors.RepositoryUpgradeRequired, NotImplemented):
 
3616
            pass
 
3617
        # FIXME do not peek!
 
3618
        if self.source._transport.listable():
 
3619
            pb = ui.ui_factory.nested_progress_bar()
 
3620
            try:
 
3621
                self.target.texts.insert_record_stream(
 
3622
                    self.source.texts.get_record_stream(
 
3623
                        self.source.texts.keys(), 'topological', False))
 
3624
                pb.update('Copying inventory', 0, 1)
 
3625
                self.target.inventories.insert_record_stream(
 
3626
                    self.source.inventories.get_record_stream(
 
3627
                        self.source.inventories.keys(), 'topological', False))
 
3628
                self.target.signatures.insert_record_stream(
 
3629
                    self.source.signatures.get_record_stream(
 
3630
                        self.source.signatures.keys(),
 
3631
                        'unordered', True))
 
3632
                self.target.revisions.insert_record_stream(
 
3633
                    self.source.revisions.get_record_stream(
 
3634
                        self.source.revisions.keys(),
 
3635
                        'topological', True))
 
3636
            finally:
 
3637
                pb.finished()
 
3638
        else:
 
3639
            self.target.fetch(self.source, revision_id=revision_id)
 
3640
 
 
3641
    @needs_read_lock
 
3642
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3643
        """See InterRepository.missing_revision_ids()."""
 
3644
        # we want all revisions to satisfy revision_id in source.
 
3645
        # but we don't want to stat every file here and there.
 
3646
        # we want then, all revisions other needs to satisfy revision_id
 
3647
        # checked, but not those that we have locally.
 
3648
        # so the first thing is to get a subset of the revisions to
 
3649
        # satisfy revision_id in source, and then eliminate those that
 
3650
        # we do already have.
 
3651
        # this is slow on high latency connection to self, but as this
 
3652
        # disk format scales terribly for push anyway due to rewriting
 
3653
        # inventory.weave, this is considered acceptable.
 
3654
        # - RBC 20060209
 
3655
        if revision_id is not None:
 
3656
            source_ids = self.source.get_ancestry(revision_id)
 
3657
            if source_ids[0] is not None:
 
3658
                raise AssertionError()
 
3659
            source_ids.pop(0)
 
3660
        else:
 
3661
            source_ids = self.source._all_possible_ids()
 
3662
        source_ids_set = set(source_ids)
 
3663
        # source_ids is the worst possible case we may need to pull.
 
3664
        # now we want to filter source_ids against what we actually
 
3665
        # have in target, but don't try to check for existence where we know
 
3666
        # we do not have a revision as that would be pointless.
 
3667
        target_ids = set(self.target._all_possible_ids())
 
3668
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
3669
        actually_present_revisions = set(
 
3670
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
3671
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
3672
        if revision_id is not None:
 
3673
            # we used get_ancestry to determine source_ids then we are assured all
 
3674
            # revisions referenced are present as they are installed in topological order.
 
3675
            # and the tip revision was validated by get_ancestry.
 
3676
            result_set = required_revisions
 
3677
        else:
 
3678
            # if we just grabbed the possibly available ids, then
 
3679
            # we only have an estimate of whats available and need to validate
 
3680
            # that against the revision records.
 
3681
            result_set = set(
 
3682
                self.source._eliminate_revisions_not_present(required_revisions))
 
3683
        return self.source.revision_ids_to_search_result(result_set)
 
3684
 
 
3685
 
 
3686
class InterKnitRepo(InterSameDataRepository):
 
3687
    """Optimised code paths between Knit based repositories."""
 
3688
 
 
3689
    @classmethod
 
3690
    def _get_repo_format_to_test(self):
 
3691
        from bzrlib.repofmt import knitrepo
 
3692
        return knitrepo.RepositoryFormatKnit1()
 
3693
 
 
3694
    @staticmethod
 
3695
    def is_compatible(source, target):
 
3696
        """Be compatible with known Knit formats.
 
3697
 
 
3698
        We don't test for the stores being of specific types because that
 
3699
        could lead to confusing results, and there is no need to be
 
3700
        overly general.
 
3701
        """
 
3702
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
 
3703
        try:
 
3704
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
 
3705
                isinstance(target._format, RepositoryFormatKnit))
 
3706
        except AttributeError:
 
3707
            return False
 
3708
        return are_knits and InterRepository._same_model(source, target)
 
3709
 
 
3710
    @needs_read_lock
 
3711
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3712
        """See InterRepository.missing_revision_ids()."""
 
3713
        if revision_id is not None:
 
3714
            source_ids = self.source.get_ancestry(revision_id)
 
3715
            if source_ids[0] is not None:
 
3716
                raise AssertionError()
 
3717
            source_ids.pop(0)
 
3718
        else:
 
3719
            source_ids = self.source.all_revision_ids()
 
3720
        source_ids_set = set(source_ids)
 
3721
        # source_ids is the worst possible case we may need to pull.
 
3722
        # now we want to filter source_ids against what we actually
 
3723
        # have in target, but don't try to check for existence where we know
 
3724
        # we do not have a revision as that would be pointless.
 
3725
        target_ids = set(self.target.all_revision_ids())
 
3726
        possibly_present_revisions = target_ids.intersection(source_ids_set)
 
3727
        actually_present_revisions = set(
 
3728
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
 
3729
        required_revisions = source_ids_set.difference(actually_present_revisions)
 
3730
        if revision_id is not None:
 
3731
            # we used get_ancestry to determine source_ids then we are assured all
 
3732
            # revisions referenced are present as they are installed in topological order.
 
3733
            # and the tip revision was validated by get_ancestry.
 
3734
            result_set = required_revisions
 
3735
        else:
 
3736
            # if we just grabbed the possibly available ids, then
 
3737
            # we only have an estimate of whats available and need to validate
 
3738
            # that against the revision records.
 
3739
            result_set = set(
 
3740
                self.source._eliminate_revisions_not_present(required_revisions))
 
3741
        return self.source.revision_ids_to_search_result(result_set)
 
3742
 
 
3743
 
3568
3744
class InterDifferingSerializer(InterRepository):
3569
3745
 
3570
3746
    @classmethod
3672
3848
                basis_id, delta, current_revision_id, parents_parents)
3673
3849
            cache[current_revision_id] = parent_tree
3674
3850
 
3675
 
    def _fetch_batch(self, revision_ids, basis_id, cache):
 
3851
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
3676
3852
        """Fetch across a few revisions.
3677
3853
 
3678
3854
        :param revision_ids: The revisions to copy
3679
3855
        :param basis_id: The revision_id of a tree that must be in cache, used
3680
3856
            as a basis for delta when no other base is available
3681
3857
        :param cache: A cache of RevisionTrees that we can use.
 
3858
        :param a_graph: A Graph object to determine the heads() of the
 
3859
            rich-root data stream.
3682
3860
        :return: The revision_id of the last converted tree. The RevisionTree
3683
3861
            for it will be in cache
3684
3862
        """
3752
3930
        if root_keys_to_create:
3753
3931
            root_stream = _mod_fetch._new_root_data_stream(
3754
3932
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3755
 
                self.source)
 
3933
                self.source, graph=a_graph)
3756
3934
            to_texts.insert_record_stream(root_stream)
3757
3935
        to_texts.insert_record_stream(from_texts.get_record_stream(
3758
3936
            text_keys, self.target._format._fetch_order,
3815
3993
        cache[basis_id] = basis_tree
3816
3994
        del basis_tree # We don't want to hang on to it here
3817
3995
        hints = []
3818
 
        a_graph = None
 
3996
        if self._converting_to_rich_root and len(revision_ids) > 100:
 
3997
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
 
3998
                                                            revision_ids)
 
3999
        else:
 
4000
            a_graph = None
3819
4001
 
3820
4002
        for offset in range(0, len(revision_ids), batch_size):
3821
4003
            self.target.start_write_group()
3823
4005
                pb.update('Transferring revisions', offset,
3824
4006
                          len(revision_ids))
3825
4007
                batch = revision_ids[offset:offset+batch_size]
3826
 
                basis_id = self._fetch_batch(batch, basis_id, cache)
 
4008
                basis_id = self._fetch_batch(batch, basis_id, cache,
 
4009
                                             a_graph=a_graph)
3827
4010
            except:
3828
4011
                self.source._safe_to_return_from_cache = False
3829
4012
                self.target.abort_write_group()
3838
4021
                  len(revision_ids))
3839
4022
 
3840
4023
    @needs_write_lock
3841
 
    def fetch(self, revision_id=None, find_ghosts=False,
 
4024
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3842
4025
            fetch_spec=None):
3843
4026
        """See InterRepository.fetch()."""
3844
4027
        if fetch_spec is not None:
3845
 
            revision_ids = fetch_spec.get_keys()
3846
 
        else:
3847
 
            revision_ids = None
 
4028
            raise AssertionError("Not implemented yet...")
3848
4029
        ui.ui_factory.warn_experimental_format_fetch(self)
3849
4030
        if (not self.source.supports_rich_root()
3850
4031
            and self.target.supports_rich_root()):
3857
4038
            ui.ui_factory.show_user_warning('cross_format_fetch',
3858
4039
                from_format=self.source._format,
3859
4040
                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()
 
4041
        revision_ids = self.target.search_missing_revision_ids(self.source,
 
4042
            revision_id, find_ghosts=find_ghosts).get_keys()
3868
4043
        if not revision_ids:
3869
4044
            return 0, 0
3870
4045
        revision_ids = tsort.topo_sort(
3874
4049
        # Walk though all revisions; get inventory deltas, copy referenced
3875
4050
        # texts that delta references, insert the delta, revision and
3876
4051
        # signature.
3877
 
        pb = ui.ui_factory.nested_progress_bar()
 
4052
        if pb is None:
 
4053
            my_pb = ui.ui_factory.nested_progress_bar()
 
4054
            pb = my_pb
 
4055
        else:
 
4056
            symbol_versioning.warn(
 
4057
                symbol_versioning.deprecated_in((1, 14, 0))
 
4058
                % "pb parameter to fetch()")
 
4059
            my_pb = None
3878
4060
        try:
3879
4061
            self._fetch_all_revisions(revision_ids, pb)
3880
4062
        finally:
3881
 
            pb.finished()
 
4063
            if my_pb is not None:
 
4064
                my_pb.finished()
3882
4065
        return len(revision_ids), 0
3883
4066
 
3884
4067
    def _get_basis(self, first_revision_id):
3895
4078
            basis_id = first_rev.parent_ids[0]
3896
4079
            # only valid as a basis if the target has it
3897
4080
            self.target.get_revision(basis_id)
3898
 
            # Try to get a basis tree - if it's a ghost it will hit the
 
4081
            # Try to get a basis tree - if its a ghost it will hit the
3899
4082
            # NoSuchRevision case.
3900
4083
            basis_tree = self.source.revision_tree(basis_id)
3901
4084
        except (IndexError, errors.NoSuchRevision):
3906
4089
 
3907
4090
InterRepository.register_optimiser(InterDifferingSerializer)
3908
4091
InterRepository.register_optimiser(InterSameDataRepository)
 
4092
InterRepository.register_optimiser(InterWeaveRepo)
 
4093
InterRepository.register_optimiser(InterKnitRepo)
3909
4094
 
3910
4095
 
3911
4096
class CopyConverter(object):
3956
4141
        pb.finished()
3957
4142
 
3958
4143
 
 
4144
_unescape_map = {
 
4145
    'apos':"'",
 
4146
    'quot':'"',
 
4147
    'amp':'&',
 
4148
    'lt':'<',
 
4149
    'gt':'>'
 
4150
}
 
4151
 
 
4152
 
 
4153
def _unescaper(match, _map=_unescape_map):
 
4154
    code = match.group(1)
 
4155
    try:
 
4156
        return _map[code]
 
4157
    except KeyError:
 
4158
        if not code.startswith('#'):
 
4159
            raise
 
4160
        return unichr(int(code[1:])).encode('utf8')
 
4161
 
 
4162
 
 
4163
_unescape_re = None
 
4164
 
 
4165
 
 
4166
def _unescape_xml(data):
 
4167
    """Unescape predefined XML entities in a string of data."""
 
4168
    global _unescape_re
 
4169
    if _unescape_re is None:
 
4170
        _unescape_re = re.compile('\&([^;]*);')
 
4171
    return _unescape_re.sub(_unescaper, data)
 
4172
 
 
4173
 
3959
4174
class _VersionedFileChecker(object):
3960
4175
 
3961
4176
    def __init__(self, repository, text_key_references=None, ancestors=None):
4020
4235
        return wrong_parents, unused_keys
4021
4236
 
4022
4237
 
 
4238
def _old_get_graph(repository, revision_id):
 
4239
    """DO NOT USE. That is all. I'm serious."""
 
4240
    graph = repository.get_graph()
 
4241
    revision_graph = dict(((key, value) for key, value in
 
4242
        graph.iter_ancestry([revision_id]) if value is not None))
 
4243
    return _strip_NULL_ghosts(revision_graph)
 
4244
 
 
4245
 
4023
4246
def _strip_NULL_ghosts(revision_graph):
4024
4247
    """Also don't use this. more compatibility code for unmigrated clients."""
4025
4248
    # Filter ghosts, and null:
4061
4284
                is_resume = False
4062
4285
            try:
4063
4286
                # 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()
 
4287
                return self._locked_insert_stream(stream, src_format,
 
4288
                    is_resume)
4080
4289
            except:
4081
4290
                self.target_repo.abort_write_group(suppress_errors=True)
4082
4291
                raise
4083
4292
        finally:
4084
4293
            self.target_repo.unlock()
4085
4294
 
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')
 
4295
    def _locked_insert_stream(self, stream, src_format, is_resume):
4104
4296
        to_serializer = self.target_repo._format._serializer
4105
4297
        src_serializer = src_format._serializer
4106
4298
        new_pack = None
4185
4377
            # cannot even attempt suspending, and missing would have failed
4186
4378
            # during stream insertion.
4187
4379
            missing_keys = set()
4188
 
        return missing_keys
 
4380
        else:
 
4381
            if missing_keys:
 
4382
                # suspend the write group and tell the caller what we is
 
4383
                # missing. We know we can suspend or else we would not have
 
4384
                # entered this code path. (All repositories that can handle
 
4385
                # missing keys can handle suspending a write group).
 
4386
                write_group_tokens = self.target_repo.suspend_write_group()
 
4387
                return write_group_tokens, missing_keys
 
4388
        hint = self.target_repo.commit_write_group()
 
4389
        if (to_serializer != src_serializer and
 
4390
            self.target_repo._format.pack_compresses):
 
4391
            self.target_repo.pack(hint=hint)
 
4392
        return [], set()
4189
4393
 
4190
4394
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4191
4395
        target_rich_root = self.target_repo._format.rich_root_data
4198
4402
                parse_result = deserialiser.parse_text_bytes(
4199
4403
                    inventory_delta_bytes)
4200
4404
            except inventory_delta.IncompatibleInventoryDelta, err:
4201
 
                mutter("Incompatible delta: %s", err.msg)
 
4405
                trace.mutter("Incompatible delta: %s", err.msg)
4202
4406
                raise errors.IncompatibleRevision(self.target_repo._format)
4203
4407
            basis_id, new_id, rich_root, tree_refs, inv_delta = parse_result
4204
4408
            revision_id = new_id
4539
4743
    except StopIteration:
4540
4744
        # No more history
4541
4745
        return
 
4746