~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-06-25 10:36:36 UTC
  • mfrom: (3350.6.12 versionedfiles)
  • Revision ID: pqm@pqm.ubuntu.com-20080625103636-6kxh4e1gmyn82f50
(mbp for robertc) VersionedFiles refactoring

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
408
408
        return self._get_delta(ie, basis_inv, path), True
409
409
 
410
410
    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
411
        # Note: as we read the content directly from the tree, we know its not
417
412
        # been turned into unicode or badly split - but a broken tree
418
413
        # implementation could give us bad output from readlines() so this is
419
414
        # not a guarantee of safety. What would be better is always checking
420
415
        # the content during test suite execution. RBC 20070912
421
 
        return versionedfile.add_lines_with_ghosts(
422
 
            self._new_revision_id, parents, new_lines,
 
416
        parent_keys = tuple((file_id, parent) for parent in parents)
 
417
        return self.repository.texts.add_lines(
 
418
            (file_id, self._new_revision_id), parent_keys, new_lines,
423
419
            nostore_sha=nostore_sha, random_id=self.random_revid,
424
420
            check_content=False)[0:2]
425
421
 
450
446
    revisions and file history.  It's normally accessed only by the Branch,
451
447
    which views a particular line of development through that history.
452
448
 
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 
 
449
    The Repository builds on top of some byte storage facilies (the revisions,
 
450
    signatures, inventories and texts attributes) and a Transport, which
 
451
    respectively provide byte storage and a means to access the (possibly
455
452
    remote) disk.
456
453
 
 
454
    The byte storage facilities are addressed via tuples, which we refer to
 
455
    as 'keys' throughout the code base. Revision_keys, inventory_keys and
 
456
    signature_keys are all 1-tuples: (revision_id,). text_keys are two-tuples:
 
457
    (file_id, revision_id). We use this interface because it allows low
 
458
    friction with the underlying code that implements disk indices, network
 
459
    encoding and other parts of bzrlib.
 
460
 
 
461
    :ivar revisions: A bzrlib.versionedfile.VersionedFiles instance containing
 
462
        the serialised revisions for the repository. This can be used to obtain
 
463
        revision graph information or to access raw serialised revisions.
 
464
        The result of trying to insert data into the repository via this store
 
465
        is undefined: it should be considered read-only except for implementors
 
466
        of repositories.
 
467
    :ivar signatures: A bzrlib.versionedfile.VersionedFiles instance containing
 
468
        the serialised signatures for the repository. This can be used to
 
469
        obtain access to raw serialised signatures.  The result of trying to
 
470
        insert data into the repository via this store is undefined: it should
 
471
        be considered read-only except for implementors of repositories.
 
472
    :ivar inventories: A bzrlib.versionedfile.VersionedFiles instance containing
 
473
        the serialised inventories for the repository. This can be used to
 
474
        obtain unserialised inventories.  The result of trying to insert data
 
475
        into the repository via this store is undefined: it should be
 
476
        considered read-only except for implementors of repositories.
 
477
    :ivar texts: A bzrlib.versionedfile.VersionedFiles instance containing the
 
478
        texts of files and directories for the repository. This can be used to
 
479
        obtain file texts or file graphs. Note that Repository.iter_file_bytes
 
480
        is usually a better interface for accessing file texts.
 
481
        The result of trying to insert data into the repository via this store
 
482
        is undefined: it should be considered read-only except for implementors
 
483
        of repositories.
457
484
    :ivar _transport: Transport for file access to repository, typically
458
485
        pointing to .bzr/repository.
459
486
    """
513
540
        if inv.root is None:
514
541
            raise AssertionError()
515
542
        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,
 
543
        return self._inventory_add_lines(revision_id, parents,
518
544
            inv_lines, check_content=False)
519
545
 
520
 
    def _inventory_add_lines(self, inv_vf, revision_id, parents, lines,
 
546
    def _inventory_add_lines(self, revision_id, parents, lines,
521
547
        check_content=True):
522
548
        """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,
 
549
        parents = [(parent,) for parent in parents]
 
550
        return self.inventories.add_lines((revision_id,), parents, lines,
528
551
            check_content=check_content)[0]
529
552
 
530
553
    def add_revision(self, revision_id, rev, inv=None, config=None):
547
570
            plaintext = Testament(rev, inv).as_short_text()
548
571
            self.store_revision_signature(
549
572
                gpg.GPGStrategy(config), plaintext, revision_id)
550
 
        inventory_vf = self.get_inventory_weave()
551
 
        if not revision_id in inventory_vf:
 
573
        # check inventory present
 
574
        if not self.inventories.get_parent_map([(revision_id,)]):
552
575
            if inv is None:
553
576
                raise errors.WeaveRevisionNotPresent(revision_id,
554
 
                                                     inventory_vf)
 
577
                                                     self.inventories)
