~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/vf_repository.py

  • Committer: Jonathan Riddell
  • Date: 2011-05-16 11:27:37 UTC
  • mto: This revision was merged to the branch mainline in revision 5869.
  • Revision ID: jriddell@canonical.com-20110516112737-gep642p24rtzp3jt
userĀ guideĀ licence

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Repository formats built around versioned files."""
18
18
 
19
 
from __future__ import absolute_import
20
 
 
21
19
 
22
20
from bzrlib.lazy_import import lazy_import
23
21
lazy_import(globals(), """
24
 
import itertools
25
 
 
26
22
from bzrlib import (
27
23
    check,
28
 
    config as _mod_config,
29
24
    debug,
30
25
    fetch as _mod_fetch,
31
26
    fifo_cache,
37
32
    revision as _mod_revision,
38
33
    serializer as _mod_serializer,
39
34
    static_tuple,
40
 
    symbol_versioning,
41
35
    tsort,
42
36
    ui,
43
37
    versionedfile,
44
 
    vf_search,
45
38
    )
46
39
 
47
40
from bzrlib.recordcounter import RecordCounter
48
41
from bzrlib.revisiontree import InventoryRevisionTree
49
42
from bzrlib.testament import Testament
50
 
from bzrlib.i18n import gettext
51
43
""")
52
44
 
53
45
from bzrlib import (
69
61
    CommitBuilder,
70
62
    InterRepository,
71
63
    MetaDirRepository,
72
 
    RepositoryFormatMetaDir,
 
64
    MetaDirRepositoryFormat,
73
65
    Repository,
74
66
    RepositoryFormat,
75
67
    )
76
68
 
77
69
from bzrlib.trace import (
78
 
    mutter
 
70
    mutter,
79
71
    )
80
72
 
81
73
 
83
75
    """Base class for all repository formats that are VersionedFiles-based."""
84
76
 
85
77
    supports_full_versioned_files = True
86
 
    supports_versioned_directories = True
87
 
    supports_unreferenced_revisions = True
88
78
 
89
79
    # Should commit add an inventory, or an inventory delta to the repository.
90
80
    _commit_inv_deltas = True
109
99
    # the default CommitBuilder does not manage trees whose root is versioned.
110
100
    _versioned_root = False
111
101
 
112
 
    def __init__(self, repository, parents, config_stack, timestamp=None,
 
102
    def __init__(self, repository, parents, config, timestamp=None,
113
103
                 timezone=None, committer=None, revprops=None,
114
104
                 revision_id=None, lossy=False):
115
105
        super(VersionedFileCommitBuilder, self).__init__(repository,
116
 
            parents, config_stack, timestamp, timezone, committer, revprops,
 
106
            parents, config, timestamp, timezone, committer, revprops,
117
107
            revision_id, lossy)
118
108
        try:
119
109
            basis_id = self.parents[0]
200
190
                       revision_id=self._new_revision_id,
201
191
                       properties=self._revprops)
202
192
        rev.parent_ids = self.parents
203
 
        if self._config_stack.get('create_signatures') == _mod_config.SIGN_ALWAYS:
204
 
            testament = Testament(rev, self.revision_tree())
205
 
            plaintext = testament.as_short_text()
206
 
            self.repository.store_revision_signature(
207
 
                gpg.GPGStrategy(self._config_stack), plaintext,
208
 
                self._new_revision_id)
209
 
        self.repository._add_revision(rev)
 
193
        self.repository.add_revision(self._new_revision_id, rev,
 
194
            self.new_inventory, self._config)
210
195
        self._ensure_fallback_inventories()
211
196
        self.repository.commit_write_group()
212
197
        return self._new_revision_id
290
275
 
291
276
    def _get_delta(self, ie, basis_inv, path):
292
277
        """Get a delta against the basis inventory for ie."""
293
 
        if not basis_inv.has_id(ie.file_id):
 
278
        if ie.file_id not in basis_inv:
294
279
            # add
295
280
            result = (None, path, ie.file_id, ie)
296
281
            self._basis_delta.append(result)
409
394
                # this masks when a change may have occurred against the basis.
410
395
                # To match this we always issue a delta, because the revision
411
396
                # of the root will always be changing.
412
 
                if basis_inv.has_id(ie.file_id):
 
397
                if ie.file_id in basis_inv:
413
398
                    delta = (basis_inv.id2path(ie.file_id), path,
414
399
                        ie.file_id, ie)
415
400
                else:
430
415
                return None, False, None
431
416
        # XXX: Friction: parent_candidates should return a list not a dict
432
417
        #      so that we don't have to walk the inventories again.
433
 
        parent_candidate_entries = ie.parent_candidates(parent_invs)
434
 
        head_set = self._heads(ie.file_id, parent_candidate_entries.keys())
 
418
        parent_candiate_entries = ie.parent_candidates(parent_invs)
 
419
        head_set = self._heads(ie.file_id, parent_candiate_entries.keys())
435
420
        heads = []
436
421
        for inv in parent_invs:
437
 
            if inv.has_id(ie.file_id):
 
422
            if ie.file_id in inv:
438
423
                old_rev = inv[ie.file_id].revision
439
424
                if old_rev in head_set:
440
425
                    heads.append(inv[ie.file_id].revision)
452
437
            store = True
453
438
        if not store:
454
439
            # There is a single head, look it up for comparison
455
 
            parent_entry = parent_candidate_entries[heads[0]]
 
440
            parent_entry = parent_candiate_entries[heads[0]]
456
441
            # if the non-content specific data has changed, we'll be writing a
457
442
            # node:
458
443
            if (parent_entry.parent_id != ie.parent_id or
570
555
        :param iter_changes: An iter_changes iterator with the changes to apply
571
556
            to basis_revision_id. The iterator must not include any items with
572
557
            a current kind of None - missing items must be either filtered out
573
 
            or errored-on before record_iter_changes sees the item.
 
558
            or errored-on beefore record_iter_changes sees the item.
574
559
        :param _entry_factory: Private method to bind entry_factory locally for
575
560
            performance.
576
561
        :return: A generator of (file_id, relpath, fs_hash) tuples for use with
604
589
                        _mod_revision.NULL_REVISION))
605
590
        # The basis inventory from a repository 
606
591
        if revtrees:
607
 
            basis_tree = revtrees[0]
 
592
            basis_inv = revtrees[0].inventory
608
593
        else:
609
 
            basis_tree = self.repository.revision_tree(
610
 
                _mod_revision.NULL_REVISION)
611
 
        basis_inv = basis_tree.root_inventory
 
594
            basis_inv = self.repository.revision_tree(
 
595
                _mod_revision.NULL_REVISION).inventory
612
596
        if len(self.parents) > 0:
613
597
            if basis_revision_id != self.parents[0] and not ghost_basis:
614
598
                raise Exception(
615
599
                    "arbitrary basis parents not yet supported with merges")
616
600
            for revtree in revtrees[1:]:
617
 
                for change in revtree.root_inventory._make_delta(basis_inv):
 
601
                for change in revtree.inventory._make_delta(basis_inv):
618
602
                    if change[1] is None:
619
603
                        # Not present in this parent.
620
604
                        continue
930
914
        """
