/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: Robert Collins
  • Date: 2010-05-06 11:08:10 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506110810-h3j07fh5gmw54s25
Cleaner matcher matching revised unlocking protocol.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
16
16
 
17
17
from bzrlib.lazy_import import lazy_import
18
18
lazy_import(globals(), """
 
19
import cStringIO
 
20
import re
19
21
import time
20
22
 
21
23
from bzrlib import (
22
24
    bzrdir,
23
25
    check,
 
26
    chk_map,
24
27
    config,
25
 
    controldir,
26
28
    debug,
 
29
    errors,
27
30
    fetch as _mod_fetch,
28
31
    fifo_cache,
29
32
    generate_ids,
30
33
    gpg,
31
34
    graph,
 
35
    inventory,
32
36
    inventory_delta,
 
37
    lazy_regex,
33
38
    lockable_files,
34
39
    lockdir,
35
40
    lru_cache,
36
41
    osutils,
37
42
    revision as _mod_revision,
38
43
    static_tuple,
 
44
    symbol_versioning,
 
45
    trace,
39
46
    tsort,
 
47
    ui,
40
48
    versionedfile,
41
49
    )
42
50
from bzrlib.bundle import serializer
43
 
from bzrlib.recordcounter import RecordCounter
44
 
from bzrlib.revisiontree import InventoryRevisionTree
 
51
from bzrlib.revisiontree import RevisionTree
45
52
from bzrlib.store.versioned import VersionedFileStore
46
53
from bzrlib.testament import Testament
47
54
""")
48
55
 
49
 
from bzrlib import (
50
 
    errors,
51
 
    registry,
52
 
    symbol_versioning,
53
 
    ui,
54
 
    )
55
56
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
56
57
from bzrlib.inter import InterObject
57
58
from bzrlib.inventory import (
60
61
    ROOT_ID,
61
62
    entry_factory,
62
63
    )
63
 
from bzrlib.lock import _RelockDebugMixin, LogicalLockResult
 
64
from bzrlib.lock import _RelockDebugMixin
 
65
from bzrlib import registry
64
66
from bzrlib.trace import (
65
67
    log_exception_quietly, note, mutter, mutter_callsite, warning)
66
68
 
69
71
_deprecation_warning_done = False
70
72
 
71
73
 
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
 
 
80
74
class CommitBuilder(object):
81
75
    """Provides an interface to build up a commit.
82
76
 
88
82
    record_root_entry = True
89
83
    # the default CommitBuilder does not manage trees whose root is versioned.
90
84
    _versioned_root = False
91
 
    # this commit builder supports the record_entry_contents interface
92
 
    supports_record_entry_contents = True
93
85
 
94
86
    def __init__(self, repository, parents, config, timestamp=None,
95
87
                 timezone=None, committer=None, revprops=None,
96
 
                 revision_id=None, lossy=False):
 
88
                 revision_id=None):
97
89
        """Initiate a CommitBuilder.
98
90
 
99
91
        :param repository: Repository to commit to.
100
92
        :param parents: Revision ids of the parents of the new revision.
 
93
        :param config: Configuration to use.
101
94
        :param timestamp: Optional timestamp recorded for commit.
102
95
        :param timezone: Optional timezone for timestamp.
103
96
        :param committer: Optional committer to set for commit.
104
97
        :param revprops: Optional dictionary of revision properties.
105
98
        :param revision_id: Optional revision id.
106
 
        :param lossy: Whether to discard data that can not be natively
107
 
            represented, when pushing to a foreign VCS 
108
99
        """
109
100
        self._config = config
110
 
        self._lossy = lossy
111
101
 
112
102
        if committer is None:
113
103
            self._committer = self._config.username()
114
 
        elif not isinstance(committer, unicode):
115
 
            self._committer = committer.decode() # throw if non-ascii
116
104
        else:
117
105
            self._committer = committer
118
106
 
172
160
            self._validate_unicode_text(value,
173
161
                                        'revision property (%s)' % (key,))
174
162
 
175
 
    def _ensure_fallback_inventories(self):
176
 
        """Ensure that appropriate inventories are available.
177
 
 
178
 
        This only applies to repositories that are stacked, and is about
179
 
        enusring the stacking invariants. Namely, that for any revision that is
180
 
        present, we either have all of the file content, or we have the parent
181
 
        inventory and the delta file content.
182
 
        """
183
 
        if not self.repository._fallback_repositories:
184
 
            return
185
 
        if not self.repository._format.supports_chks:
186
 
            raise errors.BzrError("Cannot commit directly to a stacked branch"
187
 
                " in pre-2a formats. See "
188
 
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
189
 
        # This is a stacked repo, we need to make sure we have the parent
190
 
        # inventories for the parents.
191
 
        parent_keys = [(p,) for p in self.parents]
192
 
        parent_map = self.repository.inventories._index.get_parent_map(parent_keys)
193
 
        missing_parent_keys = set([pk for pk in parent_keys
194
 
                                       if pk not in parent_map])
195
 
        fallback_repos = list(reversed(self.repository._fallback_repositories))
196
 
        missing_keys = [('inventories', pk[0])
197
 
                        for pk in missing_parent_keys]
198
 
        resume_tokens = []
199
 
        while missing_keys and fallback_repos:
200
 
            fallback_repo = fallback_repos.pop()
201
 
            source = fallback_repo._get_source(self.repository._format)
202
 
            sink = self.repository._get_sink()
203
 
            stream = source.get_stream_for_missing_keys(missing_keys)
204
 
            missing_keys = sink.insert_stream_without_locking(stream,
205
 
                self.repository._format)
206
 
        if missing_keys:
207
 
            raise errors.BzrError('Unable to fill in parent inventories for a'
208
 
                                  ' stacked branch')
209
 
 
210
163
    def commit(self, message):
211
164
        """Make the actual commit.
212
165
 
224
177
        rev.parent_ids = self.parents