555
578
            else:
556
579
                # yes, this is not suitable for adding with ghosts.
557
580
                rev.inventory_sha1 = self.add_inventory(revision_id, inv,
558
581
                                                        rev.parent_ids)
559
582
        else:
560
 
            rev.inventory_sha1 = inventory_vf.get_sha1s([revision_id])[0]
561
 
        self._revision_store.add_revision(rev, self.get_transaction())
 
583
            rev.inventory_sha1 = self.inventories.get_sha1s([(revision_id,)])[0]
 
584
        self._add_revision(rev)
562
585
 
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())
 
586
    def _add_revision(self, revision):
 
587
        text = self._serializer.write_revision_to_string(revision)
 
588
        key = (revision.revision_id,)
 
589
        parents = tuple((parent,) for parent in revision.parent_ids)
 
590
        self.revisions.add_lines(key, parents, osutils.split_lines(text))
568
591
 
569
592
    def all_revision_ids(self):
570
593
        """Returns a list of all the revision ids in the repository. 
610
633
        """Construct the current default format repository in a_bzrdir."""
611
634
        return RepositoryFormat.get_default_format().initialize(a_bzrdir)
612
635
 
613
 
    def __init__(self, _format, a_bzrdir, control_files,
614
 
                 _revision_store, control_store, text_store):
 
636
    def __init__(self, _format, a_bzrdir, control_files):
615
637
        """instantiate a Repository.
616
638
 
617
639
        :param _format: The format of the repository on disk.
628
650
        self.control_files = control_files
629
651
        self._transport = control_files._transport
630
652
        self.base = self._transport.base
631
 
        self._revision_store = _revision_store
632
 
        # backwards compatibility
633
 
        self.weave_store = text_store
634
653
        # for tests
635
654
        self._reconcile_does_inventory_gc = True
636
655
        self._reconcile_fixes_text_parents = False
637
656
        self._reconcile_backsup_inventory = True
638
657
        # not right yet - should be more semantically clear ? 
639
658
        # 
640
 
        self.control_store = control_store
641
 
        self.control_weaves = control_store
642
659
        # TODO: make sure to construct the right store classes, etc, depending
643
660
        # on whether escaping is required.
644
661
        self._warn_if_deprecated()
766
783
                last_revision.timezone)
767
784
 
768
785
        # now gather global repository information
 
786
        # XXX: This is available for many repos regardless of listability.
769
787
        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
 
788
            # XXX: do we want to __define len__() ?
 
789
            # Maybe the versionedfiles object should provide a different
 
790
            # method to get the number of keys.
 
791
            result['revisions'] = len(self.revisions.keys())
 
792
            # result['size'] = t
773
793
        return result
774
794
 
775
795
    def find_branches(self, using=False):
815
835
                branches.extend(repository.find_branches())
816
836
        return branches
817
837
 
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
838
    @needs_read_lock
873
839
    def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
874
840
        """Return the revision ids that other has that this does not.
1064
1030
        """True if this repository has a copy of the revision."""
1065
1031
        return revision_id in self.has_revisions((revision_id,))
1066
1032
 
 
1033
    @needs_read_lock
1067
1034
    def has_revisions(self, revision_ids):
1068
1035
        """Probe to find out the presence of multiple revisions.
1069
1036
 
1070
1037
        :param revision_ids: An iterable of revision_ids.
1071
1038
        :return: A set of the revision_ids that were present.
