/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Jelmer Vernooij
  • Date: 2011-05-03 13:53:46 UTC
  • mto: This revision was merged to the branch mainline in revision 5826.
  • Revision ID: jelmer@samba.org-20110503135346-l2f6xnenzn3320gv
Kill annotate_file_revision_tree() in favor annotate_file_tree().

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
51
 
from bzrlib.revisiontree import RevisionTree
 
43
from bzrlib.recordcounter import RecordCounter
 
44
from bzrlib.revisiontree import InventoryRevisionTree
52
45
from bzrlib.store.versioned import VersionedFileStore
53
46
from bzrlib.testament import Testament
54
47
""")
55
48
 
 
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,
88
 
                 revision_id=None):
 
96
                 revision_id=None, lossy=False):
89
97
        """Initiate a CommitBuilder.
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.
97
104
        :param revprops: Optional dictionary of revision properties.
98
105
        :param revision_id: Optional revision id.
 
106
        :param lossy: Whether to discard data that can not be natively
 
107
            represented, when pushing to a foreign VCS 
99
108
        """
100
109
        self._config = config
 
110
        self._lossy = lossy
101
111
 
102
112
        if committer is None:
103
113
            self._committer = self._config.username()
 
114
        elif not isinstance(committer, unicode):
 
115
            self._committer = committer.decode() # throw if non-ascii
104
116
        else:
105
117
            self._committer = committer
106
118
 
160
172
            self._validate_unicode_text(value,
161
173
                                        'revision property (%s)' % (key,))
162
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
 
163
210
    def commit(self, message):
164
211
        """Make the actual commit.
165
212
 
177
224
        rev.parent_ids = self.parents
178
225
        self.repository.add_revision(self._new_revision_id, rev,
179
226
            self.new_inventory, self._config)
 
227
        self._ensure_fallback_inventories()
180
228
        self.repository.commit_write_group()
181
229
        return self._new_revision_id
182
230
 
188
236
    def revision_tree(self):
189
237
        """Return the tree that was just committed.
190
238
 
191
 
        After calling commit() this can be called to get a RevisionTree
192
 
        representing the newly committed tree. This is preferred to
193
 
        calling Repository.revision_tree() because that may require
194
 
        deserializing the inventory, while we already have a copy in
 
239
        After calling commit() this can be called to get a
 
240
        InventoryRevisionTree representing the newly committed tree. This is
 
241
        preferred to calling Repository.revision_tree() because that may
 
242
        require deserializing the inventory, while we already have a copy in
195
243
        memory.
196
244
        """
197
245
        if self.new_inventory is None:
198
246
            self.new_inventory = self.repository.get_inventory(
199
247
                self._new_revision_id)
200
 
        return RevisionTree(self.repository, self.new_inventory,
 
248
        return InventoryRevisionTree(self.repository, self.new_inventory,
201
249
            self._new_revision_id)
202
250
 
203
251
    def finish_inventory(self):
231
279
 
232
280
    def _gen_revision_id(self):
233
281
        """Return new revision-id."""
234
 
        return generate_ids.gen_revision_id(self._config.username(),
235
 
                                            self._timestamp)
 
282
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
236
283
 
237
284
    def _generate_revision_if_needed(self):
238
285
        """Create a revision id if None was supplied.
278
325
 
279
326
        :param tree: The tree which is being committed.
280
327
        """
281
 
        # NB: if there are no parents then this method is not called, so no
282
 
        # need to guard on parents having length.
 
328
        if len(self.parents) == 0:
 
329
            raise errors.RootMissing()
283
330
        entry = entry_factory['directory'](tree.path2id(''), '',
284
331
            None)
285
332
        entry.revision = self._new_revision_id
423
470
            else:
424
471
                # we don't need to commit this, because the caller already
425
472
                # determined that an existing revision of this file is
426
 
                # appropriate. If its not being considered for committing then
 
473
                # appropriate. If it's not being considered for committing then
427
474
                # it and all its parents to the root must be unaltered so
428
475
                # no-change against the basis.
429
476
                if ie.revision == self._new_revision_id:
745
792
                    # after iter_changes examines and decides it has changed,
746
793
                    # we will unconditionally record a new version even if some
747
794
                    # other process reverts it while commit is running (with
748
 
                    # the revert happening after iter_changes did it's
 
795
                    # the revert happening after iter_changes did its
749
796
                    # examination).
750
797
                    if change[7][1]:
751
798
                        entry.executable = True
860
907
        # versioned roots do not change unless the tree found a change.
861
908
 
862
909
 
 
910
class RepositoryWriteLockResult(LogicalLockResult):
 
911
    """The result of write locking a repository.
 
912
 
 
913
    :ivar repository_token: The token obtained from the underlying lock, or
 
914
        None.
 
915
    :ivar unlock: A callable which will unlock the lock.
 
916
    """
 
917
 
 
918
    def __init__(self, unlock, repository_token):
 
919
        LogicalLockResult.__init__(self, unlock)
 
920
        self.repository_token = repository_token
 
921
 
 
922
    def __repr__(self):
 
923
        return "RepositoryWriteLockResult(%s, %s)" % (self.repository_token,
 
924
            self.unlock)
 
925
 
 
926
 
863
927
######################################################################
864
928
# Repositories
865
929
 
866
930
 
867
 
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
 
931
class Repository(_RelockDebugMixin, controldir.ControlComponent):
868
932
    """Repository holding history for one or more branches.
869
933
 
870
934
    The repository holds and retrieves historical information including
917
981
        pointing to .bzr/repository.
918
982
    """
919
983
 
920
 
    # What class to use for a CommitBuilder. Often its simpler to change this
 
984
    # What class to use for a CommitBuilder. Often it's simpler to change this
921
985
    # in a Repository class subclass rather than to override
922
986
    # get_commit_builder.
923
987
    _commit_builder_class = CommitBuilder
924
 
    # The search regex used by xml based repositories to determine what things
925
 
    # where changed in a single commit.
926
 
    _file_ids_altered_regex = lazy_regex.lazy_compile(
927
 
        r'file_id="(?P<file_id>[^"]+)"'
928
 
        r'.* revision="(?P<revision_id>[^"]+)"'
929
 
        )
930
988
 
931
989
    def abort_write_group(self, suppress_errors=False):
932
990
        """Commit the contents accrued within the current write group.
1018
1076
                " id and insertion revid (%r, %r)"
1019
1077
                % (inv.revision_id, revision_id))
1020
1078
        if inv.root is None:
1021
 
            raise AssertionError()
 
1079
            raise errors.RootMissing()
1022
1080
        return self._add_inventory_checked(revision_id, inv, parents)
1023
1081
 
1024
1082
    def _add_inventory_checked(self, revision_id, inv, parents):
1105
1163
        if config is not None and config.signature_needed():
1106
1164
            if inv is None:
1107
1165
                inv = self.get_inventory(revision_id)
1108
 
            plaintext = Testament(rev, inv).as_short_text()
 
1166
            tree = InventoryRevisionTree(self, inv, revision_id)
 
1167
            testament = Testament(rev, tree)
 
1168
            plaintext = testament.as_short_text()
1109
1169
            self.store_revision_signature(
1110
1170
                gpg.GPGStrategy(config), plaintext, revision_id)
1111
1171
        # check inventory present
1376
1436
        data during reads, and allows a 'write_group' to be obtained. Write
1377
1437
        groups must be used for actual data insertion.
1378
1438
 
 
1439
        A token should be passed in if you know that you have locked the object
 
1440
        some other way, and need to synchronise this object's state with that
 
1441
        fact.
 
1442
 
 
1443
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1444
 
1379
1445
        :param token: if this is already locked, then lock_write will fail
1380
1446
            unless the token matches the existing lock.
1381
1447
        :returns: a token if this instance supports tokens, otherwise None.
1384
1450
        :raises MismatchedToken: if the specified token doesn't match the token
1385
1451
            of the existing lock.
1386
1452
        :seealso: start_write_group.
1387
 
 
1388
 
        A token should be passed in if you know that you have locked the object
1389
 
        some other way, and need to synchronise this object's state with that
1390
 
        fact.
1391
 
 
1392
 
        XXX: this docstring is duplicated in many places, e.g. lockable_files.py
 
1453
        :return: A RepositoryWriteLockResult.
1393
1454
        """
1394
1455
        locked = self.is_locked()
1395
 
        result = self.control_files.lock_write(token=token)
 
1456
        token = self.control_files.lock_write(token=token)
1396
1457
        if not locked:
1397
1458
            self._warn_if_deprecated()
1398
1459
            self._note_lock('w')
1400
1461
                # Writes don't affect fallback repos
1401
1462
                repo.lock_read()
1402
1463
            self._refresh_data()
1403
 
        return result
 
1464
        return RepositoryWriteLockResult(self.unlock, token)
1404
1465
 
1405
1466
    def lock_read(self):
 
1467
        """Lock the repository for read operations.
 
1468
 
 
1469
        :return: An object with an unlock method which will release the lock
 
1470
            obtained.
 
1471
        """
1406
1472
        locked = self.is_locked()
1407
1473
        self.control_files.lock_read()
1408
1474
        if not locked:
1411
1477
            for repo in self._fallback_repositories:
1412
1478
                repo.lock_read()
1413
1479
            self._refresh_data()
 
1480
        return LogicalLockResult(self.unlock)
1414
1481
 
1415
1482
    def get_physical_lock_status(self):
1416
1483
        return self.control_files.get_physical_lock_status()
1522
1589
        return ret
1523
1590
 
1524
1591
    @needs_read_lock
1525
 
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
 
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):
1526
1595
        """Return the revision ids that other has that this does not.
1527
1596
 
1528
1597
        These are returned in topological order.
1529
1598
 
1530
1599
        revision_id: only return revision ids included by revision_id.
1531
1600
        """
 
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]
1532
1611
        return InterRepository.get(other, self).search_missing_revision_ids(
1533
 
            revision_id, find_ghosts)
 