225
178
        self.repository.add_revision(self._new_revision_id, rev,
226
179
            self.new_inventory, self._config)
227
 
        self._ensure_fallback_inventories()
228
180
        self.repository.commit_write_group()
229
181
        return self._new_revision_id
230
182
 
236
188
    def revision_tree(self):
237
189
        """Return the tree that was just committed.
238
190
 
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
 
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
243
195
        memory.
244
196
        """
245
197
        if self.new_inventory is None:
246
198
            self.new_inventory = self.repository.get_inventory(
247
199
                self._new_revision_id)
248
 
        return InventoryRevisionTree(self.repository, self.new_inventory,
 
200
        return RevisionTree(self.repository, self.new_inventory,
249
201
            self._new_revision_id)
250
202
 
251
203
    def finish_inventory(self):
279
231
 
280
232
    def _gen_revision_id(self):
281
233
        """Return new revision-id."""
282
 
        return generate_ids.gen_revision_id(self._committer, self._timestamp)
 
234
        return generate_ids.gen_revision_id(self._config.username(),
 
235
                                            self._timestamp)
283
236
 
284
237
    def _generate_revision_if_needed(self):
285
238
        """Create a revision id if None was supplied.
325
278
 
326
279
        :param tree: The tree which is being committed.
327
280
        """
328
 
        if len(self.parents) == 0:
329
 
            raise errors.RootMissing()
 
281
        # NB: if there are no parents then this method is not called, so no
 
282
        # need to guard on parents having length.
330
283
        entry = entry_factory['directory'](tree.path2id(''), '',
331
284
            None)
332
285
        entry.revision = self._new_revision_id
470
423
            else:
471
424
                # we don't need to commit this, because the caller already
472
425
                # determined that an existing revision of this file is
473
 
                # appropriate. If it's not being considered for committing then
 
426
                # appropriate. If its not being considered for committing then
474
427
                # it and all its parents to the root must be unaltered so
475
428
                # no-change against the basis.
476
429
                if ie.revision == self._new_revision_id:
792
745
                    # after iter_changes examines and decides it has changed,
793
746
                    # we will unconditionally record a new version even if some
794
747
                    # other process reverts it while commit is running (with
795
 
                    # the revert happening after iter_changes did its
 
748
                    # the revert happening after iter_changes did it's
796
749
                    # examination).
797
750
                    if change[7][1]:
798
751
                        entry.executable = True
907
860
        # versioned roots do not change unless the tree found a change.
908
861
 
909
862
 
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
 
 
927
863
######################################################################
928
864
# Repositories
929
865
 
930
866
 
931
 
class Repository(_RelockDebugMixin, controldir.ControlComponent):
 
867
class Repository(_RelockDebugMixin, bzrdir.ControlComponent):
932
868
    """Repository holding history for one or more branches.
933
869
 
934
870
    The repository holds and retrieves historical information including
981
917
        pointing to .bzr/repository.
982
918
    """
983
919
 
984
 
    # What class to use for a CommitBuilder. Often it's simpler to change this
 
920
    # What class to use for a CommitBuilder. Often its simpler to change this
985
921
    # in a Repository class subclass rather than to override
986
922
    # get_commit_builder.
987
923
    _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
        )
988
930
 
989
931
    def abort_write_group(self, suppress_errors=False):
990
932
        """Commit the contents accrued within the current write group.
1076
1018
                " id and insertion revid (%r, %r)"
1077
1019
                % (inv.revision_id, revision_id))
1078
1020
        if inv.root is None:
1079
 
            raise errors.RootMissing()
 
1021
            raise AssertionError()
1080
1022
        return self._add_inventory_checked(revision_id, inv, parents)
1081
1023
 
1082
1024
    def _add_inventory_checked(self, revision_id, inv, parents):
1163
1105
        if config is not None and config.signature_needed():
1164
1106
            if inv is None:
1165
1107
                inv = self.get_inventory(revision_id)
1166
 
            tree = InventoryRevisionTree(self, inv, revision_id)
1167
 
            testament = Testament(rev, tree)
1168
 
            plaintext = testament.as_short_text()
 
1108
            plaintext = Testament(rev, inv).as_short_text()
1169
1109
            self.store_revision_signature(
1170
1110
                gpg.GPGStrategy(config), plaintext, revision_id)
1171
1111
        # check inventory present
1436
1376
        data during reads, and allows a 'write_group' to be obtained. Write
1437
1377
        groups must be used for actual data insertion.
1438
1378
 
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
 
 
1445
1379
        :param token: if this is already locked, then lock_write will fail
1446
1380
            unless the token matches the existing lock.
1447
1381
        :returns: a token if this instance supports tokens, otherwise None.
1450
1384
        :raises MismatchedToken: if the specified token doesn't match the token
1451
1385
            of the existing lock.
1452
1386
        :seealso: start_write_group.
1453
 
        :return: A RepositoryWriteLockResult.
 
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
1454
1393
        """
1455
1394
        locked = self.is_locked()
1456
 
        token = self.control_files.lock_write(token=token)
 
1395
        result = self.control_files.lock_write(token=token)
1457
1396
        if not locked:
1458
1397
            self._warn_if_deprecated()
1459
1398
            self._note_lock('w')
1461
1400
                # Writes don't affect fallback repos
1462
1401
                repo.lock_read()
1463
1402
            self._refresh_data()
1464
 
        return RepositoryWriteLockResult(self.unlock, token)
 
1403
        return result
1465
1404
 
1466
1405
    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
 
        """
1472
1406
        locked = self.is_locked()
1473
1407
        self.control_files.lock_read()
1474
1408
        if not locked:
1477
1411
            for repo in self._fallback_repositories:
1478
1412
                repo.lock_read()
1479
1413
            self._refresh_data()
1480
 
        return LogicalLockResult(self.unlock)
1481
1414
 
1482
1415
    def get_physical_lock_status(self):
1483
1416
        return self.control_files.get_physical_lock_status()
1589
1522
        return ret
1590
1523
 
1591
1524
    @needs_read_lock
1592
 
    def search_missing_revision_ids(self, other,
1593
 
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1594
 
            find_ghosts=True, revision_ids=None, if_present_ids=None):
 
1525
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1595
1526
        """Return the revision ids that other has that this does not.