1072
1039
        """
1073
 
        raise NotImplementedError(self.has_revisions)
1074
 
 
1075
 
        return self._revision_store.has_revision_id(revision_id,
1076
 
                                                    self.get_transaction())
 
1040
        parent_map = self.revisions.get_parent_map(
 
1041
            [(rev_id,) for rev_id in revision_ids])
 
1042
        result = set()
 
1043
        if _mod_revision.NULL_REVISION in revision_ids:
 
1044
            result.add(_mod_revision.NULL_REVISION)
 
1045
        result.update([key[0] for key in parent_map])
 
1046
        return result
1077
1047
 
1078
1048
    @needs_read_lock
1079
1049
    def get_revision(self, revision_id):
1102
1072
        for rev_id in revision_ids:
1103
1073
            if not rev_id or not isinstance(rev_id, basestring):
1104
1074
                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
 
1075
        keys = [(key,) for key in revision_ids]
 
1076
        stream = self.revisions.get_record_stream(keys, 'unordered', True)
 
1077
        revs = {}
 
1078
        for record in stream:
 
1079
            if record.storage_kind == 'absent':
 
1080
                raise errors.NoSuchRevision(self, record.key[0])
 
1081
            text = record.get_bytes_as('fulltext')
 
1082
            rev = self._serializer.read_revision_from_string(text)
 
1083
            revs[record.key[0]] = rev
 
1084
        return [revs[revid] for revid in revision_ids]
1108
1085
 
1109
1086
    @needs_read_lock
1110
1087
    def get_revision_xml(self, revision_id):
1114
1091
        rev = self.get_revision(revision_id)
1115
1092
        rev_tmp = StringIO()
1116
1093
        # the current serializer..
1117
 
        self._revision_store._serializer.write_revision(rev, rev_tmp)
 
1094
        self._serializer.write_revision(rev, rev_tmp)
1118
1095
        rev_tmp.seek(0)
1119
1096
        return rev_tmp.getvalue()
1120
1097
 
1155
1132
 
1156
1133
    @needs_write_lock
1157
1134
    def add_signature_text(self, revision_id, signature):
1158
 
        self._revision_store.add_revision_signature_text(revision_id,
1159
 
                                                         signature,
1160
 
                                                         self.get_transaction())
 
1135
        self.signatures.add_lines((revision_id,), (),
 
1136
            osutils.split_lines(signature))
1161
1137
 
1162
1138
    def find_text_key_references(self):
1163
1139
        """Find the text key references within the repository.
1170
1146
            revision_id that they contain. The inventory texts from all present
1171
1147
            revision ids are assessed to generate this report.
1172
1148
        """
1173
 
        revision_ids = self.all_revision_ids()
1174
 
        w = self.get_inventory_weave()
 
1149
        revision_keys = self.revisions.keys()
 
1150
        w = self.inventories
1175
1151
        pb = ui.ui_factory.nested_progress_bar()
1176
1152
        try:
1177
1153
            return self._find_text_key_references_from_xml_inventory_lines(
1178
 
                w.iter_lines_added_or_present_in_versions(revision_ids, pb=pb))
 
1154
                w.iter_lines_added_or_present_in_keys(revision_keys, pb=pb))
1179
1155
        finally:
1180
1156
            pb.finished()
1181
1157
 
1218
1194
        search = self._file_ids_altered_regex.search
1219
1195
        unescape = _unescape_xml
1220
1196
        setdefault = result.setdefault
1221
 
        for line, version_id in line_iterator:
 
1197
        for line, line_key in line_iterator:
1222
1198
            match = search(line)
1223
1199
            if match is None:
1224
1200
                continue
1255
1231
 
1256
1232
            key = (file_id, revision_id)
1257
1233
            setdefault(key, False)
1258
 
            if revision_id == version_id:
 
1234
            if revision_id == line_key[-1]:
1259
1235
                result[key] = True
1260
1236
        return result
1261
1237
 
1276
1252
        """
1277
1253
        result = {}
1278
1254
        setdefault = result.setdefault
1279
 
        for file_id, revision_id in \
 
1255
        for key in \
1280
1256
            self._find_text_key_references_from_xml_inventory_lines(
1281
1257
                line_iterator).iterkeys():
1282
1258
            # once data is all ensured-consistent; then this is
1283
1259
            # if revision_id == version_id
1284
 
            if revision_id in revision_ids:
1285
 
                setdefault(file_id, set()).add(revision_id)
 
1260
            if key[-1:] in revision_ids:
 
1261
                setdefault(key[0], set()).add(key[-1])
1286
1262
        return result
1287
1263
 
1288
1264
    def fileids_altered_by_revision_ids(self, revision_ids, _inv_weave=None):
1295
1271
        revision_ids. Each altered file-ids has the exact revision_ids that
1296
1272
        altered it listed explicitly.
