/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/knit.py

  • Committer: Andrew Bennetts
  • Date: 2007-08-02 06:40:58 UTC
  • mfrom: (2666 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2668.
  • Revision ID: andrew.bennetts@canonical.com-20070802064058-09eblz1qbc01fcr3
Merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
76
76
    osutils,
77
77
    patiencediff,
78
78
    progress,
 
79
    merge,
79
80
    ui,
80
81
    )
81
82
from bzrlib.errors import (
153
154
    def copy(self):
154
155
        return KnitContent(self._lines[:])
155
156
 
 
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
 
156
183
 
157
184
class _KnitFactory(object):
158
185
    """Base factory for creating content objects."""
333
360
    def __init__(self, relpath, transport, file_mode=None, access_mode=None,
334
361
                 factory=None, basis_knit=DEPRECATED_PARAMETER, delta=True,
335
362
                 create=False, create_parent_dir=False, delay_create=False,
336
 
                 dir_mode=None):
 
363
                 dir_mode=None, index=None):
337
364
        """Construct a knit at location specified by relpath.
338
365
        
339
366
        :param create: If not True, only open an existing knit.
342
369
            hash-prefixes that may not exist yet)
343
370
        :param delay_create: The calling code is aware that the knit won't 
344
371
            actually be created until the first data is stored.
 
372
        :param index: An index to use for the knit.
345
373
        """
346
374
        if deprecated_passed(basis_knit):
347
375
            warnings.warn("KnitVersionedFile.__(): The basis_knit parameter is"
359
387
 
360
388
        self._max_delta_chain = 200
361
389
 
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)
 
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
366
397
        self._data = _KnitData(transport, relpath + DATA_SUFFIX,
367
398
            access_mode, create=create and not len(self), file_mode=file_mode,
368
399
            create_parent_dir=create_parent_dir, delay_create=delay_create,
519
550
                                current_values[3],
520
551
                                new_parents)
521
552
 
 
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
 
522
559
    def get_delta(self, version_id):
523
560
        """Get a delta for constructing version from some other version."""
524
561
        version_id = osutils.safe_revision_id(version_id)
554
591
        return dict(graph_items)
555
592
 
556
593
    def get_sha1(self, version_id):
 
594
        return self.get_sha1s([version_id])[0]
 
595
 
 
596
    def get_sha1s(self, version_ids):
557
597
        """See VersionedFile.get_sha1()."""
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 
 
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]
562
602
 
563
603
    @staticmethod
564
604
    def get_suffixes():
812
852
        text_map, content_map = self._get_content_maps(version_ids)
813
853
        return [text_map[v] for v in version_ids]
814
854
 
 
855
    _get_lf_split_line_list = get_line_list
 
856
 
815
857
    def _get_content_maps(self, version_ids):
816
858
        """Produce maps of text and KnitContents
817
859
        
907
949
 
908
950
        pb.update('Walking content.', total, total)
909
951
        
 
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
 
910
965
    def num_versions(self):
911
966
        """See VersionedFile.num_versions()."""
912
967
        return self._index.num_versions()
983
1038
        ver_a = osutils.safe_revision_id(ver_a)
984
1039
        ver_b = osutils.safe_revision_id(ver_b)
985
1040
        ancestors_b = set(self.get_ancestry(ver_b, topo_sorted=False))
986
 
        def status_a(revision, text):
987
 
            if revision in ancestors_b:
988
 
                return 'killed-b', text
989
 
            else:
990
 
                return 'new-a', text
991
1041
        
992
1042
        ancestors_a = set(self.get_ancestry(ver_a, topo_sorted=False))
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
 
 
999
1043
        annotated_a = self.annotate(ver_a)
1000
1044
        annotated_b = self.annotate(ver_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
 
1045
        return merge._plan_annotate_merge(annotated_a, annotated_b,
 
1046
                                          ancestors_a, ancestors_b)
1021
1047
 
1022
1048
 
1023
1049
class _KnitComponentFile(object):
1163
1189
                    self._filename, self.HEADER, mode=self._file_mode)
1164
1190
 
1165
1191
    def get_graph(self):
 
1192
        """Return a list of the node:parents lists from this knit index."""
1166
1193
        return [(vid, idx[4]) for vid, idx in self._cache.iteritems()]
1167
1194
 
1168
1195
    def get_ancestry(self, versions, topo_sorted=True):
1205
1232
                graph[version] = parents
1206
1233
        return topo_sort(graph.items())
1207
1234
 
 
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
 
1208
1250
    def num_versions(self):
1209
1251
        return len(self._history)
1210
1252
 
1211
1253
    __len__ = num_versions
1212
1254
 
1213
1255
    def get_versions(self):
 
1256
        """Get all the versions in the file. not topologically sorted."""
1214
1257
        return self._history
1215
1258
 
1216
 
    def idx_to_name(self, idx):
1217
 
        return self._history[idx]
1218
 
 
1219
 
    def lookup(self, version_id):
1220
 
        assert version_id in self._cache
1221
 
        return self._cache[version_id][5]
1222
 
 
1223
1259
    def _version_list_to_index(self, versions):
1224
1260
        result_list = []
1225
1261
        cache = self._cache
1295
1331
            return 'line-delta'
1296
1332
 
1297
1333
    def get_options(self, version_id):
 
1334
        """Return a string represention options.
 