931
915
        if not self._format.supports_external_lookups:
932
916
            raise errors.UnstackableRepositoryFormat(self._format, self.base)
933
 
        # This can raise an exception, so should be done before we lock the
934
 
        # fallback repository.
935
 
        self._check_fallback_repository(repository)
936
917
        if self.is_locked():
937
918
            # This repository will call fallback.unlock() when we transition to
938
919
            # the unlocked state, so we make sure to increment the lock count
939
920
            repository.lock_read()
 
921
        self._check_fallback_repository(repository)
940
922
        self._fallback_repositories.append(repository)
941
923
        self.texts.add_fallback_versioned_files(repository.texts)
942
924
        self.inventories.add_fallback_versioned_files(repository.inventories)
1022
1004
            # return a new inventory, but as there is no revision tree cache in
1023
1005
            # repository this is safe for now - RBC 20081013
1024
1006
            if basis_inv is None:
1025
 
                basis_inv = basis_tree.root_inventory
 
1007
                basis_inv = basis_tree.inventory
1026
1008
            basis_inv.apply_delta(delta)
1027
1009
            basis_inv.revision_id = new_revision_id
1028
1010
            return (self.add_inventory(new_revision_id, basis_inv, parents),
1039
1021
        self.inventories._access.flush()
1040
1022
        return result
1041
1023
 
1042
 
    def add_revision(self, revision_id, rev, inv=None):
 
1024
    def add_revision(self, revision_id, rev, inv=None, config=None):
1043
1025
        """Add rev to the revision store as revision_id.
1044
1026
 
1045
1027
        :param revision_id: the revision id to use.
1046
1028
        :param rev: The revision object.
1047
1029
        :param inv: The inventory for the revision. if None, it will be looked
1048
1030
                    up in the inventory storer
 
1031
        :param config: If None no digital signature will be created.
 
1032
                       If supplied its signature_needed method will be used
 
1033
                       to determine if a signature should be made.
1049
1034
        """
1050
1035
        # TODO: jam 20070210 Shouldn't we check rev.revision_id and
1051
1036
        #       rev.parent_ids?
1052
1037
        _mod_revision.check_not_reserved_id(revision_id)
 
1038
        if config is not None and config.signature_needed():
 
1039
            if inv is None:
 
1040
                inv = self.get_inventory(revision_id)
 
1041
            tree = InventoryRevisionTree(self, inv, revision_id)
 
1042
            testament = Testament(rev, tree)
 
1043
            plaintext = testament.as_short_text()
 
1044
            self.store_revision_signature(
 
1045
                gpg.GPGStrategy(config), plaintext, revision_id)
1053
1046
        # check inventory present
1054
1047
        if not self.inventories.get_parent_map([(revision_id,)]):
1055
1048
            if inv is None:
1088
1081
        keys = {'chk_bytes':set(), 'inventories':set(), 'texts':set()}
1089
1082
        kinds = ['chk_bytes', 'texts']
1090
1083
        count = len(checker.pending_keys)
1091
 
        bar.update(gettext("inventories"), 0, 2)
 
1084
        bar.update("inventories", 0, 2)
1092
1085
        current_keys = checker.pending_keys
1093
1086
        checker.pending_keys = {}
1094
1087
        # Accumulate current checks.
1114
1107
            del keys['inventories']
1115
1108
        else:
1116
1109
            return
1117
 
        bar.update(gettext("texts"), 1)
 
1110
        bar.update("texts", 1)
1118
1111
        while (checker.pending_keys or keys['chk_bytes']
1119
1112
            or keys['texts']):
1120
1113
            # Something to check.
1183
1176
                'sha1 mismatch: %s has sha1 %s expected %s referenced by %s' %
1184
1177
                (record.key, sha1, item_data[1], item_data[2]))
