~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

Clean up the lock.py code to use less indenting, and conform to better coding practise.

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 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
76
76
    osutils,
77
77
    patiencediff,
78
78
    progress,
79
 
    merge,
80
79
    ui,
81
80
    )
82
81
from bzrlib.errors import (
154
153
    def copy(self):
155
154
        return KnitContent(self._lines[:])
156
155
 
157
 
    @staticmethod
158
 
    def get_line_delta_blocks(knit_delta, source, target):
159
 
        """Extract SequenceMatcher.get_matching_blocks() from a knit delta"""
160
 
        target_len = len(target)
161
 
        s_pos = 0
162
 
        t_pos = 0
163
 
        for s_begin, s_end, t_len, new_text in knit_delta:
164
 
            true_n = s_begin - s_pos
165
 
            n = true_n
166
 
            if n > 0:
167
 
                # knit deltas do not provide reliable info about whether the
168
 
                # last line of a file matches, due to eol handling.
169
 
                if source[s_pos + n -1] != target[t_pos + n -1]:
170
 
                    n-=1
171
 
                if n > 0:
172
 
                    yield s_pos, t_pos, n
173
 
            t_pos += t_len + true_n
174
 
            s_pos = s_end
175
 
        n = target_len - t_pos
176
 
        if n > 0:
177
 
            if source[s_pos + n -1] != target[t_pos + n -1]:
178
 
                n-=1
179
 
            if n > 0:
180
 
                yield s_pos, t_pos, n
181
 
        yield s_pos + (target_len - t_pos), target_len, 0
182
 
 
183
156
 
184
157
class _KnitFactory(object):
185
158
    """Base factory for creating content objects."""
360
333
    def __init__(self, relpath, transport, file_mode=None, access_mode=None,
361
334
                 factory=None, basis_knit=DEPRECATED_PARAMETER, delta=True,
362
335
                 create=False, create_parent_dir=False, delay_create=False,
363
 
                 dir_mode=None, index=None):
 
336
                 dir_mode=None):
364
337
        """Construct a knit at location specified by relpath.
365
338
        
366
339
        :param create: If not True, only open an existing knit.
369
342
            hash-prefixes that may not exist yet)
370
343
        :param delay_create: The calling code is aware that the knit won't 
371
344
            actually be created until the first data is stored.
372
 
        :param index: An index to use for the knit.
373
345
        """
374
346
        if deprecated_passed(basis_knit):