1335
 
 
1336
        e.g. foo,bar
 
1337
        """
1298
1338
        return self._cache[version_id][1]
1299
1339
 
1300
1340
    def get_parents(self, version_id):
1314
1354
                raise RevisionNotPresent(version_id, self._filename)
1315
1355
 
1316
1356
 
 
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, keys, check_present=False):
 
1380
        """Get the entries for keys.
 
1381
        
 
1382
        :param keys: An iterable of index keys, - 1-tuples.
 
1383
        """
 
1384
        keys = set(keys)
 
1385
        found_keys = set()
 
1386
        if self._parents:
 
1387
            for node in self._graph_index.iter_entries(keys):
 
1388
                yield node
 
1389
                found_keys.add(node[0])
 
1390
        else:
 
1391
            # adapt parentless index to the rest of the code.
 
1392
            for node in self._graph_index.iter_entries(keys):
 
1393
                yield node[0], node[1], ()
 
1394
                found_keys.add(node[0])
 
1395
        if check_present:
 
1396
            missing_keys = keys.difference(found_keys)
 
1397
            if missing_keys:
 
1398
                raise RevisionNotPresent(missing_keys.pop(), self)
 
1399
 
 
1400
    def _present_keys(self, version_ids):
 
1401
        return set([
 
1402
            node[0] for node in self._get_entries(version_ids)])
 
1403
 
 
1404
    def _parentless_ancestry(self, versions):
 
1405
        """Honour the get_ancestry API for parentless knit indices."""
 
1406
        wanted_keys = self._version_ids_to_keys(versions)
 
1407
        present_keys = self._present_keys(wanted_keys)
 
1408
        missing = set(wanted_keys).difference(present_keys)
 
1409
        if missing:
 
1410
            raise RevisionNotPresent(missing.pop(), self)
 
1411
        return list(self._keys_to_version_ids(present_keys))
 
1412
 
 
1413
    def get_ancestry(self, versions, topo_sorted=True):
 
1414
        """See VersionedFile.get_ancestry."""
 
1415
        if not self._parents:
 
1416
            return self._parentless_ancestry(versions)
 
1417
        # XXX: This will do len(history) index calls - perhaps
 
1418
        # it should be altered to be a index core feature?
 
1419
        # get a graph of all the mentioned versions:
 
1420
        graph = {}
 
1421
        ghosts = set()
 
1422
        versions = self._version_ids_to_keys(versions)
 
1423
        pending = set(versions)
 
1424
        while pending:
 
1425
            # get all pending nodes
 
1426
            this_iteration = pending
 
1427
            new_nodes = self._get_entries(this_iteration)
 
1428
            found = set()
 
1429
            pending = set()
 
1430
            for (key, value, node_refs) in new_nodes:
 
1431
                # dont ask for ghosties - otherwise
 
1432
                # we we can end up looping with pending
 
1433
                # being entirely ghosted.
 
1434
                graph[key] = [parent for parent in node_refs[0]
 
1435
                    if parent not in ghosts]
 
1436
                # queue parents
 
1437
                for parent in graph[key]:
 
1438
                    # dont examine known nodes again
 
1439
                    if parent in graph:
 
1440
                        continue
 
1441
                    pending.add(parent)
 
1442
                found.add(key)
 
1443
            ghosts.update(this_iteration.difference(found))
 
1444
        if versions.difference(graph):
 
1445
            raise RevisionNotPresent(versions.difference(graph).pop(), self)
 
1446
        if topo_sorted:
 
1447
            result_keys = topo_sort(graph.items())
 
1448
        else:
 
1449
            result_keys = graph.iterkeys()
 
1450
        return [key[0] for key in result_keys]
 
1451
 
 
1452
    def get_ancestry_with_ghosts(self, versions):
 
1453
        """See VersionedFile.get_ancestry."""
 
1454
        if not self._parents:
 
1455
            return self._parentless_ancestry(versions)
 
1456
        # XXX: This will do len(history) index calls - perhaps
 
1457
        # it should be altered to be a index core feature?
 
1458
        # get a graph of all the mentioned versions:
 
1459
        graph = {}
 
1460
        versions = self._version_ids_to_keys(versions)
 
1461
        pending = set(versions)
 
1462
        while pending:
 
1463
            # get all pending nodes
 
1464
            this_iteration = pending
 
1465
            new_nodes = self._get_entries(this_iteration)
 
1466
            pending = set()
 
1467
            for (key, value, node_refs) in new_nodes:
 
1468
                graph[key] = node_refs[0]
 
1469
                # queue parents 
 
1470
                for parent in graph[key]:
 
1471
                    # dont examine known nodes again
 
1472
                    if parent in graph:
 
1473
                        continue
 
1474
                    pending.add(parent)
 
1475
            missing_versions = this_iteration.difference(graph)
 
1476
            missing_needed = versions.intersection(missing_versions)
 
1477
            if missing_needed:
 
1478
                raise RevisionNotPresent(missing_needed.pop(), self)
 
1479
            for missing_version in missing_versions:
 
1480
                # add a key, no parents
 
1481
                graph[missing_version] = []
 
1482
                pending.discard(missing_version) # don't look for it
 
1483
        result_keys = topo_sort(graph.items())
 
1484
        return [key[0] for key in result_keys]
 
1485
 
 
1486
    def get_graph(self):
 
1487
        """Return a list of the node:parents lists from this knit index."""
 
1488
        if not self._parents:
 
1489
            return [(key, ()) for key in self.get_versions()]
 
1490
        result = []
 
1491
        for key, value, refs in self._graph_index.iter_all_entries():
 
1492
            result.append((key[0], tuple([ref[0] for ref in refs[0]])))
 
1493
        return result
 
1494
 
 
1495
    def iter_parents(self, version_ids):
 
1496
        """Iterate through the parents for many version ids.
 