1612
            find_ghosts=find_ghosts, revision_ids=revision_ids,
 
1613
            if_present_ids=if_present_ids)
1534
1614
 
1535
1615
    @staticmethod
1536
1616
    def open(base):
1634
1714
        return missing_keys
1635
1715
 
1636
1716
    def refresh_data(self):
1637
 
        """Re-read any data needed to to synchronise with disk.
 
1717
        """Re-read any data needed to synchronise with disk.
1638
1718
 
1639
1719
        This method is intended to be called after another repository instance
1640
1720
        (such as one used by a smart server) has inserted data into the
1641
 
        repository. It may not be called during a write group, but may be
1642
 
        called at any other time.
 
1721
        repository. On all repositories this will work outside of write groups.
 
1722
        Some repository formats (pack and newer for bzrlib native formats)
 
1723
        support refresh_data inside write groups. If called inside a write
 
1724
        group on a repository that does not support refreshing in a write group
 
1725
        IsInWriteGroupError will be raised.
1643
1726
        """
1644
 
        if self.is_in_write_group():
1645
 
            raise errors.InternalBzrError(
1646
 
                "May not refresh_data while in a write group.")
1647
1727
        self._refresh_data()
1648
1728
 
1649
1729
    def resume_write_group(self, tokens):
1658
1738
    def _resume_write_group(self, tokens):
1659
1739
        raise errors.UnsuspendableWriteGroup(self)
1660
1740
 
1661
 
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
 
1741
    def fetch(self, source, revision_id=None, find_ghosts=False,
1662
1742
            fetch_spec=None):
1663
1743
        """Fetch the content required to construct revision_id from source.
1664
1744
 
1688
1768
                "May not fetch while in a write group.")
1689
1769
        # fast path same-url fetch operations
1690
1770
        # TODO: lift out to somewhere common with RemoteRepository
1691
 
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
 
1771
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1692
1772
        if (self.has_same_location(source)
1693
1773
            and fetch_spec is None
1694
1774
            and self._has_same_fallbacks(source)):
1698
1778
                not _mod_revision.is_null(revision_id)):
1699
1779
                self.get_revision(revision_id)
1700
1780
            return 0, []
1701
 
        # if there is no specific appropriate InterRepository, this will get
1702
 
        # the InterRepository base class, which raises an
1703
 
        # IncompatibleRepositories when asked to fetch.
1704
1781
        inter = InterRepository.get(source, self)
1705
 
        return inter.fetch(revision_id=revision_id, pb=pb,
 
1782
        return inter.fetch(revision_id=revision_id,
1706
1783
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1707
1784
 
1708
1785
    def create_bundle(self, target, base, fileobj, format=None):
1710
1787
 
1711
1788
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1712
1789
                           timezone=None, committer=None, revprops=None,
1713
 
                           revision_id=None):
 
1790
                           revision_id=None, lossy=False):
1714
1791
        """Obtain a CommitBuilder for this repository.
1715
1792
 
1716
1793
        :param branch: Branch to commit to.
1721
1798
        :param committer: Optional committer to set for commit.
1722
1799
        :param revprops: Optional dictionary of revision properties.
1723
1800
        :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
1724
1803
        """
1725
 
        if self._fallback_repositories:
1726
 
            raise errors.BzrError("Cannot commit from a lightweight checkout "
1727
 
                "to a stacked branch. See "
 
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 "
1728
1807
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1729
1808
        result = self._commit_builder_class(self, parents, config,
1730
 
            timestamp, timezone, committer, revprops, revision_id)
 
1809
            timestamp, timezone, committer, revprops, revision_id,
 
1810
            lossy)
