1323
1354
raise RevisionNotPresent(version_id, self._filename)
1357
class KnitGraphIndex(object):
1358
"""A knit index that builds on GraphIndex."""
1360
def __init__(self, graph_index, deltas=False, parents=True, add_callback=None):
1361
"""Construct a KnitGraphIndex on a graph_index.
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
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 "
1379
def _get_entries(self, version_ids, check_present=False):
1380
"""Get the entries for version_ids."""
1381
version_ids = set(version_ids)
1384
for node in self._graph_index.iter_entries(version_ids):
1386
found_keys.add(node[0])
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])
1393
missing_keys = version_ids.difference(found_keys)
1395
raise RevisionNotPresent(missing_keys.pop(), self)
1397
def _present_keys(self, version_ids):
1399
node[0] for node in self._get_entries(version_ids)])
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)
1406
raise RevisionNotPresent(missing.pop(), self)
1407
return list(present_keys)
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:
1418
versions = set(versions)
1419
pending = set(versions)
1421
# get all pending nodes
1422
this_iteration = pending
1423
new_nodes = self._get_entries(this_iteration)
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]
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)
1440
return topo_sort(graph.items())
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:
1450
versions = set(versions)
1451
pending = set(versions)
1453
# get all pending nodes
1454
this_iteration = pending
1455
new_nodes = self._get_entries(this_iteration)
1457
for (key, value, node_refs) in new_nodes:
1458
graph[key] = node_refs[0]
1460
pending.update(graph[key])
1461
missing_versions = this_iteration.difference(graph)
1462
missing_needed = versions.intersection(missing_versions)
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())
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()]
1479
def iter_parents(self, version_ids):
1480
"""Iterate through the parents for many version ids.
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.
1489
all_nodes = set(self._get_entries(version_ids))
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:
1500
for parent in node[2][0]:
1501
if parent in present_parents:
1502
parents.append(parent)
1503
yield node[0], tuple(parents)
1505
for node in self._get_entries(version_ids):
1508
def num_versions(self):
1509
return len(list(self._graph_index.iter_all_entries()))
1511
__len__ = num_versions
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()]
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
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])
1526
def get_method(self, version_id):
1527
"""Return compression method of specified version."""
1528
if not self._deltas:
1530
return self._parent_compression(self._get_node(version_id)[2][1])
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):
1539
def _get_node(self, version_id):
1540
return list(self._get_entries([version_id]))[0]
1542
def get_options(self, version_id):
1543
"""Return a string represention options.
1547
node = self._get_node(version_id)
1548
if not self._deltas:
1549
options = ['fulltext']
1551
options = [self._parent_compression(node[2][1])]
1552
if node[1][0] == 'N':
1553
options.append('no-eol')
1554
return ','.join(options)
1556
def get_parents(self, version_id):
1557
"""Return parents of specified version ignoring ghosts."""
1558
parents = list(self.iter_parents([version_id]))
1561
raise errors.RevisionNotPresent(version_id, self)
1562
return parents[0][1]
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:
1569
return nodes[0][2][0]
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)
1577
raise RevisionNotPresent(missing.pop(), self)
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),))
1583
def add_versions(self, versions):
1584
"""Add multiple versions to the index.
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.
1591
:param versions: a list of tuples:
1592
(version_id, options, pos, size, parents).
1594
if not self._add_callback:
1595
raise errors.ReadOnlyError(self)
1596
# we hope there are no repositories with inconsistent parentage
1601
for (version_id, options, pos, size, parents) in versions:
1602
if 'no-eol' in options:
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")
1612
if 'line-delta' in options:
1613
node_refs = (tuple(parents), (parents[0],))
1615
node_refs = (tuple(parents), ())
1617
node_refs = (tuple(parents), )
1620
raise KnitCorrupt(self, "attempt to add node with parents "
1621
"in parentless index.")
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]))
1632
for key, (value, node_refs) in keys.iteritems():
1633
result.append((key, value, node_refs))
1635
for key, (value, node_refs) in keys.iteritems():
1636
result.append((key, value))
1637
self._add_callback(result)
1326
1640
class _KnitData(_KnitComponentFile):
1327
1641
"""Contents of the knit data file"""