1497
 
 
1498
        :param version_ids: An iterable yielding version_ids.
 
1499
        :return: An iterator that yields (version_id, parents). Requested 
 
1500
            version_ids not present in the versioned file are simply skipped.
 
1501
            The order is undefined, allowing for different optimisations in
 
1502
            the underlying implementation.
 
1503
        """
 
1504
        if self._parents:
 
1505
            all_nodes = set(self._get_entries(self._version_ids_to_keys(version_ids)))
 
1506
            all_parents = set()
 
1507
            present_parents = set()
 
1508
            for node in all_nodes:
 
1509
                all_parents.update(node[2][0])
 
1510
                # any node we are querying must be present
 
1511
                present_parents.add(node[0])
 
1512
            unknown_parents = all_parents.difference(present_parents)
 
1513
            present_parents.update(self._present_keys(unknown_parents))
 
1514
            for node in all_nodes:
 
1515
                parents = []
 
1516
                for parent in node[2][0]:
 
1517
                    if parent in present_parents:
 
1518
                        parents.append(parent[0])
 
1519
                yield node[0][0], tuple(parents)
 
1520
        else:
 
1521
            for node in self._get_entries(self._version_ids_to_keys(version_ids)):
 
1522
                yield node[0][0], ()
 
1523
 
 
1524
    def num_versions(self):
 
1525
        return len(list(self._graph_index.iter_all_entries()))
 
1526
 
 
1527
    __len__ = num_versions
 
1528
 
 
1529
    def get_versions(self):
 
1530
        """Get all the versions in the file. not topologically sorted."""
 
1531
        return [node[0][0] for node in self._graph_index.iter_all_entries()]
 
1532
    
 
1533
    def has_version(self, version_id):
 
1534
        """True if the version is in the index."""
 
1535
        return len(self._present_keys(self._version_ids_to_keys([version_id]))) == 1
 
1536
 
 
1537
    def _keys_to_version_ids(self, keys):
 
1538
        return tuple(key[0] for key in keys)
 
1539
 
 
1540
    def get_position(self, version_id):
 
1541
        """Return data position and size of specified version."""
 
1542
        bits = self._get_node(version_id)[1][1:].split(' ')
 
1543
        return int(bits[0]), int(bits[1])
 
1544
 
 
1545
    def get_method(self, version_id):
 
1546
        """Return compression method of specified version."""
 
1547
        if not self._deltas:
 
1548
            return 'fulltext'
 
1549
        return self._parent_compression(self._get_node(version_id)[2][1])
 
1550
 
 
1551
    def _parent_compression(self, reference_list):
 
1552
        # use the second reference list to decide if this is delta'd or not.
 
1553
        if len(reference_list):
 
1554
            return 'line-delta'
 
1555
        else:
 
1556
            return 'fulltext'
 
1557
 
 
1558
    def _get_node(self, version_id):
 
1559
        return list(self._get_entries(self._version_ids_to_keys([version_id])))[0]
 
1560
 
 
1561
    def get_options(self, version_id):
 
1562
        """Return a string represention options.
 