1185
1178
 
1186
 
    @needs_read_lock
1187
 
    def _eliminate_revisions_not_present(self, revision_ids):
1188
 
        """Check every revision id in revision_ids to see if we have it.
1189
 
 
1190
 
        Returns a set of the present revisions.
1191
 
        """
1192
 
        result = []
1193
 
        graph = self.get_graph()
1194
 
        parent_map = graph.get_parent_map(revision_ids)
1195
 
        # The old API returned a list, should this actually be a set?
1196
 
        return parent_map.keys()
1197
 
 
1198
1179
    def __init__(self, _format, a_bzrdir, control_files):
1199
1180
        """Instantiate a VersionedFileRepository.
1200
1181
 
1201
1182
        :param _format: The format of the repository on disk.
1202
 
        :param controldir: The ControlDir of the repository.
 
1183
        :param a_bzrdir: The BzrDir of the repository.
1203
1184
        :param control_files: Control files to use for locking, etc.
1204
1185
        """
1205
1186
        # In the future we will have a single api for all stores for
1207
1188
        # this construct will accept instances of those things.
1208
1189
        super(VersionedFileRepository, self).__init__(_format, a_bzrdir,
1209
1190
            control_files)
1210
 
        self._transport = control_files._transport
1211
 
        self.base = self._transport.base
1212
1191
        # for tests
1213
1192
        self._reconcile_does_inventory_gc = True
1214
1193
        self._reconcile_fixes_text_parents = False
1219
1198
        # rather copying them?
1220
1199
        self._safe_to_return_from_cache = False
1221
1200
 
1222
 
    def fetch(self, source, revision_id=None, find_ghosts=False,
1223
 
            fetch_spec=None):
1224
 
        """Fetch the content required to construct revision_id from source.
1225
 
 
1226
 
        If revision_id is None and fetch_spec is None, then all content is
1227
 
        copied.
1228
 
 
1229
 
        fetch() may not be used when the repository is in a write group -
1230
 
        either finish the current write group before using fetch, or use
1231
 
        fetch before starting the write group.
1232
 
 
1233
 
        :param find_ghosts: Find and copy revisions in the source that are
1234
 
            ghosts in the target (and not reachable directly by walking out to
1235
 
            the first-present revision in target from revision_id).
1236
 
        :param revision_id: If specified, all the content needed for this
1237
 
            revision ID will be copied to the target.  Fetch will determine for
1238
 
            itself which content needs to be copied.
1239
 
        :param fetch_spec: If specified, a SearchResult or
1240
 
            PendingAncestryResult that describes which revisions to copy.  This
1241
 
            allows copying multiple heads at once.  Mutually exclusive with
1242
 
            revision_id.
1243
 
        """
1244
 
        if fetch_spec is not None and revision_id is not None:
1245
 
            raise AssertionError(
1246
 
                "fetch_spec and revision_id are mutually exclusive.")
1247
 
        if self.is_in_write_group():
1248
 
            raise errors.InternalBzrError(
1249
 
                "May not fetch while in a write group.")
1250
 
        # fast path same-url fetch operations
1251
 
        # TODO: lift out to somewhere common with RemoteRepository
1252
 
        # <https://bugs.launchpad.net/bzr/+bug/401646>
1253
 
        if (self.has_same_location(source)
1254
 
            and fetch_spec is None
1255
 
            and self._has_same_fallbacks(source)):
1256
 
            # check that last_revision is in 'from' and then return a
1257
 
            # no-operation.
1258
 
            if (revision_id is not None and
1259
 
                not _mod_revision.is_null(revision_id)):
1260
 
                self.get_revision(revision_id)
1261
 
            return 0, []
1262
 
        inter = InterRepository.get(source, self)
1263
 
        if (fetch_spec is not None and
1264
 
            not getattr(inter, "supports_fetch_spec", False)):
1265
 
            raise errors.UnsupportedOperation(
1266
 
                "fetch_spec not supported for %r" % inter)