1596
1527
 
1597
1528
        These are returned in topological order.
1598
1529
 
1599
1530
        revision_id: only return revision ids included by revision_id.
1600
1531
        """
1601
 
        if symbol_versioning.deprecated_passed(revision_id):
1602
 
            symbol_versioning.warn(
1603
 
                'search_missing_revision_ids(revision_id=...) was '
1604
 
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
1605
 
                DeprecationWarning, stacklevel=3)
1606
 
            if revision_ids is not None:
1607
 
                raise AssertionError(
1608
 
                    'revision_ids is mutually exclusive with revision_id')
1609
 
            if revision_id is not None:
1610
 
                revision_ids = [revision_id]
1611
1532
        return InterRepository.get(other, self).search_missing_revision_ids(
1612
 
            find_ghosts=find_ghosts, revision_ids=revision_ids,
1613
 
            if_present_ids=if_present_ids)
 
1533
            revision_id, find_ghosts)
1614
1534
 
1615
1535
    @staticmethod
1616
1536
    def open(base):
1714
1634
        return missing_keys
1715
1635
 
1716
1636
    def refresh_data(self):
1717
 
        """Re-read any data needed to synchronise with disk.
 
1637
        """Re-read any data needed to to synchronise with disk.
1718
1638
 
1719
1639
        This method is intended to be called after another repository instance
1720
1640
        (such as one used by a smart server) has inserted data into the
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.
 
1641
        repository. It may not be called during a write group, but may be
 
1642
        called at any other time.
1726
1643
        """
 
1644
        if self.is_in_write_group():
 
1645
            raise errors.InternalBzrError(
 
1646
                "May not refresh_data while in a write group.")
1727
1647
        self._refresh_data()
1728
1648
 
1729
1649
    def resume_write_group(self, tokens):
1738
1658
    def _resume_write_group(self, tokens):
1739
1659
        raise errors.UnsuspendableWriteGroup(self)
1740
1660
 
1741
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
 
1661
    def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1742
1662
            fetch_spec=None):
1743
1663
        """Fetch the content required to construct revision_id from source.
1744
1664
 
1768
1688
                "May not fetch while in a write group.")
1769
1689
        # fast path same-url fetch operations
1770
1690
        # TODO: lift out to somewhere common with RemoteRepository
1771
 
        # <https://bugs.launchpad.net/bzr/+bug/401646>
 
1691
        # <https://bugs.edge.launchpad.net/bzr/+bug/401646>
1772
1692
        if (self.has_same_location(source)
1773
1693
            and fetch_spec is None
1774
1694
            and self._has_same_fallbacks(source)):
1778
1698
                not _mod_revision.is_null(revision_id)):
1779
1699
                self.get_revision(revision_id)
1780
1700
            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.
1781
1704
        inter = InterRepository.get(source, self)
1782
 
        return inter.fetch(revision_id=revision_id,
 
1705
        return inter.fetch(revision_id=revision_id, pb=pb,
1783
1706
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1784
1707
 
1785
1708
    def create_bundle(self, target, base, fileobj, format=None):
1787
1710
 
1788
1711
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1789
1712
                           timezone=None, committer=None, revprops=None,
1790
 
                           revision_id=None, lossy=False):
 
1713
                           revision_id=None):
1791
1714
        """Obtain a CommitBuilder for this repository.
1792
1715
 
1793
1716
        :param branch: Branch to commit to.
1798
1721
        :param committer: Optional committer to set for commit.
1799
1722
        :param revprops: Optional dictionary of revision properties.
1800
1723
        :param revision_id: Optional revision id.
1801
 
        :param lossy: Whether to discard data that can not be natively
1802
 
            represented, when pushing to a foreign VCS
1803
1724
        """
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 "
 
1725
        if self._fallback_repositories:
 
1726
            raise errors.BzrError("Cannot commit from a lightweight checkout "
 
1727
                "to a stacked branch. See "
1807
1728
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1808
1729
        result = self._commit_builder_class(self, parents, config,
1809
 
            timestamp, timezone, committer, revprops, revision_id,
1810
 
            lossy)
 
1730
            timestamp, timezone, committer, revprops, revision_id)
1811
1731
        self.start_write_group()
1812
1732
        return result
1813
1733
 
2059
1979
        w = self.inventories
2060
1980
        pb = ui.ui_factory.nested_progress_bar()
2061
1981
        try:
2062
 
            return self._serializer._find_text_key_references(
 
1982
            return self._find_text_key_references_from_xml_inventory_lines(
2063
1983
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
2064
1984
        finally:
2065
1985
            pb.finished()
2066
1986
 
 
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._serializer._find_text_key_references(
 
2102
        seen = set(self._find_text_key_references_from_xml_inventory_lines(
2103
2103
                line_iterator).iterkeys())
2104
2104
        parent_keys = self._find_parent_keys_of_revisions(revision_keys)
2105
 
        parent_seen = set(self._serializer._find_text_key_references(
 
2105
        parent_seen = set(self._find_text_key_references_from_xml_inventory_lines(
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
 
        stop_revisions = (None, _mod_revision.NULL_REVISION)
2480
 
        return graph.iter_lefthand_ancestry(revision_id, stop_revisions)
 
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]
2481
2492
 
2482
2493
    def is_shared(self):
2483
2494
        """Return True if this repository is flagged as a shared repository."""
2515
2526
        # TODO: refactor this to use an existing revision object
2516
2527
        # so we don't need to read it in twice.
2517
2528
        if revision_id == _mod_revision.NULL_REVISION:
2518
 
            return InventoryRevisionTree(self,
2519
 
                Inventory(root_id=None), _mod_revision.NULL_REVISION)
 
2529
            return RevisionTree(self, Inventory(root_id=None),
 
2530
                                _mod_revision.NULL_REVISION)
2520
2531
        else:
2521
2532
            inv = self.get_inventory(revision_id)
2522
 
            return InventoryRevisionTree(self, inv, revision_id)
 
2533
            return RevisionTree(self, inv, revision_id)
2523
2534
 
2524
2535
    def revision_trees(self, revision_ids):
2525
2536
        """Return Trees for revisions in this repository.
