~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: John Arbash Meinel
  • Date: 2011-04-07 10:36:24 UTC
  • mfrom: (5764 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5766.
  • Revision ID: john@arbash-meinel.com-20110407103624-n76g6tjeqmznwdcd
Merge bzr.dev 5764 to resolve release-notes (aka NEWS) conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
from bzrlib.lazy_import import lazy_import
18
18
lazy_import(globals(), """
19
 
import cStringIO
20
 
import re
21
19
import time
22
20
 
23
21
from bzrlib import (
24
22
    bzrdir,
25
23
    check,
26
 
    chk_map,
27
24
    config,
 
25
    controldir,
28
26
    debug,
29
 
    errors,
30
27
    fetch as _mod_fetch,
31
28
    fifo_cache,
32
29
    generate_ids,
33
30
    gpg,
34
31
    graph,
35
 
    inventory,
36
32
    inventory_delta,
37
 
    lazy_regex,
38
33
    lockable_files,
39
34
    lockdir,
40
35
    lru_cache,
41
36
    osutils,
42
37
    revision as _mod_revision,
43
38
    static_tuple,
44
 
    symbol_versioning,
45
 
    trace,
46
39
    tsort,
47
 
    ui,
48
40
    versionedfile,
49
41
    )
50
42
from bzrlib.bundle import serializer
 
43
from bzrlib.recordcounter import RecordCounter
51
44
from bzrlib.revisiontree import RevisionTree
52
45
from bzrlib.store.versioned import VersionedFileStore
53
46
from bzrlib.testament import Testament
54
47
""")
55
48
 
 
49
from bzrlib import (
 
50
    errors,
 
51
    registry,
 
52
    symbol_versioning,
 
53
    ui,
 
54
    )
56
55
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
57
56
from bzrlib.inter import InterObject
58
57
from bzrlib.inventory import (
61
60
    ROOT_ID,
62
61
    entry_factory,
63
62
    )
64
 
from bzrlib.lock import _RelockDebugMixin
65
 
from bzrlib import registry
 
63
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
66
64
from bzrlib.trace import (
67
65
    log_exception_quietly, note, mutter, mutter_callsite, warning)
68
66
 
71
69
_deprecation_warning_done = False
72
70
 
73
71
 
 
72
class IsInWriteGroupError(errors.InternalBzrError):
 
73
 
 
74
    _fmt = "May not refresh_data of repo %(repo)s while in a write group."
 
75
 
 
76
    def __init__(self, repo):
 
77
        errors.InternalBzrError.__init__(self, repo=repo)
 
78
 
 
79
 
74
80
class CommitBuilder(object):
75
81
    """Provides an interface to build up a commit.
76
82
 
82
88
    record_root_entry = True
83
89
    # the default CommitBuilder does not manage trees whose root is versioned.
84
90
    _versioned_root = False
 
91
    # this commit builder supports the record_entry_contents interface
 
92
    supports_record_entry_contents = True
85
93
 
86
94
    def __init__(self, repository, parents, config, timestamp=None,
87
95
                 timezone=None, committer=None, revprops=None,
90
98
 
91
99
        :param repository: Repository to commit to.
92
100
        :param parents: Revision ids of the parents of the new revision.
93
 
        :param config: Configuration to use.
94
101
        :param timestamp: Optional timestamp recorded for commit.
95
102
        :param timezone: Optional timezone for timestamp.
96
103
        :param committer: Optional committer to set for commit.
101
108
 
102
109
        if committer is None:
103
110
            self._committer = self._config.username()
 
111
        elif not isinstance(committer, unicode):
 
112
            self._committer = committer.decode() # throw if non-ascii
104
113
        else:
105
114
            self._committer = committer
106
115
 
160
169
            self._validate_unicode_text(value,
161
170
                                        'revision property (%s)' % (key,))
162
171
 
 
172
    def _ensure_fallback_inventories(self):
 
173
        """Ensure that appropriate inventories are available.
 
174
 
 
175
        This only applies to repositories that are stacked, and is about
 
176
        enusring the stacking invariants. Namely, that for any revision that is
 
177
        present, we either have all of the file content, or we have the parent
 
178
        inventory and the delta file content.
 
179
        """
 
180
        if not self.repository._fallback_repositories:
 
181
            return
 
182
        if not self.repository._format.supports_chks:
 
183
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
184
                " in pre-2a formats. See "
 
185
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
 
186
        # This is a stacked repo, we need to make sure we have the parent
 
187
        # inventories for the parents.
 
188
        parent_keys = [(p,) for p in self.parents]
 
189
        parent_map = self.repository.inventories._index.get_parent_map(parent_keys)
 
190
        missing_parent_keys = set([pk for pk in parent_keys
 
191
                                       if pk not in parent_map])
 
192
        fallback_repos = list(reversed(self.repository._fallback_repositories))
 
193
        missing_keys = [('inventories', pk[0])
 
194
                        for pk in missing_parent_keys]
 
195
        resume_tokens = []
 
196
        while missing_keys and fallback_repos:
 
197
            fallback_repo = fallback_repos.pop()
 
198
            source = fallback_repo._get_source(self.repository._format)
 
199
            sink = self.repository._get_sink()
 
200
            stream = source.get_stream_for_missing_keys(missing_keys)
 
201
            missing_keys = sink.insert_stream_without_locking(stream,
 
202
                self.repository._format)
 
203
        if missing_keys:
 
204
            raise errors.BzrError('Unable to fill in parent inventories for a'
 
205
                                  ' stacked branch')
 
206
 
163
207
    def commit(self, message):
164
208
        """Make the actual commit.
165
209
 
177
221
        rev.parent_ids = self.parents
178
222
        self.repository.add_revision(self._new_revision_id, rev,
179
223
            self.new_inventory, self._config)
 
224
        self._ensure_fallback_inventories()
180
225
        self.repository.commit_write_group()
181
226
        return self._new_revision_id
182
227
 
231
276
 
232
277
    def _gen_revision_id(self):
233
278
        """Return new revision-id."""
234
 
        return generate_ids.gen_revision_id(self._config.username(),
235
 
                                            self._timestamp)
 
279
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
236
280
 
237
281
    def _generate_revision_if_needed(self):
238
282
        """Create a revision id if None was supplied.
278
322
 
279
323
        :param tree: The tree which is being committed.
280
324
        """
281
 
        # NB: if there are no parents then this method is not called, so no
282
 
        # need to guard on parents having length.
 
325
        if len(self.parents) == 0:
 
326
            raise errors.RootMissing()
283
327
        entry = entry_factory['directory'](tree.path2id(''), '',
284
328
            None)
285
329
        entry.revision = self._new_revision_id
423
467
            else:
424
468
                # we don't need to commit this, because the caller already
425
469
                # determined that an existing revision of this file is
426
 
                # appropriate. If its not being considered for committing then
 
470
                # appropriate. If it's not being considered for committing then
427
471
                # it and all its parents to the root must be unaltered so
428
472
                # no-change against the basis.
429
473
                if ie.revision == self._new_revision_id:
745
789
                    # after iter_changes examines and decides it has changed,
746
790
                    # we will unconditionally record a new version even if some
747
791
                    # other process reverts it while commit is running (with
748
 
                    # the revert happening after iter_changes did it's
 
792
                    # the revert happening after iter_changes did its
749
793
                    # examination).
750
794
                    if change[7][1]:
751
795
                        entry.executable = True
860
904
        # versioned roots do not change unless the tree found a change.
861
905
 
862
906
 
863
 
class RepositoryWriteLockResult(object):
 
907
class RepositoryWriteLockResult(LogicalLockResult):
864
908
    """The result of write locking a repository.
865
909
 
866
910
    :ivar repository_token: The token obtained from the underlying lock, or
869
913
    """
870
914
 
871
915
    def __init__(self, unlock, repository_token):
 
916
        LogicalLockResult.__init__(self, unlock)
872
917
        self.repository_token = repository_token
873
 
        self.unlock = unlock
874
918
 
875
 
    def __str__(self):
 
919
    def __repr__(self):
876
920
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
877
921
            self.unlock)