1731
1811
        self.start_write_group()
1732
1812
        return result
1733
1813
 
1979
2059
        w = self.inventories
1980
2060
        pb = ui.ui_factory.nested_progress_bar()
1981
2061
        try:
1982
 
            return self._find_text_key_references_from_xml_inventory_lines(
 
2062
            return self._serializer._find_text_key_references(
1983
2063
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
1984
2064
        finally:
1985
2065
            pb.finished()
1986
2066
 
1987
 
    def _find_text_key_references_from_xml_inventory_lines(self,
1988
 
        line_iterator):
1989
 
        """Core routine for extracting references to texts from inventories.
1990
 
 
1991
 
        This performs the translation of xml lines to revision ids.
1992
 
 
1993
 
        :param line_iterator: An iterator of lines, origin_version_id
1994
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1995
 
            to whether they were referred to by the inventory of the
1996
 
            revision_id that they contain. Note that if that revision_id was
1997
 
            not part of the line_iterator's output then False will be given -
1998
 
            even though it may actually refer to that key.
1999
 
        """
2000
 
        if not self._serializer.support_altered_by_hack:
2001
 
            raise AssertionError(
2002
 
                "_find_text_key_references_from_xml_inventory_lines only "
2003
 
                "supported for branches which store inventory as unnested xml"
2004
 
                ", not on %r" % self)
2005
 
        result = {}
2006
 
 
2007
 
        # this code needs to read every new line in every inventory for the
2008
 
        # inventories [revision_ids]. Seeing a line twice is ok. Seeing a line
2009
 
        # not present in one of those inventories is unnecessary but not
2010
 
        # harmful because we are filtering by the revision id marker in the
2011
 
        # inventory lines : we only select file ids altered in one of those
2012
 
        # revisions. We don't need to see all lines in the inventory because
2013
 
        # only those added in an inventory in rev X can contain a revision=X
2014
 
        # line.
2015
 
        unescape_revid_cache = {}
2016
 
        unescape_fileid_cache = {}
2017
 
 
2018
 
        # jam 20061218 In a big fetch, this handles hundreds of thousands
2019
 
        # of lines, so it has had a lot of inlining and optimizing done.
2020
 
        # Sorry that it is a little bit messy.
2021
 
        # Move several functions to be local variables, since this is a long
2022
 
        # running loop.
2023
 
        search = self._file_ids_altered_regex.search
2024
 
        unescape = _unescape_xml
2025
 
        setdefault = result.setdefault
2026
 
        for line, line_key in line_iterator:
2027
 
            match = search(line)
2028
 
            if match is None:
2029
 
                continue
2030
 
            # One call to match.group() returning multiple items is quite a
2031
 
            # bit faster than 2 calls to match.group() each returning 1
2032
 
            file_id, revision_id = match.group('file_id', 'revision_id')
2033
 
 
2034
 
            # Inlining the cache lookups helps a lot when you make 170,000
2035
 
            # lines and 350k ids, versus 8.4 unique ids.
2036
 
            # Using a cache helps in 2 ways:
2037
 
            #   1) Avoids unnecessary decoding calls
2038
 
            #   2) Re-uses cached strings, which helps in future set and
2039
 
            #      equality checks.
2040
 
            # (2) is enough that removing encoding entirely along with
2041
 
            # the cache (so we are using plain strings) results in no
2042
 
            # performance improvement.
2043
 
            try:
2044
 
                revision_id = unescape_revid_cache[revision_id]
2045
 
            except KeyError:
2046
 
                unescaped = unescape(revision_id)
2047
 
                unescape_revid_cache[revision_id] = unescaped
2048
 
                revision_id = unescaped
2049
 
 
2050
 
            # Note that unconditionally unescaping means that we deserialise
2051
 
            # every fileid, which for general 'pull' is not great, but we don't
2052
 
            # really want to have some many fulltexts that this matters anyway.
2053
 
            # RBC 20071114.
2054
 
            try:
2055
 
                file_id = unescape_fileid_cache[file_id]
2056
 
            except KeyError:
2057
 
                unescaped = unescape(file_id)
2058
 
                unescape_fileid_cache[file_id] = unescaped
2059
 
                file_id = unescaped
2060
 
 
2061
 
            key = (file_id, revision_id)
2062
 
            setdefault(key, False)
2063
 
            if revision_id == line_key[-1]:
2064
 
                result[key] = True
2065
 
        return result
2066
 
 
2067
2067
    def _inventory_xml_lines_for_keys(self, keys):
2068
2068
        """Get a line iterator of the sort needed for findind references.
2069
2069
 
2099
2099
        revision_ids. Each altered file-ids has the exact revision_ids that
2100
2100
        altered it listed explicitly.
2101
2101
        """
2102
 
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2102
        seen = set(self._serializer._find_text_key_references(
2103
2103
                line_iterator).iterkeys())
2104
2104
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
2105
 
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
 
2105
        parent_seen = set(self._serializer._find_text_key_references(
2106
2106
            self._inventory_xml_lines_for_keys(parent_keys)))
2107
2107
        new_keys = seen - parent_seen
2108
2108
        result = {}
2476
2476
            ancestors will be traversed.
2477
2477
        """
2478
2478
        graph = self.get_graph()
2479
 
        next_id = revision_id
2480
 
        while True:
2481
 
            if next_id in (None, _mod_revision.NULL_REVISION):
2482
 
                return
2483
 
            try:
2484
 
                parents = graph.get_parent_map([next_id])[next_id]
2485
 
            except KeyError:
2486
 
                raise errors.RevisionNotPresent(next_id, self)
2487
 
            yield next_id
2488
 
            if len(parents) == 0:
2489
 
                return
2490
 
            else:
2491
 
                next_id = parents[0]
 
2479
        stop_revisions = (None, _mod_revision.NULL_REVISION)
 
2480
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
2492
2481
 
2493
2482
    def is_shared(self):
2494
2483
        """Return True if this repository is flagged as a shared repository."""
2526
2515
        # TODO: refactor this to use an existing revision object
2527
2516
        # so we don't need to read it in twice.
2528
2517
        if revision_id == _mod_revision.NULL_REVISION:
2529
 
            return RevisionTree(self, Inventory(root_id=None),
2530
 
                                _mod_revision.NULL_REVISION)
 
2518
            return InventoryRevisionTree(self,
 
2519
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
2531
2520
        else:
2532
2521
            inv = self.get_inventory(revision_id)
2533
 
            return RevisionTree(self, inv, revision_id)
 
2522
            return InventoryRevisionTree(self, inv, revision_id)
2534
2523
 
2535
2524
    def revision_trees(self, revision_ids):
2536
2525
        """Return Trees for revisions in this repository.
2540
2529
        """
2541
2530
        inventories = self.iter_inventories(revision_ids)
2542
2531
        for inv in inventories:
2543
 
            yield RevisionTree(self, inv, inv.revision_id)
 
2532
            yield InventoryRevisionTree(self, inv, inv.revision_id)
2544
2533
 
2545
2534
    def _filtered_revision_trees(self, revision_ids, file_ids):
2546
2535
        """Return Tree for a revision on this branch with only some files.
2556
2545
            # Should we introduce a FilteredRevisionTree class rather
2557
2546
            # than pre-filter the inventory here?
2558
2547
            filtered_inv = inv.filter(file_ids)
2559
 
            yield RevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2548
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2560
2549
 
2561
2550
    @needs_read_lock
2562
2551
    def get_ancestry(self, revision_id, topo_sorted=True):
2595
2584
        types it should be a no-op that just returns.
2596
2585
 
2597
2586
        This stub method does not require a lock, but subclasses should use
2598
 
        @needs_write_lock as this is a long running call its reasonable to
 
2587
        @needs_write_lock as this is a long running call it's reasonable to
2599
2588
        implicitly lock for the user.
2600
2589
 
2601
2590
        :param hint: If not supplied, the whole repository is packed.
2747
2736
        return result
2748
2737
 
2749
2738
    def _warn_if_deprecated(self, branch=None):
 
2739
        if not self._format.is_deprecated():
 
2740
            return
2750
2741
        global _deprecation_warning_done
2751
2742
        if _deprecation_warning_done:
2752
2743
            return
2782
2773
                except UnicodeDecodeError:
2783
2774
                    raise errors.NonAsciiRevisionId(method, self)
2784
2775
 
2785
 
    def revision_graph_can_have_wrong_parents(self):
2786
 
        """Is it possible for this repository to have a revision graph with
2787
 
        incorrect parents?
 
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.
2788
2779
 
2789
 
        If True, then this repository must also implement
2790
 
        _find_inconsistent_revision_parents so that check and reconcile can
2791
 
        check for inconsistencies before proceeding with other checks that may
2792
 
        depend on the revision index being consistent.
 
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).
2793
2784
        """
2794
 
        raise NotImplementedError(self.revision_graph_can_have_wrong_parents)
2795
 
 
2796
 
 
2797
 
# remove these delegates a while after bzr 0.15
2798
 
def __make_delegated(name, from_module):
2799
 
    def _deprecated_repository_forwarder():
2800
 
        symbol_versioning.warn('%s moved to %s in bzr 0.15'
2801
 
            % (name, from_module),
2802
 
            DeprecationWarning,
2803
 
            stacklevel=2)
2804
 
        m = __import__(from_module, globals(), locals(), [name])
2805
 
        try:
2806
 
            return getattr(m, name)
2807
 
        except AttributeError:
2808
 
            raise AttributeError('module %s has no name %s'
2809
 
                    % (m, name))
2810
 
    globals()[name] = _deprecated_repository_forwarder
2811
 
 
2812
 
for _name in [
2813
 
        'AllInOneRepository',
2814
 
        'WeaveMetaDirRepository',
2815
 
        'PreSplitOutRepositoryFormat',
2816
 
        'RepositoryFormat4',
2817
 
        'RepositoryFormat5',
2818
 
        'RepositoryFormat6',
2819
 
        'RepositoryFormat7',
2820
 
        ]:
2821
 
    __make_delegated(_name, 'bzrlib.repofmt.weaverepo')
2822
 
 
2823
 
for _name in [
2824
 
        'KnitRepository',
2825
 
        'RepositoryFormatKnit',
2826
 
        'RepositoryFormatKnit1',
2827
 
        ]:
2828
 
    __make_delegated(_name, 'bzrlib.repofmt.knitrepo')
 
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
2806
 
2830
2807
 
2831
2808
def install_revision(repository, rev, revision_tree):
2890
2867
        for revision, tree in parent_trees.iteritems():
2891
2868
            if ie.file_id not in tree:
2892
2869
                continue
2893
 
            parent_id = tree.inventory[ie.file_id].revision
 
2870
            parent_id = tree.get_file_revision(ie.file_id)
2894
2871
            if parent_id in text_parents:
2895
2872
                continue
2896
2873
            text_parents.append((ie.file_id, parent_id))
2965
2942
            control_files)
2966
2943
 
2967
2944
 
 
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
 
2968
2954
network_format_registry = registry.FormatRegistry()
2969
2955
"""Registry of formats indexed by their network name.
2970
2956
 
2974
2960
"""
2975
2961
 
2976
2962
 
2977
 
format_registry = registry.FormatRegistry(network_format_registry)
 
2963
format_registry = RepositoryFormatRegistry(network_format_registry)
2978
2964
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
2979
2965
 
2980
2966
This can contain either format instances themselves, or classes/factories that
2985
2971
#####################################################################
2986
2972
# Repository Formats
2987
2973
 
2988
 
class RepositoryFormat(object):
 
2974
class RepositoryFormat(controldir.ControlComponentFormat):
2989
2975
    """A repository format.
2990
2976
 
2991
2977
    Formats provide four things:
3052
3038
    supports_tree_reference = None
3053
3039
    # Is the format experimental ?
3054
3040
    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
3055
3052
 
3056
3053
    def __repr__(self):
3057
3054
        return "%s()" % self.__class__.__name__
3082
3079
                                            kind='repository')
3083
3080
 
3084
3081
    @classmethod
 
3082
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3085
3083
    def register_format(klass, format):
3086
 
        format_registry.register(format.get_format_string(), format)
 
3084
        format_registry.register(format)
3087
3085
 
3088
3086
    @classmethod
 
3087
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3089
3088
    def unregister_format(klass, format):
3090
 
        format_registry.remove(format.get_format_string())
 
3089
        format_registry.remove(format)
3091
3090
 
3092
3091
    @classmethod
 
3092
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3093
3093
    def get_default_format(klass):
3094
3094
        """Return the current default format."""
3095
 
        from bzrlib import bzrdir
3096
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
 
3095
        return format_registry.get_default()
3097
3096
 
3098
3097
    def get_format_string(self):
3099
3098
        """Return the ASCII format string that identifies this format.
3150
3149
        """
3151
3150
        return True
3152
3151
 
 
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
 
3153
3160
    def network_name(self):
3154
3161
        """A simple byte string uniquely identifying this format for RPC calls.
3155
3162
 
3194
3201
    rich_root_data = False
3195
3202
    supports_tree_reference = False
3196
3203
    supports_external_lookups = False
 
3204
    supports_leaving_lock = True
3197
3205
 
3198
3206
    @property
3199
3207
    def _matchingbzrdir(self):
3237
3245
        return self.get_format_string()
3238
3246
 
3239
3247
 
3240
 
# Pre-0.8 formats that don't have a disk format string (because they are
3241
 
# versioned by the matching control directory). We use the control directories
3242
 
# disk format string as a key for the network_name because they meet the
3243
 
# constraints (simple string, unique, immutable).
3244
 
network_format_registry.register_lazy(
3245
 
    "Bazaar-NG branch, format 5\n",
3246
 
    'bzrlib.repofmt.weaverepo',
3247
 
    'RepositoryFormat5',
3248
 
)
3249
 
network_format_registry.register_lazy(
3250
 
    "Bazaar-NG branch, format 6\n",
3251
 
    'bzrlib.repofmt.weaverepo',
3252
 
    'RepositoryFormat6',
3253
 
)
3254
 
 
3255
3248
# formats which have no format string are not discoverable or independently
3256
3249
# creatable on disk, so are not registered in format_registry.  They're
3257
 
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
 
3250
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
3258
3251
# needed, it's constructed directly by the BzrDir.  Non-native formats where
3259
3252
# the repository is not separately opened are similar.
3260
3253
 
3261
3254
format_registry.register_lazy(
3262
 
    'Bazaar-NG Repository format 7',
3263
 
    'bzrlib.repofmt.weaverepo',
3264
 
    'RepositoryFormat7'
3265
 
    )
3266
 
 
3267
 
format_registry.register_lazy(
3268
3255
    'Bazaar-NG Knit Repository Format 1',
3269
3256
    'bzrlib.repofmt.knitrepo',
3270
3257
    'RepositoryFormatKnit1',
3287
3274
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
3288
3275
format_registry.register_lazy(
3289
3276
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
3290
 
    'bzrlib.repofmt.pack_repo',
 
3277
    'bzrlib.repofmt.knitpack_repo',
3291
3278
    'RepositoryFormatKnitPack1',
3292
3279
    )
3293
3280
format_registry.register_lazy(
3294
3281
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
3295
 
    'bzrlib.repofmt.pack_repo',
 
3282
    'bzrlib.repofmt.knitpack_repo',
3296
3283
    'RepositoryFormatKnitPack3',
3297
3284
    )
3298
3285
format_registry.register_lazy(
3299
3286
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
3300
 
    'bzrlib.repofmt.pack_repo',
 
3287
    'bzrlib.repofmt.knitpack_repo',
3301
3288
    'RepositoryFormatKnitPack4',
3302
3289
    )
3303
3290
format_registry.register_lazy(
3304
3291
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
3305
 
    'bzrlib.repofmt.pack_repo',
 
3292
    'bzrlib.repofmt.knitpack_repo',
3306
3293
    'RepositoryFormatKnitPack5',
3307
3294
    )
3308
3295
format_registry.register_lazy(
3309
3296
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
3310
 
    'bzrlib.repofmt.pack_repo',
 
3297
    'bzrlib.repofmt.knitpack_repo',
3311
3298
    'RepositoryFormatKnitPack5RichRoot',
3312
3299
    )
3313
3300
format_registry.register_lazy(
3314
3301
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
3315
 
    'bzrlib.repofmt.pack_repo',
 
3302
    'bzrlib.repofmt.knitpack_repo',
3316
3303
    'RepositoryFormatKnitPack5RichRootBroken',
3317
3304
    )
3318
3305
format_registry.register_lazy(
3319
3306
    'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
3320
 
    'bzrlib.repofmt.pack_repo',
 
3307
    'bzrlib.repofmt.knitpack_repo',
3321
3308
    'RepositoryFormatKnitPack6',
3322
3309
    )
3323
3310
format_registry.register_lazy(
3324
3311
    'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
3325
 
    'bzrlib.repofmt.pack_repo',
 
3312
    'bzrlib.repofmt.knitpack_repo',
3326
3313
    'RepositoryFormatKnitPack6RichRoot',
3327
3314
    )
 
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
    )
3328
3320
 
3329
3321
# Development formats.
3330
 
# Obsolete but kept pending a CHK based subtree format.
 
3322
# Check their docstrings to see if/when they are obsolete.
3331
3323
format_registry.register_lazy(
3332
3324
    ("Bazaar development format 2 with subtree support "
3333
3325
        "(needs bzr.dev from before 1.8)\n"),
3334
 
    'bzrlib.repofmt.pack_repo',
 
3326
    'bzrlib.repofmt.knitpack_repo',
3335
3327
    'RepositoryFormatPackDevelopment2Subtree',
3336
3328
    )
3337
 
 
3338
 
# 1.14->1.16 go below here
3339
 
format_registry.register_lazy(
3340
 
    'Bazaar development format - group compression and chk inventory'
3341
 
        ' (needs bzr.dev from 1.14)\n',
3342
 
    'bzrlib.repofmt.groupcompress_repo',
3343
 
    'RepositoryFormatCHK1',
3344
 
    )
3345
 
 
3346
 
format_registry.register_lazy(
3347
 
    'Bazaar development format - chk repository with bencode revision '
3348
 
        'serialization (needs bzr.dev from 1.16)\n',
3349
 
    'bzrlib.repofmt.groupcompress_repo',
3350
 
    'RepositoryFormatCHK2',
3351
 
    )
3352
 
format_registry.register_lazy(
3353
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3354
 
    'bzrlib.repofmt.groupcompress_repo',
3355
 
    'RepositoryFormat2a',
 
3329
format_registry.register_lazy(
 
3330
    'Bazaar development format 8\n',
 
3331
    'bzrlib.repofmt.groupcompress_repo',
 
3332
    'RepositoryFormat2aSubtree',
3356
3333
    )
3357
3334
 
3358
3335
 
3389
3366
        self.target.fetch(self.source, revision_id=revision_id)
3390
3367
 
3391
3368
    @needs_write_lock
3392
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3369
    def fetch(self, revision_id=None, find_ghosts=False,
3393
3370
            fetch_spec=None):
3394
3371
        """Fetch the content required to construct revision_id.
3395
3372
 
3397
3374
 
3398
3375
        :param revision_id: if None all content is copied, if NULL_REVISION no
3399
3376
                            content is copied.
3400
 
        :param pb: ignored.
3401
3377
        :return: None.
3402
3378
        """
3403
3379
        ui.ui_factory.warn_experimental_format_fetch(self)
3413
3389
                               fetch_spec=fetch_spec,
3414
3390
                               find_ghosts=find_ghosts)
3415
3391
 
3416
 
    def _walk_to_common_revisions(self, revision_ids):
 
3392
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
3417
3393
        """Walk out from revision_ids in source to revisions target has.
3418
3394
 
3419
3395
        :param revision_ids: The start point for the search.
3421
3397
        """
3422
3398
        target_graph = self.target.get_graph()
3423
3399
        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
3424
3404
        missing_revs = set()
3425
3405
        source_graph = self.source.get_graph()
3426
3406
        # ensure we don't pay silly lookup costs.
3427
 
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
 
3407
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
3428
3408
        null_set = frozenset([_mod_revision.NULL_REVISION])
3429
3409
        searcher_exhausted = False
3430
3410
        while True:
3466
3446
        return searcher.get_result()
3467
3447
 
3468
3448
    @needs_read_lock
3469
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
 
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):
3470
3452
        """Return the revision ids that source has that target does not.
3471
3453
 
3472
3454
        :param revision_id: only return revision ids included by this
3473
 
                            revision_id.
 
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.
3474
3463
        :param find_ghosts: If True find missing revisions in deep history
3475
3464
            rather than just finding the surface difference.
3476
3465
        :return: A bzrlib.graph.SearchResult.
3477
3466
        """
 
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
3478
        # stop searching at found target revisions.