2529
2540
        """
2530
2541
        inventories = self.iter_inventories(revision_ids)
2531
2542
        for inv in inventories:
2532
 
            yield InventoryRevisionTree(self, inv, inv.revision_id)
 
2543
            yield RevisionTree(self, inv, inv.revision_id)
2533
2544
 
2534
2545
    def _filtered_revision_trees(self, revision_ids, file_ids):
2535
2546
        """Return Tree for a revision on this branch with only some files.
2545
2556
            # Should we introduce a FilteredRevisionTree class rather
2546
2557
            # than pre-filter the inventory here?
2547
2558
            filtered_inv = inv.filter(file_ids)
2548
 
            yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
 
2559
            yield RevisionTree(self, filtered_inv, filtered_inv.revision_id)
2549
2560
 
2550
2561
    @needs_read_lock
2551
2562
    def get_ancestry(self, revision_id, topo_sorted=True):
2584
2595
        types it should be a no-op that just returns.
2585
2596
 
2586
2597
        This stub method does not require a lock, but subclasses should use
2587
 
        @needs_write_lock as this is a long running call it's reasonable to
 
2598
        @needs_write_lock as this is a long running call its reasonable to
2588
2599
        implicitly lock for the user.
2589
2600
 
2590
2601
        :param hint: If not supplied, the whole repository is packed.
2736
2747
        return result
2737
2748
 
2738
2749
    def _warn_if_deprecated(self, branch=None):
2739
 
        if not self._format.is_deprecated():
2740
 
            return
2741
2750
        global _deprecation_warning_done
2742
2751
        if _deprecation_warning_done:
2743
2752
            return
2773
2782
                except UnicodeDecodeError:
2774
2783
                    raise errors.NonAsciiRevisionId(method, self)
2775
2784
 
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.
 
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?
2779
2788
 
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).
 
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.
2784
2793
        """
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.")
 
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')
2806
2829
 
2807
2830
 
2808
2831
def install_revision(repository, rev, revision_tree):
2867
2890
        for revision, tree in parent_trees.iteritems():
2868
2891
            if ie.file_id not in tree:
2869
2892
                continue
2870
 
            parent_id = tree.get_file_revision(ie.file_id)
 
2893
            parent_id = tree.inventory[ie.file_id].revision
2871
2894
            if parent_id in text_parents:
2872
2895
                continue
2873
2896
            text_parents.append((ie.file_id, parent_id))
2942
2965
            control_files)
2943
2966
 
2944
2967
 
2945
 
class RepositoryFormatRegistry(controldir.ControlComponentFormatRegistry):
2946
 
    """Repository format registry."""
2947
 
 
2948
 
    def get_default(self):
2949
 
        """Return the current default format."""
2950
 
        from bzrlib import bzrdir
2951
 
        return bzrdir.format_registry.make_bzrdir('default').repository_format
2952
 
 
2953
 
 
2954
2968
network_format_registry = registry.FormatRegistry()
2955
2969
"""Registry of formats indexed by their network name.
2956
2970
 
2960
2974
"""
2961
2975
 
2962
2976
 
2963
 
format_registry = RepositoryFormatRegistry(network_format_registry)
 
2977
format_registry = registry.FormatRegistry(network_format_registry)
2964
2978
"""Registry of formats, indexed by their BzrDirMetaFormat format string.
2965
2979
 
2966
2980
This can contain either format instances themselves, or classes/factories that
2971
2985
#####################################################################
2972
2986
# Repository Formats
2973
2987
 
2974
 
class RepositoryFormat(controldir.ControlComponentFormat):
 
2988
class RepositoryFormat(object):
2975
2989
    """A repository format.
2976
2990
 
2977
2991
    Formats provide four things:
3038
3052
    supports_tree_reference = None
3039
3053
    # Is the format experimental ?
3040
3054
    experimental = False
3041
 
    # Does this repository format escape funky characters, or does it create files with
3042
 
    # similar names as the versioned files in its contents on disk ?
3043
 
    supports_funky_characters = None
3044
 
    # Does this repository format support leaving locks?
3045
 
    supports_leaving_lock = None
3046
 
    # Does this format support the full VersionedFiles interface?
3047
 
    supports_full_versioned_files = None
3048
 
    # Does this format support signing revision signatures?
3049
 
    supports_revision_signatures = True
3050
 
    # Can the revision graph have incorrect parents?
3051
 
    revision_graph_can_have_wrong_parents = None
3052
3055
 
3053
3056
    def __repr__(self):
3054
3057
        return "%s()" % self.__class__.__name__
3079
3082
                                            kind='repository')
3080
3083
 
3081
3084
    @classmethod
3082
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3083
3085
    def register_format(klass, format):
3084
 
        format_registry.register(format)
 
3086
        format_registry.register(format.get_format_string(), format)
3085
3087
 
3086
3088
    @classmethod
3087
 
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3088
3089
    def unregister_format(klass, format):
3089
 
        format_registry.remove(format)
 
3090
        format_registry.remove(format.get_format_string())
3090
3091
 
3091
3092
    @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
 
        return format_registry.get_default()
 
3095
        from bzrlib import bzrdir
 
3096
        return bzrdir.format_registry.make_bzrdir('default').repository_format
3096
3097
 
3097
3098
    def get_format_string(self):
3098
3099
        """Return the ASCII format string that identifies this format.