1297
1273
        """
1298
 
        selected_revision_ids = set(revision_ids)
1299
 
        w = _inv_weave or self.get_inventory_weave()
 
1274
        selected_keys = set((revid,) for revid in revision_ids)
 
1275
        w = _inv_weave or self.inventories
1300
1276
        pb = ui.ui_factory.nested_progress_bar()
1301
1277
        try:
1302
1278
            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)
 
1279
                w.iter_lines_added_or_present_in_keys(
 
1280
                    selected_keys, pb=pb),
 
1281
                selected_keys)
1306
1282
        finally:
1307
1283
            pb.finished()
1308
1284
 
1319
1295
 
1320
1296
        bytes_iterator is an iterable of bytestrings for the file.  The
1321
1297
        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().
 
1298
        this implementation, it is a list of bytes produced by
 
1299
        VersionedFile.get_record_stream().
1324
1300
 
1325
1301
        :param desired_files: a list of (file_id, revision_id, identifier)
1326
1302
            triples
1327
1303
        """
1328
1304
        transaction = self.get_transaction()
 
1305
        text_keys = {}
1329
1306
        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)
 
1307
            text_keys[(file_id, revision_id)] = callable_data
 
1308
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
 
1309
            if record.storage_kind == 'absent':
 
1310
                raise errors.RevisionNotPresent(record.key, self)
 
1311
            yield text_keys[record.key], record.get_bytes_as('fulltext')
1335
1312
 
1336
1313
    def _generate_text_key_index(self, text_key_references=None,
1337
1314
        ancestors=None):
1454
1431
        # maybe this generator should explicitly have the contract that it
1455
1432
        # should not be iterated until the previously yielded item has been
1456
1433
        # processed?
1457
 
        inv_w = self.get_inventory_weave()
 
1434
        inv_w = self.inventories
1458
1435
 
1459
1436
        # file ids that changed
1460
1437
        file_ids = self.fileids_altered_by_revision_ids(revision_ids, inv_w)
1488
1465
        yield ("revisions", None, revision_ids)
1489
1466
 
1490
1467
    @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
1468
    def get_inventory(self, revision_id):
1497
1469
        """Get Inventory object by revision id."""
1498
1470
        return self.iter_inventories([revision_id]).next()
1513
1485
 
1514
1486
    def _iter_inventories(self, revision_ids):
1515
1487
        """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):
 
1488
        for text, revision_id in self._iter_inventory_xmls(revision_ids):
1518
1489
            yield self.deserialise_inventory(revision_id, text)
1519
1490
 
 
1491
    def _iter_inventory_xmls(self, revision_ids):
 
1492
        keys = [(revision_id,) for revision_id in revision_ids]
 
1493
        stream = self.inventories.get_record_stream(keys, 'unordered', True)
 
1494
        texts = {}
 
1495
        for record in stream:
 
1496
            if record.storage_kind != 'absent':
 
1497
                texts[record.key] = record.get_bytes_as('fulltext')
 
1498
            else:
 
1499
                raise errors.NoSuchRevision(self, record.key)
 
1500
        for key in keys:
 
1501
            yield texts[key], key[-1]
 
1502
 
1520
1503
    def deserialise_inventory(self, revision_id, xml):
1521
1504
        """Transform the xml into an inventory object. 
1522
1505
 
1541
1524
    @needs_read_lock
1542
1525
    def get_inventory_xml(self, revision_id):
1543
1526
        """Get inventory XML as a file object."""
 
1527
        texts = self._iter_inventory_xmls([revision_id])
1544
1528
        try:
1545
 
            iw = self.get_inventory_weave()
1546
 
            return iw.get_text(revision_id)
1547
 
        except IndexError:
 
1529
            text, revision_id = texts.next()
 
1530
        except StopIteration:
1548
1531
            raise errors.HistoryMissing(self, 'inventory', revision_id)
 
1532
        return text
1549
1533
 
1550
1534
    @needs_read_lock
1551
1535
    def get_inventory_sha1(self, revision_id):
1652
1636
            return [None]
1653
1637
        if not self.has_revision(revision_id):
1654
1638
            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)
 
1639
        graph = self.get_graph()
 
1640
        keys = set()
 
1641
        search = graph._make_breadth_first_searcher([revision_id])
 
1642
        while True:
 
1643
            try:
 
1644
                found, ghosts = search.next_with_ghosts()
 
1645
            except StopIteration:
 
1646
                break
 
1647
            keys.update(found)
 
1648
        if _mod_revision.NULL_REVISION in keys:
 
1649
            keys.remove(_mod_revision.NULL_REVISION)
 
1650
        if topo_sorted:
 
1651
            parent_map = graph.get_parent_map(keys)
 