3479
 
        if not find_ghosts and revision_id is not None:
3480
 
            return self._walk_to_common_revisions([revision_id])
 
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)
3481
3483
        # generic, possibly worst case, slow code path.
3482
3484
        target_ids = set(self.target.all_revision_ids())
3483
 
        if revision_id is not None:
3484
 
            source_ids = self.source.get_ancestry(revision_id)
3485
 
            if source_ids[0] is not None:
3486
 
                raise AssertionError()
3487
 
            source_ids.pop(0)
3488
 
        else:
3489
 
            source_ids = self.source.all_revision_ids()
 
3485
        source_ids = self._present_source_revisions_for(
 
3486
            revision_ids, if_present_ids)
3490
3487
        result_set = set(source_ids).difference(target_ids)
3491
3488
        return self.source.revision_ids_to_search_result(result_set)
3492
3489
 
 
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
 
3493
3522
    @staticmethod
3494
3523
    def _same_model(source, target):
3495
3524
        """True if source and target have the same data representation.
3536
3565
        return InterRepository._same_model(source, target)
3537
3566
 
3538
3567
 
3539
 
class InterWeaveRepo(InterSameDataRepository):
3540
 
    """Optimised code paths between Weave based repositories.
3541
 
 
3542
 
    This should be in bzrlib/repofmt/weaverepo.py but we have not yet