3106
3107
        """Return the short description for this format."""
3107
3108
        raise NotImplementedError(self.get_format_description)
3108
3109
 
 
3110
    # TODO: this shouldn't be in the base class, it's specific to things that
 
3111
    # use weaves or knits -- mbp 20070207
 
3112
    def _get_versioned_file_store(self,
 
3113
                                  name,
 
3114
                                  transport,
 
3115
                                  control_files,
 
3116
                                  prefixed=True,
 
3117
                                  versionedfile_class=None,
 
3118
                                  versionedfile_kwargs={},
 
3119
                                  escaped=False):
 
3120
        if versionedfile_class is None:
 
3121
            versionedfile_class = self._versionedfile_class
 
3122
        weave_transport = control_files._transport.clone(name)
 
3123
        dir_mode = control_files._dir_mode
 
3124
        file_mode = control_files._file_mode
 
3125
        return VersionedFileStore(weave_transport, prefixed=prefixed,
 
3126
                                  dir_mode=dir_mode,
 
3127
                                  file_mode=file_mode,
 
3128
                                  versionedfile_class=versionedfile_class,
 
3129
                                  versionedfile_kwargs=versionedfile_kwargs,
 
3130
                                  escaped=escaped)
 
3131
 
3109
3132
    def initialize(self, a_bzrdir, shared=False):
3110
3133
        """Initialize a repository of this format in a_bzrdir.
3111
3134
 
3127
3150
        """
3128
3151
        return True
3129
3152
 
3130
 
    def is_deprecated(self):
3131
 
        """Is this format deprecated?
3132
 
 
3133
 
        Deprecated formats may trigger a user-visible warning recommending
3134
 
        the user to upgrade. They are still fully supported.
3135
 
        """
3136
 
        return False
3137
 
 
3138
3153
    def network_name(self):
3139
3154
        """A simple byte string uniquely identifying this format for RPC calls.
3140
3155
 
3179
3194
    rich_root_data = False
3180
3195
    supports_tree_reference = False
3181
3196
    supports_external_lookups = False
3182
 
    supports_leaving_lock = True
3183
3197
 
3184
3198
    @property
3185
3199
    def _matchingbzrdir(self):
3223
3237
        return self.get_format_string()
3224
3238
 
3225
3239
 
 
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
 
3226
3255
# formats which have no format string are not discoverable or independently
3227
3256
# creatable on disk, so are not registered in format_registry.  They're
3228
 
# all in bzrlib.repofmt.knitreponow.  When an instance of one of these is
 
3257
# all in bzrlib.repofmt.weaverepo now.  When an instance of one of these is
3229
3258
# needed, it's constructed directly by the BzrDir.  Non-native formats where
3230
3259
# the repository is not separately opened are similar.
3231
3260
 
3232
3261
format_registry.register_lazy(
 
3262
    'Bazaar-NG Repository format 7',
 
3263
    'bzrlib.repofmt.weaverepo',
 
3264
    'RepositoryFormat7'
 
3265
    )
 
3266
 
 
3267
format_registry.register_lazy(
3233
3268
    'Bazaar-NG Knit Repository Format 1',
3234
3269
    'bzrlib.repofmt.knitrepo',
3235
3270
    'RepositoryFormatKnit1',
3252
3287
# NOTE: These are experimental in 0.92. Stable in 1.0 and above
3253
3288
format_registry.register_lazy(
3254
3289
    'Bazaar pack repository format 1 (needs bzr 0.92)\n',
3255
 
    'bzrlib.repofmt.knitpack_repo',
 
3290
    'bzrlib.repofmt.pack_repo',
3256
3291
    'RepositoryFormatKnitPack1',
3257
3292
    )
3258
3293
format_registry.register_lazy(
3259
3294
    'Bazaar pack repository format 1 with subtree support (needs bzr 0.92)\n',
3260
 
    'bzrlib.repofmt.knitpack_repo',
 
3295
    'bzrlib.repofmt.pack_repo',
3261
3296
    'RepositoryFormatKnitPack3',
3262
3297
    )
3263
3298
format_registry.register_lazy(
3264
3299
    'Bazaar pack repository format 1 with rich root (needs bzr 1.0)\n',
3265
 
    'bzrlib.repofmt.knitpack_repo',
 
3300
    'bzrlib.repofmt.pack_repo',
3266
3301
    'RepositoryFormatKnitPack4',
3267
3302
    )
3268
3303
format_registry.register_lazy(
3269
3304
    'Bazaar RepositoryFormatKnitPack5 (bzr 1.6)\n',
3270
 
    'bzrlib.repofmt.knitpack_repo',
 
3305
    'bzrlib.repofmt.pack_repo',
3271
3306
    'RepositoryFormatKnitPack5',
3272
3307
    )
3273
3308
format_registry.register_lazy(
3274
3309
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6.1)\n',
3275
 
    'bzrlib.repofmt.knitpack_repo',
 
3310
    'bzrlib.repofmt.pack_repo',
3276
3311
    'RepositoryFormatKnitPack5RichRoot',
3277
3312
    )
3278
3313
format_registry.register_lazy(
3279
3314
    'Bazaar RepositoryFormatKnitPack5RichRoot (bzr 1.6)\n',
3280
 
    'bzrlib.repofmt.knitpack_repo',
 
3315
    'bzrlib.repofmt.pack_repo',
3281
3316
    'RepositoryFormatKnitPack5RichRootBroken',
3282
3317
    )
3283
3318
format_registry.register_lazy(
3284
3319
    'Bazaar RepositoryFormatKnitPack6 (bzr 1.9)\n',
3285
 
    'bzrlib.repofmt.knitpack_repo',
 
3320
    'bzrlib.repofmt.pack_repo',
3286
3321
    'RepositoryFormatKnitPack6',
3287
3322
    )
3288
3323
format_registry.register_lazy(
3289
3324
    'Bazaar RepositoryFormatKnitPack6RichRoot (bzr 1.9)\n',
3290
 
    'bzrlib.repofmt.knitpack_repo',
 
3325
    'bzrlib.repofmt.pack_repo',
3291
3326
    'RepositoryFormatKnitPack6RichRoot',
3292
3327
    )
3293
 
format_registry.register_lazy(
3294
 
    'Bazaar repository format 2a (needs bzr 1.16 or later)\n',
3295
 
    'bzrlib.repofmt.groupcompress_repo',
3296
 
    'RepositoryFormat2a',
3297
 
    )
3298
3328
 
3299
3329
# Development formats.
3300
 
# Check their docstrings to see if/when they are obsolete.
 
3330
# Obsolete but kept pending a CHK based subtree format.
3301
3331
format_registry.register_lazy(
3302
3332
    ("Bazaar development format 2 with subtree support "
3303
3333
        "(needs bzr.dev from before 1.8)\n"),
3304
 
    'bzrlib.repofmt.knitpack_repo',
 
3334
    'bzrlib.repofmt.pack_repo',
3305
3335
    'RepositoryFormatPackDevelopment2Subtree',
3306
3336
    )
3307
 
format_registry.register_lazy(
3308
 
    'Bazaar development format 8\n',
3309
 
    'bzrlib.repofmt.groupcompress_repo',
3310
 
    'RepositoryFormat2aSubtree',
 
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',
3311
3356
    )
3312
3357
 
3313
3358
 
3344
3389
        self.target.fetch(self.source, revision_id=revision_id)
3345
3390
 
3346
3391
    @needs_write_lock
3347
 
    def fetch(self, revision_id=None, find_ghosts=False,
 
3392
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3348
3393
            fetch_spec=None):
3349
3394
        """Fetch the content required to construct revision_id.
