/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: Robert Collins
  • Date: 2007-08-07 22:59:45 UTC
  • mfrom: (2681 +trunk)
  • mto: This revision was merged to the branch mainline in revision 2682.
  • Revision ID: robertc@robertcollins.net-20070807225945-dlxppeb3we4lh897
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 (
359
360
    def __init__(self, relpath, transport, file_mode=None, access_mode=None,
360
361
                 factory=None, basis_knit=DEPRECATED_PARAMETER, delta=True,
361
362
                 create=False, create_parent_dir=False, delay_create=False,
362
 
                 dir_mode=None):
 
363
                 dir_mode=None, index=None):
363
364
        """Construct a knit at location specified by relpath.
364
365
        
365
366
        :param create: If not True, only open an existing knit.
368
369
            hash-prefixes that may not exist yet)
369
370
        :param delay_create: The calling code is aware that the knit won't 
370
371
            actually be created until the first data is stored.
 
372
        :param index: An index to use for the knit.
371
373
        """
372
374
        if deprecated_passed(basis_knit):
373
375
            warnings.warn("KnitVersionedFile.__(): The basis_knit parameter is"
385
387
 
386
388
        self._max_delta_chain = 200
387
389
 
388
 
        self._index = _KnitIndex(transport, relpath + INDEX_SUFFIX,
389
 
            access_mode, create=create, file_mode=file_mode,
390
 
            create_parent_dir=create_parent_dir, delay_create=delay_create,
391
 
            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
392
397
        self._data = _KnitData(transport, relpath + DATA_SUFFIX,
393
398
            access_mode, create=create and not len(self), file_mode=file_mode,
394
399
            create_parent_dir=create_parent_dir, delay_create=delay_create,
944
949
 
945
950
        pb.update('Walking content.', total, total)
946
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
 
947
965
    def num_versions(self):
948
966
        """See VersionedFile.num_versions()."""
949
967
        return self._index.num_versions()
1020
1038
        ver_a = osutils.safe_revision_id(ver_a)
1021
1039
        ver_b = osutils.safe_revision_id(ver_b)
1022
1040
        ancestors_b = set(self.get_ancestry(ver_b, topo_sorted=False))
1023
 
        def status_a(revision, text):
1024
 
            if revision in ancestors_b:
1025
 
                return 'killed-b', text
1026
 
            else:
1027
 
                return 'new-a', text
1028
1041
        
1029
1042
        ancestors_a = set(self.get_ancestry(ver_a, topo_sorted=False))
1030
 
        def status_b(revision, text):
1031
 
            if revision in ancestors_a:
1032
 
                return 'killed-a', text
1033
 
            else:
1034
 
                return 'new-b', text
1035
 
 
1036
1043
        annotated_a = self.annotate(ver_a)
1037
1044
        annotated_b = self.annotate(ver_b)
1038
 
        plain_a = [t for (a, t) in annotated_a]
1039
 
        plain_b = [t for (a, t) in annotated_b]
1040
 
        blocks = KnitSequenceMatcher(None, plain_a, plain_b).get_matching_blocks()
1041
 
        a_cur = 0
1042
 
        b_cur = 0
1043
 
        for ai, bi, l in blocks:
1044
 
            # process all mismatched sections
1045
 
            # (last mismatched section is handled because blocks always
1046
 
            # includes a 0-length last block)
1047
 
            for revision, text in annotated_a[a_cur:ai]:
1048
 
                yield status_a(revision, text)
1049
 
            for revision, text in annotated_b[b_cur:bi]:
1050
 
                yield status_b(revision, text)
1051
 
 
1052
 
            # and now the matched section
1053
 
            a_cur = ai + l
1054
 
            b_cur = bi + l
1055
 
            for text_a, text_b in zip(plain_a[ai:a_cur], plain_b[bi:b_cur]):
1056
 
                assert text_a == text_b
1057
 
                yield "unchanged", text_a
 
1045
        return merge._plan_annotate_merge(annotated_a, annotated_b,
 
1046
                                          ancestors_a, ancestors_b)
1058
1047
 
1059
1048
 
1060
1049
class _KnitComponentFile(object):
1200
1189
                    self._filename, self.HEADER, mode=self._file_mode)
1201
1190
 
1202
1191
    def get_graph(self):
 
1192
        """Return a list of the node:parents lists from this knit index."""
1203
1193
        return [(vid, idx[4]) for vid, idx in self._cache.iteritems()]
1204
1194
 
1205
1195
    def get_ancestry(self, versions, topo_sorted=True):
1242
1232
                graph[version] = parents
1243
1233
        return topo_sort(graph.items())
1244
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
 
1245
1250
    def num_versions(self):
1246
1251
        return len(self._history)
1247
1252
 
1248
1253
    __len__ = num_versions
1249
1254
 
1250
1255
    def get_versions(self):
 
1256
        """Get all the versions in the file. not topologically sorted."""
1251
1257
        return self._history
1252
1258
 
1253
 
    def idx_to_name(self, idx):
1254
 
        return self._history[idx]
1255
 
 
1256
 
    def lookup(self, version_id):
1257
 
        assert version_id in self._cache
1258
 
        return self._cache[version_id][5]
1259
 
 
1260
1259
    def _version_list_to_index(self, versions):
1261
1260
        result_list = []
1262
1261
        cache = self._cache
1332
1331
            return 'line-delta'
1333
1332
 
1334
1333
    def get_options(self, version_id):
 
1334
        """Return a string represention options.
 
1335
 
 
1336
        e.g. foo,bar
 
1337
        """
1335
1338
        return self._cache[version_id][1]
1336
1339
 
1337
1340
    def get_parents(self, version_id):
1351
1354
                raise RevisionNotPresent(version_id, self._filename)
1352
1355
 
1353
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
 
1354
1666
class _KnitData(_KnitComponentFile):
1355
1667
    """Contents of the knit data file"""
1356
1668