277
213
key = <object>temp_key
278
214
parent_keys = <object>temp_parent_keys
279
215
node = self._get_or_create_node(key)
280
self._populate_parents(node, parent_keys)
282
def _find_tails(self):
283
cdef PyObject *temp_node
284
cdef _KnownGraphNode node
216
# We know how many parents, so we could pre allocate an exact sized
218
num_parent_keys = len(parent_keys)
219
parent_nodes = PyTuple_New(num_parent_keys)
220
# We use iter here, because parent_keys maybe be a list or tuple
221
for pos2 from 0 <= pos2 < num_parent_keys:
222
parent_key = parent_keys[pos2]
223
parent_node = self._get_or_create_node(parent_keys[pos2])
224
# PyTuple_SET_ITEM will steal a reference, so INCREF first
225
Py_INCREF(parent_node)
226
PyTuple_SET_ITEM(parent_nodes, pos2, parent_node)
227
PyList_Append(parent_node.children, node)
228
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
289
306
while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
290
307
node = <_KnownGraphNode>temp_node
291
308
if node.parents is None or PyTuple_GET_SIZE(node.parents) == 0:
293
309
PyList_Append(tails, node)
296
def _find_tips(self):
297
cdef PyObject *temp_node
298
cdef _KnownGraphNode node
303
while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
304
node = <_KnownGraphNode>temp_node
305
if PyList_GET_SIZE(node.children) == 0:
306
PyList_Append(tips, node)
309
312
def _find_gdfo(self):
313
cdef Py_ssize_t pos, pos2
310
314
cdef _KnownGraphNode node
311
cdef _KnownGraphNode child
315
cdef Py_ssize_t last_item
318
pending = self._find_tails()
320
last_item = PyList_GET_SIZE(pending) - 1
321
while last_item >= 0:
322
# Avoid pop followed by push, instead, peek, and replace
323
# timing shows this is 930ms => 770ms for OOo
324
node = _get_list_node(pending, last_item)
325
last_item = last_item - 1
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)
326
328
next_gdfo = node.gdfo + 1
327
330
for pos from 0 <= pos < PyList_GET_SIZE(node.children):
328
child = _get_list_node(node.children, pos)
329
if next_gdfo > child.gdfo:
330
child.gdfo = next_gdfo
331
child.seen = child.seen + 1
332
if child.seen == PyTuple_GET_SIZE(child.parents):
333
# This child is populated, queue it to be walked
334
last_item = last_item + 1
335
if last_item < PyList_GET_SIZE(pending):
336
Py_INCREF(child) # SetItem steals a ref
337
PyList_SetItem(pending, last_item, child)
339
PyList_Append(pending, child)
340
# We have queued this node, we don't need to track it
344
def add_node(self, key, parent_keys):
345
"""Add a new node to the graph.
347
If this fills in a ghost, then the gdfos of all children will be
350
:param key: The node being added. If this is a duplicate, this is a
352
:param parent_keys: The parents of the given node.
353
:return: None (should we return if this was a ghost, etc?)
355
cdef PyObject *maybe_node
356
cdef _KnownGraphNode node, parent_node, child_node
357
cdef long parent_gdfo, next_gdfo
359
maybe_node = PyDict_GetItem(self._nodes, key)
360
if maybe_node != NULL:
361
node = <_KnownGraphNode>maybe_node
362
if node.parents is None:
363
# We are filling in a ghost
364
self._populate_parents(node, parent_keys)
365
# We can't trust cached heads anymore
366
self._known_heads.clear()
367
else: # Ensure that the parent_key list matches
368
existing_parent_keys = []
369
for parent_node in node.parents:
370
existing_parent_keys.append(parent_node.key)
371
# Make sure we use a list for the comparison, in case it was a
373
parent_keys = list(parent_keys)
374
if existing_parent_keys == parent_keys:
375
# Exact match, nothing more to do
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))
378
raise ValueError('Parent key mismatch, existing node %s'
379
' has parents of %s not %s'
380
% (key, existing_parent_keys, parent_keys))
382
node = _KnownGraphNode(key)
383
PyDict_SetItem(self._nodes, key, node)
384
self._populate_parents(node, parent_keys)
386
for parent_node in node.parents:
387
if parent_node.gdfo == -1:
388
# This is a newly introduced ghost, so it gets gdfo of 1
390
if parent_gdfo < parent_node.gdfo:
391
parent_gdfo = parent_node.gdfo
392
node.gdfo = parent_gdfo + 1
393
# Now fill the gdfo to all children
394
# Note that this loop is slightly inefficient, in that we may visit the
395
# same child (and its decendents) more than once, however, it is
396
# 'efficient' in that we only walk to nodes that would be updated,
397
# rather than all nodes
398
# We use a deque rather than a simple list stack, to go for BFD rather
399
# than DFD. So that if a longer path is possible, we walk it before we
400
# get to the final child
401
pending = deque([node])
402
pending_popleft = pending.popleft
403
pending_append = pending.append
405
node = pending_popleft()
406
next_gdfo = node.gdfo + 1
407
for child_node in node.children:
408
if child_node.gdfo < next_gdfo:
409
# This child is being updated, we need to check its
411
child_node.gdfo = next_gdfo
412
pending_append(child_node)
357
heappush(todo, (child_gdfo, child_node))
414
361
def heads(self, keys):
415
362
"""Return the heads from amongst keys.
428
376
cdef PyObject *maybe_node
429
377
cdef PyObject *maybe_heads
430
cdef PyObject *temp_node
431
cdef _KnownGraphNode node
432
cdef Py_ssize_t pos, last_item
435
heads_key = frozenset(keys)
379
heads_key = PyFrozenSet_New(keys)
436
380
maybe_heads = PyDict_GetItem(self._known_heads, heads_key)
437
381
if maybe_heads != NULL:
438
382
return <object>maybe_heads
439
384
# Not cached, compute it ourselves
440
385
candidate_nodes = {}
442
maybe_node = PyDict_GetItem(self._nodes, key)
388
maybe_node = PyDict_GetItem(nodes, key)
443
389
if maybe_node == NULL:
444
390
raise KeyError('key %s not in nodes' % (key,))
445
391
PyDict_SetItem(candidate_nodes, key, <object>maybe_node)
446
maybe_node = PyDict_GetItem(candidate_nodes, NULL_REVISION)
447
if maybe_node != NULL:
392
if revision.NULL_REVISION in candidate_nodes:
448
393
# NULL_REVISION is only a head if it is the only entry
449
candidate_nodes.pop(NULL_REVISION)
394
candidate_nodes.pop(revision.NULL_REVISION)
450
395
if not candidate_nodes:
451
return frozenset([NULL_REVISION])
396
return set([revision.NULL_REVISION])
452
397
# The keys changed, so recalculate heads_key
453
heads_key = frozenset(candidate_nodes)
398
heads_key = PyFrozenSet_New(candidate_nodes)
399
if len(candidate_nodes) < 2:
401
dom_to_node = self._get_dominators_to_nodes(candidate_nodes)
454
402
if PyDict_Size(candidate_nodes) < 2:
459
# we know a gdfo cannot be longer than a linear chain of all nodes
460
min_gdfo = PyDict_Size(self._nodes) + 1
461
# Build up nodes that need to be walked, note that starting nodes are
462
# not added to seen()
464
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
465
node = <_KnownGraphNode>temp_node
466
if node.parents is not None:
467
pending.extend(node.parents)
468
if node.gdfo < min_gdfo:
471
# Now do all the real work
472
last_item = PyList_GET_SIZE(pending) - 1
473
while last_item >= 0:
474
node = _get_list_node(pending, last_item)
475
last_item = last_item - 1
477
# node already appears in some ancestry
479
PyList_Append(cleanup, node)
481
if node.gdfo <= min_gdfo:
483
if node.parents is not None and PyTuple_GET_SIZE(node.parents) > 0:
484
for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
485
parent_node = _get_tuple_node(node.parents, pos)
486
last_item = last_item + 1
487
if last_item < PyList_GET_SIZE(pending):
488
Py_INCREF(parent_node) # SetItem steals a ref
489
PyList_SetItem(pending, last_item, parent_node)
491
PyList_Append(pending, parent_node)
494
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
495
node = <_KnownGraphNode>temp_node
497
PyList_Append(heads, node.key)
498
heads = frozenset(heads)
499
for pos from 0 <= pos < PyList_GET_SIZE(cleanup):
500
node = _get_list_node(cleanup, pos)
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)
502
413
if self.do_cache:
503
PyDict_SetItem(self._known_heads, heads_key, heads)
414
self._cache_heads(heads, heads_key, dom_lookup_key, candidate_nodes)
507
"""Return the nodes in topological order.
509
All parents must occur before all children.
511
# This is, for the most part, the same iteration order that we used for
512
# _find_gdfo, consider finding a way to remove the duplication
513
# In general, we find the 'tails' (nodes with no parents), and then
514
# walk to the children. For children that have all of their parents
515
# yielded, we queue up the child to be yielded as well.
516
cdef _KnownGraphNode node
517
cdef _KnownGraphNode child
521
cdef Py_ssize_t last_item
523
pending = self._find_tails()
524
if PyList_GET_SIZE(pending) == 0 and len(self._nodes) > 0:
525
raise errors.GraphCycleError(self._nodes)
529
last_item = PyList_GET_SIZE(pending) - 1
530
while last_item >= 0:
531
# Avoid pop followed by push, instead, peek, and replace
532
# timing shows this is 930ms => 770ms for OOo
533
node = _get_list_node(pending, last_item)
534
last_item = last_item - 1
535
if node.parents is not None:
536
# We don't include ghost parents
537
PyList_Append(topo_order, node.key)
538
for pos from 0 <= pos < PyList_GET_SIZE(node.children):
539
child = _get_list_node(node.children, pos)
541
# We know we have a graph cycle because a node has a parent
542
# which we couldn't find
543
raise errors.GraphCycleError(self._nodes)
544
child.seen = child.seen + 1
545
if child.seen == PyTuple_GET_SIZE(child.parents):
546
# All parents of this child have been yielded, queue this
547
# one to be yielded as well
548
last_item = last_item + 1
549
if last_item < PyList_GET_SIZE(pending):
550
Py_INCREF(child) # SetItem steals a ref
551
PyList_SetItem(pending, last_item, child)
553
PyList_Append(pending, child)
554
# We have queued this node, we don't need to track it
557
# We started from the parents, so we don't need to do anymore work
561
"""Return a reverse topological ordering which is 'stable'.
563
There are a few constraints:
564
1) Reverse topological (all children before all parents)
566
3) 'stable' sorting, so that we get the same result, independent of
567
machine, or extra data.
568
To do this, we use the same basic algorithm as topo_sort, but when we
569
aren't sure what node to access next, we sort them lexicographically.
572
cdef Py_ssize_t pos, last_item
573
cdef _KnownGraphNode node, node2, parent_node
575
tips = self._find_tips()
576
# Split the tips based on prefix
578
for pos from 0 <= pos < PyList_GET_SIZE(tips):
579
node = _get_list_node(tips, pos)
580
if PyString_CheckExact(node.key) or len(node.key) == 1:
584
temp = PyDict_GetItem(prefix_tips, prefix)
586
prefix_tips[prefix] = [node]
588
tip_nodes = <object>temp
589
PyList_Append(tip_nodes, node)
592
for prefix in sorted(prefix_tips):
593
temp = PyDict_GetItem(prefix_tips, prefix)
595
tip_nodes = <object>temp
596
pending = _sort_list_nodes(tip_nodes, 1)
597
last_item = PyList_GET_SIZE(pending) - 1
598
while last_item >= 0:
599
node = _get_list_node(pending, last_item)
600
last_item = last_item - 1
601
if node.parents is None:
604
PyList_Append(result, node.key)
605
# Sorting the parent keys isn't strictly necessary for stable
606
# sorting of a given graph. But it does help minimize the
607
# differences between graphs
608
# For bzr.dev ancestry:
610
# 7.73ms RichCompareBool sort
611
parents = _sort_list_nodes(node.parents, 1)
612
for pos from 0 <= pos < len(parents):
613
if PyTuple_CheckExact(parents):
614
parent_node = _get_tuple_node(parents, pos)
616
parent_node = _get_list_node(parents, pos)
617
# TODO: GraphCycle detection
618
parent_node.seen = parent_node.seen + 1
620
== PyList_GET_SIZE(parent_node.children)):
621
# All children have been processed, queue up this
623
last_item = last_item + 1
624
if last_item < PyList_GET_SIZE(pending):
625
Py_INCREF(parent_node) # SetItem steals a ref
626
PyList_SetItem(pending, last_item, parent_node)
628
PyList_Append(pending, parent_node)
632
def merge_sort(self, tip_key):
633
"""Compute the merge sorted graph output."""
634
cdef _MergeSorter sorter
636
# TODO: consider disabling gc since we are allocating a lot of nodes
637
# that won't be collectable anyway. real world testing has not
638
# shown a specific impact, yet.
639
sorter = _MergeSorter(self, tip_key)
640
return sorter.topo_order()
642
def get_parent_keys(self, key):
643
"""Get the parents for a key
645
Returns a list containg the parents keys. If the key is a ghost,
646
None is returned. A KeyError will be raised if the key is not in
649
:param keys: Key to check (eg revision_id)
650
:return: A list of parents
652
return self._nodes[key].parent_keys
654
def get_child_keys(self, key):
655
"""Get the children for a key
657
Returns a list containg the children keys. A KeyError will be raised
658
if the key is not in the graph.
660
:param keys: Key to check (eg revision_id)
661
:return: A list of children
663
return self._nodes[key].child_keys
666
cdef class _MergeSortNode:
667
"""Tracks information about a node during the merge_sort operation."""
670
cdef public object key
671
cdef public long merge_depth
672
cdef public object end_of_merge # True/False Is this the end of the current merge
674
# Private api, used while computing the information
675
cdef _KnownGraphNode left_parent
676
cdef _KnownGraphNode left_pending_parent
677
cdef object pending_parents # list of _KnownGraphNode for non-left parents
678
cdef long _revno_first
679
cdef long _revno_second
680
cdef long _revno_last
681
# TODO: turn these into flag/bit fields rather than individual members
682
cdef int is_first_child # Is this the first child?
683
cdef int seen_by_child # A child node has seen this parent
684
cdef int completed # Fully Processed
686
def __init__(self, key):
688
self.merge_depth = -1
689
self.left_parent = None
690
self.left_pending_parent = None
691
self.pending_parents = None
692
self._revno_first = -1
693
self._revno_second = -1
694
self._revno_last = -1
695
self.is_first_child = 0
696
self.seen_by_child = 0
700
return '%s(%s depth:%s rev:%s,%s,%s first:%s seen:%s)' % (
701
self.__class__.__name__, self.key,
703
self._revno_first, self._revno_second, self._revno_last,
704
self.is_first_child, self.seen_by_child)
706
cdef int has_pending_parents(self): # cannot_raise
707
if self.left_pending_parent is not None or self.pending_parents:
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))
711
cdef object _revno(self):
712
if self._revno_first == -1:
713
if self._revno_second != -1:
714
raise RuntimeError('Something wrong with: %s' % (self,))
715
return (self._revno_last,)
717
return (self._revno_first, self._revno_second, self._revno_last)
724
cdef class _MergeSorter:
725
"""This class does the work of computing the merge_sort ordering.
727
We have some small advantages, in that we get all the extra information
728
that KnownGraph knows, like knowing the child lists, etc.
731
# Current performance numbers for merge_sort(bzr_dev_parent_map):
732
# 302ms tsort.merge_sort()
733
# 91ms graph.KnownGraph().merge_sort()
734
# 40ms kg.merge_sort()
736
cdef KnownGraph graph
737
cdef object _depth_first_stack # list
738
cdef Py_ssize_t _last_stack_item # offset to last item on stack
739
# cdef object _ms_nodes # dict of key => _MergeSortNode
740
cdef object _revno_to_branch_count # {revno => num child branches}
741
cdef object _scheduled_nodes # List of nodes ready to be yielded
743
def __init__(self, known_graph, tip_key):
541
cdef object _heads_from_candidate_nodes(self, candidate_nodes, dom_to_node):
744
542
cdef _KnownGraphNode node
746
self.graph = known_graph
747
# self._ms_nodes = {}
748
self._revno_to_branch_count = {}
749
self._depth_first_stack = []
750
self._last_stack_item = -1
751
self._scheduled_nodes = []
752
if (tip_key is not None and tip_key != NULL_REVISION
753
and tip_key != (NULL_REVISION,)):
754
node = self.graph._nodes[tip_key]
755
self._push_node(node, 0)
757
cdef _MergeSortNode _get_ms_node(self, _KnownGraphNode node):
543
cdef _KnownGraphNode parent_node
544
cdef Py_ssize_t num_candidates
545
cdef int num_parents, replace_item
758
547
cdef PyObject *temp_node
759
cdef _MergeSortNode ms_node
761
if node.extra is None:
762
ms_node = _MergeSortNode(node.key)
765
ms_node = <_MergeSortNode>node.extra
768
cdef _push_node(self, _KnownGraphNode node, long merge_depth):
769
cdef _KnownGraphNode parent_node
770
cdef _MergeSortNode ms_node, ms_parent_node
773
ms_node = self._get_ms_node(node)
774
ms_node.merge_depth = merge_depth
775
if node.parents is None:
776
raise RuntimeError('ghost nodes should not be pushed'
777
' onto the stack: %s' % (node,))
778
if PyTuple_GET_SIZE(node.parents) > 0:
779
parent_node = _get_tuple_node(node.parents, 0)
780
ms_node.left_parent = parent_node
781
if parent_node.parents is None: # left-hand ghost
782
ms_node.left_pending_parent = None
783
ms_node.left_parent = None
785
ms_node.left_pending_parent = parent_node
786
if PyTuple_GET_SIZE(node.parents) > 1:
787
ms_node.pending_parents = []
788
for pos from 1 <= pos < PyTuple_GET_SIZE(node.parents):
789
parent_node = _get_tuple_node(node.parents, pos)
790
if parent_node.parents is None: # ghost
792
PyList_Append(ms_node.pending_parents, parent_node)
794
ms_node.is_first_child = 1
795
if ms_node.left_parent is not None:
796
ms_parent_node = self._get_ms_node(ms_node.left_parent)
797
if ms_parent_node.seen_by_child:
798
ms_node.is_first_child = 0
799
ms_parent_node.seen_by_child = 1
800
self._last_stack_item = self._last_stack_item + 1
801
if self._last_stack_item < PyList_GET_SIZE(self._depth_first_stack):
802
Py_INCREF(node) # SetItem steals a ref
803
PyList_SetItem(self._depth_first_stack, self._last_stack_item,
806
PyList_Append(self._depth_first_stack, node)
808
cdef _pop_node(self):
810
cdef _MergeSortNode ms_node, ms_parent_node, ms_prev_node
811
cdef _KnownGraphNode node, parent_node, prev_node
813
node = _get_list_node(self._depth_first_stack, self._last_stack_item)
814
ms_node = <_MergeSortNode>node.extra
815
self._last_stack_item = self._last_stack_item - 1
816
if ms_node.left_parent is not None:
817
# Assign the revision number from the left-hand parent
818
ms_parent_node = <_MergeSortNode>ms_node.left_parent.extra
819
if ms_node.is_first_child:
820
# First child just increments the final digit
821
ms_node._revno_first = ms_parent_node._revno_first
822
ms_node._revno_second = ms_parent_node._revno_second
823
ms_node._revno_last = ms_parent_node._revno_last + 1
825
# Not the first child, make a new branch
826
# (mainline_revno, branch_count, 1)
827
if ms_parent_node._revno_first == -1:
828
# Mainline ancestor, the increment is on the last digit
829
base_revno = ms_parent_node._revno_last
831
base_revno = ms_parent_node._revno_first
832
temp = PyDict_GetItem(self._revno_to_branch_count,
837
branch_count = (<object>temp) + 1
838
PyDict_SetItem(self._revno_to_branch_count, base_revno,
840
ms_node._revno_first = base_revno
841
ms_node._revno_second = branch_count
842
ms_node._revno_last = 1
844
temp = PyDict_GetItem(self._revno_to_branch_count, 0)
846
# The first root node doesn't have a 3-digit revno
848
ms_node._revno_first = -1
849
ms_node._revno_second = -1
850
ms_node._revno_last = 1
852
root_count = (<object>temp) + 1
853
ms_node._revno_first = 0
854
ms_node._revno_second = root_count
855
ms_node._revno_last = 1
856
PyDict_SetItem(self._revno_to_branch_count, 0, root_count)
857
ms_node.completed = 1
858
if PyList_GET_SIZE(self._scheduled_nodes) == 0:
859
# The first scheduled node is always the end of merge
860
ms_node.end_of_merge = True
862
prev_node = _get_list_node(self._scheduled_nodes,
863
PyList_GET_SIZE(self._scheduled_nodes) - 1)
864
ms_prev_node = <_MergeSortNode>prev_node.extra
865
if ms_prev_node.merge_depth < ms_node.merge_depth:
866
# The previously pushed node is to our left, so this is the end
867
# of this right-hand chain
868
ms_node.end_of_merge = True
869
elif (ms_prev_node.merge_depth == ms_node.merge_depth
870
and prev_node not in node.parents):
871
# The next node is not a direct parent of this node
872
ms_node.end_of_merge = True
874
ms_node.end_of_merge = False
875
PyList_Append(self._scheduled_nodes, node)
877
cdef _schedule_stack(self):
878
cdef _KnownGraphNode last_node, next_node
879
cdef _MergeSortNode ms_node, ms_last_node, ms_next_node
880
cdef long next_merge_depth
882
while self._last_stack_item >= 0:
883
# Peek at the last item on the stack
884
last_node = _get_list_node(self._depth_first_stack,
885
self._last_stack_item)
886
if last_node.gdfo == -1:
887
# if _find_gdfo skipped a node, that means there is a graph
888
# cycle, error out now
889
raise errors.GraphCycleError(self.graph._nodes)
890
ms_last_node = <_MergeSortNode>last_node.extra
891
if not ms_last_node.has_pending_parents():
892
# Processed all parents, pop this node
895
while ms_last_node.has_pending_parents():
896
if ms_last_node.left_pending_parent is not None:
897
# recurse depth first into the primary parent
898
next_node = ms_last_node.left_pending_parent
899
ms_last_node.left_pending_parent = None
901
# place any merges in right-to-left order for scheduling
902
# which gives us left-to-right order after we reverse
903
# the scheduled queue.
904
# Note: This has the effect of allocating common-new
905
# revisions to the right-most subtree rather than the
906
# left most, which will display nicely (you get
907
# smaller trees at the top of the combined merge).
908
next_node = ms_last_node.pending_parents.pop()
909
ms_next_node = self._get_ms_node(next_node)
910
if ms_next_node.completed:
911
# this parent was completed by a child on the
912
# call stack. skip it.
914
# otherwise transfer it from the source graph into the
915
# top of the current depth first search stack.
917
if next_node is ms_last_node.left_parent:
918
next_merge_depth = ms_last_node.merge_depth
920
next_merge_depth = ms_last_node.merge_depth + 1
921
self._push_node(next_node, next_merge_depth)
922
# and do not continue processing parents until this 'call'
926
cdef topo_order(self):
927
cdef _MergeSortNode ms_node
928
cdef _KnownGraphNode node
930
cdef PyObject *temp_key, *temp_node
932
# Note: allocating a _MergeSortNode and deallocating it for all nodes
933
# costs approx 8.52ms (21%) of the total runtime
934
# We might consider moving the attributes into the base
936
self._schedule_stack()
938
# We've set up the basic schedule, now we can continue processing the
940
# Note: This final loop costs us 40.0ms => 28.8ms (11ms, 25%) on
941
# bzr.dev, to convert the internal Object representation into a
942
# Tuple representation...
943
# 2ms is walking the data and computing revno tuples
944
# 7ms is computing the return tuple
945
# 4ms is PyList_Append()
947
# output the result in reverse order, and separate the generated info
948
for pos from PyList_GET_SIZE(self._scheduled_nodes) > pos >= 0:
949
node = _get_list_node(self._scheduled_nodes, pos)
950
ms_node = <_MergeSortNode>node.extra
951
PyList_Append(ordered, ms_node)
953
# Clear out the scheduled nodes now that we're done
954
self._scheduled_nodes = []
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)