375
347
            warnings.warn("KnitVersionedFile.__(): The basis_knit parameter is"
387
359
 
388
360
        self._max_delta_chain = 200
389
361
 
390
 
        if index is None:
391
 
            self._index = _KnitIndex(transport, relpath + INDEX_SUFFIX,
392
 
                access_mode, create=create, file_mode=file_mode,
393
 
                create_parent_dir=create_parent_dir, delay_create=delay_create,
394
 
                dir_mode=dir_mode)
395
 
        else:
396
 
            self._index = index
 
362
        self._index = _KnitIndex(transport, relpath + INDEX_SUFFIX,
 
363
            access_mode, create=create, file_mode=file_mode,
 
364
            create_parent_dir=create_parent_dir, delay_create=delay_create,
 
365
            dir_mode=dir_mode)
397
366
        self._data = _KnitData(transport, relpath + DATA_SUFFIX,
398
367
            access_mode, create=create and not len(self), file_mode=file_mode,
399
368
            create_parent_dir=create_parent_dir, delay_create=delay_create,
550
519
                                current_values[3],
551
520
                                new_parents)
552
521
 
553
 
    def _extract_blocks(self, version_id, source, target):
554
 
        if self._index.get_method(version_id) != 'line-delta':
555
 
            return None
556
 
        parent, sha1, noeol, delta = self.get_delta(version_id)
557
 
        return KnitContent.get_line_delta_blocks(delta, source, target)
558
 
 
559
522
    def get_delta(self, version_id):
560
523
        """Get a delta for constructing version from some other version."""
561
524
        version_id = osutils.safe_revision_id(version_id)
591
554
        return dict(graph_items)
592
555
 
593
556
    def get_sha1(self, version_id):
594
 
        return self.get_sha1s([version_id])[0]
595
 
 
596
 
    def get_sha1s(self, version_ids):
597
557
        """See VersionedFile.get_sha1()."""
598
 
        version_ids = [osutils.safe_revision_id(v) for v in version_ids]
599
 
        record_map = self._get_record_map(version_ids)
600
 
        # record entry 2 is the 'digest'.
601
 
        return [record_map[v][2] for v in version_ids]
 
558
        version_id = osutils.safe_revision_id(version_id)
 
559
        record_map = self._get_record_map([version_id])
 
560
        method, content, digest, next = record_map[version_id]
 
561
        return digest 
602
562
 
603
563
    @staticmethod
604
564
    def get_suffixes():
852
812
        text_map, content_map = self._get_content_maps(version_ids)
853
813
        return [text_map[v] for v in version_ids]
854
814
 
855
 
    _get_lf_split_line_list = get_line_list
856
 
 
857
815
    def _get_content_maps(self, version_ids):
858
816
        """Produce maps of text and KnitContents
859
817
        
949
907
 
950
908
        pb.update('Walking content.', total, total)
951
909
        
952
 
    def iter_parents(self, version_ids):
953
 
        """Iterate through the parents for many version ids.
954
 
 
955
 
        :param version_ids: An iterable yielding version_ids.
956
 
        :return: An iterator that yields (version_id, parents). Requested 
957
 
            version_ids not present in the versioned file are simply skipped.
958
 
            The order is undefined, allowing for different optimisations in
959
 
            the underlying implementation.
960
 
        """
961
 
        version_ids = [osutils.safe_revision_id(version_id) for
962
 
            version_id in version_ids]
963
 
        return self._index.iter_parents(version_ids)
964
 
 
965
910
    def num_versions(self):
966
911
        """See VersionedFile.num_versions()."""
967
912
        return self._index.num_versions()
994
939
        except KeyError:
995
940
            raise RevisionNotPresent(version_id, self.filename)
996
941
 
997
 
    def get_ancestry(self, versions, topo_sorted=True):
 
942
    def get_ancestry(self, versions):
998
943
        """See VersionedFile.get_ancestry."""
999
944
        if isinstance(versions, basestring):
1000
945
            versions = [versions]
1001
946
        if not versions:
1002
947
            return []
1003
948
        versions = [osutils.safe_revision_id(v) for v in versions]
1004
 
        return self._index.get_ancestry(versions, topo_sorted)
 
949
        return self._index.get_ancestry(versions)
1005
950
 
1006
951
    def get_ancestry_with_ghosts(self, versions):
1007
952
        """See VersionedFile.get_ancestry_with_ghosts."""
1022
967
        from bzrlib.weave import Weave
1023
968
 
1024
969
        w = Weave(self.filename)
1025
 
        ancestry = set(self.get_ancestry(version_ids, topo_sorted=False))
 
970
        ancestry = self.get_ancestry(version_ids)
1026
971
        sorted_graph = topo_sort(self._index.get_graph())
1027
972
        version_list = [vid for vid in sorted_graph if vid in ancestry]
1028
973
        
1037
982
        """See VersionedFile.plan_merge."""
1038
983
        ver_a = osutils.safe_revision_id(ver_a)
1039
984
        ver_b = osutils.safe_revision_id(ver_b)
1040
 
        ancestors_b = set(self.get_ancestry(ver_b, topo_sorted=False))
 
985
        ancestors_b = set(self.get_ancestry(ver_b))
 
986
        def status_a(revision, text):
 
987
            if revision in ancestors_b:
 
988
                return 'killed-b', text
 
989
            else:
 
990
                return 'new-a', text
1041
991
        
1042
 
        ancestors_a = set(self.get_ancestry(ver_a, topo_sorted=False))
 
992
        ancestors_a = set(self.get_ancestry(ver_a))
 
993
        def status_b(revision, text):
 
994
            if revision in ancestors_a:
 
995
                return 'killed-a', text
 
996
            else:
 
997
                return 'new-b', text
 
998
 
1043
999
        annotated_a = self.annotate(ver_a)
1044
1000
        annotated_b = self.annotate(ver_b)
1045
 
        return merge._plan_annotate_merge(annotated_a, annotated_b,
1046
 
                                          ancestors_a, ancestors_b)
 
1001
        plain_a = [t for (a, t) in annotated_a]
 
1002
        plain_b = [t for (a, t) in annotated_b]
 
1003
        blocks = KnitSequenceMatcher(None, plain_a, plain_b).get_matching_blocks()
 
1004
        a_cur = 0
 
1005
        b_cur = 0
 
1006
        for ai, bi, l in blocks:
 
1007
            # process all mismatched sections
 
1008
            # (last mismatched section is handled because blocks always
 
1009
            # includes a 0-length last block)
 
1010
            for revision, text in annotated_a[a_cur:ai]:
 
1011
                yield status_a(revision, text)
 
1012
            for revision, text in annotated_b[b_cur:bi]:
 
1013
                yield status_b(revision, text)
 
1014
 
 
1015
            # and now the matched section
 
1016
            a_cur = ai + l
 
1017
            b_cur = bi + l
 
1018
            for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
 
1019
                assert text_a == text_b
 
1020
                yield "unchanged", text_a
1047
1021
 
1048
1022
 
1049
1023
class _KnitComponentFile(object):
1176
1150
            try:
1177
1151
                # _load_data may raise NoSuchFile if the target knit is
1178
1152
                # completely empty.
1179
 
                _load_data(self, fp)
 
1153
                self._load_data(fp)
1180
1154
            finally:
1181
1155
                fp.close()
1182
1156
        except NoSuchFile:
1188
1162
                self._transport.put_bytes_non_atomic(
1189
1163
                    self._filename, self.HEADER, mode=self._file_mode)
1190
1164
 
 
1165
    def _load_data(self, fp):
 
1166
        cache = self._cache
 
1167
        history = self._history
 
1168
 
 
1169
        self.check_header(fp)
 
1170
        # readlines reads the whole file at once:
 
1171
        # bad for transports like http, good for local disk
 
1172
        # we save 60 ms doing this one change (
 
1173
        # from calling readline each time to calling
 
1174
        # readlines once.
 
1175
        # probably what we want for nice behaviour on
 
1176
        # http is a incremental readlines that yields, or
 
1177
        # a check for local vs non local indexes,
 
1178
        history_top = len(history) - 1
 
1179
        for line in fp.readlines():
 
1180
            rec = line.split()
 
1181
            if len(rec) < 5 or rec[-1] != ':':
 
1182
                # corrupt line.
 
1183
                # FIXME: in the future we should determine if its a
 
1184
                # short write - and ignore it 
 
1185
                # or a different failure, and raise. RBC 20060407
 
1186
                continue
 
1187
 
 
1188
            parents = []
 
1189
            for value in rec[4:-1]:
 
1190
                if value[0] == '.':
 
1191
                    # uncompressed reference
 
1192
                    parent_id = value[1:]
 
1193
                else:
 
1194
                    parent_id = history[int(value)]
 
1195
                parents.append(parent_id)
 
1196
 
 
1197
            version_id, options, pos, size = rec[:4]
 
1198
            version_id = version_id
 
1199
 
 
1200
            # See self._cache_version
 
1201
            # only want the _history index to reference the 1st 
 
1202
            # index entry for version_id
 
1203
            if version_id not in cache:
 
1204
                history_top += 1
 
1205
                index = history_top
 
1206
                history.append(version_id)
 
1207
            else:
 
1208
                index = cache[version_id][5]
 
1209
            cache[version_id] = (version_id,
 
1210
                                 options.split(','),
 
1211
                                 int(pos),
 
1212
                                 int(size),
 
1213
                                 parents,
 
1214
                                 index)
 
1215
            # end self._cache_version 
 
1216
 
1191
1217
    def get_graph(self):
1192
 
        """Return a list of the node:parents lists from this knit index."""
1193
1218
        return [(vid, idx[4]) for vid, idx in self._cache.iteritems()]
1194
1219
 
1195
 
    def get_ancestry(self, versions, topo_sorted=True):
 
1220
    def get_ancestry(self, versions):
1196
1221
        """See VersionedFile.get_ancestry."""
1197
1222
        # get a graph of all the mentioned versions:
1198
1223
        graph = {}
1208
1233
            # if not completed and not a ghost
1209
1234
            pending.update([p for p in parents if p not in graph])
1210
1235
            graph[version] = parents
1211
 
        if not topo_sorted:
1212
 
            return graph.keys()
1213
1236
        return topo_sort(graph.items())
1214
1237
 
1215
1238
    def get_ancestry_with_ghosts(self, versions):
1232
1255
                graph[version] = parents
1233
1256
        return topo_sort(graph.items())
1234
1257
 
1235
 
    def iter_parents(self, version_ids):
1236
 
        """Iterate through the parents for many version ids.
1237
 
 
1238
 
        :param version_ids: An iterable yielding version_ids.
1239
 
        :return: An iterator that yields (version_id, parents). Requested 
1240
 
            version_ids not present in the versioned file are simply skipped.
1241
 
            The order is undefined, allowing for different optimisations in
1242
 
            the underlying implementation.
1243
 
        """
1244
 
        for version_id in version_ids:
1245
 
            try:
1246
 
                yield version_id, tuple(self.get_parents(version_id))
1247
 
            except KeyError:
1248
 
                pass
1249
 
 
1250
1258
    def num_versions(self):
1251
1259
        return len(self._history)
1252
1260
 
1253
1261
    __len__ = num_versions
1254
1262
 
1255
1263
    def get_versions(self):
1256
 
        """Get all the versions in the file. not topologically sorted."""
1257
1264
        return self._history
1258
1265
 
 
1266
    def idx_to_name(self, idx):
 
1267
        return self._history[idx]
 
1268
 
 
1269
    def lookup(self, version_id):
 
1270
        assert version_id in self._cache
 
1271
        return self._cache[version_id][5]
 
1272
 
1259
1273
    def _version_list_to_index(self, versions):
1260
1274
        result_list = []
1261
1275
        cache = self._cache
1331
1345
            return 'line-delta'
1332
1346
 
1333
1347
    def get_options(self, version_id):
1334
 
        """Return a string represention options.
1335
 
 
1336
 
        e.g. foo,bar
1337
 
        """
1338
1348
        return self._cache[version_id][1]
1339
1349
 
1340
1350
    def get_parents(self, version_id):
1354
1364
                raise RevisionNotPresent(version_id, self._filename)
1355
1365
 
1356
1366
 
1357
 
class KnitGraphIndex(object):
1358
 
    """A knit index that builds on GraphIndex."""
1359
 
 
1360
 
    def __init__(self, graph_index, deltas=False, parents=True, add_callback=None):
1361
 
        """Construct a KnitGraphIndex on a graph_index.
1362
 
 
1363
 
        :param graph_index: An implementation of bzrlib.index.GraphIndex.
1364
 
        :param deltas: Allow delta-compressed records.
1365
 
        :param add_callback: If not None, allow additions to the index and call
1366
 
            this callback with a list of added GraphIndex nodes:
1367
 
            [(node, value, node_refs), ...]
1368
 
        :param parents: If True, record knits parents, if not do not record 
1369
 
            parents.
1370
 
        """
1371
 
        self._graph_index = graph_index
1372
 
        self._deltas = deltas
1373
 
        self._add_callback = add_callback
1374
 
        self._parents = parents
1375
 
        if deltas and not parents:
1376
 
            raise KnitCorrupt(self, "Cannot do delta compression without "
1377
 
                "parent tracking.")
1378
 
 
1379
 
    def _get_entries(self, version_ids, check_present=False):
1380
 
        """Get the entries for version_ids."""
1381
 
        version_ids = set(version_ids)
1382
 
        found_keys = set()
1383
 
        if self._parents:
1384
 
            for node in self._graph_index.iter_entries(version_ids):
1385
 
                yield node
1386
 
                found_keys.add(node[0])
1387
 
        else:
1388
 
            # adapt parentless index to the rest of the code.
1389
 
            for node in self._graph_index.iter_entries(version_ids):
1390
 
                yield node[0], node[1], ()
1391
 
                found_keys.add(node[0])
1392
 
        if check_present:
1393
 
            missing_keys = version_ids.difference(found_keys)
1394
 
            if missing_keys:
1395
 
                raise RevisionNotPresent(missing_keys.pop(), self)
1396
 
 
1397
 
    def _present_keys(self, version_ids):
1398
 
        return set([
1399
 
            node[0] for node in self._get_entries(version_ids)])
1400
 
 
1401
 
    def _parentless_ancestry(self, versions):
1402
 
        """Honour the get_ancestry API for parentless knit indices."""
1403
 
        present_keys = self._present_keys(versions)
1404
 
        missing = set(versions).difference(present_keys)
1405
 
        if missing:
1406
 
            raise RevisionNotPresent(missing.pop(), self)
1407
 
        return list(present_keys)
1408
 
 
1409
 
    def get_ancestry(self, versions, topo_sorted=True):
1410
 
        """See VersionedFile.get_ancestry."""
1411
 
        if not self._parents:
1412
 
            return self._parentless_ancestry(versions)
1413
 
        # XXX: This will do len(history) index calls - perhaps
1414
 
        # it should be altered to be a index core feature?
1415
 
        # get a graph of all the mentioned versions:
1416
 
        graph = {}
1417
 
        ghosts = set()
1418
 
        versions = set(versions)
1419
 
        pending = set(versions)
1420
 
        while pending:
1421
 
            # get all pending nodes
1422
 
            this_iteration = pending
1423
 
            new_nodes = self._get_entries(this_iteration)
1424
 
            pending = set()
1425
 
            for (key, value, node_refs) in new_nodes:
1426
 
                # dont ask for ghosties - otherwise
1427
 
                # we we can end up looping with pending
1428
 
                # being entirely ghosted.
1429
 
                graph[key] = [parent for parent in node_refs[0]
1430
 
                    if parent not in ghosts]
1431
 
                # queue parents 
1432
 
                pending.update(graph[key])
1433
 
            ghosts.difference_update(graph)
1434
 
            # dont examine known nodes
1435
 
            pending.difference_update(graph)
1436
 
        if versions.difference(graph):
1437
 
            raise RevisionNotPresent(versions.difference(graph).pop(), self)
1438
 
        if not topo_sorted:
1439
 
            return graph.keys()
1440
 
        return topo_sort(graph.items())
1441
 
 
1442
 
    def get_ancestry_with_ghosts(self, versions):
1443
 
        """See VersionedFile.get_ancestry."""
1444
 
        if not self._parents:
1445
 
            return self._parentless_ancestry(versions)
1446
 
        # XXX: This will do len(history) index calls - perhaps
1447
 
        # it should be altered to be a index core feature?
1448
 
        # get a graph of all the mentioned versions:
1449
 
        graph = {}
1450
 
        versions = set(versions)
1451
 
        pending = set(versions)
1452
 
        while pending:
1453
 
            # get all pending nodes
1454
 
            this_iteration = pending
1455
 
            new_nodes = self._get_entries(this_iteration)
1456
 
            pending = set()
1457
 
            for (key, value, node_refs) in new_nodes:
1458
 
                graph[key] = node_refs[0]
1459
 
                # queue parents 
1460
 
                pending.update(graph[key])
1461
 
            missing_versions = this_iteration.difference(graph)
1462
 
            missing_needed = versions.intersection(missing_versions)
1463
 
            if missing_needed:
1464
 
                raise RevisionNotPresent(missing_needed.pop(), self)
1465
 
            for missing_version in missing_versions:
1466
 
                # add a key, no parents
1467
 
                graph[missing_version] = []
1468
 
            # dont examine known nodes
1469
 
            pending.difference_update(graph)
1470
 
        return topo_sort(graph.items())
1471
 
 
1472
 
    def get_graph(self):
1473
 
        """Return a list of the node:parents lists from this knit index."""
1474
 
        if not self._parents:
1475
 
            return [(key, ()) for key in self.get_versions()]
1476
 
        return [(key, refs[0]) for (key, value, refs) in 
1477
 
            self._graph_index.iter_all_entries()]
1478
 
 
1479
 
    def iter_parents(self, version_ids):
1480
 
        """Iterate through the parents for many version ids.
1481
 
 
1482
 
        :param version_ids: An iterable yielding version_ids.
1483
 
        :return: An iterator that yields (version_id, parents). Requested 
1484
 
            version_ids not present in the versioned file are simply skipped.
1485
 
            The order is undefined, allowing for different optimisations in
1486
 
            the underlying implementation.
1487
 
        """
1488
 
        if self._parents:
1489
 
            all_nodes = set(self._get_entries(version_ids))
1490
 
            all_parents = set()
1491
 
            present_parents = set()
1492
 
            for node in all_nodes:
1493
 
                all_parents.update(node[2][0])
1494
 
                # any node we are querying must be present
1495
 
                present_parents.add(node[0])
1496
 
            unknown_parents = all_parents.difference(present_parents)
1497
 
            present_parents.update(self._present_keys(unknown_parents))
1498
 
            for node in all_nodes:
1499
 
                parents = []
1500
 
                for parent in node[2][0]:
1501
 
                    if parent in present_parents:
1502
 
                        parents.append(parent)
1503
 
                yield node[0], tuple(parents)
1504
 
        else:
1505
 
            for node in self._get_entries(version_ids):
1506
 
                yield node[0], ()
1507
 
 
1508
 
    def num_versions(self):
1509
 
        return len(list(self._graph_index.iter_all_entries()))
1510
 
 
1511
 
    __len__ = num_versions
1512
 
 
1513
 
    def get_versions(self):
1514
 
        """Get all the versions in the file. not topologically sorted."""
1515
 
        return [node[0] for node in self._graph_index.iter_all_entries()]
1516
 
    
1517
 
    def has_version(self, version_id):
1518
 
        """True if the version is in the index."""
1519
 
        return len(self._present_keys([version_id])) == 1
1520
 
 
1521
 
    def get_position(self, version_id):
1522
 
        """Return data position and size of specified version."""
1523
 
        bits = self._get_node(version_id)[1][1:].split(' ')
1524
 
        return int(bits[0]), int(bits[1])
1525
 
 
1526
 
    def get_method(self, version_id):
1527
 
        """Return compression method of specified version."""
1528
 
        if not self._deltas:
1529
 
            return 'fulltext'
1530
 
        return self._parent_compression(self._get_node(version_id)[2][1])
1531
 
 
1532
 
    def _parent_compression(self, reference_list):
1533
 
        # use the second reference list to decide if this is delta'd or not.
1534
 
        if len(reference_list):
1535
 
            return 'line-delta'
1536
 
        else:
1537
 
            return 'fulltext'
1538
 
 
1539
 
    def _get_node(self, version_id):
1540
 
        return list(self._get_entries([version_id]))[0]
1541
 
 
1542
 
    def get_options(self, version_id):
1543
 
        """Return a string represention options.
1544
 
 
1545
 
        e.g. foo,bar
1546
 
        """
1547
 
        node = self._get_node(version_id)
1548
 
        if not self._deltas:
1549
 
            options = ['fulltext']
1550
 
        else:
1551
 
            options = [self._parent_compression(node[2][1])]
1552
 
        if node[1][0] == 'N':
1553
 
            options.append('no-eol')
1554
 
        return ','.join(options)
1555
 
 
1556
 
    def get_parents(self, version_id):
1557
 
        """Return parents of specified version ignoring ghosts."""
1558
 
        parents = list(self.iter_parents([version_id]))
1559
 
        if not parents:
1560
 
            # missing key
1561
 
            raise errors.RevisionNotPresent(version_id, self)
1562
 
        return parents[0][1]
1563
 
 
1564
 
    def get_parents_with_ghosts(self, version_id):
1565
 
        """Return parents of specified version with ghosts."""
1566
 
        nodes = list(self._get_entries([version_id], check_present=True))
1567
 
        if not self._parents:
1568
 
            return ()
1569
 
        return nodes[0][2][0]
1570
 
 
1571
 
    def check_versions_present(self, version_ids):
1572
 
        """Check that all specified versions are present."""
1573
 
        version_ids = set(version_ids)
1574
 
        present = self._present_keys(version_ids)
1575
 
        missing = version_ids.difference(present)
1576
 
        if missing:
1577
 
            raise RevisionNotPresent(missing.pop(), self)
1578
 
 
1579
 
    def add_version(self, version_id, options, pos, size, parents):
1580
 
        """Add a version record to the index."""
1581
 
        return self.add_versions(((version_id, options, pos, size, parents),))
1582
 
 
1583
 
    def add_versions(self, versions):
1584
 
        """Add multiple versions to the index.
1585
 
        
1586
 
        This function does not insert data into the Immutable GraphIndex
1587
 
        backing the KnitGraphIndex, instead it prepares data for insertion by
1588
 
        the caller and checks that it is safe to insert then calls
1589
 
        self._add_callback with the prepared GraphIndex nodes.
1590
 
 
1591
 
        :param versions: a list of tuples:
1592
 
                         (version_id, options, pos, size, parents).
1593
 
        """
1594
 
        if not self._add_callback:
1595
 
            raise errors.ReadOnlyError(self)
1596
 
        # we hope there are no repositories with inconsistent parentage
1597
 
        # anymore.
1598
 
        # check for dups
1599
 
 
1600
 
        keys = {}
1601
 
        for (version_id, options, pos, size, parents) in versions:
1602
 
            if 'no-eol' in options:
1603
 
                value = 'N'
1604
 
            else:
1605
 
                value = ' '
1606
 
            value += "%d %d" % (pos, size)
1607
 
            if not self._deltas:
1608
 
                if 'line-delta' in options:
1609
 
                    raise KnitCorrupt(self, "attempt to add line-delta in non-delta knit")
1610
 
            if self._parents:
1611
 
                if self._deltas:
1612
 
                    if 'line-delta' in options:
1613
 
                        node_refs = (tuple(parents), (parents[0],))
1614
 
                    else:
1615
 
                        node_refs = (tuple(parents), ())
1616
 
                else:
1617
 
                    node_refs = (tuple(parents), )
1618
 
            else:
1619
 
                if parents:
1620
 
                    raise KnitCorrupt(self, "attempt to add node with parents "
1621
 
                        "in parentless index.")
1622
 
                node_refs = ()
1623
 
            keys[version_id] = (value, node_refs)
1624
 
        present_nodes = self._get_entries(keys)
1625
 
        for (key, value, node_refs) in present_nodes:
1626
 
            if (value, node_refs) != keys[key]:
1627
 
                raise KnitCorrupt(self, "inconsistent details in add_versions"
1628
 
                    ": %s %s" % ((value, node_refs), keys[key]))
1629
 
            del keys[key]
1630
 
        result = []
1631
 
        if self._parents:
1632
 
            for key, (value, node_refs) in keys.iteritems():
1633
 
                result.append((key, value, node_refs))
1634
 
        else:
1635
 
            for key, (value, node_refs) in keys.iteritems():
1636
 
                result.append((key, value))
1637
 
        self._add_callback(result)
1638
 
        
1639
 
 
1640
1367
class _KnitData(_KnitComponentFile):
1641
1368
    """Contents of the knit data file"""
1642
1369
 
2220
1947
            bestsize = bestsize + 1
2221
1948
 
2222
1949
        return besti, bestj, bestsize
2223
 
 
2224
 
 
2225
 
try:
2226
 
    from bzrlib._knit_load_data_c import _load_data_c as _load_data
2227
 
except ImportError:
2228
 
    from bzrlib._knit_load_data_py import _load_data_py as _load_data