3350
3395
 
3352
3397
 
3353
3398
        :param revision_id: if None all content is copied, if NULL_REVISION no
3354
3399
                            content is copied.
 
3400
        :param pb: ignored.
3355
3401
        :return: None.
3356
3402
        """
3357
3403
        ui.ui_factory.warn_experimental_format_fetch(self)
3367
3413
                               fetch_spec=fetch_spec,
3368
3414
                               find_ghosts=find_ghosts)
3369
3415
 
3370
 
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
 
3416
    def _walk_to_common_revisions(self, revision_ids):
3371
3417
        """Walk out from revision_ids in source to revisions target has.
3372
3418
 
3373
3419
        :param revision_ids: The start point for the search.
3375
3421
        """
3376
3422
        target_graph = self.target.get_graph()
3377
3423
        revision_ids = frozenset(revision_ids)
3378
 
        if if_present_ids:
3379
 
            all_wanted_revs = revision_ids.union(if_present_ids)
3380
 
        else:
3381
 
            all_wanted_revs = revision_ids
3382
3424
        missing_revs = set()
3383
3425
        source_graph = self.source.get_graph()
3384
3426
        # ensure we don't pay silly lookup costs.
3385
 
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
 
3427
        searcher = source_graph._make_breadth_first_searcher(revision_ids)
3386
3428
        null_set = frozenset([_mod_revision.NULL_REVISION])
3387
3429
        searcher_exhausted = False
3388
3430
        while True:
3424
3466
        return searcher.get_result()
3425
3467
 
3426
3468
    @needs_read_lock
3427
 
    def search_missing_revision_ids(self,
3428
 
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
3429
 
            find_ghosts=True, revision_ids=None, if_present_ids=None):
 
3469
    def search_missing_revision_ids(self, revision_id=None, find_ghosts=True):
3430
3470
        """Return the revision ids that source has that target does not.
3431
3471
 
3432
3472
        :param revision_id: only return revision ids included by this
3433
 
            revision_id.
3434
 
        :param revision_ids: return revision ids included by these
3435
 
            revision_ids.  NoSuchRevision will be raised if any of these
3436
 
            revisions are not present.
3437
 
        :param if_present_ids: like revision_ids, but will not cause
3438
 
            NoSuchRevision if any of these are absent, instead they will simply
3439
 
            not be in the result.  This is useful for e.g. finding revisions
3440
 
            to fetch for tags, which may reference absent revisions.
 
3473
                            revision_id.
3441
3474
        :param find_ghosts: If True find missing revisions in deep history
3442
3475
            rather than just finding the surface difference.
3443
3476
        :return: A bzrlib.graph.SearchResult.
3444
3477
        """
3445
 
        if symbol_versioning.deprecated_passed(revision_id):
3446
 
            symbol_versioning.warn(
3447
 
                'search_missing_revision_ids(revision_id=...) was '
3448
 
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
3449
 
                DeprecationWarning, stacklevel=2)
3450
 
            if revision_ids is not None:
3451
 
                raise AssertionError(
3452
 
                    'revision_ids is mutually exclusive with revision_id')
3453
 
            if revision_id is not None:
3454
 
                revision_ids = [revision_id]
3455
 
        del revision_id
3456
3478
        # stop searching at found target revisions.
3457
 
        if not find_ghosts and (revision_ids is not None or if_present_ids is
3458
 
                not None):
3459
 
            return self._walk_to_common_revisions(revision_ids,
3460
 
                    if_present_ids=if_present_ids)
 
3479
        if not find_ghosts and revision_id is not None:
 
3480
            return self._walk_to_common_revisions([revision_id])
3461
3481
        # generic, possibly worst case, slow code path.
3462
3482
        target_ids = set(self.target.all_revision_ids())
3463
 
        source_ids = self._present_source_revisions_for(
3464
 
            revision_ids, if_present_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()
3465
3490
        result_set = set(source_ids).difference(target_ids)
3466
3491
        return self.source.revision_ids_to_search_result(result_set)
3467
3492
 
3468
 
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
3469
 
        """Returns set of all revisions in ancestry of revision_ids present in
3470
 
        the source repo.
3471
 
 
3472
 
        :param revision_ids: if None, all revisions in source are returned.