1652
            keys = tsort.topo_sort(parent_map)
 
1653
        return [None] + list(keys)
1658
1654
 
1659
1655
    def pack(self):
1660
1656
        """Compress the data within the repository.
1768
1764
    @needs_read_lock
1769
1765
    def has_signature_for_revision_id(self, revision_id):
1770
1766
        """Query for a revision signature for revision_id in the repository."""
1771
 
        return self._revision_store.has_signature(revision_id,
1772
 
                                                  self.get_transaction())
 
1767
        if not self.has_revision(revision_id):
 
1768
            raise errors.NoSuchRevision(self, revision_id)
 
1769
        sig_present = (1 == len(
 
1770
            self.signatures.get_parent_map([(revision_id,)])))
 
1771
        return sig_present
1773
1772
 
1774
1773
    @needs_read_lock
1775
1774
    def get_signature_text(self, revision_id):
1776
1775
        """Return the text for a signature."""
1777
 
        return self._revision_store.get_signature_text(revision_id,
1778
 
                                                       self.get_transaction())
 
1776
        stream = self.signatures.get_record_stream([(revision_id,)],
 
1777
            'unordered', True)
 
1778
        record = stream.next()
 
1779
        if record.storage_kind == 'absent':
 
1780
            raise errors.NoSuchRevision(self, revision_id)
 
1781
        return record.get_bytes_as('fulltext')
1779
1782
 
1780
1783
    @needs_read_lock
1781
1784
    def check(self, revision_ids=None):
1908
1911
        path, root = entries.next()
1909
1912
        if root.revision != rev.revision_id:
1910
1913
            raise errors.IncompatibleRevision(repr(repository))
 
1914
    text_keys = {}
 
1915
    for path, ie in entries:
 
1916
        text_keys[(ie.file_id, ie.revision)] = ie
 
1917
    text_parent_map = repository.texts.get_parent_map(text_keys)
 
1918
    missing_texts = set(text_keys) - set(text_parent_map)
1911
1919
    # 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)
 
1920
    for text_key in missing_texts:
 
1921
        ie = text_keys[text_key]
 
1922
        text_parents = []
 
1923
        # FIXME: TODO: The following loop overlaps/duplicates that done by
 
1924
        # commit to determine parents. There is a latent/real bug here where
 
1925
        # the parents inserted are not those commit would do - in particular
 
1926
        # they are not filtered by heads(). RBC, AB
 
1927
        for revision, tree in parent_trees.iteritems():
 
1928
            if ie.file_id not in tree:
 
1929
                continue
 
1930
            parent_id = tree.inventory[ie.file_id].revision
 
1931
            if parent_id in text_parents:
 
1932
                continue
 
1933
            text_parents.append((ie.file_id, parent_id))
 
1934
        lines = revision_tree.get_file(ie.file_id).readlines()
 
1935
        repository.texts.add_lines(text_key, text_parents, lines)
1933
1936
    try:
1934
1937
        # install the inventory
1935
1938
        repository.add_inventory(rev.revision_id, inv, present_parents)
1947
1950
        typically pointing to .bzr/repository.
1948
1951
    """
1949
1952
 
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)
 
1953
    def __init__(self, _format, a_bzrdir, control_files):
 
1954
        super(MetaDirRepository, self).__init__(_format, a_bzrdir, control_files)
1957
1955
        self._transport = control_files._transport
1958
1956
 
1959
1957
    @needs_read_lock
1988
1986
class MetaDirVersionedFileRepository(MetaDirRepository):
1989
1987
    """Repositories in a meta-dir, that work via versioned file objects."""
1990
1988
 
1991
 
    def __init__(self, _format, a_bzrdir, control_files, _revision_store, control_store, text_store):
 
1989
    def __init__(self, _format, a_bzrdir, control_files):
1992
1990
        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
 
1991
            control_files)
1997
1992
 
1998
1993
 
1999
1994
class RepositoryFormatRegistry(registry.Registry):
2095
2090
        from bzrlib import bzrdir
2096
2091
        return bzrdir.format_registry.make_bzrdir('default').repository_format
2097
2092
 
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
2093
    def get_format_string(self):