1267
 
        return inter.fetch(revision_id=revision_id,
1268
 
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1269
 
 
1270
1201
    @needs_read_lock
1271
1202
    def gather_stats(self, revid=None, committers=None):
1272
1203
        """See Repository.gather_stats()."""
1281
1212
            # result['size'] = t
1282
1213
        return result
1283
1214
 
1284
 
    def get_commit_builder(self, branch, parents, config_stack, timestamp=None,
 
1215
    def get_commit_builder(self, branch, parents, config, timestamp=None,
1285
1216
                           timezone=None, committer=None, revprops=None,
1286
1217
                           revision_id=None, lossy=False):
1287
1218
        """Obtain a CommitBuilder for this repository.
1288
1219
 
1289
1220
        :param branch: Branch to commit to.
1290
1221
        :param parents: Revision ids of the parents of the new revision.
1291
 
        :param config_stack: Configuration stack to use.
 
1222
        :param config: Configuration to use.
1292
1223
        :param timestamp: Optional timestamp recorded for commit.
1293
1224
        :param timezone: Optional timezone for timestamp.
1294
1225
        :param committer: Optional committer to set for commit.
1301
1232
            raise errors.BzrError("Cannot commit directly to a stacked branch"
1302
1233
                " in pre-2a formats. See "
1303
1234
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1304
 
        result = self._commit_builder_class(self, parents, config_stack,
 
1235
        result = self._commit_builder_class(self, parents, config,
1305
1236
            timestamp, timezone, committer, revprops, revision_id,
1306
1237
            lossy)
1307
1238
        self.start_write_group()
1493
1424
            the revision key from each parsed line will be looked up in the
1494
1425
            revision_keys filter.
1495
1426
        :return: a dictionary mapping altered file-ids to an iterable of
1496
 
            revision_ids. Each altered file-ids has the exact revision_ids that
1497
 
            altered it listed explicitly.
 
1427
        revision_ids. Each altered file-ids has the exact revision_ids that
 
1428
        altered it listed explicitly.
1498
1429
        """
1499
1430
        seen = set(self._serializer._find_text_key_references(
1500
1431
                line_iterator).iterkeys())
1529
1460
        :param _inv_weave: The inventory weave from this repository or None.
1530
1461
            If None, the inventory weave will be opened automatically.
1531
1462
        :return: a dictionary mapping altered file-ids to an iterable of
1532
 
            revision_ids. Each altered file-ids has the exact revision_ids that
1533
 
            altered it listed explicitly.
 
1463
        revision_ids. Each altered file-ids has the exact revision_ids that
 
1464
        altered it listed explicitly.
1534
1465
        """
1535
1466
        selected_keys = set((revid,) for revid in revision_ids)
1536
1467
        w = _inv_weave or self.inventories
1563
1494
            text_keys[(file_id, revision_id)] = callable_data
1564
1495
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1565
1496
            if record.storage_kind == 'absent':
1566
 
                raise errors.RevisionNotPresent(record.key[1], record.key[0])
 
1497
                raise errors.RevisionNotPresent(record.key, self)
1567
1498
            yield text_keys[record.key], record.get_bytes_as('chunked')
1568
1499
 
1569
1500
    def _generate_text_key_index(self, text_key_references=None,
1619
1550
        batch_size = 10 # should be ~150MB on a 55K path tree
1620
1551
        batch_count = len(revision_order) / batch_size + 1
1621
1552
        processed_texts = 0
1622
 
        pb.update(gettext("Calculating text parents"), processed_texts, text_count)
 
1553
        pb.update("Calculating text parents", processed_texts, text_count)
1623
1554
        for offset in xrange(batch_count):
1624
1555
            to_query = revision_order[offset * batch_size:(offset + 1) *
1625
1556
                batch_size]
1628
1559
            for revision_id in to_query:
1629
1560
                parent_ids = ancestors[revision_id]
1630
1561
                for text_key in revision_keys[revision_id]:
1631
 
                    pb.update(gettext("Calculating text parents"), processed_texts)
 
1562
                    pb.update("Calculating text parents", processed_texts)
1632
1563
                    processed_texts += 1
1633
1564
                    candidate_parents = []
1634
1565
                    for parent_id in parent_ids:
1648
1579
                            try:
1649
1580
                                inv = inventory_cache[parent_id]
1650
1581
                            except KeyError:
1651
 
                                inv = self.revision_tree(parent_id).root_inventory
 
1582
                                inv = self.revision_tree(parent_id).inventory
1652
1583
                                inventory_cache[parent_id] = inv
1653
1584
                            try:
1654
1585
                                parent_entry = inv[text_key[0]]
1704
1635
        num_file_ids = len(file_ids)
1705
1636
        for file_id, altered_versions in file_ids.iteritems():
1706
1637
            if pb is not None:
1707
 
                pb.update(gettext("Fetch texts"), count, num_file_ids)
 
1638
                pb.update("Fetch texts", count, num_file_ids)
1708
1639
            count += 1
1709
1640
            yield ("file", file_id, altered_versions)
1710
1641
 
1747
1678
        if ((None in revision_ids)
1748
1679
            or (_mod_revision.NULL_REVISION in revision_ids)):
1749
1680
            raise ValueError('cannot get null revision inventory')
1750
 
        for inv, revid in self._iter_inventories(revision_ids, ordering):
1751
 
            if inv is None:
1752
 
                raise errors.NoSuchRevision(self, revid)
1753
 
            yield inv
 
1681
        return self._iter_inventories(revision_ids, ordering)
1754
1682
 
1755
1683
    def _iter_inventories(self, revision_ids, ordering):
1756
1684
        """single-document based inventory iteration."""
1757
1685
        inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
1758
1686
        for text, revision_id in inv_xmls:
1759
 
            if text is None:
1760
 
                yield None, revision_id
1761
 
            else:
1762
 
                yield self._deserialise_inventory(revision_id, text), revision_id
 
1687
            yield self._deserialise_inventory(revision_id, text)
1763
1688
 
1764
1689
    def _iter_inventory_xmls(self, revision_ids, ordering):
1765
1690
        if ordering is None:
1783
1708
                else:
1784
1709
                    yield ''.join(chunks), record.key[-1]
1785
1710
            else:
1786
 
                yield None, record.key[-1]
 
1711
                raise errors.NoSuchRevision(self, record.key)
1787
1712
            if order_as_requested:
1788
1713
                # Yield as many results as we can while preserving order.
1789
1714
                while next_key in text_chunks:
1818
1743
    def _get_inventory_xml(self, revision_id):
1819
1744
        """Get serialized inventory as a string."""
1820
1745
        texts = self._iter_inventory_xmls([revision_id], 'unordered')
1821
 
        text, revision_id = texts.next()
1822
 
        if text is None:
1823
 
            raise errors.NoSuchRevision(self, revision_id)
 
1746
        try:
 
1747
            text, revision_id = texts.next()
 
1748
        except StopIteration:
 
1749
            raise errors.HistoryMissing(self, 'inventory', revision_id)
1824
1750
        return text
1825
1751
 
1826
1752
    @needs_read_lock
1901
1827
        """Return the graph walker for text revisions."""
1902
1828
        return graph.Graph(self.texts)
1903
1829
 
1904
 
    def revision_ids_to_search_result(self, result_set):
1905
 
        """Convert a set of revision ids to a graph SearchResult."""
1906
 
        result_parents = set()
1907
 
        for parents in self.get_graph().get_parent_map(
1908
 
            result_set).itervalues():
1909
 
            result_parents.update(parents)
1910
 
        included_keys = result_set.intersection(result_parents)
1911
 
        start_keys = result_set.difference(included_keys)
1912
 
        exclude_keys = result_parents.difference(result_set)
1913
 
        result = vf_search.SearchResult(start_keys, exclude_keys,
1914
 
            len(result_set), result_set)
1915
 
        return result
1916
 
 
1917
1830
    def _get_versioned_file_checker(self, text_key_references=None,
1918
1831
        ancestors=None):
1919
1832
        """Return an object suitable for checking versioned files.
2004
1917
            control_files)
2005
1918
 
2006
1919
 
2007
 
class MetaDirVersionedFileRepositoryFormat(RepositoryFormatMetaDir,
 
1920
class MetaDirVersionedFileRepositoryFormat(MetaDirRepositoryFormat,
2008
1921
        VersionedFileRepositoryFormat):
2009
1922
    """Base class for repository formats using versioned files in metadirs."""
2010
1923
 
2442
2355
        invs_sent_so_far = set([_mod_revision.NULL_REVISION])
2443
2356
        inventory_cache = lru_cache.LRUCache(50)
2444
2357
        null_inventory = from_repo.revision_tree(
2445
 
            _mod_revision.NULL_REVISION).root_inventory
 
2358
            _mod_revision.NULL_REVISION).inventory
2446
2359
        # XXX: ideally the rich-root/tree-refs flags would be per-revision, not
2447
2360
        # per-repo (e.g.  streaming a non-rich-root revision out of a rich-root
2448
2361
        # repo back into a non-rich-root repo ought to be allowed)
2533
2446
            self.text_index.iterkeys()])