3543
 
    implemented lazy inter-object optimisation.
3544
 
    """
3545
 
 
3546
 
    @classmethod
3547
 
    def _get_repo_format_to_test(self):
3548
 
        from bzrlib.repofmt import weaverepo
3549
 
        return weaverepo.RepositoryFormat7()
3550
 
 
3551
 
    @staticmethod
3552
 
    def is_compatible(source, target):
3553
 
        """Be compatible with known Weave formats.
3554
 
 
3555
 
        We don't test for the stores being of specific types because that
3556
 
        could lead to confusing results, and there is no need to be
3557
 
        overly general.
3558
 
        """
3559
 
        from bzrlib.repofmt.weaverepo import (
3560
 
                RepositoryFormat5,
3561
 
                RepositoryFormat6,
3562
 
                RepositoryFormat7,
3563
 
                )
3564
 
        try:
3565
 
            return (isinstance(source._format, (RepositoryFormat5,
3566
 
                                                RepositoryFormat6,
3567
 
                                                RepositoryFormat7)) and
3568
 
                    isinstance(target._format, (RepositoryFormat5,
3569
 
                                                RepositoryFormat6,
3570
 
                                                RepositoryFormat7)))
3571
 
        except AttributeError:
3572
 
            return False
3573
 
 
3574
 
    @needs_write_lock
3575
 
    def copy_content(self, revision_id=None):
3576
 
        """See InterRepository.copy_content()."""
3577
 
        # weave specific optimised path:
3578
 
        try:
3579
 
            self.target.set_make_working_trees(self.source.make_working_trees())
3580
 
        except (errors.RepositoryUpgradeRequired, NotImplemented):
3581
 
            pass
3582
 
        # FIXME do not peek!
3583
 
        if self.source._transport.listable():
3584
 
            pb = ui.ui_factory.nested_progress_bar()
3585
 
            try:
3586
 
                self.target.texts.insert_record_stream(
3587
 
                    self.source.texts.get_record_stream(
3588
 
                        self.source.texts.keys(), 'topological', False))
3589
 
                pb.update('Copying inventory', 0, 1)
3590
 
                self.target.inventories.insert_record_stream(
3591
 
                    self.source.inventories.get_record_stream(
3592
 
                        self.source.inventories.keys(), 'topological', False))
3593
 
                self.target.signatures.insert_record_stream(
3594
 
                    self.source.signatures.get_record_stream(
3595
 
                        self.source.signatures.keys(),
3596
 
                        'unordered', True))
3597
 
                self.target.revisions.insert_record_stream(
3598
 
                    self.source.revisions.get_record_stream(
3599
 
                        self.source.revisions.keys(),
3600
 
                        'topological', True))
3601
 
            finally:
3602
 
                pb.finished()
3603
 
        else:
3604
 
            self.target.fetch(self.source, revision_id=revision_id)
3605
 
 
3606
 
    @needs_read_lock
3607
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3608
 
        """See InterRepository.missing_revision_ids()."""
3609
 
        # we want all revisions to satisfy revision_id in source.
3610
 
        # but we don't want to stat every file here and there.
3611
 
        # we want then, all revisions other needs to satisfy revision_id
3612
 
        # checked, but not those that we have locally.
3613
 
        # so the first thing is to get a subset of the revisions to
3614
 
        # satisfy revision_id in source, and then eliminate those that
3615
 
        # we do already have.
3616
 
        # this is slow on high latency connection to self, but as this
3617
 
        # disk format scales terribly for push anyway due to rewriting
3618
 
        # inventory.weave, this is considered acceptable.
3619
 
        # - RBC 20060209
3620
 
        if revision_id is not None:
3621
 
            source_ids = self.source.get_ancestry(revision_id)
3622
 
            if source_ids[0] is not None:
3623
 
                raise AssertionError()
3624
 
            source_ids.pop(0)
3625
 
        else:
3626
 
            source_ids = self.source._all_possible_ids()
3627
 
        source_ids_set = set(source_ids)
3628
 
        # source_ids is the worst possible case we may need to pull.
3629
 
        # now we want to filter source_ids against what we actually
3630
 
        # have in target, but don't try to check for existence where we know
3631
 
        # we do not have a revision as that would be pointless.
3632
 
        target_ids = set(self.target._all_possible_ids())
3633
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3634
 
        actually_present_revisions = set(
3635
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3636
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3637
 
        if revision_id is not None:
3638
 
            # we used get_ancestry to determine source_ids then we are assured all
3639
 
            # revisions referenced are present as they are installed in topological order.
3640
 
            # and the tip revision was validated by get_ancestry.
3641
 
            result_set = required_revisions
3642
 
        else:
3643
 
            # if we just grabbed the possibly available ids, then
3644
 
            # we only have an estimate of whats available and need to validate
3645
 
            # that against the revision records.
3646
 
            result_set = set(
3647
 
                self.source._eliminate_revisions_not_present(required_revisions))
3648
 
        return self.source.revision_ids_to_search_result(result_set)
3649
 
 
3650
 
 
3651
 
class InterKnitRepo(InterSameDataRepository):
3652
 
    """Optimised code paths between Knit based repositories."""
3653
 
 
3654
 
    @classmethod
3655
 
    def _get_repo_format_to_test(self):
3656
 
        from bzrlib.repofmt import knitrepo
3657
 
        return knitrepo.RepositoryFormatKnit1()
3658
 
 
3659
 
    @staticmethod
3660
 
    def is_compatible(source, target):
3661
 
        """Be compatible with known Knit formats.