2103
2094
        """Return the ASCII format string that identifies this format.
2104
2095
        
2111
2102
        """Return the short description for this format."""
2112
2103
        raise NotImplementedError(self.get_format_description)
2113
2104
 
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
2105
    # TODO: this shouldn't be in the base class, it's specific to things that
2142
2106
    # use weaves or knits -- mbp 20070207
2143
2107
    def _get_versioned_file_store(self,
2520
2484
        if self.source._transport.listable():
2521
2485
            pb = ui.ui_factory.nested_progress_bar()
2522
2486
            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())
 
2487
                self.target.texts.insert_record_stream(
 
2488
                    self.source.texts.get_record_stream(
 
2489
                        self.source.texts.keys(), 'topological', False))
2528
2490
                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)
 
2491
                self.target.inventories.insert_record_stream(
 
2492
                    self.source.inventories.get_record_stream(
 
2493
                        self.source.inventories.keys(), 'topological', False))
 
2494
                self.target.signatures.insert_record_stream(
 
2495
                    self.source.signatures.get_record_stream(
 
2496
                        self.source.signatures.keys(),
 
2497
                        'unordered', True))
 
2498
                self.target.revisions.insert_record_stream(
 
2499
                    self.source.revisions.get_record_stream(
 
2500
                        self.source.revisions.keys(),
 
2501
                        'topological', True))
2536
2502
            finally:
2537
2503
                pb.finished()
2538
2504
        else:
2905
2871
        return len(revision_ids), 0
2906
2872
 
2907
2873
 
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
2874
class InterOtherToRemote(InterRepository):
2946
2875
 
2947
2876
    def __init__(self, source, target):
2981
2910
InterRepository.register_optimiser(InterModel1and2)
2982
2911
InterRepository.register_optimiser(InterKnit1and2)
2983
2912
InterRepository.register_optimiser(InterPackRepo)
2984
 
InterRepository.register_optimiser(InterRemoteToOther)
2985
2913
InterRepository.register_optimiser(InterOtherToRemote)
2986
2914
 
2987
2915
 
3073
3001
        self.repository = repository
3074
3002
        self.text_index = self.repository._generate_text_key_index()
3075
3003
    
3076
 
    def calculate_file_version_parents(self, revision_id, file_id):
 
3004
    def calculate_file_version_parents(self, text_key):
3077
3005
        """Calculate the correct parents for a file version according to
3078
3006
        the inventories.
3079
3007
        """
3080
 
        parent_keys = self.text_index[(file_id, revision_id)]
 
3008
        parent_keys = self.text_index[text_key]
3081
3009
        if parent_keys == [_mod_revision.NULL_REVISION]:
3082
3010
            return ()
3083
 
        # strip the file_id, for the weave api
3084
 
        return tuple([revision_id for file_id, revision_id in parent_keys])
 
3011
        return tuple(parent_keys)
3085
3012
 
3086
 
    def check_file_version_parents(self, weave, file_id):
 
3013
    def check_file_version_parents(self, texts, progress_bar=None):
3087
3014
        """Check the parents stored in a versioned file are correct.
3088
3015
 
3089
3016
        It also detects file versions that are not referenced by their
3097
3024
            file, but not used by the corresponding inventory.
3098
3025
        """
3099
3026
        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):
 
3027
        self.file_ids = set([file_id for file_id, _ in
 
3028
            self.text_index.iterkeys()])
 
3029
        # text keys is now grouped by file_id
 
3030
        n_weaves = len(self.file_ids)
 
3031
        files_in_revisions = {}
 
3032
        revisions_of_files = {}
 
3033
        n_versions = len(self.text_index)
 
3034
        progress_bar.update('loading text store', 0, n_versions)
 
3035
        parent_map = self.repository.texts.get_parent_map(self.text_index)
 
3036
        # On unlistable transports this could well be empty/error...
 
3037
        text_keys = self.repository.texts.keys()
 
3038
        unused_keys = frozenset(text_keys) - set(self.text_index)
 
3039
        for num, key in enumerate(self.text_index.iterkeys()):
 
3040
            if progress_bar is not None:
 
3041
                progress_bar.update('checking text graph', num, n_versions)
 
3042
            correct_parents = self.calculate_file_version_parents(key)
3104
3043
            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
 
3044
                knit_parents = parent_map[key]
 
3045
            except errors.RevisionNotPresent:
 
3046
                # Missing text!
 
3047
                knit_parents = None
 
3048
            if correct_parents != knit_parents:
 
3049
                wrong_parents[key] = (knit_parents, correct_parents)
 
3050
        return wrong_parents, unused_keys
3118
3051
 
3119
3052
 
3120
3053
def _old_get_graph(repository, revision_id):