2534
2447
        # text keys is now grouped by file_id
2535
2448
        n_versions = len(self.text_index)
2536
 
        progress_bar.update(gettext('loading text store'), 0, n_versions)
 
2449
        progress_bar.update('loading text store', 0, n_versions)
2537
2450
        parent_map = self.repository.texts.get_parent_map(self.text_index)
2538
2451
        # On unlistable transports this could well be empty/error...
2539
2452
        text_keys = self.repository.texts.keys()
2540
2453
        unused_keys = frozenset(text_keys) - set(self.text_index)
2541
2454
        for num, key in enumerate(self.text_index.iterkeys()):
2542
 
            progress_bar.update(gettext('checking text graph'), num, n_versions)
 
2455
            progress_bar.update('checking text graph', num, n_versions)
2543
2456
            correct_parents = self.calculate_file_version_parents(key)
2544
2457
            try:
2545
2458
                knit_parents = parent_map[key]
2551
2464
        return wrong_parents, unused_keys
2552
2465
 
2553
2466
 
2554
 
class InterVersionedFileRepository(InterRepository):
2555
 
 
2556
 
    _walk_to_common_revisions_batch_size = 50
2557
 
 
2558
 
    supports_fetch_spec = True
2559
 
 
2560
 
    @needs_write_lock
