277
216
key = <object>temp_key
278
217
parent_keys = <object>temp_parent_keys
279
218
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
219
assert node.parents is None
220
# We know how many parents, so we could pre allocate an exact sized
222
num_parent_keys = len(parent_keys)
223
parent_nodes = PyTuple_New(num_parent_keys)
224
# We use iter here, because parent_keys maybe be a list or tuple
225
for pos2 from 0 <= pos2 < num_parent_keys:
226
parent_key = parent_keys[pos2]
227
parent_node = self._get_or_create_node(parent_keys[pos2])
228
# PyTuple_SET_ITEM will steal a reference, so INCREF first
229
Py_INCREF(parent_node)
230
PyTuple_SET_ITEM(parent_nodes, pos2, parent_node)
231
PyList_Append(parent_node.children, node)
232
node.parents = parent_nodes
234
cdef _KnownGraphNode _check_is_linear(self, _KnownGraphNode node):
235
"""Check to see if a given node is part of a linear chain."""
236
cdef _KnownGraphNode parent_node
237
if node.parents is None or PyTuple_GET_SIZE(node.parents) != 1:
238
# This node is either a ghost, a tail, or has multiple parents
239
# It its own dominator
240
node.linear_dominator_node = node
241
node.dominator_distance = 0
243
parent_node = _get_parent(node.parents, 0)
244
if PyList_GET_SIZE(parent_node.children) > 1:
245
# The parent has multiple children, so *this* node is the
247
node.linear_dominator_node = node
248
node.dominator_distance = 0
250
# The parent is already filled in, so add and continue
251
if parent_node.linear_dominator_node is not None:
252
node.linear_dominator_node = parent_node.linear_dominator_node
253
node.dominator_distance = parent_node.dominator_distance + 1
255
# We don't know this node, or its parent node, so start walking to
259
def _find_linear_dominators(self):
260
"""For each node in the set, find any linear dominators.
262
For any given node, the 'linear dominator' is an ancestor, such that
263
all parents between this node and that one have a single parent, and a
264
single child. So if A->B->C->D then B,C,D all have a linear dominator
265
of A. Because there are no interesting siblings, we can quickly skip to
266
the nearest dominator when doing comparisons.
268
cdef PyObject *temp_node
270
cdef _KnownGraphNode node
271
cdef _KnownGraphNode next_node
272
cdef _KnownGraphNode dominator
273
cdef int i, num_elements
276
while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
277
node = <_KnownGraphNode>temp_node
278
# The parent is not filled in, so walk until we get somewhere
279
if node.linear_dominator_node is not None: #already done
281
next_node = self._check_is_linear(node)
282
if next_node is None:
283
# Nothing more needs to be done
286
while next_node is not None:
287
PyList_Append(stack, node)
289
next_node = self._check_is_linear(node)
290
# The stack now contains the linear chain, and 'node' should have
292
assert node.linear_dominator_node is not None
293
dominator = node.linear_dominator_node
294
num_elements = len(stack)
295
for i from num_elements > i >= 0:
296
next_node = _get_list_node(stack, i)
297
next_node.linear_dominator_node = dominator
298
next_node.dominator_distance = node.dominator_distance + 1
301
cdef object _find_tails(self):
303
cdef PyObject *temp_node
305
cdef _KnownGraphNode node
289
309
while PyDict_Next(self._nodes, &pos, NULL, &temp_node):
290
310
node = <_KnownGraphNode>temp_node
291
311
if node.parents is None or PyTuple_GET_SIZE(node.parents) == 0:
293
312
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
315
def _find_gdfo(self):
316
cdef Py_ssize_t pos, pos2
310
317
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
318
cdef _KnownGraphNode child_node
319
cdef _KnownGraphNode parent_node
320
cdef int replace_node, missing_parent
322
tails = self._find_tails()
324
for pos from 0 <= pos < PyList_GET_SIZE(tails):
325
node = _get_list_node(tails, pos)
327
PyList_Append(todo, (1, node))
328
# No need to heapify, because all tails have priority=1
329
max_gdfo = len(self._nodes) + 1
330
while PyList_GET_SIZE(todo) > 0:
331
node = _peek_node(todo)
326
332
next_gdfo = node.gdfo + 1
333
assert next_gdfo <= max_gdfo
327
335
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
336
child_node = _get_list_node(node.children, pos)
337
# We should never have numbered children before we numbered
339
if child_node.gdfo != -1:
340
assert child_node.gdfo >= next_gdfo
342
# Only enque children when all of their parents have been
343
# resolved. With a single parent, we can just take 'this' value
344
child_gdfo = next_gdfo
345
if PyTuple_GET_SIZE(child_node.parents) > 1:
347
for pos2 from 0 <= pos2 < PyTuple_GET_SIZE(child_node.parents):
348
parent_node = _get_parent(child_node.parents, pos2)
349
if parent_node.gdfo == -1:
352
if parent_node.gdfo >= child_gdfo:
353
child_gdfo = parent_node.gdfo + 1
355
# One of the parents is not numbered, so wait until we get
358
child_node.gdfo = child_gdfo
360
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)
363
heappush(todo, (child_gdfo, child_node))
414
367
def heads(self, keys):
415
368
"""Return the heads from amongst keys.
428
382
cdef PyObject *maybe_node
429
383
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)
385
heads_key = PyFrozenSet_New(keys)
436
386
maybe_heads = PyDict_GetItem(self._known_heads, heads_key)
437
387
if maybe_heads != NULL:
438
388
return <object>maybe_heads
439
390
# Not cached, compute it ourselves
440
391
candidate_nodes = {}
442
maybe_node = PyDict_GetItem(self._nodes, key)
394
maybe_node = PyDict_GetItem(nodes, key)
443
395
if maybe_node == NULL:
444
396
raise KeyError('key %s not in nodes' % (key,))
445
397
PyDict_SetItem(candidate_nodes, key, <object>maybe_node)
446
maybe_node = PyDict_GetItem(candidate_nodes, NULL_REVISION)
447
if maybe_node != NULL:
398
if revision.NULL_REVISION in candidate_nodes:
448
399
# NULL_REVISION is only a head if it is the only entry
449
candidate_nodes.pop(NULL_REVISION)
400
candidate_nodes.pop(revision.NULL_REVISION)
450
401
if not candidate_nodes:
451
return frozenset([NULL_REVISION])
402
return set([revision.NULL_REVISION])
452
403
# The keys changed, so recalculate heads_key
453
heads_key = frozenset(candidate_nodes)
454
if PyDict_Size(candidate_nodes) < 2:
404
heads_key = PyFrozenSet_New(candidate_nodes)
405
if len(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)
407
# Check the linear dominators of these keys, to see if we already
408
# know the heads answer
409
dom_lookup_key, heads = self._heads_from_dominators(candidate_nodes)
410
if heads is not None:
412
# This heads was not in the cache, or it would have been caught
413
# earlier, but the dom head *was*, so do the simple cache
414
PyDict_SetItem(self._known_heads, heads_key, heads)
416
heads = self._heads_from_candidate_nodes(candidate_nodes)
502
417
if self.do_cache:
503
PyDict_SetItem(self._known_heads, heads_key, heads)
418
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
421
cdef object _cache_heads(self, heads, heads_key, dom_lookup_key,
423
cdef PyObject *maybe_node
424
cdef _KnownGraphNode node
426
PyDict_SetItem(self._known_heads, heads_key, heads)
429
maybe_node = PyDict_GetItem(candidate_nodes, key)
430
if maybe_node == NULL:
432
node = <_KnownGraphNode>maybe_node
433
PyList_Append(dom_heads, node.linear_dominator_node.key)
434
PyDict_SetItem(self._known_heads, dom_lookup_key,
435
PyFrozenSet_New(dom_heads))
437
cdef object _heads_from_dominators(self, candidate_nodes):
438
cdef PyObject *maybe_heads
439
cdef PyObject *maybe_key
440
cdef _KnownGraphNode node
519
441
cdef Py_ssize_t pos
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:
442
cdef PyObject *temp_node
446
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
447
node = <_KnownGraphNode>temp_node
448
PyList_Append(dom_list_key, node.linear_dominator_node.key)
449
dom_lookup_key = PyFrozenSet_New(dom_list_key)
450
maybe_heads = PyDict_GetItem(self._known_heads, dom_lookup_key)
451
if maybe_heads == NULL:
452
return dom_lookup_key, None
453
# We need to map back from the dominator head to the original keys
454
dom_heads = <object>maybe_heads
457
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
458
node = <_KnownGraphNode>temp_node
459
PyDict_SetItem(dom_to_key, node.linear_dominator_node.key,
462
for dom_key in dom_heads:
463
maybe_key = PyDict_GetItem(dom_to_key, dom_key)
464
if maybe_key == NULL:
465
# Should never happen
467
PyList_Append(heads, <object>maybe_key)
468
return dom_lookup_key, PyFrozenSet_New(heads)
470
cdef int _process_parent(self, _KnownGraphNode node,
471
_KnownGraphNode parent_node,
473
queue, int *replace_item) except -1:
474
"""Process the parent of a node, seeing if we need to walk it."""
475
cdef PyObject *maybe_candidate
476
maybe_candidate = PyDict_GetItem(candidate_nodes, parent_node.key)
477
if maybe_candidate != NULL:
478
candidate_nodes.pop(parent_node.key)
479
# We could pass up a flag that tells the caller to stop processing,
480
# but it doesn't help much, and makes the code uglier
482
if parent_node.ancestor_of is None:
483
# This node hasn't been walked yet, so just project node's ancestor
484
# info directly to parent_node, and enqueue it for later processing
485
parent_node.ancestor_of = node.ancestor_of
487
heapreplace(queue, (-parent_node.gdfo, parent_node))
490
heappush(queue, (-parent_node.gdfo, parent_node))
491
PyList_Append(self._to_cleanup, parent_node)
492
elif parent_node.ancestor_of != node.ancestor_of:
493
# Combine to get the full set of parents
494
# Rewrite using PySet_* functions, unfortunately you have to use
495
# PySet_Add since there is no PySet_Update... :(
496
all_ancestors = set(parent_node.ancestor_of)
497
for k in node.ancestor_of:
498
PySet_Add(all_ancestors, k)
499
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):
502
cdef object _heads_from_candidate_nodes(self, candidate_nodes):
744
503
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):
504
cdef _KnownGraphNode parent_node
505
cdef Py_ssize_t num_candidates
506
cdef int num_parents, replace_item
758
508
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 = []
512
while PyDict_Next(candidate_nodes, &pos, NULL, &temp_node):
513
node = <_KnownGraphNode>temp_node
514
assert node.ancestor_of is None
515
node.ancestor_of = (node.key,)
516
PyList_Append(queue, (-node.gdfo, node))
517
PyList_Append(self._to_cleanup, node)
519
# These are nodes that we determined are 'common' that we are no longer
521
# Now we walk nodes until all nodes that are being walked are 'common'
522
num_candidates = len(candidate_nodes)
524
while PyList_GET_SIZE(queue) > 0 and PyDict_Size(candidate_nodes) > 1:
526
# We still need to pop the smallest member out of the queue
527
# before we peek again
529
if PyList_GET_SIZE(queue) == 0:
531
# peek at the smallest item. We don't pop, because we expect we'll
532
# need to push more things into the queue anyway
533
node = _peek_node(queue)
535
if PyTuple_GET_SIZE(node.ancestor_of) == num_candidates:
536
# This node is now considered 'common'
537
# Make sure all parent nodes are marked as such
538
for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
539
parent_node = _get_parent(node.parents, pos)
540
if parent_node.ancestor_of is not None:
541
parent_node.ancestor_of = node.ancestor_of
542
if node.linear_dominator_node is not node:
543
parent_node = node.linear_dominator_node
544
if parent_node.ancestor_of is not None:
545
parent_node.ancestor_of = node.ancestor_of
547
if node.parents is None:
550
# Now project the current nodes ancestor list to the parent nodes,
551
# and queue them up to be walked
552
if node.linear_dominator_node is not node:
553
# We are at the tip of a long linear region
554
# We know that there is nothing between here and the tail
555
# that is interesting, so skip to the end
556
self._process_parent(node, node.linear_dominator_node,
557
candidate_nodes, queue, &replace_item)
559
for pos from 0 <= pos < PyTuple_GET_SIZE(node.parents):
560
parent_node = _get_parent(node.parents, pos)
561
self._process_parent(node, parent_node, candidate_nodes,
562
queue, &replace_item)
563
for pos from 0 <= pos < PyList_GET_SIZE(self._to_cleanup):
564
node = _get_list_node(self._to_cleanup, pos)
565
node.ancestor_of = None
566
self._to_cleanup = []
567
return PyFrozenSet_New(candidate_nodes)