227
197
PyList_Append(parent_node.children, node)
228
198
node.parents = parent_nodes
230
cdef _KnownGraphNode _check_is_linear(self, _KnownGraphNode node):
231
"""Check to see if a given node is part of a linear chain."""
232
cdef _KnownGraphNode parent_node
233
if node.parents is None or PyTuple_GET_SIZE(node.parents) != 1:
234
# This node is either a ghost, a tail, or has multiple parents
235
# It its own dominator
236
node.linear_dominator_node = node
238
parent_node = _get_parent(node.parents, 0)
239
if PyList_GET_SIZE(parent_node.children) > 1:
240
# The parent has multiple children, so *this* node is the
242
node.linear_dominator_node = node
244
# The parent is already filled in, so add and continue
245
if parent_node.linear_dominator_node is not None:
246
node.linear_dominator_node = parent_node.linear_dominator_node
248
# We don't know this node, or its parent node, so start walking to
252
def _find_linear_dominators(self):
254
For any given node, the 'linear dominator' is an ancestor, such that
255
all parents between this node and that one have a single parent, and a
256
single child. So if A->B->C->D then B,C,D all have a linear dominator
259
There are two main benefits:
260
1) When walking the graph, we can jump to the nearest linear dominator,
261
rather than walking all of the nodes inbetween.
262
2) When caching heads() results, dominators give the "same" results as
263
their children. (If the dominator is a head, then the descendant is
264
a head, if the dominator is not a head, then the child isn't
267
cdef PyObject *temp_node
269
cdef _KnownGraphNode node
270
cdef _KnownGraphNode next_node
271
cdef _KnownGraphNode dominator
272
cdef int i, num_elements
275
while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
276
node = <_KnownGraphNode>temp_node
277
# The parent is not filled in, so walk until we get somewhere
278
if node.linear_dominator_node is not None: #already done
280
next_node = self._check_is_linear(node)
281
if next_node is None:
282
# Nothing more needs to be done
285
while next_node is not None:
286
PyList_Append(stack, node)
288
next_node = self._check_is_linear(node)
289
# The stack now contains the linear chain, and 'node' should have
291
dominator = node.linear_dominator_node
292
num_elements = len(stack)
293
for i from num_elements > i >= 0:
294
next_node = _get_list_node(stack, i)
295
next_node.linear_dominator_node = dominator
298
cdef object _find_tails(self):
300
cdef PyObject *temp_node
302
cdef _KnownGraphNode node
200
def _find_tails(self):
201
cdef PyObject *temp_node
202
cdef _KnownGraphNode node
306
207
while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
307
208
node = <_KnownGraphNode>temp_node
308
209
if node.parents is None or PyTuple_GET_SIZE(node.parents) == 0:
309
211
PyList_Append(tails, node)
312
214
def _find_gdfo(self):
313
cdef Py_ssize_t pos, pos2
314
215
cdef _KnownGraphNode node
315
cdef _KnownGraphNode child_node
316
cdef _KnownGraphNode parent_node
317
cdef int replace_node, missing_parent
319
tails = self._find_tails()
321
for pos from 0 <= pos < PyList_GET_SIZE(tails):
322
node = _get_list_node(tails, pos)
324
PyList_Append(todo, (1, node))
325
# No need to heapify, because all tails have priority=1
326
while PyList_GET_SIZE(todo) > 0:
327
node = _peek_node(todo)
216
cdef _KnownGraphNode child
220
cdef Py_ssize_t last_item
223
pending = self._find_tails()
225
last_item = PyList_GET_SIZE(pending) - 1
226
while last_item >= 0:
227
# Avoid pop followed by push, instead, peek, and replace
228
# timing shows this is 930ms => 770ms for OOo
229
node = _get_list_node(pending, last_item)
230
last_item = last_item - 1
328
231
next_gdfo = node.gdfo + 1
330
232
for pos from 0 <= pos < PyList_GET_SIZE(node.children):
331
child_node = _get_list_node(node.children, pos)
332
# We should never have numbered children before we numbered
334
if child_node.gdfo != -1:
336
# Only enque children when all of their parents have been
337
# resolved. With a single parent, we can just take 'this' value
338
child_gdfo = next_gdfo
339
if PyTuple_GET_SIZE(child_node.parents) > 1:
341
for pos2 from 0 <= pos2 < PyTuple_GET_SIZE(child_node.parents):
342
parent_node = _get_parent(child_node.parents, pos2)
343
if parent_node.gdfo == -1:
346
if parent_node.gdfo >= child_gdfo:
347
child_gdfo = parent_node.gdfo + 1
349
# One of the parents is not numbered, so wait until we get
352
child_node.gdfo = child_gdfo
354
heapreplace(todo, (child_gdfo, child_node))
357
heappush(todo, (child_gdfo, child_node))
233
child = _get_list_node(node.children, pos)
234
if next_gdfo > child.gdfo:
235
child.gdfo = next_gdfo
236
child.seen = child.seen + 1
237
if child.seen == PyTuple_GET_SIZE(child.parents):
238
# This child is populated, queue it to be walked
239
last_item = last_item + 1
240
if last_item < PyList_GET_SIZE(pending):
241
Py_INCREF(child) # SetItem steals a ref
242
PyList_SetItem(pending, last_item, child)
244
PyList_Append(pending, child)
245
# We have queued this node, we don't need to track it
361
249
def heads(self, keys):
362
250
"""Return the heads from amongst keys.
376
263
cdef PyObject *maybe_node
377
264
cdef PyObject *maybe_heads
265
cdef PyObject *temp_node
266
cdef _KnownGraphNode node
267
cdef Py_ssize_t pos, last_item
379
270
heads_key = PyFrozenSet_New(keys)
380
271
maybe_heads = PyDict_GetItem(self._known_heads, heads_key)
381
272
if maybe_heads != NULL:
382
273
return <object>maybe_heads
384
274
# Not cached, compute it ourselves
385
275
candidate_nodes = {}
388
maybe_node = PyDict_GetItem(nodes, key)
277
maybe_node = PyDict_GetItem(self._nodes, key)
389
278
if maybe_node == NULL:
390
279
raise KeyError('key %s not in nodes' % (key,))
391
280
PyDict_SetItem(candidate_nodes, key, <object>maybe_node)
392
if revision.NULL_REVISION in candidate_nodes:
281
maybe_node = PyDict_GetItem(candidate_nodes, NULL_REVISION)
282
if maybe_node != NULL:
393
283
# NULL_REVISION is only a head if it is the only entry
394
candidate_nodes.pop(revision.NULL_REVISION)
284
candidate_nodes.pop(NULL_REVISION)
395
285
if not candidate_nodes:
396
return set([revision.NULL_REVISION])
286
return frozenset([NULL_REVISION])
397
287
# The keys changed, so recalculate heads_key
398
288
heads_key = PyFrozenSet_New(candidate_nodes)
399
if len(candidate_nodes) < 2:
289
if PyDict_Size(candidate_nodes) < 2:
401
dom_to_node = self._get_dominators_to_nodes(candidate_nodes)
402
if PyDict_Size(candidate_nodes) < 2:
403
return frozenset(candidate_nodes)
404
dom_lookup_key, heads = self._heads_from_dominators(candidate_nodes,
406
if heads is not None:
408
# This heads was not in the cache, or it would have been caught
409
# earlier, but the dom head *was*, so do the simple cache
410
PyDict_SetItem(self._known_heads, heads_key, heads)
412
heads = self._heads_from_candidate_nodes(candidate_nodes, dom_to_node)
294
# we know a gdfo cannot be longer than a linear chain of all nodes
295
min_gdfo = PyDict_Size(self._nodes) + 1
296
# Build up nodes that need to be walked, note that starting nodes are
297
# not added to seen()
299
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
300
node = <_KnownGraphNode>temp_node
301
if node.parents is not None:
302
pending.extend(node.parents)
303
if node.gdfo < min_gdfo:
306
# Now do all the real work
307
last_item = PyList_GET_SIZE(pending) - 1
308
while last_item >= 0:
309
node = _get_list_node(pending, last_item)
310
last_item = last_item - 1
312
# node already appears in some ancestry
314
PyList_Append(cleanup, node)
316
if node.gdfo <= min_gdfo:
318
if node.parents is not None and PyTuple_GET_SIZE(node.parents) > 0:
319
for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
320
parent_node = _get_parent(node.parents, pos)
321
last_item = last_item + 1
322
if last_item < PyList_GET_SIZE(pending):
323
Py_INCREF(parent_node) # SetItem steals a ref
324
PyList_SetItem(pending, last_item, parent_node)
326
PyList_Append(pending, parent_node)
329
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
330
node = <_KnownGraphNode>temp_node
332
PyList_Append(heads, node.key)
333
heads = PyFrozenSet_New(heads)
334
for pos from 0 <= pos < PyList_GET_SIZE(cleanup):
335
node = _get_list_node(cleanup, pos)
413
337
if self.do_cache:
414
self._cache_heads(heads, heads_key, dom_lookup_key, candidate_nodes)
338
PyDict_SetItem(self._known_heads, heads_key, heads)
417
cdef object _cache_heads(self, heads, heads_key, dom_lookup_key,
419
cdef PyObject *maybe_node
420
cdef _KnownGraphNode node
422
PyDict_SetItem(self._known_heads, heads_key, heads)
425
maybe_node = PyDict_GetItem(candidate_nodes, key)
426
if maybe_node == NULL:
428
node = <_KnownGraphNode>maybe_node
429
PyList_Append(dom_heads, node.linear_dominator_node.key)
430
PyDict_SetItem(self._known_heads, dom_lookup_key,
431
PyFrozenSet_New(dom_heads))
433
cdef _get_dominators_to_nodes(self, candidate_nodes):
434
"""Get the reverse mapping from dominator_key => candidate_nodes.
436
As a side effect, this can also remove potential candidate nodes if we
437
determine that they share a dominator.
440
cdef _KnownGraphNode node, other_node
441
cdef PyObject *temp_node
442
cdef PyObject *maybe_node
447
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
448
node = <_KnownGraphNode>temp_node
449
dom_key = node.linear_dominator_node.key
450
maybe_node = PyDict_GetItem(dom_to_node, dom_key)
451
if maybe_node == NULL:
452
PyDict_SetItem(dom_to_node, dom_key, node)
454
other_node = <_KnownGraphNode>maybe_node
455
# These nodes share a dominator, one of them obviously
456
# supersedes the other, figure out which
457
if other_node.gdfo > node.gdfo:
458
PyList_Append(keys_to_remove, node.key)
460
# This wins, replace the other
461
PyList_Append(keys_to_remove, other_node.key)
462
PyDict_SetItem(dom_to_node, dom_key, node)
463
for pos from 0 <= pos < PyList_GET_SIZE(keys_to_remove):
464
key = <object>PyList_GET_ITEM(keys_to_remove, pos)
465
candidate_nodes.pop(key)
468
cdef object _heads_from_dominators(self, candidate_nodes, dom_to_node):
469
cdef PyObject *maybe_heads
470
cdef PyObject *maybe_node
471
cdef _KnownGraphNode node
473
cdef PyObject *temp_node
477
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
478
node = <_KnownGraphNode>temp_node
479
PyList_Append(dom_list_key, node.linear_dominator_node.key)
480
dom_lookup_key = PyFrozenSet_New(dom_list_key)
481
maybe_heads = PyDict_GetItem(self._known_heads, dom_lookup_key)
482
if maybe_heads == NULL:
483
return dom_lookup_key, None
484
# We need to map back from the dominator head to the original keys
485
dom_heads = <object>maybe_heads
487
for dom_key in dom_heads:
488
maybe_node = PyDict_GetItem(dom_to_node, dom_key)
489
if maybe_node == NULL:
490
# Should never happen
492
node = <_KnownGraphNode>maybe_node
493
PyList_Append(heads, node.key)
494
return dom_lookup_key, PyFrozenSet_New(heads)
496
cdef int _process_parent(self, _KnownGraphNode node,
497
_KnownGraphNode parent_node,
498
candidate_nodes, dom_to_node,
499
queue, int *replace_item) except -1:
500
"""Process the parent of a node, seeing if we need to walk it."""
501
cdef PyObject *maybe_candidate
502
cdef PyObject *maybe_node
503
cdef _KnownGraphNode dom_child_node
504
maybe_candidate = PyDict_GetItem(candidate_nodes, parent_node.key)
505
if maybe_candidate != NULL:
506
candidate_nodes.pop(parent_node.key)
507
# We could pass up a flag that tells the caller to stop processing,
508
# but it doesn't help much, and makes the code uglier
510
maybe_node = PyDict_GetItem(dom_to_node, parent_node.key)
511
if maybe_node != NULL:
512
# This is a dominator of a node
513
dom_child_node = <_KnownGraphNode>maybe_node
514
if dom_child_node is not node:
515
# It isn't a dominator of a node we are searching, so we should
516
# remove it from the search
517
maybe_candidate = PyDict_GetItem(candidate_nodes, dom_child_node.key)
518
if maybe_candidate != NULL:
519
candidate_nodes.pop(dom_child_node.key)
521
if parent_node.ancestor_of is None:
522
# This node hasn't been walked yet, so just project node's ancestor
523
# info directly to parent_node, and enqueue it for later processing
524
parent_node.ancestor_of = node.ancestor_of
526
heapreplace(queue, (-parent_node.gdfo, parent_node))
529
heappush(queue, (-parent_node.gdfo, parent_node))
530
PyList_Append(self._to_cleanup, parent_node)
531
elif parent_node.ancestor_of != node.ancestor_of:
532
# Combine to get the full set of parents
533
# Rewrite using PySet_* functions, unfortunately you have to use
534
# PySet_Add since there is no PySet_Update... :(
535
all_ancestors = set(parent_node.ancestor_of)
536
for k in node.ancestor_of:
537
PySet_Add(all_ancestors, k)
538
parent_node.ancestor_of = tuple(sorted(all_ancestors))
541
cdef object _heads_from_candidate_nodes(self, candidate_nodes, dom_to_node):
542
cdef _KnownGraphNode node
543
cdef _KnownGraphNode parent_node
544
cdef Py_ssize_t num_candidates
545
cdef int num_parents, replace_item
547
cdef PyObject *temp_node
551
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
552
node = <_KnownGraphNode>temp_node
553
node.ancestor_of = (node.key,)
554
PyList_Append(queue, (-node.gdfo, node))
555
PyList_Append(self._to_cleanup, node)
557
# These are nodes that we determined are 'common' that we are no longer
559
# Now we walk nodes until all nodes that are being walked are 'common'
560
num_candidates = len(candidate_nodes)
562
while PyList_GET_SIZE(queue) > 0 and PyDict_Size(candidate_nodes) > 1:
564
# We still need to pop the smallest member out of the queue
565
# before we peek again
567
if PyList_GET_SIZE(queue) == 0:
569
# peek at the smallest item. We don't pop, because we expect we'll
570
# need to push more things into the queue anyway
571
node = _peek_node(queue)
573
if PyTuple_GET_SIZE(node.ancestor_of) == num_candidates:
574
# This node is now considered 'common'
575
# Make sure all parent nodes are marked as such
576
for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
577
parent_node = _get_parent(node.parents, pos)
578
if parent_node.ancestor_of is not None:
579
parent_node.ancestor_of = node.ancestor_of
580
if node.linear_dominator_node is not node:
581
parent_node = node.linear_dominator_node
582
if parent_node.ancestor_of is not None:
583
parent_node.ancestor_of = node.ancestor_of
585
if node.parents is None:
588
# Now project the current nodes ancestor list to the parent nodes,
589
# and queue them up to be walked
590
if node.linear_dominator_node is not node:
591
# We are at the tip of a long linear region
592
# We know that there is nothing between here and the tail
593
# that is interesting, so skip to the end
594
self._process_parent(node, node.linear_dominator_node,
595
candidate_nodes, dom_to_node, queue, &replace_item)
597
for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
598
parent_node = _get_parent(node.parents, pos)
599
self._process_parent(node, parent_node, candidate_nodes,
600
dom_to_node, queue, &replace_item)
601
for pos from 0 <= pos < PyList_GET_SIZE(self._to_cleanup):
602
node = _get_list_node(self._to_cleanup, pos)
603
node.ancestor_of = None
604
self._to_cleanup = []
605
return PyFrozenSet_New(candidate_nodes)