3662
 
 
3663
 
        We don't test for the stores being of specific types because that
3664
 
        could lead to confusing results, and there is no need to be
3665
 
        overly general.
3666
 
        """
3667
 
        from bzrlib.repofmt.knitrepo import RepositoryFormatKnit
3668
 
        try:
3669
 
            are_knits = (isinstance(source._format, RepositoryFormatKnit) and
3670
 
                isinstance(target._format, RepositoryFormatKnit))
3671
 
        except AttributeError:
3672
 
            return False
3673
 
        return are_knits and InterRepository._same_model(source, target)
3674
 
 
3675
 
    @needs_read_lock
3676
 
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3677
 
        """See InterRepository.missing_revision_ids()."""
3678
 
        if revision_id is not None:
3679
 
            source_ids = self.source.get_ancestry(revision_id)
3680
 
            if source_ids[0] is not None:
3681
 
                raise AssertionError()
3682
 
            source_ids.pop(0)
3683
 
        else:
3684
 
            source_ids = self.source.all_revision_ids()
3685
 
        source_ids_set = set(source_ids)
3686
 
        # source_ids is the worst possible case we may need to pull.
3687
 
        # now we want to filter source_ids against what we actually
3688
 
        # have in target, but don't try to check for existence where we know
3689
 
        # we do not have a revision as that would be pointless.
3690
 
        target_ids = set(self.target.all_revision_ids())
3691
 
        possibly_present_revisions = target_ids.intersection(source_ids_set)
3692
 
        actually_present_revisions = set(
3693
 
            self.target._eliminate_revisions_not_present(possibly_present_revisions))
3694
 
        required_revisions = source_ids_set.difference(actually_present_revisions)
3695
 
        if revision_id is not None:
3696
 
            # we used get_ancestry to determine source_ids then we are assured all
3697
 
            # revisions referenced are present as they are installed in topological order.
3698
 
            # and the tip revision was validated by get_ancestry.
3699
 
            result_set = required_revisions
3700
 
        else:
3701
 
            # if we just grabbed the possibly available ids, then
3702
 
            # we only have an estimate of whats available and need to validate
3703
 
            # that against the revision records.
3704
 
            result_set = set(
3705
 
                self.source._eliminate_revisions_not_present(required_revisions))
3706
 
        return self.source.revision_ids_to_search_result(result_set)
3707
 
 
3708
 
 
3709
3568
class InterDifferingSerializer(InterRepository):
3710
3569
 
3711
3570
    @classmethod
3813
3672
                basis_id, delta, current_revision_id, parents_parents)
3814
3673
            cache[current_revision_id] = parent_tree
3815
3674
 
3816
 
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
 
3675
    def _fetch_batch(self, revision_ids, basis_id, cache):
3817
3676
        """Fetch across a few revisions.