3473
 
        :param if_present_ids: like revision_ids, but if any/all of these are
3474
 
            absent no error is raised.
3475
 
        """
3476
 
        if revision_ids is not None or if_present_ids is not None:
3477
 
            # First, ensure all specified revisions exist.  Callers expect
3478
 
            # NoSuchRevision when they pass absent revision_ids here.
3479
 
            if revision_ids is None:
3480
 
                revision_ids = set()
3481
 
            if if_present_ids is None:
3482
 
                if_present_ids = set()
3483
 
            revision_ids = set(revision_ids)
3484
 
            if_present_ids = set(if_present_ids)
3485
 
            all_wanted_ids = revision_ids.union(if_present_ids)
3486
 
            graph = self.source.get_graph()
3487
 
            present_revs = set(graph.get_parent_map(all_wanted_ids))
3488
 
            missing = revision_ids.difference(present_revs)
3489
 
            if missing:
3490
 
                raise errors.NoSuchRevision(self.source, missing.pop())
3491
 
            found_ids = all_wanted_ids.intersection(present_revs)
3492
 
            source_ids = [rev_id for (rev_id, parents) in
3493
 
                          graph.iter_ancestry(found_ids)
3494
 
                          if rev_id != _mod_revision.NULL_REVISION
3495
 
                          and parents is not None]
3496
 
        else:
3497
 
            source_ids = self.source.all_revision_ids()
3498
 
        return set(source_ids)
3499
 
 
3500
3493
    @staticmethod
3501
3494
    def _same_model(source, target):
3502
3495
        """True if source and target have the same data representation.
3543
3536
        return InterRepository._same_model(source, target)
3544
3537
 
3545
3538
 
 
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
 
3546
3709
class InterDifferingSerializer(InterRepository):
3547
3710
 
3548
3711
    @classmethod
3650
3813
                basis_id, delta, current_revision_id, parents_parents)
3651
3814
            cache[current_revision_id] = parent_tree
3652
3815
 
3653
 
    def _fetch_batch(self, revision_ids, basis_id, cache):
 
3816
    def _fetch_batch(self, revision_ids, basis_id, cache, a_graph=None):
3654
3817
        """Fetch across a few revisions.
3655
3818
 
3656
3819
        :param revision_ids: The revisions to copy
3657
3820
        :param basis_id: The revision_id of a tree that must be in cache, used
3658
3821
            as a basis for delta when no other base is available
3659
3822
        :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.
3660
3825
        :return: The revision_id of the last converted tree. The RevisionTree
3661
3826
            for it will be in cache
3662
3827
        """
3730
3895
        if root_keys_to_create:
3731
3896
            root_stream = _mod_fetch._new_root_data_stream(
3732
3897
                root_keys_to_create, self._revision_id_to_root_id, parent_map,
3733
 
                self.source)
 
3898
                self.source, graph=a_graph)
3734
3899
            to_texts.insert_record_stream(root_stream)