878
922
 
881
925
# Repositories
882
926
 
883
927
 
884
 
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
 
928
class Repository(_RelockDebugMixin, controldir.ControlComponent):
885
929
    """Repository holding history for one or more branches.
886
930
 
887
931
    The repository holds and retrieves historical information including
934
978
        pointing to .bzr/repository.
935
979
    """
936
980
 
937
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
981
    # What class to use for a CommitBuilder. Often it's simpler to change this
938
982
    # in a Repository class subclass rather than to override
939
983
    # get_commit_builder.
940
984
    _commit_builder_class = CommitBuilder
941
 
    # The search regex used by xml based repositories to determine what things
942
 
    # where changed in a single commit.
943
 
    _file_ids_altered_regex = lazy_regex.lazy_compile(
944
 
        r'file_id="(?P<file_id>[^"]+)"'
945
 
        r'.* revision="(?P<revision_id>[^"]+)"'
946
 
        )
947
985
 
948
986
    def abort_write_group(self, suppress_errors=False):
949
987
        """Commit the contents accrued within the current write group.
1035
1073
                " id and insertion revid (%r, %r)"
1036
1074
                % (inv.revision_id, revision_id))
1037
1075
        if inv.root is None:
1038
 
            raise AssertionError()
 
1076
            raise errors.RootMissing()
1039
1077
        return self._add_inventory_checked(revision_id, inv, parents)
1040
1078
 
1041
1079
    def _add_inventory_checked(self, revision_id, inv, parents):
1434
1472
            for repo in self._fallback_repositories:
1435
1473
                repo.lock_read()
1436
1474
            self._refresh_data()
1437
 
        return self
 
1475
        return LogicalLockResult(self.unlock)
1438
1476
 
1439
1477
    def get_physical_lock_status(self):
1440
1478
        return self.control_files.get_physical_lock_status()
1546
1584
        return ret
1547
1585
 
1548
1586
    @needs_read_lock
1549
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
1587
    def search_missing_revision_ids(self, other,
 
1588
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
1589
            find_ghosts=True, revision_ids=None, if_present_ids=None):
1550
1590
        """Return the revision ids that other has that this does not.
1551
1591
 
1552
1592
        These are returned in topological order.
1553
1593
 
1554
1594
        revision_id: only return revision ids included by revision_id.
1555
1595
        """
 
1596
        if symbol_versioning.deprecated_passed(revision_id):
 
1597
            symbol_versioning.warn(
 
1598
                'search_missing_revision_ids(revision_id=...) was '
 
1599
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
1600
                DeprecationWarning, stacklevel=3)
 
1601
            if revision_ids is not None:
 
1602
                raise AssertionError(
 
1603
                    'revision_ids is mutually exclusive with revision_id')
 
1604
            if revision_id is not None:
 
1605
                revision_ids = [revision_id]
1556
1606
        return InterRepository.get(other, self).search_missing_revision_ids(
1557
 
            revision_id, find_ghosts)
 
1607
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1608
            if_present_ids=if_present_ids)
1558
1609
 
1559
1610
    @staticmethod
1560
1611
    def open(base):
1658
1709
        return missing_keys
1659
1710
 
1660
1711
    def refresh_data(self):
1661
 
        """Re-read any data needed to to synchronise with disk.
 
1712
        """Re-read any data needed to synchronise with disk.
1662
1713
 
1663
1714
        This method is intended to be called after another repository instance
1664
1715
        (such as one used by a smart server) has inserted data into the
1665
 
        repository. It may not be called during a write group, but may be
1666
 
        called at any other time.
 
1716
        repository. On all repositories this will work outside of write groups.
 
1717
        Some repository formats (pack and newer for bzrlib native formats)
 
1718
        support refresh_data inside write groups. If called inside a write
 
1719
        group on a repository that does not support refreshing in a write group
 
1720
        IsInWriteGroupError will be raised.
1667
1721
        """
1668
 
        if self.is_in_write_group():
1669
 
            raise errors.InternalBzrError(
1670
 
                "May not refresh_data while in a write group.")
1671
1722
        self._refresh_data()
1672
1723
 
1673
1724
    def resume_write_group(self, tokens):
1682
1733
    def _resume_write_group(self, tokens):
1683
1734
        raise errors.UnsuspendableWriteGroup(self)
1684
1735
 
1685
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1736
    def fetch(self, source, revision_id=None, find_ghosts=False,
1686
1737
            fetch_spec=None):
1687
1738
        """Fetch the content required to construct revision_id from source.
1688
1739
 
1712
1763
                "May not fetch while in a write group.")
1713
1764
        # fast path same-url fetch operations
1714
1765
        # TODO: lift out to somewhere common with RemoteRepository
1715
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1766
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1716
1767
        if (self.has_same_location(source)
1717
1768
            and fetch_spec is None
1718
1769
            and self._has_same_fallbacks(source)):
1722
1773
                not _mod_revision.is_null(revision_id)):
1723
1774
                self.get_revision(revision_id)
1724
1775
            return 0, []
1725
 
        # if there is no specific appropriate InterRepository, this will get
1726
 
        # the InterRepository base class, which raises an
1727
 
        # IncompatibleRepositories when asked to fetch.
1728
1776
        inter = InterRepository.get(source, self)
1729
 
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1777
        return inter.fetch(revision_id=revision_id,
1730
1778
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1731
1779
 
1732
1780
    def create_bundle(self, target, base, fileobj, format=None):
1746
1794
        :param revprops: Optional dictionary of revision properties.
1747
1795
        :param revision_id: Optional revision id.
1748
1796
        """
1749
 
        if self._fallback_repositories:
1750
 
            raise errors.BzrError("Cannot commit from a lightweight checkout "
1751
 
                "to a stacked branch. See "
 
1797
        if self._fallback_repositories and not self._format.supports_chks:
 
1798
            raise errors.BzrError("Cannot commit directly to a stacked branch"
 
1799
                " in pre-2a formats. See "
1752
1800
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1753
1801
        result = self._commit_builder_class(self, parents, config,
1754
1802
            timestamp, timezone, committer, revprops, revision_id)
2003
2051
        w = self.inventories
2004
2052
        pb = ui.ui_factory.nested_progress_bar()
2005
2053
        try:
2006
 
            return self._find_text_key_references_from_xml_inventory_lines(
 
2054
            return self._serializer._find_text_key_references(
2007
2055
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
2008
2056
        finally:
2009
2057
            pb.finished()
2010
2058
 
2011
 
    def _find_text_key_references_from_xml_inventory_lines(self,
2012
 
        line_iterator):
2013
 
        """Core routine for extracting references to texts from inventories.
2014
 
 
2015
 
        This performs the translation of xml lines to revision ids.
2016
 
 
2017
 
        :param line_iterator: An iterator of lines, origin_version_id
2018
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
2019
 
            to whether they were referred to by the inventory of the
2020
 
            revision_id that they contain. Note that if that revision_id was
2021
 
            not part of the line_iterator's output then False will be given -
2022
 
            even though it may actually refer to that key.
2023
 
        """
2024
 
        if not self._serializer.support_altered_by_hack:
2025
 
            raise AssertionError(
2026
 
                "_find_text_key_references_from_xml_inventory_lines only "
2027
 
                "supported for branches which store inventory as unnested xml"
2028
 
                ", not on %r" % self)
2029
 
        result = {}
2030
 
 
2031
 
        # this code needs to read every new line in every inventory for the
2032
 
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
2033
 
        # not present in one of those inventories is unnecessary but not
2034
 
        # harmful because we are filtering by the revision id marker in the
2035
 
        # inventory lines : we only select file ids altered in one of those
2036
 
        # revisions. We don't need to see all lines in the inventory because
2037
 
        # only those added in an inventory in rev X can contain a revision=X
2038
 
        # line.
2039
 
        unescape_revid_cache = {}
2040
 
        unescape_fileid_cache = {}
2041
 
 
2042
 
        # jam 20061218 In a big fetch, this handles hundreds of thousands
2043
 
        # of lines, so it has had a lot of inlining and optimizing done.
2044
 
        # Sorry that it is a little bit messy.
2045
 
        # Move several functions to be local variables, since this is a long
2046
 
        # running loop.
2047
 
        search = self._file_ids_altered_regex.search
2048
 
        unescape = _unescape_xml
2049
 
        setdefault = result.setdefault
2050
 
        for line, line_key in line_iterator:
2051
 
            match = search(line)
2052
 
            if match is None:
2053
 
                continue
2054
 
            # One call to match.group() returning multiple items is quite a
2055
 
            # bit faster than 2 calls to match.group() each returning 1
2056
 
            file_id, revision_id = match.group('file_id', 'revision_id')
2057
 
 
2058
 
            # Inlining the cache lookups helps a lot when you make 170,000
2059
 
            # lines and 350k ids, versus 8.4 unique ids.
2060
 
            # Using a cache helps in 2 ways:
2061
 
            #   1) Avoids unnecessary decoding calls
2062
 
            #   2) Re-uses cached strings, which helps in future set and
2063
 
            #      equality checks.
2064
 
            # (2) is enough that removing encoding entirely along with
2065
 
            # the cache (so we are using plain strings) results in no
2066
 
            # performance improvement.
2067
 
            try:
2068
 
                revision_id = unescape_revid_cache[revision_id]
2069
 
            except KeyError:
2070
 
                unescaped = unescape(revision_id)
2071
 
                unescape_revid_cache[revision_id] = unescaped
2072
 
                revision_id = unescaped
2073
 
 
2074
 
            # Note that unconditionally unescaping means that we deserialise
2075
 
            # every fileid, which for general 'pull' is not great, but we don't
2076
 
            # really want to have some many fulltexts that this matters anyway.
2077
 
            # RBC 20071114.
2078
 
            try:
2079
 
                file_id = unescape_fileid_cache[file_id]
2080
 
            except KeyError:
2081
 
                unescaped = unescape(file_id)
2082
 
                unescape_fileid_cache[file_id] = unescaped
2083
 
                file_id = unescaped
2084
 
 
2085
 
            key = (file_id, revision_id)
2086
 
            setdefault(key, False)
2087
 
            if revision_id == line_key[-1]:
2088
 
                result[key] = True
2089
 
        return result
2090
 
 
2091
2059
    def _inventory_xml_lines_for_keys(self, keys):
2092
2060
        """Get a line iterator of the sort needed for findind references.
2093
2061
 
2123
2091
        revision_ids. Each altered file-ids has the exact revision_ids that
2124
2092
        altered it listed explicitly.
2125
2093
        """
2126
 
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2094
        seen = set(self._serializer._find_text_key_references(
2127
2095
                line_iterator).iterkeys())
2128
2096
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
2129
 
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2097
        parent_seen = set(self._serializer._find_text_key_references(
2130
2098
            self._inventory_xml_lines_for_keys(parent_keys)))
2131
2099
        new_keys = seen - parent_seen
2132
2100
        result = {}
2500
2468
            ancestors will be traversed.
2501
2469
        """
2502
2470
        graph = self.get_graph()
2503
 
        next_id = revision_id
2504
 
        while True:
2505
 
            if next_id in (None, _mod_revision.NULL_REVISION):
2506
 
                return
2507
 
            try:
2508
 
                parents = graph.get_parent_map([next_id])[next_id]
2509
 
            except KeyError:
2510
 
                raise errors.RevisionNotPresent(next_id, self)
2511
 
            yield next_id
2512
 
            if len(parents) == 0:
2513
 
                return
2514
 
            else:
2515
 
                next_id = parents[0]
 
2471
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2472
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2516
2473
 
2517
2474
    def is_shared(self):
2518
2475
        """Return True if this repository is flagged as a shared repository."""
2619
2576
        types it should be a no-op that just returns.
2620
2577
 
2621
2578
        This stub method does not require a lock, but subclasses should use
2622
 
        @needs_write_lock as this is a long running call its reasonable to
 
2579
        @needs_write_lock as this is a long running call it's reasonable to
2623
2580
        implicitly lock for the user.
2624
2581
 
2625
2582
        :param hint: If not supplied, the whole repository is packed.
2771
2728
        return result
2772
2729
 
2773
2730
    def _warn_if_deprecated(self, branch=None):
 
2731
        if not self._format.is_deprecated():
 
2732
            return
2774
2733
        global _deprecation_warning_done
2775
2734
        if _deprecation_warning_done:
2776
2735
            return
2818
2777
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
2819
2778
 
2820
2779
 
2821
 
# remove these delegates a while after bzr 0.15
2822
 
def __make_delegated(name, from_module):
2823
 
    def _deprecated_repository_forwarder():
2824
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
2825
 
            % (name, from_module),
2826
 
            DeprecationWarning,
2827
 
            stacklevel=2)
2828
 
        m = __import__(from_module, globals(), locals(), [name])
2829
 
        try:
2830
 
            return getattr(m, name)
2831
 
        except AttributeError:
2832
 
            raise AttributeError('module %s has no name %s'
2833
 
                    % (m, name))
2834
 
    globals()[name] = _deprecated_repository_forwarder
2835
 
 
2836
 
for _name in [
2837
 
        'AllInOneRepository',
2838
 
        'WeaveMetaDirRepository',
2839
 
        'PreSplitOutRepositoryFormat',
2840
 
        'RepositoryFormat4',
2841
 
        'RepositoryFormat5',
2842
 
        'RepositoryFormat6',
2843
 
        'RepositoryFormat7',
2844
 
        ]:
2845
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
2846
 
 
2847
 
for _name in [
2848
 
        'KnitRepository',
2849
 
        'RepositoryFormatKnit',
2850
 
        'RepositoryFormatKnit1',
2851
 
        ]:
2852
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
2853
 
 
2854
 
 
2855
2780
def install_revision(repository, rev, revision_tree):
2856
2781
    """Install all revision data into a repository."""
2857
2782
    install_revisions(repository, [(rev, revision_tree, None)])
2989
2914
            control_files)
2990
2915
 
2991
2916
 
 
2917
class RepositoryFormatRegistry(controldir.ControlComponentFormatRegistry):
 
2918
    """Repository format registry."""
 
2919
 
 
2920
    def get_default(self):
 
2921
        """Return the current default format."""
 
2922
        from bzrlib import bzrdir
 
2923
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
2924
 
 
2925
 
2992
2926
network_format_registry = registry.FormatRegistry()
2993
2927
"""Registry of formats indexed by their network name.
2994
2928
 
2998
2932
"""
2999
2933
 
3000
2934
 
3001
 
format_registry = registry.FormatRegistry(network_format_registry)
 
2935
format_registry = RepositoryFormatRegistry(network_format_registry)
3002
2936
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
3003
2937
 
3004
2938
This can contain either format instances themselves, or classes/factories that
3009
2943
#####################################################################
3010
2944
# Repository Formats
3011
2945
 
3012
 
class RepositoryFormat(object):
 
2946
class RepositoryFormat(controldir.ControlComponentFormat):
3013
2947
    """A repository format.
3014
2948
 
3015
2949
    Formats provide four things:
3076
3010
    supports_tree_reference = None
3077
3011
    # Is the format experimental ?
3078
3012
    experimental = False
 
3013
    # Does this repository format escape funky characters, or does it create files with
 
3014
    # similar names as the versioned files in its contents on disk ?
 
3015
    supports_funky_characters = None
 
3016
    # Does this repository format support leaving locks?
 
3017
    supports_leaving_lock = None
 
3018
    # Does this format support the full VersionedFiles interface?
 
3019
    supports_full_versioned_files = None
3079
3020
 
3080
3021
    def __repr__(self):
3081
3022
        return "%s()" % self.__class__.__name__
3106
3047
                                            kind='repository')
3107
3048
 
3108
3049
    @classmethod
 
3050
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3109
3051
    def register_format(klass, format):
3110
 
        format_registry.register(format.get_format_string(), format)
 
3052
        format_registry.register(format)
3111
3053
 
3112
3054
    @classmethod
 
3055
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3113
3056
    def unregister_format(klass, format):
3114
 
        format_registry.remove(format.get_format_string())
 
3057
        format_registry.remove(format)
3115
3058
 
3116
3059
    @classmethod
 
3060
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3117
3061
    def get_default_format(klass):
3118
3062
        """Return the current default format."""
3119
 
        from bzrlib import bzrdir
3120
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
3063
        return format_registry.get_default()
3121
3064
 
3122
3065
    def get_format_string(self):
3123
3066
        """Return the ASCII format string that identifies this format.
3174
3117
        """
3175
3118
        return True
3176
3119
 
 
3120
    def is_deprecated(self):
 
3121
        """Is this format deprecated?
 
3122
 
 
3123
        Deprecated formats may trigger a user-visible warning recommending
 
3124
        the user to upgrade. They are still fully supported.
 
3125
        """
 
3126
        return False
 
3127
 
3177
3128
    def network_name(self):
3178
3129
        """A simple byte string uniquely identifying this format for RPC calls.
3179
3130
 
3218
3169
    rich_root_data = False
3219
3170
    supports_tree_reference = False
3220
3171
    supports_external_lookups = False
 
3172
    supports_leaving_lock = True
3221
3173
 
3222
3174
    @property
3223
3175
    def _matchingbzrdir(self):
3261
3213
        return self.get_format_string()
3262
3214
 
3263
3215
 
3264
 
# Pre-0.8 formats that don't have a disk format string (because they are
3265
 
# versioned by the matching control directory). We use the control directories
3266
 
# disk format string as a key for the network_name because they meet the
3267
 
# constraints (simple string, unique, immutable).
3268
 
network_format_registry.register_lazy(
3269
 
    "Bazaar-NG branch, format 5\n",
3270
 
    'bzrlib.repofmt.weaverepo',
3271
 
    'RepositoryFormat5',
3272
 
)
3273
 
network_format_registry.register_lazy(
3274
 
    "Bazaar-NG branch, format 6\n",
3275
 
    'bzrlib.repofmt.weaverepo',
3276
 
    'RepositoryFormat6',
3277
 
)
3278
 
 
3279
3216
# formats which have no format string are not discoverable or independently
3280
3217
# creatable on disk, so are not registered in format_registry.  They're
3281
 
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
 
3218
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
3282
3219
# needed, it's constructed directly by the BzrDir.  Non-native formats where
3283
3220
# the repository is not separately opened are similar.
3284
3221
 
3285
3222
format_registry.register_lazy(
3286
 
    'Bazaar-NG Repository format 7',
3287
 
    'bzrlib.repofmt.weaverepo',
3288
 
    'RepositoryFormat7'
3289
 
    )
3290
 
 
3291
 
format_registry.register_lazy(
3292
3223
    'Bazaar-NG Knit Repository Format 1',
3293
3224
    'bzrlib.repofmt.knitrepo',
3294
3225
    'RepositoryFormatKnit1',
3349
3280
    'bzrlib.repofmt.pack_repo',
3350
3281
    'RepositoryFormatKnitPack6RichRoot',
3351
3282
    )
 
3283
format_registry.register_lazy(
 
3284
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
 
3285
    'bzrlib.repofmt.groupcompress_repo',
 
3286
    'RepositoryFormat2a',
 
3287
    )
3352
3288
 
3353
3289
# Development formats.
3354
 
# Obsolete but kept pending a CHK based subtree format.
 
3290
# Check their docstrings to see if/when they are obsolete.
3355
3291
format_registry.register_lazy(
3356
3292
    ("Bazaar development format 2 with subtree support "
3357
3293
        "(needs bzr.dev from before 1.8)\n"),
3358
3294
    'bzrlib.repofmt.pack_repo',
3359
3295
    'RepositoryFormatPackDevelopment2Subtree',
3360
3296
    )
3361
 
 
3362
 
# 1.14->1.16 go below here
3363
 
format_registry.register_lazy(
3364
 
    'Bazaar development format - group compression and chk inventory'
3365
 
        ' (needs bzr.dev from 1.14)\n',
3366
 
    'bzrlib.repofmt.groupcompress_repo',
3367
 
    'RepositoryFormatCHK1',
3368
 
    )
3369
 
 
3370
 
format_registry.register_lazy(
3371
 
    'Bazaar development format - chk repository with bencode revision '
3372
 
        'serialization (needs bzr.dev from 1.16)\n',
3373
 
    'bzrlib.repofmt.groupcompress_repo',
3374
 
    'RepositoryFormatCHK2',
3375
 
    )
3376
 
format_registry.register_lazy(
3377
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3378
 
    'bzrlib.repofmt.groupcompress_repo',
3379
 
    'RepositoryFormat2a',
 
3297
format_registry.register_lazy(
 
3298
    'Bazaar development format 8\n',
 
3299
    'bzrlib.repofmt.groupcompress_repo',
 
3300
    'RepositoryFormat2aSubtree',
3380
3301
    )
3381
3302
 
3382
3303
 
3413
3334
        self.target.fetch(self.source, revision_id=revision_id)
3414
3335
 
3415
3336
    @needs_write_lock
3416
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3337
    def fetch(self, revision_id=None, find_ghosts=False,
3417
3338
            fetch_spec=None):
3418
3339
        """Fetch the content required to construct revision_id.
3419
3340
 
3421
3342
 
3422
3343
        :param revision_id: if None all content is copied, if NULL_REVISION no
3423
3344
                            content is copied.
3424
 
        :param pb: ignored.
3425
3345
        :return: None.
3426
3346
        """
3427
3347
        ui.ui_factory.warn_experimental_format_fetch(self)
3437
3357
                               fetch_spec=fetch_spec,
3438
3358
                               find_ghosts=find_ghosts)
3439
3359
 
3440
 
    def _walk_to_common_revisions(self, revision_ids):
 
3360
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
3441
3361
        """Walk out from revision_ids in source to revisions target has.
3442
3362
 
3443
3363
        :param revision_ids: The start point for the search.
3445
3365
        """
3446
3366
        target_graph = self.target.get_graph()
3447
3367
        revision_ids = frozenset(revision_ids)
 
3368
        if if_present_ids:
 
3369
            all_wanted_revs = revision_ids.union(if_present_ids)
 
3370
        else:
 
3371
            all_wanted_revs = revision_ids
3448
3372
        missing_revs = set()
3449
3373
        source_graph = self.source.get_graph()
3450
3374
        # ensure we don't pay silly lookup costs.
3451
 
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
 
3375
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
3452
3376
        null_set = frozenset([_mod_revision.NULL_REVISION])
3453
3377
        searcher_exhausted = False
3454
3378
        while True:
3490
3414
        return searcher.get_result()
3491
3415
 
3492
3416
    @needs_read_lock
3493
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
3417
    def search_missing_revision_ids(self,
 
3418
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
 
3419
            find_ghosts=True, revision_ids=None, if_present_ids=None):
3494
3420
        """Return the revision ids that source has that target does not.
3495
3421
 
3496
3422
        :param revision_id: only return revision ids included by this
3497
 
                            revision_id.
 
3423
            revision_id.
 
3424
        :param revision_ids: return revision ids included by these
 
3425
            revision_ids.  NoSuchRevision will be raised if any of these
 
3426
            revisions are not present.
 
3427
        :param if_present_ids: like revision_ids, but will not cause
 
3428
            NoSuchRevision if any of these are absent, instead they will simply
 
3429
            not be in the result.  This is useful for e.g. finding revisions
 
3430
            to fetch for tags, which may reference absent revisions.
3498
3431
        :param find_ghosts: If True find missing revisions in deep history
3499
3432
            rather than just finding the surface difference.
3500
3433
        :return: A bzrlib.graph.SearchResult.
3501
3434
        """
 
3435
        if symbol_versioning.deprecated_passed(revision_id):
 
3436
            symbol_versioning.warn(
 
3437
                'search_missing_revision_ids(revision_id=...) was '
 
3438
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
 
3439
                DeprecationWarning, stacklevel=2)
 
3440
            if revision_ids is not None:
 
3441
                raise AssertionError(
 
3442
                    'revision_ids is mutually exclusive with revision_id')
 
3443
            if revision_id is not None:
 
3444
                revision_ids = [revision_id]
 
3445
        del revision_id
3502
3446
        # stop searching at found target revisions.
3503
 
        if not find_ghosts and revision_id is not None:
3504
 
            return self._walk_to_common_revisions([revision_id])
 
3447
        if not find_ghosts and (revision_ids is not None or if_present_ids is
 
3448
                not None):
 
3449
            return self._walk_to_common_revisions(revision_ids,
 
3450
                    if_present_ids=if_present_ids)
3505
3451
        # generic, possibly worst case, slow code path.
3506
3452
        target_ids = set(self.target.all_revision_ids())
3507
 
        if revision_id is not None:
3508
 
            source_ids = self.source.get_ancestry(revision_id)
3509
 
            if source_ids[0] is not None:
3510
 
                raise AssertionError()
3511
 
            source_ids.pop(0)
3512
 
        else:
3513
 
            source_ids = self.source.all_revision_ids()
 
3453
        source_ids = self._present_source_revisions_for(
 
3454
            revision_ids, if_present_ids)
3514
3455
        result_set = set(source_ids).difference(target_ids)
3515
3456
        return self.source.revision_ids_to_search_result(result_set)
3516
3457
 
 
3458
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
 
3459
        """Returns set of all revisions in ancestry of revision_ids present in
 
3460
        the source repo.
 
3461
 
 
3462
        :param revision_ids: if None, all revisions in source are returned.
 
3463
        :param if_present_ids: like revision_ids, but if any/all of these are
 
3464
            absent no error is raised.
 
3465
        """
 
3466
        if revision_ids is not None or if_present_ids is not None:
 
3467
            # First, ensure all specified revisions exist.  Callers expect
 
3468
            # NoSuchRevision when they pass absent revision_ids here.
 
3469
            if revision_ids is None:
 
3470
                revision_ids = set()
 
3471
            if if_present_ids is None:
 
3472
                if_present_ids = set()
 
3473
            revision_ids = set(revision_ids)
 
3474
            if_present_ids = set(if_present_ids)
 
3475
            all_wanted_ids = revision_ids.union(if_present_ids)
 
3476
            graph = self.source.get_graph()
 
3477
            present_revs = set(graph.get_parent_map(all_wanted_ids))
 
3478
            missing = revision_ids.difference(present_revs)
 
3479
            if missing:
 
3480
                raise errors.NoSuchRevision(self.source, missing.pop())
 
3481
            found_ids = all_wanted_ids.intersection(present_revs)
 
3482
            source_ids = [rev_id for (rev_id, parents) in
 
3483
                          graph.iter_ancestry(found_ids)
 
3484
                          if rev_id != _mod_revision.NULL_REVISION
 
3485
                          and parents is not None]
 
3486
        else:
 
3487
            source_ids = self.source.all_revision_ids()
 
3488
        return set(source_ids)
 
3489
 
3517
3490
    @staticmethod
3518
3491
    def _same_model(source, target):
3519
3492
        """True if source and target have the same data representation.
3560
3533
        return InterRepository._same_model(source, target)
3561
3534
 
3562
3535
 
3563
 
class InterWeaveRepo(InterSameDataRepository):
3564
 
    """Optimised code paths between Weave based repositories.
3565
 
 
3566
 
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
3567
 
    implemented lazy inter-object optimisation.
3568
 
    """
3569
 
 
3570
 
    @classmethod
3571
 
    def _get_repo_format_to_test(self):
3572
 
        from bzrlib.repofmt import weaverepo
3573
 
        return weaverepo.RepositoryFormat7()
3574
 
 
3575
 
    @staticmethod
3576
 
    def is_compatible(source, target):
3577
 
        """Be compatible with known Weave formats.
3578
 
 
3579
 
        We don't test for the stores being of specific types because that
3580
 
        could lead to confusing results, and there is no need to be
3581
 
        overly general.
3582
 
        """
3583
 
        from bzrlib.repofmt.weaverepo import (
3584
 
                RepositoryFormat5,
3585
 
                RepositoryFormat6,
3586
 
                RepositoryFormat7,
3587
 
                )
3588
 
        try:
3589
 
            return (isinstance(source._format, (RepositoryFormat5,
3590
 
                                                RepositoryFormat6,
3591
 
                                                RepositoryFormat7)) and
3592
 
                    isinstance(target._format, (RepositoryFormat5,
3593
 
                                                RepositoryFormat6,
3594
 
                                                RepositoryFormat7)))
3595
 
        except AttributeError:
3596
 
            return False
3597
 
 
3598
 
    @needs_write_lock
3599
 
    def copy_content(self, revision_id=None):
3600
 
        """See InterRepository.copy_content()."""
3601
 
        # weave specific optimised path:
3602
 
        try:
3603
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3604
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
3605
 
            pass
3606
 
        # FIXME do not peek!
3607
 
        if self.source._transport.listable():
3608
 
            pb = ui.ui_factory.nested_progress_bar()
3609
 
            try:
3610
 
                self.target.texts.insert_record_stream(
3611
 
                    self.source.texts.get_record_stream(
3612
 
                        self.source.texts.keys(), 'topological', False))
3613
 
                pb.update('Copying inventory', 0, 1)
3614
 
                self.target.inventories.insert_record_stream(
3615
 
                    self.source.inventories.get_record_stream(
3616
 
                        self.source.inventories.keys(), 'topological', False))
3617
 
                self.target.signatures.insert_record_stream(
3618
 
                    self.source.signatures.get_record_stream(
3619
 
                        self.source.signatures.keys(),
3620
 
                        'unordered', True))
3621
 
                self.target.revisions.insert_record_stream(
3622
 
                    self.source.revisions.get_record_stream(
3623
 
                        self.source.revisions.keys(),
3624
 
                        'topological', True))
3625
 
            finally:
3626
 
                pb.finished()
3627
 
        else:
3628
 
            self.target.fetch(self.source, revision_id=revision_id)
3629
 
 
3630
 
    @needs_read_lock
3631
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3632
 
        """See InterRepository.missing_revision_ids()."""
3633
 
        # we want all revisions to satisfy revision_id in source.
3634
 
        # but we don't want to stat every file here and there.
3635
 
        # we want then, all revisions other needs to satisfy revision_id
3636
 
        # checked, but not those that we have locally.
3637
 
        # so the first thing is to get a subset of the revisions to
3638
 
        # satisfy revision_id in source, and then eliminate those that
3639
 
        # we do already have.
3640
 
        # this is slow on high latency connection to self, but as this
3641
 
        # disk format scales terribly for push anyway due to rewriting
3642
 
        # inventory.weave, this is considered acceptable.
3643
 
        # - RBC 20060209
3644
 
        if revision_id is not None:
3645
 
            source_ids = self.source.get_ancestry(revision_id)
3646
 
            if source_ids[0] is not None:
3647
 
                raise AssertionError()
3648
 
            source_ids.pop(0)
3649
 
        else:
3650
 
            source_ids = self.source._all_possible_ids()
3651
 
        source_ids_set = set(source_ids)
3652
 
        # source_ids is the worst possible case we may need to pull.
3653
 
        # now we want to filter source_ids against what we actually
3654
 
        # have in target, but don't try to check for existence where we know
3655
 
        # we do not have a revision as that would be pointless.
3656
 
        target_ids = set(self.target._all_possible_ids())
3657
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3658
 
        actually_present_revisions = set(
3659
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3660
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3661
 
        if revision_id is not None:
3662
 
            # we used get_ancestry to determine source_ids then we are assured all
3663
 
            # revisions referenced are present as they are installed in topological order.
3664
 
            # and the tip revision was validated by get_ancestry.
3665
 
            result_set = required_revisions
3666
 
        else:
3667
 
            # if we just grabbed the possibly available ids, then
3668
 
            # we only have an estimate of whats available and need to validate
3669
 
            # that against the revision records.
3670
 
            result_set = set(
3671
 
                self.source._eliminate_revisions_not_present(required_revisions))
3672
 
        return self.source.revision_ids_to_search_result(result_set)
3673
 
 
3674
 
 
3675
 
class InterKnitRepo(InterSameDataRepository):
3676
 
    """Optimised code paths between Knit based repositories."""
3677
 
 
3678
 
    @classmethod
3679
 
    def _get_repo_format_to_test(self):
3680
 
        from bzrlib.repofmt import knitrepo
3681
 
        return knitrepo.RepositoryFormatKnit1()
3682
 
 
3683
 
    @staticmethod
3684
 
    def is_compatible(source, target):
3685
 
        """Be compatible with known Knit formats.
3686
 
 
3687
 
        We don't test for the stores being of specific types because that
3688
 
        could lead to confusing results, and there is no need to be
3689
 
        overly general.
3690
 
        """
3691
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
3692
 
        try:
3693
 
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
3694
 
                isinstance(target._format, RepositoryFormatKnit))
3695
 
        except AttributeError:
3696
 
            return False
3697
 
        return are_knits and InterRepository._same_model(source, target)
3698
 
 
3699
 
    @needs_read_lock
3700
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3701
 
        """See InterRepository.missing_revision_ids()."""
3702
 
        if revision_id is not None:
3703
 
            source_ids = self.source.get_ancestry(revision_id)
3704
 
            if source_ids[0] is not None:
3705
 
                raise AssertionError()
3706
 
            source_ids.pop(0)
3707
 
        else:
3708
 
            source_ids = self.source.all_revision_ids()
3709
 
        source_ids_set = set(source_ids)
3710
 
        # source_ids is the worst possible case we may need to pull.
3711
 
        # now we want to filter source_ids against what we actually
3712
 
        # have in target, but don't try to check for existence where we know
3713
 
        # we do not have a revision as that would be pointless.
3714
 
        target_ids = set(self.target.all_revision_ids())
3715
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3716
 
        actually_present_revisions = set(
3717
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3718
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3719
 
        if revision_id is not None:
3720
 
            # we used get_ancestry to determine source_ids then we are assured all
3721
 
            # revisions referenced are present as they are installed in topological order.
3722
 
            # and the tip revision was validated by get_ancestry.
3723
 
            result_set = required_revisions
3724
 
        else:
3725
 
            # if we just grabbed the possibly available ids, then
3726
 
            # we only have an estimate of whats available and need to validate
3727
 
            # that against the revision records.
3728
 
            result_set = set(
3729
 
                self.source._eliminate_revisions_not_present(required_revisions))
3730
 
        return self.source.revision_ids_to_search_result(result_set)
3731
 
 
3732
 
 
3733
3536
class InterDifferingSerializer(InterRepository):
3734
3537
 
3735
3538
    @classmethod
3837
3640
                basis_id, delta, current_revision_id, parents_parents)
3838
3641
            cache[current_revision_id] = parent_tree
3839
3642
 
3840
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3643
    def _fetch_batch(self, revision_ids, basis_id, cache):
3841
3644
        """Fetch across a few revisions.
3842
3645
 
3843
3646
        :param revision_ids: The revisions to copy
3844
3647
        :param basis_id: The revision_id of a tree that must be in cache, used
3845
3648
            as a basis for delta when no other base is available
3846
3649
        :param cache: A cache of RevisionTrees that we can use.
3847
 
        :param a_graph: A Graph object to determine the heads() of the
3848
 
            rich-root data stream.
3849
3650
        :return: The revision_id of the last converted tree. The RevisionTree
3850
3651
            for it will be in cache
3851
3652
        """
3919
3720
        if root_keys_to_create:
3920
3721
            root_stream = _mod_fetch._new_root_data_stream(
3921
3722
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3922
 
                self.source, graph=a_graph)
 
3723
                self.source)
3923
3724
            to_texts.insert_record_stream(root_stream)
3924
3725
        to_texts.insert_record_stream(from_texts.get_record_stream(
3925
3726
            text_keys, self.target._format._fetch_order,
3982
3783
        cache[basis_id] = basis_tree
3983
3784
        del basis_tree # We don't want to hang on to it here
3984
3785
        hints = []
3985
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3986
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3987
 
                                                            revision_ids)
3988
 
        else:
3989
 
            a_graph = None
 
3786
        a_graph = None
3990
3787
 
3991
3788
        for offset in range(0, len(revision_ids), batch_size):
3992
3789
            self.target.start_write_group()
3994
3791
                pb.update('Transferring revisions', offset,
3995
3792
                          len(revision_ids))
3996
3793
                batch = revision_ids[offset:offset+batch_size]
3997
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
3998
 
                                             a_graph=a_graph)
 
3794
                basis_id = self._fetch_batch(batch, basis_id, cache)
3999
3795
            except:
4000
3796
                self.source._safe_to_return_from_cache = False
4001
3797
                self.target.abort_write_group()
4010
3806
                  len(revision_ids))
4011
3807
 
4012
3808
    @needs_write_lock
4013
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3809
    def fetch(self, revision_id=None, find_ghosts=False,
4014
3810
            fetch_spec=None):
4015
3811
        """See InterRepository.fetch()."""
4016
3812
        if fetch_spec is not None:
4017
 
            raise AssertionError("Not implemented yet...")
 
3813
            revision_ids = fetch_spec.get_keys()
 
3814
        else:
 
3815
            revision_ids = None
4018
3816
        ui.ui_factory.warn_experimental_format_fetch(self)
4019
3817
        if (not self.source.supports_rich_root()
4020
3818
            and self.target.supports_rich_root()):
4027
3825
            ui.ui_factory.show_user_warning('cross_format_fetch',
4028
3826
                from_format=self.source._format,
4029
3827
                to_format=self.target._format)
4030
 
        revision_ids = self.target.search_missing_revision_ids(self.source,
4031
 
            revision_id, find_ghosts=find_ghosts).get_keys()
 
3828
        if revision_ids is None:
 
3829
            if revision_id:
 
3830
                search_revision_ids = [revision_id]
 
3831
            else:
 
3832
                search_revision_ids = None
 
3833
            revision_ids = self.target.search_missing_revision_ids(self.source,
 
3834
                revision_ids=search_revision_ids,
 
3835
                find_ghosts=find_ghosts).get_keys()
4032
3836
        if not revision_ids:
4033
3837
            return 0, 0
4034
3838
        revision_ids = tsort.topo_sort(
4038
3842
        # Walk though all revisions; get inventory deltas, copy referenced
4039
3843
        # texts that delta references, insert the delta, revision and
4040
3844
        # signature.
4041
 
        if pb is None:
4042
 
            my_pb = ui.ui_factory.nested_progress_bar()
4043
 
            pb = my_pb
4044
 
        else:
4045
 
            symbol_versioning.warn(
4046
 
                symbol_versioning.deprecated_in((1, 14, 0))
4047
 
                % "pb parameter to fetch()")
4048
 
            my_pb = None
 
3845
        pb = ui.ui_factory.nested_progress_bar()
4049
3846
        try:
4050
3847
            self._fetch_all_revisions(revision_ids, pb)
4051
3848
        finally:
4052
 
            if my_pb is not None:
4053
 
                my_pb.finished()
 
3849
            pb.finished()
4054
3850
        return len(revision_ids), 0
4055
3851
 
4056
3852
    def _get_basis(self, first_revision_id):
4067
3863
            basis_id = first_rev.parent_ids[0]
4068
3864
            # only valid as a basis if the target has it
4069
3865
            self.target.get_revision(basis_id)
4070
 
            # Try to get a basis tree - if its a ghost it will hit the
 
3866
            # Try to get a basis tree - if it's a ghost it will hit the
4071
3867
            # NoSuchRevision case.
4072
3868
            basis_tree = self.source.revision_tree(basis_id)
4073
3869
        except (IndexError, errors.NoSuchRevision):
4078
3874
 
4079
3875
InterRepository.register_optimiser(InterDifferingSerializer)
4080
3876
InterRepository.register_optimiser(InterSameDataRepository)
4081
 
InterRepository.register_optimiser(InterWeaveRepo)
4082
 
InterRepository.register_optimiser(InterKnitRepo)
4083
3877
 
4084
3878
 
4085
3879
class CopyConverter(object):
4130
3924
        pb.finished()
4131
3925
 
4132
3926
 
4133
 
_unescape_map = {
4134
 
    'apos':"'",
4135
 
    'quot':'"',
4136
 
    'amp':'&',
4137
 
    'lt':'<',
4138
 
    'gt':'>'
4139
 
}
4140
 
 
4141
 
 
4142
 
def _unescaper(match, _map=_unescape_map):
4143
 
    code = match.group(1)
4144
 
    try:
4145
 
        return _map[code]
4146
 
    except KeyError:
4147
 
        if not code.startswith('#'):
4148
 
            raise
4149
 
        return unichr(int(code[1:])).encode('utf8')
4150
 
 
4151
 
 
4152
 
_unescape_re = None
4153
 
 
4154
 
 
4155
 
def _unescape_xml(data):
4156
 
    """Unescape predefined XML entities in a string of data."""
4157
 
    global _unescape_re
4158
 
    if _unescape_re is None:
4159
 
        _unescape_re = re.compile('\&([^;]*);')
4160
 
    return _unescape_re.sub(_unescaper, data)
4161
 
 
4162
 
 
4163
3927
class _VersionedFileChecker(object):
4164
3928
 
4165
3929
    def __init__(self, repository, text_key_references=None, ancestors=None):
4224
3988
        return wrong_parents, unused_keys
4225
3989
 
4226
3990
 
4227
 
def _old_get_graph(repository, revision_id):
4228
 
    """DO NOT USE. That is all. I'm serious."""
4229
 
    graph = repository.get_graph()
4230
 
    revision_graph = dict(((key, value) for key, value in
4231
 
        graph.iter_ancestry([revision_id]) if value is not None))
4232
 
    return _strip_NULL_ghosts(revision_graph)
4233
 
 
4234
 
 
4235
3991
def _strip_NULL_ghosts(revision_graph):
4236
3992
    """Also don't use this. more compatibility code for unmigrated clients."""
4237
3993
    # Filter ghosts, and null:
4273
4029
                is_resume = False
4274
4030
            try:
4275
4031
                # locked_insert_stream performs a commit|suspend.
4276
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
4032
                missing_keys = self.insert_stream_without_locking(stream,
 
4033
                                    src_format, is_resume)
 
4034
                if missing_keys:
 
4035
                    # suspend the write group and tell the caller what we is
 
4036
                    # missing. We know we can suspend or else we would not have
 
4037
                    # entered this code path. (All repositories that can handle
 
4038
                    # missing keys can handle suspending a write group).
 
4039
                    write_group_tokens = self.target_repo.suspend_write_group()
 
4040
                    return write_group_tokens, missing_keys
 
4041
                hint = self.target_repo.commit_write_group()
 
4042
                to_serializer = self.target_repo._format._serializer
 
4043
                src_serializer = src_format._serializer
 
4044
                if (to_serializer != src_serializer and
 
4045
                    self.target_repo._format.pack_compresses):
 
4046
                    self.target_repo.pack(hint=hint)
 
4047
                return [], set()
4277
4048
            except:
4278
4049
                self.target_repo.abort_write_group(suppress_errors=True)
4279
4050
                raise
4280
4051
        finally:
4281
4052
            self.target_repo.unlock()
4282
4053
 
4283
 
    def _locked_insert_stream(self, stream, src_format, is_resume):
 
4054
    def insert_stream_without_locking(self, stream, src_format,
 
4055
                                      is_resume=False):
 
4056
        """Insert a stream's content into the target repository.
 
4057
 
 
4058
        This assumes that you already have a locked repository and an active
 
4059
        write group.
 
4060
 
 
4061
        :param src_format: a bzr repository format.
 
4062
        :param is_resume: Passed down to get_missing_parent_inventories to
 
4063
            indicate if we should be checking for missing texts at the same
 
4064
            time.
 
4065
 
 
4066
        :return: A set of keys that are missing.
 
4067
        """
 
4068
        if not self.target_repo.is_write_locked():
 
4069
            raise errors.ObjectNotLocked(self)
 
4070
        if not self.target_repo.is_in_write_group():
 
4071
            raise errors.BzrError('you must already be in a write group')
4284
4072
        to_serializer = self.target_repo._format._serializer
4285
4073
        src_serializer = src_format._serializer
4286
4074
        new_pack = None
4326
4114
                # required if the serializers are different only in terms of
4327
4115
                # the inventory.
4328
4116
                if src_serializer == to_serializer:
4329
 
                    self.target_repo.revisions.insert_record_stream(
4330
 
                        substream)
 
4117
                    self.target_repo.revisions.insert_record_stream(substream)
4331
4118
                else:
4332
4119
                    self._extract_and_insert_revisions(substream,
4333
4120
                        src_serializer)
4366
4153
            # cannot even attempt suspending, and missing would have failed
4367
4154
            # during stream insertion.
4368
4155
            missing_keys = set()
4369
 
        else:
4370
 
            if missing_keys:
4371
 
                # suspend the write group and tell the caller what we is
4372
 
                # missing. We know we can suspend or else we would not have
4373
 
                # entered this code path. (All repositories that can handle
4374
 
                # missing keys can handle suspending a write group).
4375
 
                write_group_tokens = self.target_repo.suspend_write_group()
4376
 
                return write_group_tokens, missing_keys
4377
 
        hint = self.target_repo.commit_write_group()
4378
 
        if (to_serializer != src_serializer and
4379
 
            self.target_repo._format.pack_compresses):
4380
 
            self.target_repo.pack(hint=hint)
4381
 
        return [], set()
 
4156
        return missing_keys
4382
4157
 
4383
4158
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4384
4159
        target_rich_root = self.target_repo._format.rich_root_data
4391
4166
                parse_result = deserialiser.parse_text_bytes(
4392
4167
                    inventory_delta_bytes)
4393
4168
            except inventory_delta.IncompatibleInventoryDelta, err:
4394
 
                trace.mutter("Incompatible delta: %s", err.msg)
 
4169
                mutter("Incompatible delta: %s", err.msg)
4395
4170
                raise errors.IncompatibleRevision(self.target_repo._format)
4396
4171
            basis_id, new_id, rich_root, tree_refs, inv_delta = parse_result
4397
4172
            revision_id = new_id
4441
4216
        """Create a StreamSource streaming from from_repository."""
4442
4217
        self.from_repository = from_repository
4443
4218
        self.to_format = to_format
 
4219
        self._record_counter = RecordCounter()
4444
4220
 
4445
4221
    def delta_on_metadata(self):
4446
4222
        """Return True if delta's are permitted on metadata streams.
4731
4507
    except StopIteration:
4732
4508
        # No more history
4733
4509
        return
4734