~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

Merge with annotate

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007, 2008 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
55
55
from bzrlib.inventory import Inventory, InventoryDirectory, ROOT_ID
56
56
from bzrlib.symbol_versioning import (
57
57
        deprecated_method,
 
58
        one_one,
 
59
        one_two,
 
60
        one_three,
 
61
        one_six,
58
62
        )
59
63
from bzrlib.trace import mutter, mutter_callsite, note, warning
60
64
 
408
412
        return self._get_delta(ie, basis_inv, path), True
409
413
 
410
414
    def _add_text_to_weave(self, file_id, new_lines, parents, nostore_sha):
411
 
        versionedfile = self.repository.weave_store.get_weave_or_empty(
412
 
            file_id, self.repository.get_transaction())
413
 
        # Don't change this to add_lines - add_lines_with_ghosts is cheaper
414
 
        # than add_lines, and allows committing when a parent is ghosted for
415
 
        # some reason.
416
415
        # Note: as we read the content directly from the tree, we know its not
417
416
        # been turned into unicode or badly split - but a broken tree
418
417
        # implementation could give us bad output from readlines() so this is
419
418
        # not a guarantee of safety. What would be better is always checking
420
419
        # the content during test suite execution. RBC 20070912
421
 
        return versionedfile.add_lines_with_ghosts(
422
 
            self._new_revision_id, parents, new_lines,
 
420
        parent_keys = tuple((file_id, parent) for parent in parents)
 
421
        return self.repository.texts.add_lines(
 
422
            (file_id, self._new_revision_id), parent_keys, new_lines,
423
423
            nostore_sha=nostore_sha, random_id=self.random_revid,
424
424
            check_content=False)[0:2]
425
425
 
450
450
    revisions and file history.  It's normally accessed only by the Branch,
451
451
    which views a particular line of development through that history.
452
452
 
453
 
    The Repository builds on top of Stores and a Transport, which respectively 
454
 
    describe the disk data format and the way of accessing the (possibly 
 
453
    The Repository builds on top of some byte storage facilies (the revisions,
 
454
    signatures, inventories and texts attributes) and a Transport, which
 
455
    respectively provide byte storage and a means to access the (possibly
455
456
    remote) disk.
456
457
 
 
458
    The byte storage facilities are addressed via tuples, which we refer to
 
459
    as 'keys' throughout the code base. Revision_keys, inventory_keys and
 
460
    signature_keys are all 1-tuples: (revision_id,). text_keys are two-tuples:
 
461
    (file_id, revision_id). We use this interface because it allows low
 
462
    friction with the underlying code that implements disk indices, network
 
463
    encoding and other parts of bzrlib.
 
464
 
 
465
    :ivar revisions: A bzrlib.versionedfile.VersionedFiles instance containing
 
466
        the serialised revisions for the repository. This can be used to obtain
 
467
        revision graph information or to access raw serialised revisions.
 
468
        The result of trying to insert data into the repository via this store
 
469
        is undefined: it should be considered read-only except for implementors
 
470
        of repositories.
 
471
    :ivar signatures: A bzrlib.versionedfile.VersionedFiles instance containing
 
472
        the serialised signatures for the repository. This can be used to
 
473
        obtain access to raw serialised signatures.  The result of trying to
 
474
        insert data into the repository via this store is undefined: it should
 
475
        be considered read-only except for implementors of repositories.
 
476
    :ivar inventories: A bzrlib.versionedfile.VersionedFiles instance containing
 
477
        the serialised inventories for the repository. This can be used to
 
478
        obtain unserialised inventories.  The result of trying to insert data
 
479
        into the repository via this store is undefined: it should be
 
480
        considered read-only except for implementors of repositories.
 
481
    :ivar texts: A bzrlib.versionedfile.VersionedFiles instance containing the
 
482
        texts of files and directories for the repository. This can be used to
 
483
        obtain file texts or file graphs. Note that Repository.iter_file_bytes
 
484
        is usually a better interface for accessing file texts.
 
485
        The result of trying to insert data into the repository via this store
 
486
        is undefined: it should be considered read-only except for implementors
 
487
        of repositories.
457
488
    :ivar _transport: Transport for file access to repository, typically
458
489
        pointing to .bzr/repository.
459
490
    """
513
544
        if inv.root is None:
514
545
            raise AssertionError()
515
546
        inv_lines = self._serialise_inventory_to_lines(inv)
516
 
        inv_vf = self.get_inventory_weave()
517
 
        return self._inventory_add_lines(inv_vf, revision_id, parents,
 
547
        return self._inventory_add_lines(revision_id, parents,
518
548
            inv_lines, check_content=False)
519
549
 
520
 
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines,
 
550
    def _inventory_add_lines(self, revision_id, parents, lines,
521
551
        check_content=True):
522
552
        """Store lines in inv_vf and return the sha1 of the inventory."""
523
 
        final_parents = []
524
 
        for parent in parents:
525
 
            if parent in inv_vf:
526
 
                final_parents.append(parent)
527
 
        return inv_vf.add_lines(revision_id, final_parents, lines,
 
553
        parents = [(parent,) for parent in parents]
 
554
        return self.inventories.add_lines((revision_id,), parents, lines,
528
555
            check_content=check_content)[0]
529
556
 
530
557
    def add_revision(self, revision_id, rev, inv=None, config=None):
547
574
            plaintext = Testament(rev, inv).as_short_text()
548
575
            self.store_revision_signature(
549
576
                gpg.GPGStrategy(config), plaintext, revision_id)
550
 
        inventory_vf = self.get_inventory_weave()
551
 
        if not revision_id in inventory_vf:
 
577
        # check inventory present
 
578
        if not self.inventories.get_parent_map([(revision_id,)]):
552
579
            if inv is None:
553
580
                raise errors.WeaveRevisionNotPresent(revision_id,
554
 
                                                     inventory_vf)
 
581
                                                     self.inventories)
555
582
            else:
556
583
                # yes, this is not suitable for adding with ghosts.
557
584
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
558
585
                                                        rev.parent_ids)
559
586
        else:
560
 
            rev.inventory_sha1 = inventory_vf.get_sha1s([revision_id])[0]
561
 
        self._revision_store.add_revision(rev, self.get_transaction())
 
587
            key = (revision_id,)
 
588
            rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
 
589
        self._add_revision(rev)
562
590
 
563
 
    def _add_revision_text(self, revision_id, text):
564
 
        revision = self._revision_store._serializer.read_revision_from_string(
565
 
            text)
566
 
        self._revision_store._add_revision(revision, StringIO(text),
567
 
                                           self.get_transaction())
 
591
    def _add_revision(self, revision):
 
592
        text = self._serializer.write_revision_to_string(revision)
 
593
        key = (revision.revision_id,)
 
594
        parents = tuple((parent,) for parent in revision.parent_ids)
 
595
        self.revisions.add_lines(key, parents, osutils.split_lines(text))
568
596
 
569
597
    def all_revision_ids(self):
570
598
        """Returns a list of all the revision ids in the repository. 
610
638
        """Construct the current default format repository in a_bzrdir."""
611
639
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
612
640
 
613
 
    def __init__(self, _format, a_bzrdir, control_files,
614
 
                 _revision_store, control_store, text_store):
 
641
    def __init__(self, _format, a_bzrdir, control_files):
615
642
        """instantiate a Repository.
616
643
 
617
644
        :param _format: The format of the repository on disk.
628
655
        self.control_files = control_files
629
656
        self._transport = control_files._transport
630
657
        self.base = self._transport.base
631
 
        self._revision_store = _revision_store
632
 
        # backwards compatibility
633
 
        self.weave_store = text_store
634
658
        # for tests
635
659
        self._reconcile_does_inventory_gc = True
636
660
        self._reconcile_fixes_text_parents = False
637
661
        self._reconcile_backsup_inventory = True
638
662
        # not right yet - should be more semantically clear ? 
639
663
        # 
640
 
        self.control_store = control_store
641
 
        self.control_weaves = control_store
642
664
        # TODO: make sure to construct the right store classes, etc, depending
643
665
        # on whether escaping is required.
644
666
        self._warn_if_deprecated()
766
788
                last_revision.timezone)
767
789
 
768
790
        # now gather global repository information
 
791
        # XXX: This is available for many repos regardless of listability.
769
792
        if self.bzrdir.root_transport.listable():
770
 
            c, t = self._revision_store.total_size(self.get_transaction())
771
 
            result['revisions'] = c
772
 
            result['size'] = t
 
793
            # XXX: do we want to __define len__() ?
 
794
            # Maybe the versionedfiles object should provide a different
 
795
            # method to get the number of keys.
 
796
            result['revisions'] = len(self.revisions.keys())
 
797
            # result['size'] = t
773
798
        return result
774
799
 
775
800
    def find_branches(self, using=False):
815
840
                branches.extend(repository.find_branches())
816
841
        return branches
817
842
 
818
 
    def get_data_stream(self, revision_ids):
819
 
        raise NotImplementedError(self.get_data_stream)
820
 
 
821
 
    def get_data_stream_for_search(self, search_result):
822
 
        """Get a data stream that can be inserted to a repository.
823
 
 
824
 
        :param search_result: A bzrlib.graph.SearchResult selecting the
825
 
            revisions to get.
826
 
        :return: A data stream that can be inserted into a repository using
827
 
            insert_data_stream.
828
 
        """
829
 
        raise NotImplementedError(self.get_data_stream_for_search)
830
 
 
831
 
    def insert_data_stream(self, stream):
832
 
        """XXX What does this really do? 
833
 
        
834
 
        Is it a substitute for fetch? 
835
 
        Should it manage its own write group ?
836
 
        """
837
 
        revisions_inserted = 0
838
 
        for item_key, bytes in stream:
839
 
            if item_key[0] == 'file':
840
 
                (file_id,) = item_key[1:]
841
 
                knit = self.weave_store.get_weave_or_empty(
842
 
                    file_id, self.get_transaction())
843
 
            elif item_key == ('inventory',):
844
 
                knit = self.get_inventory_weave()
845
 
            elif item_key == ('revisions',):
846
 
                knit = self._revision_store.get_revision_file(
847
 
                    self.get_transaction())
848
 
                revisions_inserted += 1
849
 
            elif item_key == ('signatures',):
850
 
                knit = self._revision_store.get_signature_file(
851
 
                    self.get_transaction())
852
 
            else:
853
 
                raise errors.RepositoryDataStreamError(
854
 
                    "Unrecognised data stream key '%s'" % (item_key,))
855
 
            decoded_list = bencode.bdecode(bytes)
856
 
            format = decoded_list.pop(0)
857
 
            data_list = []
858
 
            knit_bytes = ''
859
 
            for version, options, parents, some_bytes in decoded_list:
860
 
                data_list.append((version, options, len(some_bytes), parents))
861
 
                knit_bytes += some_bytes
862
 
            buffer = StringIO(knit_bytes)
863
 
            def reader_func(count):
864
 
                if count is None:
865
 
                    return buffer.read()
866
 
                else:
867
 
                    return buffer.read(count)
868
 
            knit.insert_data_stream(
869
 
                (format, data_list, reader_func))
870
 
        return revisions_inserted
871
 
 
872
843
    @needs_read_lock
873
844
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
874
845
        """Return the revision ids that other has that this does not.
880
851
        return InterRepository.get(other, self).search_missing_revision_ids(
881
852
            revision_id, find_ghosts)
882
853
 
883
 
    @deprecated_method(symbol_versioning.one_two)
 
854
    @deprecated_method(one_two)
884
855
    @needs_read_lock
885
856
    def missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
886
857
        """Return the revision ids that other has that this does not.
1064
1035
        """True if this repository has a copy of the revision."""
1065
1036
        return revision_id in self.has_revisions((revision_id,))
1066
1037
 
 
1038
    @needs_read_lock
1067
1039
    def has_revisions(self, revision_ids):
1068
1040
        """Probe to find out the presence of multiple revisions.
1069
1041
 
1070
1042
        :param revision_ids: An iterable of revision_ids.
1071
1043
        :return: A set of the revision_ids that were present.
1072
1044
        """
1073
 
        raise NotImplementedError(self.has_revisions)
1074
 
 
1075
 
        return self._revision_store.has_revision_id(revision_id,
1076
 
                                                    self.get_transaction())
 
1045
        parent_map = self.revisions.get_parent_map(
 
1046
            [(rev_id,) for rev_id in revision_ids])
 
1047
        result = set()
 
1048
        if _mod_revision.NULL_REVISION in revision_ids:
 
1049
            result.add(_mod_revision.NULL_REVISION)
 
1050
        result.update([key[0] for key in parent_map])
 
1051
        return result
1077
1052
 
1078
1053
    @needs_read_lock
1079
1054
    def get_revision(self, revision_id):
1102
1077
        for rev_id in revision_ids:
1103
1078
            if not rev_id or not isinstance(rev_id, basestring):
1104
1079
                raise errors.InvalidRevisionId(revision_id=rev_id, branch=self)
1105
 
        revs = self._revision_store.get_revisions(revision_ids,
1106
 
                                                  self.get_transaction())
1107
 
        return revs
 
1080
        keys = [(key,) for key in revision_ids]
 
1081
        stream = self.revisions.get_record_stream(keys, 'unordered', True)
 
1082
        revs = {}
 
1083
        for record in stream:
 
1084
            if record.storage_kind == 'absent':
 
1085
                raise errors.NoSuchRevision(self, record.key[0])
 
1086
            text = record.get_bytes_as('fulltext')
 
1087
            rev = self._serializer.read_revision_from_string(text)
 
1088
            revs[record.key[0]] = rev
 
1089
        return [revs[revid] for revid in revision_ids]
1108
1090
 
1109
1091
    @needs_read_lock
1110
1092
    def get_revision_xml(self, revision_id):
1114
1096
        rev = self.get_revision(revision_id)
1115
1097
        rev_tmp = StringIO()
1116
1098
        # the current serializer..
1117
 
        self._revision_store._serializer.write_revision(rev, rev_tmp)
 
1099
        self._serializer.write_revision(rev, rev_tmp)
1118
1100
        rev_tmp.seek(0)
1119
1101
        return rev_tmp.getvalue()
1120
1102
 
1155
1137
 
1156
1138
    @needs_write_lock
1157
1139
    def add_signature_text(self, revision_id, signature):
1158
 
        self._revision_store.add_revision_signature_text(revision_id,
1159
 
                                                         signature,
1160
 
                                                         self.get_transaction())
 
1140
        self.signatures.add_lines((revision_id,), (),
 
1141
            osutils.split_lines(signature))
1161
1142
 
1162
1143
    def find_text_key_references(self):
1163
1144
        """Find the text key references within the repository.
1170
1151
            revision_id that they contain. The inventory texts from all present
1171
1152
            revision ids are assessed to generate this report.
1172
1153
        """
1173
 
        revision_ids = self.all_revision_ids()
1174
 
        w = self.get_inventory_weave()
 
1154
        revision_keys = self.revisions.keys()
 
1155
        w = self.inventories
1175
1156
        pb = ui.ui_factory.nested_progress_bar()
1176
1157
        try:
1177
1158
            return self._find_text_key_references_from_xml_inventory_lines(
1178
 
                w.iter_lines_added_or_present_in_versions(revision_ids, pb=pb))
 
1159
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
1179
1160
        finally:
1180
1161
            pb.finished()
1181
1162
 
1218
1199
        search = self._file_ids_altered_regex.search
1219
1200
        unescape = _unescape_xml
1220
1201
        setdefault = result.setdefault
1221
 
        for line, version_id in line_iterator:
 
1202
        for line, line_key in line_iterator:
1222
1203
            match = search(line)
1223
1204
            if match is None:
1224
1205
                continue
1255
1236
 
1256
1237
            key = (file_id, revision_id)
1257
1238
            setdefault(key, False)
1258
 
            if revision_id == version_id:
 
1239
            if revision_id == line_key[-1]:
1259
1240
                result[key] = True
1260
1241
        return result
1261
1242
 
1276
1257
        """
1277
1258
        result = {}
1278
1259
        setdefault = result.setdefault
1279
 
        for file_id, revision_id in \
 
1260
        for key in \
1280
1261
            self._find_text_key_references_from_xml_inventory_lines(
1281
1262
                line_iterator).iterkeys():
1282
1263
            # once data is all ensured-consistent; then this is
1283
1264
            # if revision_id == version_id
1284
 
            if revision_id in revision_ids:
1285
 
                setdefault(file_id, set()).add(revision_id)
 
1265
            if key[-1:] in revision_ids:
 
1266
                setdefault(key[0], set()).add(key[-1])
1286
1267
        return result
1287
1268
 
1288
1269
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1295
1276
        revision_ids. Each altered file-ids has the exact revision_ids that
1296
1277
        altered it listed explicitly.
1297
1278
        """
1298
 
        selected_revision_ids = set(revision_ids)
1299
 
        w = _inv_weave or self.get_inventory_weave()
 
1279
        selected_keys = set((revid,) for revid in revision_ids)
 
1280
        w = _inv_weave or self.inventories
1300
1281
        pb = ui.ui_factory.nested_progress_bar()
1301
1282
        try:
1302
1283
            return self._find_file_ids_from_xml_inventory_lines(
1303
 
                w.iter_lines_added_or_present_in_versions(
1304
 
                    selected_revision_ids, pb=pb),
1305
 
                selected_revision_ids)
 
1284
                w.iter_lines_added_or_present_in_keys(
 
1285
                    selected_keys, pb=pb),
 
1286
                selected_keys)
1306
1287
        finally:
1307
1288
            pb.finished()
1308
1289
 
1319
1300
 
1320
1301
        bytes_iterator is an iterable of bytestrings for the file.  The
1321
1302
        kind of iterable and length of the bytestrings are unspecified, but for
1322
 
        this implementation, it is a list of lines produced by
1323
 
        VersionedFile.get_lines().
 
1303
        this implementation, it is a list of bytes produced by
 
1304
        VersionedFile.get_record_stream().
1324
1305
 
1325
1306
        :param desired_files: a list of (file_id, revision_id, identifier)
1326
1307
            triples
1327
1308
        """
1328
1309
        transaction = self.get_transaction()
 
1310
        text_keys = {}
1329
1311
        for file_id, revision_id, callable_data in desired_files:
1330
 
            try:
1331
 
                weave = self.weave_store.get_weave(file_id, transaction)
1332
 
            except errors.NoSuchFile:
1333
 
                raise errors.NoSuchIdInRepository(self, file_id)
1334
 
            yield callable_data, weave.get_lines(revision_id)
 
1312
            text_keys[(file_id, revision_id)] = callable_data
 
1313
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
 
1314
            if record.storage_kind == 'absent':
 
1315
                raise errors.RevisionNotPresent(record.key, self)
 
1316
            yield text_keys[record.key], record.get_bytes_as('fulltext')
1335
1317
 
1336
1318
    def _generate_text_key_index(self, text_key_references=None,
1337
1319
        ancestors=None):
1454
1436
        # maybe this generator should explicitly have the contract that it
1455
1437
        # should not be iterated until the previously yielded item has been
1456
1438
        # processed?
1457
 
        inv_w = self.get_inventory_weave()
 
1439
        inv_w = self.inventories
1458
1440
 
1459
1441
        # file ids that changed
1460
1442
        file_ids = self.fileids_altered_by_revision_ids(revision_ids, inv_w)
1488
1470
        yield ("revisions", None, revision_ids)
1489
1471
 
1490
1472
    @needs_read_lock
1491
 
    def get_inventory_weave(self):
1492
 
        return self.control_weaves.get_weave('inventory',
1493
 
            self.get_transaction())
1494
 
 
1495
 
    @needs_read_lock
1496
1473
    def get_inventory(self, revision_id):
1497
1474
        """Get Inventory object by revision id."""
1498
1475
        return self.iter_inventories([revision_id]).next()
1513
1490
 
1514
1491
    def _iter_inventories(self, revision_ids):
1515
1492
        """single-document based inventory iteration."""
1516
 
        texts = self.get_inventory_weave().get_texts(revision_ids)
1517
 
        for text, revision_id in zip(texts, revision_ids):
 
1493
        for text, revision_id in self._iter_inventory_xmls(revision_ids):
1518
1494
            yield self.deserialise_inventory(revision_id, text)
1519
1495
 
 
1496
    def _iter_inventory_xmls(self, revision_ids):
 
1497
        keys = [(revision_id,) for revision_id in revision_ids]
 
1498
        stream = self.inventories.get_record_stream(keys, 'unordered', True)
 
1499
        texts = {}
 
1500
        for record in stream:
 
1501
            if record.storage_kind != 'absent':
 
1502
                texts[record.key] = record.get_bytes_as('fulltext')
 
1503
            else:
 
1504
                raise errors.NoSuchRevision(self, record.key)
 
1505
        for key in keys:
 
1506
            yield texts[key], key[-1]
 
1507
 
1520
1508
    def deserialise_inventory(self, revision_id, xml):
1521
1509
        """Transform the xml into an inventory object. 
1522
1510
 
1541
1529
    @needs_read_lock
1542
1530
    def get_inventory_xml(self, revision_id):
1543
1531
        """Get inventory XML as a file object."""
 
1532
        texts = self._iter_inventory_xmls([revision_id])
1544
1533
        try:
1545
 
            iw = self.get_inventory_weave()
1546
 
            return iw.get_text(revision_id)
1547
 
        except IndexError:
 
1534
            text, revision_id = texts.next()
 
1535
        except StopIteration:
1548
1536
            raise errors.HistoryMissing(self, 'inventory', revision_id)
 
1537
        return text
1549
1538
 
1550
1539
    @needs_read_lock
1551
1540
    def get_inventory_sha1(self, revision_id):
1652
1641
            return [None]
1653
1642
        if not self.has_revision(revision_id):
1654
1643
            raise errors.NoSuchRevision(self, revision_id)
1655
 
        w = self.get_inventory_weave()
1656
 
        candidates = w.get_ancestry(revision_id, topo_sorted)
1657
 
        return [None] + candidates # self._eliminate_revisions_not_present(candidates)
 
1644
        graph = self.get_graph()
 
1645
        keys = set()
 
1646
        search = graph._make_breadth_first_searcher([revision_id])
 
1647
        while True:
 
1648
            try:
 
1649
                found, ghosts = search.next_with_ghosts()
 
1650
            except StopIteration:
 
1651
                break
 
1652
            keys.update(found)
 
1653
        if _mod_revision.NULL_REVISION in keys:
 
1654
            keys.remove(_mod_revision.NULL_REVISION)
 
1655
        if topo_sorted:
 
1656
            parent_map = graph.get_parent_map(keys)
 
1657
            keys = tsort.topo_sort(parent_map)
 
1658
        return [None] + list(keys)
1658
1659
 
1659
1660
    def pack(self):
1660
1661
        """Compress the data within the repository.
1668
1669
        """
1669
1670
 
1670
1671
    @needs_read_lock
 
1672
    @deprecated_method(one_six)
1671
1673
    def print_file(self, file, revision_id):
1672
1674
        """Print `file` to stdout.
1673
1675
        
1688
1690
    def get_transaction(self):
1689
1691
        return self.control_files.get_transaction()
1690
1692
 
1691
 
    @deprecated_method(symbol_versioning.one_one)
 
1693
    @deprecated_method(one_one)
1692
1694
    def get_parents(self, revision_ids):
1693
1695
        """See StackedParentsProvider.get_parents"""
1694
1696
        parent_map = self.get_parent_map(revision_ids)
1768
1770
    @needs_read_lock
1769
1771
    def has_signature_for_revision_id(self, revision_id):
1770
1772
        """Query for a revision signature for revision_id in the repository."""
1771
 
        return self._revision_store.has_signature(revision_id,
1772
 
                                                  self.get_transaction())
 
1773
        if not self.has_revision(revision_id):
 
1774
            raise errors.NoSuchRevision(self, revision_id)
 
1775
        sig_present = (1 == len(
 
1776
            self.signatures.get_parent_map([(revision_id,)])))
 
1777
        return sig_present
1773
1778
 
1774
1779
    @needs_read_lock
1775
1780
    def get_signature_text(self, revision_id):
1776
1781
        """Return the text for a signature."""
1777
 
        return self._revision_store.get_signature_text(revision_id,
1778
 
                                                       self.get_transaction())
 
1782
        stream = self.signatures.get_record_stream([(revision_id,)],
 
1783
            'unordered', True)
 
1784
        record = stream.next()
 
1785
        if record.storage_kind == 'absent':
 
1786
            raise errors.NoSuchRevision(self, revision_id)
 
1787
        return record.get_bytes_as('fulltext')
1779
1788
 
1780
1789
    @needs_read_lock
1781
1790
    def check(self, revision_ids=None):
1908
1917
        path, root = entries.next()
1909
1918
        if root.revision != rev.revision_id:
1910
1919
            raise errors.IncompatibleRevision(repr(repository))
 
1920
    text_keys = {}
 
1921
    for path, ie in entries:
 
1922
        text_keys[(ie.file_id, ie.revision)] = ie
 
1923
    text_parent_map = repository.texts.get_parent_map(text_keys)
 
1924
    missing_texts = set(text_keys) - set(text_parent_map)
1911
1925
    # Add the texts that are not already present
1912
 
    for path, ie in entries:
1913
 
        w = repository.weave_store.get_weave_or_empty(ie.file_id,
1914
 
                repository.get_transaction())
1915
 
        if ie.revision not in w:
1916
 
            text_parents = []
1917
 
            # FIXME: TODO: The following loop *may* be overlapping/duplicate
1918
 
            # with InventoryEntry.find_previous_heads(). if it is, then there
1919
 
            # is a latent bug here where the parents may have ancestors of each
1920
 
            # other. RBC, AB
1921
 
            for revision, tree in parent_trees.iteritems():
1922
 
                if ie.file_id not in tree:
1923
 
                    continue
1924
 
                parent_id = tree.inventory[ie.file_id].revision
1925
 
                if parent_id in text_parents:
1926
 
                    continue
1927
 
                text_parents.append(parent_id)
1928
 
                    
1929
 
            vfile = repository.weave_store.get_weave_or_empty(ie.file_id, 
1930
 
                repository.get_transaction())
1931
 
            lines = revision_tree.get_file(ie.file_id).readlines()
1932
 
            vfile.add_lines(rev.revision_id, text_parents, lines)
 
1926
    for text_key in missing_texts:
 
1927
        ie = text_keys[text_key]
 
1928
        text_parents = []
 
1929
        # FIXME: TODO: The following loop overlaps/duplicates that done by
 
1930
        # commit to determine parents. There is a latent/real bug here where
 
1931
        # the parents inserted are not those commit would do - in particular
 
1932
        # they are not filtered by heads(). RBC, AB
 
1933
        for revision, tree in parent_trees.iteritems():
 
1934
            if ie.file_id not in tree:
 
1935
                continue
 
1936
            parent_id = tree.inventory[ie.file_id].revision
 
1937
            if parent_id in text_parents:
 
1938
                continue
 
1939
            text_parents.append((ie.file_id, parent_id))
 
1940
        lines = revision_tree.get_file(ie.file_id).readlines()
 
1941
        repository.texts.add_lines(text_key, text_parents, lines)
1933
1942
    try:
1934
1943
        # install the inventory
1935
1944
        repository.add_inventory(rev.revision_id, inv, present_parents)
1947
1956
        typically pointing to .bzr/repository.
1948
1957
    """
1949
1958
 
1950
 
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
1951
 
        super(MetaDirRepository, self).__init__(_format,
1952
 
                                                a_bzrdir,
1953
 
                                                control_files,
1954
 
                                                _revision_store,
1955
 
                                                control_store,
1956
 
                                                text_store)
 
1959
    def __init__(self, _format, a_bzrdir, control_files):
 
1960
        super(MetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
1957
1961
        self._transport = control_files._transport
1958
1962
 
1959
1963
    @needs_read_lock
1988
1992
class MetaDirVersionedFileRepository(MetaDirRepository):
1989
1993
    """Repositories in a meta-dir, that work via versioned file objects."""
1990
1994
 
1991
 
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
1995
    def __init__(self, _format, a_bzrdir, control_files):
1992
1996
        super(MetaDirVersionedFileRepository, self).__init__(_format, a_bzrdir,
1993
 
            control_files, _revision_store, control_store, text_store)
1994
 
        _revision_store.get_scope = self.get_transaction
1995
 
        control_store.get_scope = self.get_transaction
1996
 
        text_store.get_scope = self.get_transaction
 
1997
            control_files)
1997
1998
 
1998
1999
 
1999
2000
class RepositoryFormatRegistry(registry.Registry):
2095
2096
        from bzrlib import bzrdir
2096
2097
        return bzrdir.format_registry.make_bzrdir('default').repository_format
2097
2098
 
2098
 
    def _get_control_store(self, repo_transport, control_files):
2099
 
        """Return the control store for this repository."""
2100
 
        raise NotImplementedError(self._get_control_store)
2101
 
 
2102
2099
    def get_format_string(self):
2103
2100
        """Return the ASCII format string that identifies this format.
2104
2101
        
2111
2108
        """Return the short description for this format."""
2112
2109
        raise NotImplementedError(self.get_format_description)
2113
2110
 
2114
 
    def _get_revision_store(self, repo_transport, control_files):
2115
 
        """Return the revision store object for this a_bzrdir."""
2116
 
        raise NotImplementedError(self._get_revision_store)
2117
 
 
2118
 
    def _get_text_rev_store(self,
2119
 
                            transport,
2120
 
                            control_files,
2121
 
                            name,
2122
 
                            compressed=True,
2123
 
                            prefixed=False,
2124
 
                            serializer=None):
2125
 
        """Common logic for getting a revision store for a repository.
2126
 
        
2127
 
        see self._get_revision_store for the subclass-overridable method to 
2128
 
        get the store for a repository.
2129
 
        """
2130
 
        from bzrlib.store.revision.text import TextRevisionStore
2131
 
        dir_mode = control_files._dir_mode
2132
 
        file_mode = control_files._file_mode
2133
 
        text_store = TextStore(transport.clone(name),
2134
 
                              prefixed=prefixed,
2135
 
                              compressed=compressed,
2136
 
                              dir_mode=dir_mode,
2137
 
                              file_mode=file_mode)
2138
 
        _revision_store = TextRevisionStore(text_store, serializer)
2139
 
        return _revision_store
2140
 
 
2141
2111
    # TODO: this shouldn't be in the base class, it's specific to things that
2142
2112
    # use weaves or knits -- mbp 20070207
2143
2113
    def _get_versioned_file_store(self,
2366
2336
            searcher.stop_searching_any(have_revs)
2367
2337
        return searcher.get_result()
2368
2338
   
2369
 
    @deprecated_method(symbol_versioning.one_two)
 
2339
    @deprecated_method(one_two)
2370
2340
    @needs_read_lock
2371
2341
    def missing_revision_ids(self, revision_id=None, find_ghosts=True):
2372
2342
        """Return the revision ids that source has that target does not.
2520
2490
        if self.source._transport.listable():
2521
2491
            pb = ui.ui_factory.nested_progress_bar()
2522
2492
            try:
2523
 
                self.target.weave_store.copy_all_ids(
2524
 
                    self.source.weave_store,
2525
 
                    pb=pb,
2526
 
                    from_transaction=self.source.get_transaction(),
2527
 
                    to_transaction=self.target.get_transaction())
 
2493
                self.target.texts.insert_record_stream(
 
2494
                    self.source.texts.get_record_stream(
 
2495
                        self.source.texts.keys(), 'topological', False))
2528
2496
                pb.update('copying inventory', 0, 1)
2529
 
                self.target.control_weaves.copy_multi(
2530
 
                    self.source.control_weaves, ['inventory'],
2531
 
                    from_transaction=self.source.get_transaction(),
2532
 
                    to_transaction=self.target.get_transaction())
2533
 
                self.target._revision_store.text_store.copy_all_ids(
2534
 
                    self.source._revision_store.text_store,
2535
 
                    pb=pb)
 
2497
                self.target.inventories.insert_record_stream(
 
2498
                    self.source.inventories.get_record_stream(
 
2499
                        self.source.inventories.keys(), 'topological', False))
 
2500
                self.target.signatures.insert_record_stream(
 
2501
                    self.source.signatures.get_record_stream(
 
2502
                        self.source.signatures.keys(),
 
2503
                        'unordered', True))
 
2504
                self.target.revisions.insert_record_stream(
 
2505
                    self.source.revisions.get_record_stream(
 
2506
                        self.source.revisions.keys(),
 
2507
                        'topological', True))
2536
2508
            finally:
2537
2509
                pb.finished()
2538
2510
        else:
2905
2877
        return len(revision_ids), 0
2906
2878
 
2907
2879
 
2908
 
class InterRemoteToOther(InterRepository):
2909
 
 
2910
 
    def __init__(self, source, target):
2911
 
        InterRepository.__init__(self, source, target)
2912
 
        self._real_inter = None
2913
 
 
2914
 
    @staticmethod
2915
 
    def is_compatible(source, target):
2916
 
        if not isinstance(source, remote.RemoteRepository):
2917
 
            return False
2918
 
        # Is source's model compatible with target's model?
2919
 
        source._ensure_real()
2920
 
        real_source = source._real_repository
2921
 
        if isinstance(real_source, remote.RemoteRepository):
2922
 
            raise NotImplementedError(
2923
 
                "We don't support remote repos backed by remote repos yet.")
2924
 
        return InterRepository._same_model(real_source, target)
2925
 
 
2926
 
    @needs_write_lock
2927
 
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2928
 
        """See InterRepository.fetch()."""
2929
 
        from bzrlib.fetch import RemoteToOtherFetcher
2930
 
        mutter("Using fetch logic to copy between %s(remote) and %s(%s)",
2931
 
               self.source, self.target, self.target._format)
2932
 
        # TODO: jam 20070210 This should be an assert, not a translate
2933
 
        revision_id = osutils.safe_revision_id(revision_id)
2934
 
        f = RemoteToOtherFetcher(to_repository=self.target,
2935
 
                                 from_repository=self.source,
2936
 
                                 last_revision=revision_id,
2937
 
                                 pb=pb, find_ghosts=find_ghosts)
2938
 
        return f.count_copied, f.failed_revisions
2939
 
 
2940
 
    @classmethod
2941
 
    def _get_repo_format_to_test(self):
2942
 
        return None
2943
 
 
2944
 
 
2945
2880
class InterOtherToRemote(InterRepository):
2946
2881
 
2947
2882
    def __init__(self, source, target):
2966
2901
 
2967
2902
    def fetch(self, revision_id=None, pb=None, find_ghosts=False):
2968
2903
        self._ensure_real_inter()
2969
 
        self._real_inter.fetch(revision_id=revision_id, pb=pb,
 
2904
        return self._real_inter.fetch(revision_id=revision_id, pb=pb,
2970
2905
            find_ghosts=find_ghosts)
2971
2906
 
2972
2907
    @classmethod
2981
2916
InterRepository.register_optimiser(InterModel1and2)
2982
2917
InterRepository.register_optimiser(InterKnit1and2)
2983
2918
InterRepository.register_optimiser(InterPackRepo)
2984
 
InterRepository.register_optimiser(InterRemoteToOther)
2985
2919
InterRepository.register_optimiser(InterOtherToRemote)
2986
2920
 
2987
2921
 
3073
3007
        self.repository = repository
3074
3008
        self.text_index = self.repository._generate_text_key_index()
3075
3009
    
3076
 
    def calculate_file_version_parents(self, revision_id, file_id):
 
3010
    def calculate_file_version_parents(self, text_key):
3077
3011
        """Calculate the correct parents for a file version according to
3078
3012
        the inventories.
3079
3013
        """
3080
 
        parent_keys = self.text_index[(file_id, revision_id)]
 
3014
        parent_keys = self.text_index[text_key]
3081
3015
        if parent_keys == [_mod_revision.NULL_REVISION]:
3082
3016
            return ()
3083
 
        # strip the file_id, for the weave api
3084
 
        return tuple([revision_id for file_id, revision_id in parent_keys])
 
3017
        return tuple(parent_keys)
3085
3018
 
3086
 
    def check_file_version_parents(self, weave, file_id):
 
3019
    def check_file_version_parents(self, texts, progress_bar=None):
3087
3020
        """Check the parents stored in a versioned file are correct.
3088
3021
 
3089
3022
        It also detects file versions that are not referenced by their
3097
3030
            file, but not used by the corresponding inventory.
3098
3031
        """
3099
3032
        wrong_parents = {}
3100
 
        unused_versions = set()
3101
 
        versions = weave.versions()
3102
 
        parent_map = weave.get_parent_map(versions)
3103
 
        for num, revision_id in enumerate(versions):
 
3033
        self.file_ids = set([file_id for file_id, _ in
 
3034
            self.text_index.iterkeys()])
 
3035
        # text keys is now grouped by file_id
 
3036
        n_weaves = len(self.file_ids)
 
3037
        files_in_revisions = {}
 
3038
        revisions_of_files = {}
 
3039
        n_versions = len(self.text_index)
 
3040
        progress_bar.update('loading text store', 0, n_versions)
 
3041
        parent_map = self.repository.texts.get_parent_map(self.text_index)
 
3042
        # On unlistable transports this could well be empty/error...
 
3043
        text_keys = self.repository.texts.keys()
 
3044
        unused_keys = frozenset(text_keys) - set(self.text_index)
 
3045
        for num, key in enumerate(self.text_index.iterkeys()):
 
3046
            if progress_bar is not None:
 
3047
                progress_bar.update('checking text graph', num, n_versions)
 
3048
            correct_parents = self.calculate_file_version_parents(key)
3104
3049
            try:
3105
 
                correct_parents = self.calculate_file_version_parents(
3106
 
                    revision_id, file_id)
3107
 
            except KeyError:
3108
 
                # The version is not part of the used keys.
3109
 
                unused_versions.add(revision_id)
3110
 
            else:
3111
 
                try:
3112
 
                    knit_parents = tuple(parent_map[revision_id])
3113
 
                except errors.RevisionNotPresent:
3114
 
                    knit_parents = None
3115
 
                if correct_parents != knit_parents:
3116
 
                    wrong_parents[revision_id] = (knit_parents, correct_parents)
3117
 
        return wrong_parents, unused_versions
 
3050
                knit_parents = parent_map[key]
 
3051
            except errors.RevisionNotPresent:
 
3052
                # Missing text!
 
3053
                knit_parents = None
 
3054
            if correct_parents != knit_parents:
 
3055
                wrong_parents[key] = (knit_parents, correct_parents)
 
3056
        return wrong_parents, unused_keys
3118
3057
 
3119
3058
 
3120
3059
def _old_get_graph(repository, revision_id):