2561
 
    def fetch(self, revision_id=None, find_ghosts=False,
2562
 
            fetch_spec=None):
2563
 
        """Fetch the content required to construct revision_id.
2564
 
 
2565
 
        The content is copied from self.source to self.target.
2566
 
 
2567
 
        :param revision_id: if None all content is copied, if NULL_REVISION no
2568
 
                            content is copied.
2569
 
        :return: None.
2570
 
        """
2571
 
        if self.target._format.experimental:
2572
 
            ui.ui_factory.show_user_warning('experimental_format_fetch',
2573
 
                from_format=self.source._format,
2574
 
                to_format=self.target._format)
2575
 
        from bzrlib.fetch import RepoFetcher
2576
 
        # See <https://launchpad.net/bugs/456077> asking for a warning here
2577
 
        if self.source._format.network_name() != self.target._format.network_name():
2578
 
            ui.ui_factory.show_user_warning('cross_format_fetch',
2579
 
                from_format=self.source._format,
2580
 
                to_format=self.target._format)
2581
 
        f = RepoFetcher(to_repository=self.target,
2582
 
                               from_repository=self.source,
2583
 
                               last_revision=revision_id,
2584
 
                               fetch_spec=fetch_spec,
2585
 
                               find_ghosts=find_ghosts)
2586
 
 
2587
 
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
2588
 
        """Walk out from revision_ids in source to revisions target has.
2589
 
 
2590
 
        :param revision_ids: The start point for the search.
2591
 
        :return: A set of revision ids.
2592
 
        """
2593
 
        target_graph = self.target.get_graph()
2594
 
        revision_ids = frozenset(revision_ids)
2595
 
        if if_present_ids:
2596
 
            all_wanted_revs = revision_ids.union(if_present_ids)
2597
 
        else:
2598
 
            all_wanted_revs = revision_ids
2599
 
        missing_revs = set()
2600
 
        source_graph = self.source.get_graph()
2601
 
        # ensure we don't pay silly lookup costs.
2602
 
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
2603
 
        null_set = frozenset([_mod_revision.NULL_REVISION])
2604
 
        searcher_exhausted = False
2605
 
        while True:
2606
 
            next_revs = set()
2607
 
            ghosts = set()
2608
 
            # Iterate the searcher until we have enough next_revs
2609
 
            while len(next_revs) < self._walk_to_common_revisions_batch_size:
2610
 
                try:
2611
 
                    next_revs_part, ghosts_part = searcher.next_with_ghosts()
2612
 
                    next_revs.update(next_revs_part)
2613
 
                    ghosts.update(ghosts_part)
2614
 
                except StopIteration:
2615
 
                    searcher_exhausted = True
2616
 
                    break
2617
 
            # If there are ghosts in the source graph, and the caller asked for
2618
 
            # them, make sure that they are present in the target.
2619
 
            # We don't care about other ghosts as we can't fetch them and
2620
 
            # haven't been asked to.
2621
 
            ghosts_to_check = set(revision_ids.intersection(ghosts))
2622
 
            revs_to_get = set(next_revs).union(ghosts_to_check)
2623
 
            if revs_to_get:
2624
 
                have_revs = set(target_graph.get_parent_map(revs_to_get))
2625
 
                # we always have NULL_REVISION present.
2626
 
                have_revs = have_revs.union(null_set)
2627
 
                # Check if the target is missing any ghosts we need.
2628
 
                ghosts_to_check.difference_update(have_revs)
2629
 
                if ghosts_to_check:
2630
 
                    # One of the caller's revision_ids is a ghost in both the
2631
 
                    # source and the target.
2632
 
                    raise errors.NoSuchRevision(
2633
 
                        self.source, ghosts_to_check.pop())
2634
 
                missing_revs.update(next_revs - have_revs)
2635
 
                # Because we may have walked past the original stop point, make
2636
 
                # sure everything is stopped
2637
 
                stop_revs = searcher.find_seen_ancestors(have_revs)
2638
 
                searcher.stop_searching_any(stop_revs)
2639
 
            if searcher_exhausted:
2640
 
                break
2641
 
        (started_keys, excludes, included_keys) = searcher.get_state()
2642
 
        return vf_search.SearchResult(started_keys, excludes,
2643
 
            len(included_keys), included_keys)
2644
 
 
2645
 
    @needs_read_lock
2646
 
    def search_missing_revision_ids(self,
2647
 
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2648
 
            find_ghosts=True, revision_ids=None, if_present_ids=None,
2649
 
            limit=None):
