69
72
class GraphIndexBuilder(object):
70
73
"""A builder that can build a GraphIndex.
72
The resulting graph has the structure:
75
The resulting graph has the structure::
74
_SIGNATURE OPTIONS NODES NEWLINE
75
_SIGNATURE := 'Bazaar Graph Index 1' NEWLINE
76
OPTIONS := 'node_ref_lists=' DIGITS NEWLINE
78
NODE := KEY NULL ABSENT? NULL REFERENCES NULL VALUE NEWLINE
79
KEY := Not-whitespace-utf8
81
REFERENCES := REFERENCE_LIST (TAB REFERENCE_LIST){node_ref_lists - 1}
82
REFERENCE_LIST := (REFERENCE (CR REFERENCE)*)?
83
REFERENCE := DIGITS ; digits is the byte offset in the index of the
85
VALUE := no-newline-no-null-bytes
77
_SIGNATURE OPTIONS NODES NEWLINE
78
_SIGNATURE := 'Bazaar Graph Index 1' NEWLINE
79
OPTIONS := 'node_ref_lists=' DIGITS NEWLINE
81
NODE := KEY NULL ABSENT? NULL REFERENCES NULL VALUE NEWLINE
82
KEY := Not-whitespace-utf8
84
REFERENCES := REFERENCE_LIST (TAB REFERENCE_LIST){node_ref_lists - 1}
85
REFERENCE_LIST := (REFERENCE (CR REFERENCE)*)?
86
REFERENCE := DIGITS ; digits is the byte offset in the index of the
88
VALUE := no-newline-no-null-bytes
88
91
def __init__(self, reference_lists=0, key_elements=1):
225
233
# There may be duplicates, but I don't think it is worth worrying
227
235
self._nodes[reference] = ('a', (), '')
236
self._absent_keys.update(absent_references)
237
self._absent_keys.discard(key)
228
238
self._nodes[key] = ('', node_refs, value)
230
239
if self._nodes_by_key is not None and self._key_length > 1:
231
240
self._update_nodes_by_key(key, value, node_refs)
242
def clear_cache(self):
243
"""See GraphIndex.clear_cache()
245
This is a no-op, but we need the api to conform to a generic 'Index'
233
249
def finish(self):
252
:returns: cStringIO holding the full context of the index as it
253
should be written to disk.
234
255
lines = [_SIGNATURE]
235
256
lines.append(_OPTION_NODE_REFS + str(self.reference_lists) + '\n')
236
257
lines.append(_OPTION_KEY_ELEMENTS + str(self._key_length) + '\n')
237
lines.append(_OPTION_LEN + str(len(self._keys)) + '\n')
258
key_count = len(self._nodes) - len(self._absent_keys)
259
lines.append(_OPTION_LEN + str(key_count) + '\n')
238
260
prefix_length = sum(len(x) for x in lines)
239
261
# references are byte offsets. To avoid having to do nasty
240
262
# polynomial work to resolve offsets (references to later in the
703
762
# the last thing looked up was a terminal element
704
763
yield (self, ) + key_dict
765
def _find_ancestors(self, keys, ref_list_num, parent_map, missing_keys):
766
"""See BTreeIndex._find_ancestors."""
767
# The api can be implemented as a trivial overlay on top of
768
# iter_entries, it is not an efficient implementation, but it at least
772
for index, key, value, refs in self.iter_entries(keys):
773
parent_keys = refs[ref_list_num]
775
parent_map[key] = parent_keys
776
search_keys.update(parent_keys)
777
# Figure out what, if anything, was missing
778
missing_keys.update(set(keys).difference(found_keys))
779
search_keys = search_keys.difference(parent_map)
706
782
def key_count(self):
707
783
"""Return an estimate of the number of keys in this index.
1186
1278
self._indices = indices
1187
1279
self._reload_func = reload_func
1280
# Sibling indices are other CombinedGraphIndex that we should call
1281
# _move_to_front_by_name on when we auto-reorder ourself.
1282
self._sibling_indices = []
1283
# A list of names that corresponds to the instances in self._indices,
1284
# so _index_names[0] is always the name for _indices[0], etc. Sibling
1285
# indices must all use the same set of names as each other.
1286
self._index_names = [None] * len(self._indices)
1189
1288
def __repr__(self):
1190
1289
return "%s(%s)" % (
1191
1290
self.__class__.__name__,
1192
1291
', '.join(map(repr, self._indices)))
1293
def clear_cache(self):
1294
"""See GraphIndex.clear_cache()"""
1295
for index in self._indices:
1194
1298
def get_parent_map(self, keys):
1195
"""See graph._StackedParentsProvider.get_parent_map"""
1299
"""See graph.StackedParentsProvider.get_parent_map"""
1196
1300
search_keys = set(keys)
1197
if NULL_REVISION in search_keys:
1198
search_keys.discard(NULL_REVISION)
1199
found_parents = {NULL_REVISION:[]}
1301
if _mod_revision.NULL_REVISION in search_keys:
1302
search_keys.discard(_mod_revision.NULL_REVISION)
1303
found_parents = {_mod_revision.NULL_REVISION:[]}
1201
1305
found_parents = {}
1202
1306
for index, key, value, refs in self.iter_entries(search_keys):
1203
1307
parents = refs[0]
1204
1308
if not parents:
1205
parents = (NULL_REVISION,)
1309
parents = (_mod_revision.NULL_REVISION,)
1206
1310
found_parents[key] = parents
1207
1311
return found_parents
1209
1313
has_key = _has_key_from_parent_map
1211
def insert_index(self, pos, index):
1315
def insert_index(self, pos, index, name=None):
1212
1316
"""Insert a new index in the list of indices to query.
1214
1318
:param pos: The position to insert the index.
1215
1319
:param index: The index to insert.
1320
:param name: a name for this index, e.g. a pack name. These names can
1321
be used to reflect index reorderings to related CombinedGraphIndex
1322
instances that use the same names. (see set_sibling_indices)
1217
1324
self._indices.insert(pos, index)
1325
self._index_names.insert(pos, name)
1219
1327
def iter_all_entries(self):
1220
1328
"""Iterate over all keys within the index
1288
1402
seen_keys = set()
1291
1406
for index in self._indices:
1292
1408
for node in index.iter_entries_prefix(keys):
1293
1409
if node[1] in seen_keys:
1295
1411
seen_keys.add(node[1])
1415
hit_indices.append(index)
1298
1417
except errors.NoSuchFile:
1299
1418
self._reload_or_raise()
1419
self._move_to_front(hit_indices)
1421
def _move_to_front(self, hit_indices):
1422
"""Rearrange self._indices so that hit_indices are first.
1424
Order is maintained as much as possible, e.g. the first unhit index
1425
will be the first index in _indices after the hit_indices, and the
1426
hit_indices will be present in exactly the order they are passed to
1429
_move_to_front propagates to all objects in self._sibling_indices by
1430
calling _move_to_front_by_name.
1432
if self._indices[:len(hit_indices)] == hit_indices:
1433
# The 'hit_indices' are already at the front (and in the same
1434
# order), no need to re-order
1436
hit_names = self._move_to_front_by_index(hit_indices)
1437
for sibling_idx in self._sibling_indices:
1438
sibling_idx._move_to_front_by_name(hit_names)
1440
def _move_to_front_by_index(self, hit_indices):
1441
"""Core logic for _move_to_front.
1443
Returns a list of names corresponding to the hit_indices param.
1445
indices_info = zip(self._index_names, self._indices)
1446
if 'index' in debug.debug_flags:
1447
trace.mutter('CombinedGraphIndex reordering: currently %r, '
1448
'promoting %r', indices_info, hit_indices)
1451
new_hit_indices = []
1454
for offset, (name, idx) in enumerate(indices_info):
1455
if idx in hit_indices:
1456
hit_names.append(name)
1457
new_hit_indices.append(idx)
1458
if len(new_hit_indices) == len(hit_indices):
1459
# We've found all of the hit entries, everything else is
1461
unhit_names.extend(self._index_names[offset+1:])
1462
unhit_indices.extend(self._indices[offset+1:])
1465
unhit_names.append(name)
1466
unhit_indices.append(idx)
1468
self._indices = new_hit_indices + unhit_indices
1469
self._index_names = hit_names + unhit_names
1470
if 'index' in debug.debug_flags:
1471
trace.mutter('CombinedGraphIndex reordered: %r', self._indices)
1474
def _move_to_front_by_name(self, hit_names):
1475
"""Moves indices named by 'hit_names' to front of the search order, as
1476
described in _move_to_front.
1478
# Translate names to index instances, and then call
1479
# _move_to_front_by_index.
1480
indices_info = zip(self._index_names, self._indices)
1482
for name, idx in indices_info:
1483
if name in hit_names:
1484
hit_indices.append(idx)
1485
self._move_to_front_by_index(hit_indices)
1487
def find_ancestry(self, keys, ref_list_num):
1488
"""Find the complete ancestry for the given set of keys.
1490
Note that this is a whole-ancestry request, so it should be used
1493
:param keys: An iterable of keys to look for
1494
:param ref_list_num: The reference list which references the parents
1496
:return: (parent_map, missing_keys)
1498
# XXX: make this call _move_to_front?
1499
missing_keys = set()
1501
keys_to_lookup = set(keys)
1503
while keys_to_lookup:
1504
# keys that *all* indexes claim are missing, stop searching them
1506
all_index_missing = None
1507
# print 'gen\tidx\tsub\tn_keys\tn_pmap\tn_miss'
1508
# print '%4d\t\t\t%4d\t%5d\t%5d' % (generation, len(keys_to_lookup),
1510
# len(missing_keys))
1511
for index_idx, index in enumerate(self._indices):
1512
# TODO: we should probably be doing something with
1513
# 'missing_keys' since we've already determined that
1514
# those revisions have not been found anywhere
1515
index_missing_keys = set()
1516
# Find all of the ancestry we can from this index
1517
# keep looking until the search_keys set is empty, which means
1518
# things we didn't find should be in index_missing_keys
1519
search_keys = keys_to_lookup
1521
# print ' \t%2d\t\t%4d\t%5d\t%5d' % (
1522
# index_idx, len(search_keys),
1523
# len(parent_map), len(index_missing_keys))
1526
# TODO: ref_list_num should really be a parameter, since
1527
# CombinedGraphIndex does not know what the ref lists
1529
search_keys = index._find_ancestors(search_keys,
1530
ref_list_num, parent_map, index_missing_keys)
1531
# print ' \t \t%2d\t%4d\t%5d\t%5d' % (
1532
# sub_generation, len(search_keys),
1533
# len(parent_map), len(index_missing_keys))
1534
# Now set whatever was missing to be searched in the next index
1535
keys_to_lookup = index_missing_keys
1536
if all_index_missing is None:
1537
all_index_missing = set(index_missing_keys)
1539
all_index_missing.intersection_update(index_missing_keys)
1540
if not keys_to_lookup:
1542
if all_index_missing is None:
1543
# There were no indexes, so all search keys are 'missing'
1544
missing_keys.update(keys_to_lookup)
1545
keys_to_lookup = None
1547
missing_keys.update(all_index_missing)
1548
keys_to_lookup.difference_update(all_index_missing)
1549
return parent_map, missing_keys
1301
1551
def key_count(self):
1302
1552
"""Return an estimate of the number of keys in this index.