3818
3677
 
3819
3678
        :param revision_ids: The revisions to copy
3820
3679
        :param basis_id: The revision_id of a tree that must be in cache, used
3821
3680
            as a basis for delta when no other base is available
3822
3681
        :param cache: A cache of RevisionTrees that we can use.
3823
 
        :param a_graph: A Graph object to determine the heads() of the
3824
 
            rich-root data stream.
3825
3682
        :return: The revision_id of the last converted tree. The RevisionTree
3826
3683
            for it will be in cache
3827
3684
        """
3895
3752
        if root_keys_to_create:
3896
3753
            root_stream = _mod_fetch._new_root_data_stream(
3897
3754
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3898
 
                self.source, graph=a_graph)
 
3755
                self.source)
3899
3756
            to_texts.insert_record_stream(root_stream)
3900
3757
        to_texts.insert_record_stream(from_texts.get_record_stream(
3901
3758
            text_keys, self.target._format._fetch_order,
3958
3815
        cache[basis_id] = basis_tree
3959
3816
        del basis_tree # We don't want to hang on to it here
3960
3817
        hints = []
3961
 
        if self._converting_to_rich_root and len(revision_ids) > 100:
3962
 
            a_graph = _mod_fetch._get_rich_root_heads_graph(self.source,
3963
 
                                                            revision_ids)
3964
 
        else:
3965
 
            a_graph = None
 
3818
        a_graph = None
3966
3819
 
3967
3820
        for offset in range(0, len(revision_ids), batch_size):
3968
3821
            self.target.start_write_group()
3970
3823
                pb.update('Transferring revisions', offset,
3971
3824
                          len(revision_ids))
3972
3825
                batch = revision_ids[offset:offset+batch_size]
3973
 
                basis_id = self._fetch_batch(batch, basis_id, cache,
3974
 
                                             a_graph=a_graph)
 
3826
                basis_id = self._fetch_batch(batch, basis_id, cache)
3975
3827
            except:
3976
3828
                self.source._safe_to_return_from_cache = False
3977
3829
                self.target.abort_write_group()
3986
3838
                  len(revision_ids))
3987
3839
 
3988
3840
    @needs_write_lock
3989
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
 
3841
    def fetch(self, revision_id=None, find_ghosts=False,
3990
3842
            fetch_spec=None):
3991
3843
        """See InterRepository.fetch()."""
3992
3844
        if fetch_spec is not None:
3993
 
            raise AssertionError("Not implemented yet...")
 
3845
            revision_ids = fetch_spec.get_keys()
 
3846
        else:
 
3847
            revision_ids = None
3994
3848
        ui.ui_factory.warn_experimental_format_fetch(self)
3995
3849
        if (not self.source.supports_rich_root()
3996
3850
            and self.target.supports_rich_root()):
4003
3857
            ui.ui_factory.show_user_warning('cross_format_fetch',
4004
3858
                from_format=self.source._format,
4005
3859
                to_format=self.target._format)
4006
 
        revision_ids = self.target.search_missing_revision_ids(self.source,
4007
 
            revision_id, find_ghosts=find_ghosts).get_keys()
 
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()
4008
3868
        if not revision_ids:
4009
3869
            return 0, 0
4010
3870
        revision_ids = tsort.topo_sort(
4014
3874
        # Walk though all revisions; get inventory deltas, copy referenced
4015
3875
        # texts that delta references, insert the delta, revision and
4016
3876
        # signature.
4017
 
        if pb is None:
4018
 
            my_pb = ui.ui_factory.nested_progress_bar()
4019
 
            pb = my_pb
4020
 
        else:
4021
 
            symbol_versioning.warn(
4022
 
                symbol_versioning.deprecated_in((1, 14, 0))
4023
 
                % "pb parameter to fetch()")
4024
 
            my_pb = None
 
3877
        pb = ui.ui_factory.nested_progress_bar()
4025
3878
        try:
4026
3879
            self._fetch_all_revisions(revision_ids, pb)
4027
3880
        finally:
4028
 
            if my_pb is not None:
4029
 
                my_pb.finished()
 
3881
            pb.finished()
4030
3882
        return len(revision_ids), 0
4031
3883
 
4032
3884
    def _get_basis(self, first_revision_id):
4043
3895
            basis_id = first_rev.parent_ids[0]
4044
3896
            # only valid as a basis if the target has it
4045
3897
            self.target.get_revision(basis_id)
4046
 
            # Try to get a basis tree - if its a ghost it will hit the
 
3898
            # Try to get a basis tree - if it's a ghost it will hit the
4047
3899
            # NoSuchRevision case.
4048
3900
            basis_tree = self.source.revision_tree(basis_id)
4049
3901
        except (IndexError, errors.NoSuchRevision):
4054
3906
 
4055
3907
InterRepository.register_optimiser(InterDifferingSerializer)
4056
3908
InterRepository.register_optimiser(InterSameDataRepository)
4057
 
InterRepository.register_optimiser(InterWeaveRepo)
4058
 
InterRepository.register_optimiser(InterKnitRepo)
4059
3909
 
4060
3910
 
4061
3911
class CopyConverter(object):
4106
3956
        pb.finished()
4107
3957
 
4108
3958
 
4109
 
_unescape_map = {
4110
 
    'apos':"'",
4111
 
    'quot':'"',
4112
 
    'amp':'&',
4113
 
    'lt':'<',
4114
 
    'gt':'>'
4115
 
}
4116
 
 
4117
 
 
4118
 
def _unescaper(match, _map=_unescape_map):
4119
 
    code = match.group(1)
4120
 
    try:
4121
 
        return _map[code]
4122
 
    except KeyError:
4123
 
        if not code.startswith('#'):
4124
 
            raise
4125
 
        return unichr(int(code[1:])).encode('utf8')
4126
 
 
4127
 
 
4128
 
_unescape_re = None
4129
 
 
4130
 
 
4131
 
def _unescape_xml(data):
4132
 
    """Unescape predefined XML entities in a string of data."""
4133
 
    global _unescape_re
4134
 
    if _unescape_re is None:
4135
 
        _unescape_re = re.compile('\&([^;]*);')
4136
 
    return _unescape_re.sub(_unescaper, data)
4137
 
 
4138
 
 
4139
3959
class _VersionedFileChecker(object):
4140
3960
 
4141
3961
    def __init__(self, repository, text_key_references=None, ancestors=None):
4200
4020
        return wrong_parents, unused_keys
4201
4021
 
4202
4022
 
4203
 
def _old_get_graph(repository, revision_id):
4204
 
    """DO NOT USE. That is all. I'm serious."""