1563
 
 
1564
        e.g. foo,bar
 
1565
        """
 
1566
        node = self._get_node(version_id)
 
1567
        if not self._deltas:
 
1568
            options = ['fulltext']
 
1569
        else:
 
1570
            options = [self._parent_compression(node[2][1])]
 
1571
        if node[1][0] == 'N':
 
1572
            options.append('no-eol')
 
1573
        return options
 
1574
 
 
1575
    def get_parents(self, version_id):
 
1576
        """Return parents of specified version ignoring ghosts."""
 
1577
        parents = list(self.iter_parents([version_id]))
 
1578
        if not parents:
 
1579
            # missing key
 
1580
            raise errors.RevisionNotPresent(version_id, self)
 
1581
        return parents[0][1]
 
1582
 
 
1583
    def get_parents_with_ghosts(self, version_id):
 
1584
        """Return parents of specified version with ghosts."""
 
1585
        nodes = list(self._get_entries(self._version_ids_to_keys([version_id]),
 
1586
            check_present=True))
 
1587
        if not self._parents:
 
1588
            return ()
 
1589
        return self._keys_to_version_ids(nodes[0][2][0])
 
1590
 
 
1591
    def check_versions_present(self, version_ids):
 
1592
        """Check that all specified versions are present."""
 
1593
        keys = self._version_ids_to_keys(version_ids)
 
1594
        present = self._present_keys(keys)
 
1595
        missing = keys.difference(present)
 
1596
        if missing:
 
1597
            raise RevisionNotPresent(missing.pop(), self)
 
1598
 
 
1599
    def add_version(self, version_id, options, pos, size, parents):
 
1600
        """Add a version record to the index."""
 
1601
        return self.add_versions(((version_id, options, pos, size, parents),))
 
1602
 
 
1603
    def add_versions(self, versions):
 
1604
        """Add multiple versions to the index.
 
1605
        
 
1606
        This function does not insert data into the Immutable GraphIndex
 
1607
        backing the KnitGraphIndex, instead it prepares data for insertion by
 
1608
        the caller and checks that it is safe to insert then calls
 
1609
        self._add_callback with the prepared GraphIndex nodes.
 
1610
 
 
1611
        :param versions: a list of tuples:
 
1612
                         (version_id, options, pos, size, parents).
 
1613
        """
 
1614
        if not self._add_callback:
 
1615
            raise errors.ReadOnlyError(self)
 
1616
        # we hope there are no repositories with inconsistent parentage
 
1617
        # anymore.
 
1618
        # check for dups
 
1619
 
 
1620
        keys = {}
 
1621
        for (version_id, options, pos, size, parents) in versions:
 
1622
            # index keys are tuples:
 
1623
            key = (version_id, )
 
1624
            parents = tuple((parent, ) for parent in parents)
 
1625
            if 'no-eol' in options:
 
1626
                value = 'N'
 
1627
            else:
 
1628
                value = ' '
 
1629
            value += "%d %d" % (pos, size)
 
1630
            if not self._deltas:
 
1631
                if 'line-delta' in options:
 
1632
                    raise KnitCorrupt(self, "attempt to add line-delta in non-delta knit")
 
1633
            if self._parents:
 
1634
                if self._deltas:
 
1635
                    if 'line-delta' in options:
 
1636
                        node_refs = (parents, (parents[0],))
 
1637
                    else:
 
1638
                        node_refs = (parents, ())
 
1639
                else:
 
1640
                    node_refs = (parents, )
 
1641
            else:
 
1642
                if parents:
 
1643
                    raise KnitCorrupt(self, "attempt to add node with parents "
 
1644
                        "in parentless index.")
 
1645
                node_refs = ()
 
1646
            keys[key] = (value, node_refs)
 
1647
        present_nodes = self._get_entries(keys)
 
1648
        for (key, value, node_refs) in present_nodes:
 
1649
            if (value, node_refs) != keys[key]:
 
1650
                raise KnitCorrupt(self, "inconsistent details in add_versions"
 
1651
                    ": %s %s" % ((value, node_refs), keys[key]))
 
1652
            del keys[key]
 
1653
        result = []
 
1654
        if self._parents:
 
1655
            for key, (value, node_refs) in keys.iteritems():
 
1656
                result.append((key, value, node_refs))
 
1657
        else:
 
1658
            for key, (value, node_refs) in keys.iteritems():
 
1659
                result.append((key, value))
 
1660
        self._add_callback(result)
 
1661
        
 
1662
    def _version_ids_to_keys(self, version_ids):
 
1663
        return set((version_id, ) for version_id in version_ids)
 
1664
        
 
1665
 
1317
1666
class _KnitData(_KnitComponentFile):
1318
1667
    """Contents of the knit data file"""
1319
1668