3735
3900
        to_texts.insert_record_stream(from_texts.get_record_stream(
3736
3901
            text_keys, self.target._format._fetch_order,
3793
3958
        cache[basis_id] = basis_tree
3794
3959
        del basis_tree # We don't want to hang on to it here
3795
3960
        hints = []
3796
 
        a_graph = None
 
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
3797
3966
 
3798
3967
        for offset in range(0, len(revision_ids), batch_size):
3799
3968
            self.target.start_write_group()
3801
3970
                pb.update('Transferring revisions', offset,
3802
3971
                          len(revision_ids))
3803
3972
                batch = revision_ids[offset:offset+batch_size]
3804
 
                basis_id = self._fetch_batch(batch, basis_id, cache)
 
3973
                basis_id = self._fetch_batch(batch, basis_id, cache,
 
3974
                                             a_graph=a_graph)
3805
3975
            except:
3806
3976
                self.source._safe_to_return_from_cache = False
3807
3977
                self.target.abort_write_group()
3816
3986
                  len(revision_ids))
3817
3987
 
3818
3988
    @needs_write_lock
3819
 
    def fetch(self, revision_id=None, find_ghosts=False,
 
3989
    def fetch(self, revision_id=None, pb=None, find_ghosts=False,
3820
3990
            fetch_spec=None):
3821
3991
        """See InterRepository.fetch()."""
3822
3992
        if fetch_spec is not None:
3823
 
            revision_ids = fetch_spec.get_keys()
3824
 
        else:
3825
 
            revision_ids = None
 
3993
            raise AssertionError("Not implemented yet...")
3826
3994
        ui.ui_factory.warn_experimental_format_fetch(self)
3827
3995
        if (not self.source.supports_rich_root()
3828
3996
            and self.target.supports_rich_root()):
3835
4003
            ui.ui_factory.show_user_warning('cross_format_fetch',
3836
4004
                from_format=self.source._format,
3837
4005
                to_format=self.target._format)
3838
 
        if revision_ids is None:
3839
 
            if revision_id:
3840
 
                search_revision_ids = [revision_id]
3841
 
            else:
3842
 
                search_revision_ids = None
3843
 
            revision_ids = self.target.search_missing_revision_ids(self.source,
3844
 
                revision_ids=search_revision_ids,
3845
 
                find_ghosts=find_ghosts).get_keys()
 
4006
        revision_ids = self.target.search_missing_revision_ids(self.source,
 
4007
            revision_id, find_ghosts=find_ghosts).get_keys()
3846
4008
        if not revision_ids:
3847
4009
            return 0, 0
3848
4010
        revision_ids = tsort.topo_sort(
3852
4014
        # Walk though all revisions; get inventory deltas, copy referenced
3853
4015
        # texts that delta references, insert the delta, revision and
3854
4016
        # signature.
3855
 
        pb = ui.ui_factory.nested_progress_bar()
 
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
3856
4025
        try:
3857
4026
            self._fetch_all_revisions(revision_ids, pb)
3858
4027
        finally:
3859
 
            pb.finished()
 
4028
            if my_pb is not None:
 
4029
                my_pb.finished()
3860
4030
        return len(revision_ids), 0
3861
4031
 
3862
4032
    def _get_basis(self, first_revision_id):
3873
4043
            basis_id = first_rev.parent_ids[0]
3874
4044
            # only valid as a basis if the target has it
3875
4045
            self.target.get_revision(basis_id)
3876
 
            # Try to get a basis tree - if it's a ghost it will hit the
 
4046
            # Try to get a basis tree - if its a ghost it will hit the
3877
4047
            # NoSuchRevision case.
3878
4048
            basis_tree = self.source.revision_tree(basis_id)
3879
4049
        except (IndexError, errors.NoSuchRevision):
3884
4054
 
3885
4055
InterRepository.register_optimiser(InterDifferingSerializer)
3886
4056
InterRepository.register_optimiser(InterSameDataRepository)
 
4057
InterRepository.register_optimiser(InterWeaveRepo)
 
4058
InterRepository.register_optimiser(InterKnitRepo)
3887
4059
 
3888
4060
 
3889
4061
class CopyConverter(object):
3934
4106
        pb.finished()
3935
4107
 
3936
4108
 
 
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
 
3937
4139
class _VersionedFileChecker(object):
3938
4140
 
3939
4141
    def __init__(self, repository, text_key_references=None, ancestors=None):
3998
4200
        return wrong_parents, unused_keys
3999
4201
 
4000
4202
 
 
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
 
4001
4211
def _strip_NULL_ghosts(revision_graph):
4002
4212
    """Also don't use this. more compatibility code for unmigrated clients."""
4003
4213
    # Filter ghosts, and null:
4039
4249
                is_resume = False
4040
4250
            try:
4041
4251
                # locked_insert_stream performs a commit|suspend.
4042
 
                missing_keys = self.insert_stream_without_locking(stream,
4043
 
                                    src_format, is_resume)
4044
 
                if missing_keys:
4045
 
                    # suspend the write group and tell the caller what we is
4046
 
                    # missing. We know we can suspend or else we would not have
4047
 
                    # entered this code path. (All repositories that can handle
4048
 
                    # missing keys can handle suspending a write group).
4049
 
                    write_group_tokens = self.target_repo.suspend_write_group()
4050
 
                    return write_group_tokens, missing_keys
4051
 
                hint = self.target_repo.commit_write_group()
4052
 
                to_serializer = self.target_repo._format._serializer
4053
 
                src_serializer = src_format._serializer
4054
 
                if (to_serializer != src_serializer and
4055
 
                    self.target_repo._format.pack_compresses):
4056
 
                    self.target_repo.pack(hint=hint)
4057
 
                return [], set()
 
4252
                return self._locked_insert_stream(stream, src_format, is_resume)
4058
4253
            except:
4059
4254
                self.target_repo.abort_write_group(suppress_errors=True)
4060
4255
                raise
4061
4256
        finally:
4062
4257
            self.target_repo.unlock()
4063
4258
 
4064
 
    def insert_stream_without_locking(self, stream, src_format,
4065
 
                                      is_resume=False):
4066
 
        """Insert a stream's content into the target repository.
4067
 
 
4068
 
        This assumes that you already have a locked repository and an active
4069
 
        write group.
4070
 
 
4071
 
        :param src_format: a bzr repository format.
4072
 
        :param is_resume: Passed down to get_missing_parent_inventories to
4073
 
            indicate if we should be checking for missing texts at the same
4074
 
            time.
4075
 
 
4076
 
        :return: A set of keys that are missing.
4077
 
        """
4078
 
        if not self.target_repo.is_write_locked():
4079
 
            raise errors.ObjectNotLocked(self)
4080
 
        if not self.target_repo.is_in_write_group():
4081
 
            raise errors.BzrError('you must already be in a write group')
 
4259
    def _locked_insert_stream(self, stream, src_format, is_resume):
4082
4260
        to_serializer = self.target_repo._format._serializer
4083
4261
        src_serializer = src_format._serializer
4084
4262
        new_pack = None
4124
4302
                # required if the serializers are different only in terms of
4125
4303
                # the inventory.
4126
4304
                if src_serializer == to_serializer:
4127
 
                    self.target_repo.revisions.insert_record_stream(substream)
 
4305
                    self.target_repo.revisions.insert_record_stream(
 
4306
                        substream)
4128
4307
                else:
4129
4308
                    self._extract_and_insert_revisions(substream,
4130
4309
                        src_serializer)
4163
4342
            # cannot even attempt suspending, and missing would have failed
4164
4343
            # during stream insertion.
4165
4344
            missing_keys = set()
4166
 
        return missing_keys
 
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()
4167
4358
 
4168
4359
    def _extract_and_insert_inventory_deltas(self, substream, serializer):
4169
4360
        target_rich_root = self.target_repo._format.rich_root_data
4176
4367
                parse_result = deserialiser.parse_text_bytes(
4177
4368
                    inventory_delta_bytes)
4178
4369
            except inventory_delta.IncompatibleInventoryDelta, err:
4179
 
                mutter("Incompatible delta: %s", err.msg)
 
4370
                trace.mutter("Incompatible delta: %s", err.msg)
4180
4371
                raise errors.IncompatibleRevision(self.target_repo._format)
4181
4372
            basis_id, new_id, rich_root, tree_refs, inv_delta = parse_result
4182
4373
            revision_id = new_id
4226
4417
        """Create a StreamSource streaming from from_repository."""
4227
4418
        self.from_repository = from_repository
4228
4419
        self.to_format = to_format
4229
 
        self._record_counter = RecordCounter()
4230
4420
 
4231
4421
    def delta_on_metadata(self):
4232
4422
        """Return True if delta's are permitted on metadata streams.
4517
4707
    except StopIteration:
4518
4708
        # No more history
4519
4709
        return
 
4710