4205
 
    graph = repository.get_graph()
4206
 
    revision_graph = dict(((key, value) for key, value in
4207
 
        graph.iter_ancestry([revision_id]) if value is not None))
4208
 
    return _strip_NULL_ghosts(revision_graph)
4209
 
 
4210
 
 
4211
4023
def _strip_NULL_ghosts(revision_graph):
4212
4024
    """Also don't use this. more compatibility code for unmigrated clients."""
4213
4025
    # Filter ghosts, and null:
4249
4061
                is_resume = False
4250
4062
            try:
4251
4063
                # locked_insert_stream performs a commit|suspend.
4252
 
                return self._locked_insert_stream(stream, src_format, is_resume)
 
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()
4253
4080
            except:
4254
4081
                self.target_repo.abort_write_group(suppress_errors=True)
4255
4082
                raise
4256
4083
        finally:
4257
4084
            self.target_repo.unlock()
4258
4085
 
4259
 
    def _locked_insert_stream(self, stream, src_format, is_resume):
 
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')
4260
4104
        to_serializer = self.target_repo._format._serializer
4261
4105
        src_serializer = src_format._serializer
4262
4106
        new_pack = None
4302
4146
                # required if the serializers are different only in terms of
4303
4147
                # the inventory.
4304
4148
                if src_serializer == to_serializer:
4305
 
                    self.target_repo.revisions.insert_record_stream(
4306
 
                        substream)
 
4149
                    self.target_repo.revisions.insert_record_stream(substream)
4307
4150
                else:
4308
4151
                    self._extract_and_insert_revisions(substream,
4309
4152
                        src_serializer)
4342
4185
            # cannot even attempt suspending, and missing would have failed
4343
4186
            # during stream insertion.
4344
4187
            missing_keys = set()
4345
 
        else:
4346
 
            if missing_keys:
4347
 
                # suspend the write group and tell the caller what we is
4348
 
                # missing. We know we can suspend or else we would not have
4349
 
                # entered this code path. (All repositories that can handle
4350
 
                # missing keys can handle suspending a write group).
4351
 
                write_group_tokens = self.target_repo.suspend_write_group()
4352
 
                return write_group_tokens, missing_keys
4353
 
        hint = self.target_repo.commit_write_group()
4354
 
        if (to_serializer != src_serializer and
4355
 
            self.target_repo._format.pack_compresses):
4356
 
            self.target_repo.pack(hint=hint)
4357
 
        return [], set()
 
4188
        return missing_keys
4358
4189
 
4359
4190
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4360
4191
        target_rich_root = self.target_repo._format.rich_root_data
4367
4198
                parse_result = deserialiser.parse_text_bytes(
4368
4199
                    inventory_delta_bytes)
4369
4200
            except inventory_delta.IncompatibleInventoryDelta, err:
4370
 
                trace.mutter("Incompatible delta: %s", err.msg)
 
4201
                mutter("Incompatible delta: %s", err.msg)
4371
4202
                raise errors.IncompatibleRevision(self.target_repo._format)
4372
4203
            basis_id, new_id, rich_root, tree_refs, inv_delta = parse_result
4373
4204
            revision_id = new_id
4417
4248
        """Create a StreamSource streaming from from_repository."""
4418
4249
        self.from_repository = from_repository
4419
4250
        self.to_format = to_format
 
4251
        self._record_counter = RecordCounter()
4420
4252
 
4421
4253
    def delta_on_metadata(self):
4422
4254
        """Return True if delta's are permitted on metadata streams.
4707
4539
    except StopIteration:
4708
4540
        # No more history
4709
4541
        return
4710