243
187
PyList_Append(parent_node.children, node)
244
188
node.parents = parent_nodes
246
cdef _KnownGraphNode _check_is_linear(self, _KnownGraphNode node):
247
"""Check to see if a given node is part of a linear chain."""
248
cdef _KnownGraphNode parent_node
249
if node.parents is None or PyTuple_GET_SIZE(node.parents) != 1:
250
# This node is either a ghost, a tail, or has multiple parents
251
# It its own dominator
252
node.linear_dominator_node = node
254
parent_node = _get_parent(node.parents, 0)
255
if PyList_GET_SIZE(parent_node.children) > 1:
256
# The parent has multiple children, so *this* node is the
258
node.linear_dominator_node = node
260
# The parent is already filled in, so add and continue
261
if parent_node.linear_dominator_node is not None:
262
node.linear_dominator_node = parent_node.linear_dominator_node
264
# We don't know this node, or its parent node, so start walking to
268
def _find_linear_dominators(self):
270
For any given node, the 'linear dominator' is an ancestor, such that
271
all parents between this node and that one have a single parent, and a
272
single child. So if A->B->C->D then B,C,D all have a linear dominator
275
There are two main benefits:
276
1) When walking the graph, we can jump to the nearest linear dominator,
277
rather than walking all of the nodes inbetween.
278
2) When caching heads() results, dominators give the "same" results as
279
their children. (If the dominator is a head, then the descendant is
280
a head, if the dominator is not a head, then the child isn't
283
cdef PyObject *temp_node
285
cdef _KnownGraphNode node
286
cdef _KnownGraphNode next_node
287
cdef _KnownGraphNode dominator
288
cdef int i, num_elements
291
while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
292
node = <_KnownGraphNode>temp_node
293
# The parent is not filled in, so walk until we get somewhere
294
if node.linear_dominator_node is not None: #already done
296
next_node = self._check_is_linear(node)
297
if next_node is None:
298
# Nothing more needs to be done
301
while next_node is not None:
302
PyList_Append(stack, node)
304
next_node = self._check_is_linear(node)
305
# The stack now contains the linear chain, and 'node' should have
307
dominator = node.linear_dominator_node
308
num_elements = len(stack)
309
for i from num_elements > i >= 0:
310
next_node = _get_list_node(stack, i)
311
next_node.linear_dominator_node = dominator
314
cdef object _find_tails(self):
316
cdef PyObject *temp_node
318
cdef _KnownGraphNode node
322
while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
323
node = <_KnownGraphNode>temp_node
324
if node.parents is None or PyTuple_GET_SIZE(node.parents) == 0:
325
PyList_Append(tails, node)
328
190
def _find_gdfo(self):
329
191
cdef _KnownGraphNode node
330
192
cdef _KnownGraphNode child
469
282
if self.do_cache:
470
283
self._known_heads[heads_key] = heads
473
def xheads(self, keys):
474
"""Return the heads from amongst keys.
476
This is done by searching the ancestries of each key. Any key that is
477
reachable from another key is not returned; all the others are.
479
This operation scales with the relative depth between any two keys. If
480
any two keys are completely disconnected all ancestry of both sides
483
:param keys: An iterable of keys.
484
:return: A set of the heads. Note that as a set there is no ordering
485
information. Callers will need to filter their input to create
486
order if they need it.
488
cdef PyObject *maybe_node
489
cdef PyObject *maybe_heads
491
heads_key = PyFrozenSet_New(keys)
492
maybe_heads = PyDict_GetItem(self._known_heads, heads_key)
493
if maybe_heads != NULL:
494
return <object>maybe_heads
496
# Not cached, compute it ourselves
500
maybe_node = PyDict_GetItem(nodes, key)
501
if maybe_node == NULL:
502
raise KeyError('key %s not in nodes' % (key,))
503
PyDict_SetItem(candidate_nodes, key, <object>maybe_node)
504
if revision.NULL_REVISION in candidate_nodes:
505
# NULL_REVISION is only a head if it is the only entry
506
candidate_nodes.pop(revision.NULL_REVISION)
507
if not candidate_nodes:
508
return set([revision.NULL_REVISION])
509
# The keys changed, so recalculate heads_key
510
heads_key = PyFrozenSet_New(candidate_nodes)
511
if len(candidate_nodes) < 2:
513
dom_to_node = self._get_dominators_to_nodes(candidate_nodes)
514
if PyDict_Size(candidate_nodes) < 2:
515
return frozenset(candidate_nodes)
516
dom_lookup_key, heads = self._heads_from_dominators(candidate_nodes,
518
if heads is not None:
520
# This heads was not in the cache, or it would have been caught
521
# earlier, but the dom head *was*, so do the simple cache
522
PyDict_SetItem(self._known_heads, heads_key, heads)
524
heads = self._heads_from_candidate_nodes(candidate_nodes, dom_to_node)
526
self._cache_heads(heads, heads_key, dom_lookup_key, candidate_nodes)
529
cdef object _cache_heads(self, heads, heads_key, dom_lookup_key,
531
cdef PyObject *maybe_node
532
cdef _KnownGraphNode node
534
PyDict_SetItem(self._known_heads, heads_key, heads)
537
maybe_node = PyDict_GetItem(candidate_nodes, key)
538
if maybe_node == NULL:
540
node = <_KnownGraphNode>maybe_node
541
PyList_Append(dom_heads, node.linear_dominator_node.key)
542
PyDict_SetItem(self._known_heads, dom_lookup_key,
543
PyFrozenSet_New(dom_heads))
545
cdef _get_dominators_to_nodes(self, candidate_nodes):
546
"""Get the reverse mapping from dominator_key => candidate_nodes.
548
As a side effect, this can also remove potential candidate nodes if we
549
determine that they share a dominator.
552
cdef _KnownGraphNode node, other_node
553
cdef PyObject *temp_node
554
cdef PyObject *maybe_node
559
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
560
node = <_KnownGraphNode>temp_node
561
dom_key = node.linear_dominator_node.key
562
maybe_node = PyDict_GetItem(dom_to_node, dom_key)
563
if maybe_node == NULL:
564
PyDict_SetItem(dom_to_node, dom_key, node)
566
other_node = <_KnownGraphNode>maybe_node
567
# These nodes share a dominator, one of them obviously
568
# supersedes the other, figure out which
569
if other_node.gdfo > node.gdfo:
570
PyList_Append(keys_to_remove, node.key)
572
# This wins, replace the other
573
PyList_Append(keys_to_remove, other_node.key)
574
PyDict_SetItem(dom_to_node, dom_key, node)
575
for pos from 0 <= pos < PyList_GET_SIZE(keys_to_remove):
576
key = <object>PyList_GET_ITEM(keys_to_remove, pos)
577
candidate_nodes.pop(key)
580
cdef object _heads_from_dominators(self, candidate_nodes, dom_to_node):
581
cdef PyObject *maybe_heads
582
cdef PyObject *maybe_node
583
cdef _KnownGraphNode node
585
cdef PyObject *temp_node
589
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
590
node = <_KnownGraphNode>temp_node
591
PyList_Append(dom_list_key, node.linear_dominator_node.key)
592
dom_lookup_key = PyFrozenSet_New(dom_list_key)
593
maybe_heads = PyDict_GetItem(self._known_heads, dom_lookup_key)
594
if maybe_heads == NULL:
595
return dom_lookup_key, None
596
# We need to map back from the dominator head to the original keys
597
dom_heads = <object>maybe_heads
599
for dom_key in dom_heads:
600
maybe_node = PyDict_GetItem(dom_to_node, dom_key)
601
if maybe_node == NULL:
602
# Should never happen
604
node = <_KnownGraphNode>maybe_node
605
PyList_Append(heads, node.key)
606
return dom_lookup_key, PyFrozenSet_New(heads)
608
cdef int _process_parent(self, _KnownGraphNode node,
609
_KnownGraphNode parent_node,
610
candidate_nodes, dom_to_node,
611
queue, int *replace_item, min_gdfo) except -1:
612
"""Process the parent of a node, seeing if we need to walk it."""
613
cdef PyObject *maybe_candidate
614
cdef PyObject *maybe_node
615
cdef _KnownGraphNode dom_child_node
616
maybe_candidate = PyDict_GetItem(candidate_nodes, parent_node.key)
617
if maybe_candidate != NULL:
618
candidate_nodes.pop(parent_node.key)
619
# We could pass up a flag that tells the caller to stop processing,
620
# but it doesn't help much, and makes the code uglier
622
maybe_node = PyDict_GetItem(dom_to_node, parent_node.key)
623
if maybe_node != NULL:
624
# This is a dominator of a node
625
dom_child_node = <_KnownGraphNode>maybe_node
626
if dom_child_node is not node:
627
# It isn't a dominator of a node we are searching, so we should
628
# remove it from the search
629
maybe_candidate = PyDict_GetItem(candidate_nodes, dom_child_node.key)
630
if maybe_candidate != NULL:
631
candidate_nodes.pop(dom_child_node.key)
633
if parent_node.gdfo < min_gdfo:
634
# Do not enque this node, it is too old
636
if parent_node.ancestor_of is None:
637
# This node hasn't been walked yet, so just project node's ancestor
638
# info directly to parent_node, and enqueue it for later processing
639
parent_node.ancestor_of = node.ancestor_of
641
heapreplace(queue, (-parent_node.gdfo, parent_node))
644
heappush(queue, (-parent_node.gdfo, parent_node))
645
PyList_Append(self._to_cleanup, parent_node)
646
elif parent_node.ancestor_of != node.ancestor_of:
647
# Combine to get the full set of parents
648
# Rewrite using PySet_* functions, unfortunately you have to use
649
# PySet_Add since there is no PySet_Update... :(
650
all_ancestors = set(parent_node.ancestor_of)
651
for k in node.ancestor_of:
652
PySet_Add(all_ancestors, k)
653
parent_node.ancestor_of = tuple(sorted(all_ancestors))
656
cdef object _heads_from_candidate_nodes(self, candidate_nodes, dom_to_node):
657
cdef _KnownGraphNode node
658
cdef _KnownGraphNode parent_node
659
cdef Py_ssize_t num_candidates
660
cdef int num_parents, replace_item
662
cdef PyObject *temp_node
667
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
668
node = <_KnownGraphNode>temp_node
669
node.ancestor_of = (node.key,)
670
PyList_Append(queue, (-node.gdfo, node))
671
PyList_Append(self._to_cleanup, node)
674
elif node.gdfo < min_gdfo:
677
# These are nodes that we determined are 'common' that we are no longer
679
# Now we walk nodes until all nodes that are being walked are 'common'
680
num_candidates = len(candidate_nodes)
682
while PyList_GET_SIZE(queue) > 0 and PyDict_Size(candidate_nodes) > 1:
684
# We still need to pop the smallest member out of the queue
685
# before we peek again
687
if PyList_GET_SIZE(queue) == 0:
689
# peek at the smallest item. We don't pop, because we expect we'll
690
# need to push more things into the queue anyway
691
node = _peek_node(queue)
693
if PyTuple_GET_SIZE(node.ancestor_of) == num_candidates:
694
# This node is now considered 'common'
695
# Make sure all parent nodes are marked as such
696
for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
697
parent_node = _get_parent(node.parents, pos)
698
if parent_node.ancestor_of is not None:
699
parent_node.ancestor_of = node.ancestor_of
700
if node.linear_dominator_node is not node:
701
parent_node = node.linear_dominator_node
702
if parent_node.ancestor_of is not None:
703
parent_node.ancestor_of = node.ancestor_of
705
if node.parents is None:
708
# Now project the current nodes ancestor list to the parent nodes,
709
# and queue them up to be walked
710
if node.linear_dominator_node is not node:
711
# We are at the tip of a long linear region
712
# We know that there is nothing between here and the tail
713
# that is interesting, so skip to the end
714
self._process_parent(node, node.linear_dominator_node,
715
candidate_nodes, dom_to_node, queue,
716
&replace_item, min_gdfo)
718
for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
719
parent_node = _get_parent(node.parents, pos)
720
self._process_parent(node, parent_node, candidate_nodes,
721
dom_to_node, queue, &replace_item,
723
for pos from 0 <= pos < PyList_GET_SIZE(self._to_cleanup):
724
node = _get_list_node(self._to_cleanup, pos)
725
node.ancestor_of = None
726
self._to_cleanup = []
727
return PyFrozenSet_New(candidate_nodes)