2650
 
        """Return the revision ids that source has that target does not.
2651
 
 
2652
 
        :param revision_id: only return revision ids included by this
2653
 
            revision_id.
2654
 
        :param revision_ids: return revision ids included by these
2655
 
            revision_ids.  NoSuchRevision will be raised if any of these
2656
 
            revisions are not present.
2657
 
        :param if_present_ids: like revision_ids, but will not cause
2658
 
            NoSuchRevision if any of these are absent, instead they will simply
2659
 
            not be in the result.  This is useful for e.g. finding revisions
2660
 
            to fetch for tags, which may reference absent revisions.
2661
 
        :param find_ghosts: If True find missing revisions in deep history
2662
 
            rather than just finding the surface difference.
2663
 
        :return: A bzrlib.graph.SearchResult.
2664
 
        """
2665
 
        if symbol_versioning.deprecated_passed(revision_id):
2666
 
            symbol_versioning.warn(
2667
 
                'search_missing_revision_ids(revision_id=...) was '
2668
 
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
2669
 
                DeprecationWarning, stacklevel=2)
2670
 
            if revision_ids is not None:
2671
 
                raise AssertionError(
2672
 
                    'revision_ids is mutually exclusive with revision_id')
2673
 
            if revision_id is not None:
2674
 
                revision_ids = [revision_id]
2675
 
        del revision_id
2676
 
        # stop searching at found target revisions.
2677
 
        if not find_ghosts and (revision_ids is not None or if_present_ids is
2678
 
                not None):
2679
 
            result = self._walk_to_common_revisions(revision_ids,
2680
 
                    if_present_ids=if_present_ids)
2681
 
            if limit is None:
2682
 
                return result
2683
 
            result_set = result.get_keys()
2684
 
        else:
2685
 
            # generic, possibly worst case, slow code path.
2686
 
            target_ids = set(self.target.all_revision_ids())
2687
 
            source_ids = self._present_source_revisions_for(
2688
 
                revision_ids, if_present_ids)
2689
 
            result_set = set(source_ids).difference(target_ids)
2690
 
        if limit is not None:
2691
 
            topo_ordered = self.source.get_graph().iter_topo_order(result_set)
2692
 
            result_set = set(itertools.islice(topo_ordered, limit))
2693
 
        return self.source.revision_ids_to_search_result(result_set)
2694
 
 
2695
 
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
2696
 
        """Returns set of all revisions in ancestry of revision_ids present in
2697
 
        the source repo.
2698
 
 
2699
 
        :param revision_ids: if None, all revisions in source are returned.
2700
 
        :param if_present_ids: like revision_ids, but if any/all of these are
2701
 
            absent no error is raised.
2702
 
        """
2703
 
        if revision_ids is not None or if_present_ids is not None:
2704
 
            # First, ensure all specified revisions exist.  Callers expect
2705
 
            # NoSuchRevision when they pass absent revision_ids here.
2706
 
            if revision_ids is None:
2707
 
                revision_ids = set()
2708
 
            if if_present_ids is None:
2709
 
                if_present_ids = set()
2710
 
            revision_ids = set(revision_ids)
2711
 
            if_present_ids = set(if_present_ids)
2712
 
            all_wanted_ids = revision_ids.union(if_present_ids)
2713
 
            graph = self.source.get_graph()
2714
 
            present_revs = set(graph.get_parent_map(all_wanted_ids))
2715
 
            missing = revision_ids.difference(present_revs)
2716
 
            if missing:
2717
 
                raise errors.NoSuchRevision(self.source, missing.pop())
2718
 
            found_ids = all_wanted_ids.intersection(present_revs)
2719
 
            source_ids = [rev_id for (rev_id, parents) in
2720
 
                          graph.iter_ancestry(found_ids)
2721
 
                          if rev_id != _mod_revision.NULL_REVISION
2722
 
                          and parents is not None]
2723
 
        else:
2724
 
            source_ids = self.source.all_revision_ids()
2725
 
        return set(source_ids)
2726
 
 
2727
 
    @classmethod
2728
 
    def _get_repo_format_to_test(self):
2729
 
        return None
2730
 
 
2731
 
    @classmethod
2732
 
    def is_compatible(cls, source, target):
2733
 
        # The default implementation is compatible with everything
2734
 
        return (source._format.supports_full_versioned_files and
2735
 
                target._format.supports_full_versioned_files)
2736
 
 
2737
 
 
2738
 
class InterDifferingSerializer(InterVersionedFileRepository):
 
2467
class InterDifferingSerializer(InterRepository):
2739
2468
 
2740
2469
    @classmethod
2741
2470
    def _get_repo_format_to_test(self):
2795
2524
        """
2796
2525
        deltas = []
2797
2526
        # Generate deltas against each tree, to find the shortest.
2798
 
        # FIXME: Support nested trees
2799
2527
        texts_possibly_new_in_tree = set()
2800
2528
        for basis_id, basis_tree in possible_trees:
2801
 
            delta = tree.root_inventory._make_delta(basis_tree.root_inventory)
 
2529
            delta = tree.inventory._make_delta(basis_tree.inventory)
2802
2530
            for old_path, new_path, file_id, new_entry in delta:
2803
2531
                if new_path is None:
2804
2532
                    # This file_id isn't present in the new rev, so we don't
2841
2569
            parents_parents = [key[-1] for key in parents_parents_keys]
2842
2570
            basis_id = _mod_revision.NULL_REVISION
2843
2571
            basis_tree = self.source.revision_tree(basis_id)
2844
 
            delta = parent_tree.root_inventory._make_delta(
2845
 
                basis_tree.root_inventory)
 
2572
            delta = parent_tree.inventory._make_delta(basis_tree.inventory)
2846
2573
            self.target.add_inventory_by_delta(
2847
2574
                basis_id, delta, current_revision_id, parents_parents)
2848
2575
            cache[current_revision_id] = parent_tree
2907
2634
                kind = entry.kind
2908
2635
                texts_possibly_new_in_tree.add((file_id, entry.revision))
2909
2636
            for basis_id, basis_tree in possible_trees:
2910
 
                basis_inv = basis_tree.root_inventory
 
2637
                basis_inv = basis_tree.inventory
2911
2638
                for file_key in list(texts_possibly_new_in_tree):
2912
2639
                    file_id, file_revision = file_key
2913
2640
                    try:
2995
2722
        for offset in range(0, len(revision_ids), batch_size):
2996
2723
            self.target.start_write_group()
2997
2724
            try:
2998
 
                pb.update(gettext('Transferring revisions'), offset,
 
2725
                pb.update('Transferring revisions', offset,
2999
2726
                          len(revision_ids))
3000
2727
                batch = revision_ids[offset:offset+batch_size]
3001
2728
                basis_id = self._fetch_batch(batch, basis_id, cache)
3009
2736
                    hints.extend(hint)
3010
2737
        if hints and self.target._format.pack_compresses:
3011
2738
            self.target.pack(hint=hints)
3012
 
        pb.update(gettext('Transferring revisions'), len(revision_ids),
 
2739
        pb.update('Transferring revisions', len(revision_ids),
3013
2740
                  len(revision_ids))
3014
2741
 
3015
2742
    @needs_write_lock
3020
2747
            revision_ids = fetch_spec.get_keys()
3021
2748
        else:
3022
2749
            revision_ids = None
3023
 
        if self.source._format.experimental:
3024
 
            ui.ui_factory.show_user_warning('experimental_format_fetch',
3025
 
                from_format=self.source._format,
3026
 
                to_format=self.target._format)
 
2750
        ui.ui_factory.warn_experimental_format_fetch(self)
3027
2751
        if (not self.source.supports_rich_root()
3028
2752
            and self.target.supports_rich_root()):
3029
2753
            self._converting_to_rich_root = True
3082
2806
        return basis_id, basis_tree
3083
2807
 
3084
2808
 
3085
 
class InterSameDataRepository(InterVersionedFileRepository):
 
2809
class InterSameDataRepository(InterRepository):
3086
2810
    """Code for converting between repositories that represent the same data.
3087
2811
 
3088
2812
    Data format and model must match for this to work.
3106
2830
            target._format.supports_full_versioned_files)
3107
2831
 
3108
2832
 
3109
 
InterRepository.register_optimiser(InterVersionedFileRepository)
3110
2833
InterRepository.register_optimiser(InterDifferingSerializer)
3111
2834
InterRepository.register_optimiser(InterSameDataRepository)
3112
2835
 
3124
2847
            _install_revision(repository, revision, revision_tree, signature,
3125
2848
                inventory_cache)
3126
2849
            if pb is not None:
3127
 
                pb.update(gettext('Transferring revisions'), n + 1, num_revisions)
 
2850
                pb.update('Transferring revisions', n + 1, num_revisions)
3128
2851
    except:
3129
2852
        repository.abort_write_group()
3130
2853
        raise
3145
2868
            parent_trees[p_id] = repository.revision_tree(
3146
2869
                                     _mod_revision.NULL_REVISION)
3147
2870
 
3148
 
    # FIXME: Support nested trees
3149
 
    inv = revision_tree.root_inventory
 
2871
    inv = revision_tree.inventory
3150
2872
    entries = inv.iter_entries()
3151
2873
    # backwards compatibility hack: skip the root id.
3152
2874
    if not repository.supports_rich_root():
3167
2889
        # the parents inserted are not those commit would do - in particular
3168
2890
        # they are not filtered by heads(). RBC, AB
3169
2891
        for revision, tree in parent_trees.iteritems():
3170
 
            if not tree.has_id(ie.file_id):
 
2892
            if ie.file_id not in tree:
3171
2893
                continue
3172
2894
            parent_id = tree.get_file_revision(ie.file_id)
3173
2895
            if parent_id in text_parents: