27
25
class _KnownGraphNode(object):
28
26
"""Represents a single object in the known graph."""
30
__slots__ = ('key', 'parent_keys', 'child_keys', 'linear_dominator',
31
'gdfo', 'ancestor_of')
28
__slots__ = ('key', 'parent_keys', 'child_keys', 'gdfo')
33
30
def __init__(self, key, parent_keys):
35
32
self.parent_keys = parent_keys
36
33
self.child_keys = []
37
# oldest ancestor, such that no parents between here and there have >1
39
self.linear_dominator = None
40
34
# Greatest distance from origin
42
# This will become a tuple of known heads that have this node as an
44
self.ancestor_of = None
46
37
def __repr__(self):
47
return '%s(%s gdfo:%s par:%s child:%s %s)' % (
38
return '%s(%s gdfo:%s par:%s child:%s)' % (
48
39
self.__class__.__name__, self.key, self.gdfo,
49
self.parent_keys, self.child_keys,
50
self.linear_dominator)
40
self.parent_keys, self.child_keys)
53
43
class KnownGraph(object):
100
89
tails.add(parent_node)
101
90
parent_node.child_keys.append(key)
103
def _find_linear_dominators(self):
104
"""For each node in the set, find any linear dominators.
106
For any given node, the 'linear dominator' is an ancestor, such that
107
all parents between this node and that one have a single parent, and a
108
single child. So if A->B->C->D then B,C,D all have a linear dominator
111
There are two main benefits:
112
1) When walking the graph, we can jump to the nearest linear dominator,
113
rather than walking all of the nodes inbetween.
114
2) When caching heads() results, dominators give the "same" results as
115
their children. (If the dominator is a head, then the descendant is
116
a head, if the dominator is not a head, then the child isn't
119
def check_node(node):
120
if node.parent_keys is None or len(node.parent_keys) != 1:
121
# This node is either a ghost, a tail, or has multiple parents
122
# It its own dominator
123
node.linear_dominator = node.key
125
parent_node = self._nodes[node.parent_keys[0]]
126
if len(parent_node.child_keys) > 1:
127
# The parent has multiple children, so *this* node is the
129
node.linear_dominator = node.key
131
# The parent is already filled in, so add and continue
132
if parent_node.linear_dominator is not None:
133
node.linear_dominator = parent_node.linear_dominator
135
# We don't know this node, or its parent node, so start walking to
139
for node in self._nodes.itervalues():
140
# The parent is not filled in, so walk until we get somewhere
141
if node.linear_dominator is not None: #already done
143
next_node = check_node(node)
144
if next_node is None:
145
# Nothing more needs to be done
148
while next_node is not None:
151
next_node = check_node(node)
152
# The stack now contains the linear chain, and 'node' should have
154
dominator = node.linear_dominator
156
next_node = stack.pop()
157
next_node.linear_dominator = dominator
160
92
def _find_gdfo(self):
161
93
nodes = self._nodes
94
known_parent_gdfos = {}
163
known_parent_gdfos = dict.fromkeys(nodes.keys(), 0)
165
97
for node in self._tails:
167
known_parent_gdfos[node.key] = 0
168
99
pending.append(node)
182
113
# continue from there
183
114
pending.append(child)
185
def x_find_gdfo(self):
187
return [node for node in self._nodes.itervalues()
188
if not node.parent_keys]
191
heappush = heapq.heappush
192
heappop = heapq.heappop
196
heappush(todo, (1, node))
199
gdfo, next = heappop(todo)
201
if next.gdfo is not None and gdfo < next.gdfo:
202
# This node was reached from a longer path, we assume it was
203
# enqued correctly with the longer gdfo, so don't continue
207
for child_key in next.child_keys:
208
child_node = nodes[child_key]
209
if child_node.gdfo is None or child_node.gdfo < next_gdfo:
210
# Only enque children when all of their parents have been
212
for parent_key in child_node.parent_keys:
213
# We know that 'this' parent is counted
214
if parent_key != next.key:
215
parent_node = nodes[parent_key]
216
if parent_node.gdfo is None:
219
child_node.gdfo = next_gdfo
220
heappush(todo, (next_gdfo, child_node))
222
def _get_dominators_to_nodes(self, candidate_nodes):
223
"""Get the reverse mapping from dominator_key => candidate_nodes.
225
As a side effect, this can also remove potential candidate nodes if we
226
determine that they share a dominator.
230
for node in candidate_nodes.values():
231
if node.linear_dominator in dom_to_node:
232
# This node already exists, resolve which node supersedes the
234
other_node = dom_to_node[node.linear_dominator]
235
# There should be no way that nodes sharing a dominator could
237
if other_node.gdfo > node.gdfo:
238
# The other node has this node as an ancestor
239
keys_to_remove.append(node.key)
241
# Replace the other node, and set this as the new key
242
keys_to_remove.append(other_node.key)
243
dom_to_node[node.linear_dominator] = node
245
dom_to_node[node.linear_dominator] = node
246
for key in keys_to_remove:
247
candidate_nodes.pop(key)
250
116
def heads(self, keys):
251
117
"""Return the heads from amongst keys.
306
172
self._known_heads[heads_key] = heads
309
def xheads(self, keys):
310
"""Return the heads from amongst keys.
312
This is done by searching the ancestries of each key. Any key that is
313
reachable from another key is not returned; all the others are.
315
This operation scales with the relative depth between any two keys. If
316
any two keys are completely disconnected all ancestry of both sides
319
:param keys: An iterable of keys.
320
:return: A set of the heads. Note that as a set there is no ordering
321
information. Callers will need to filter their input to create
322
order if they need it.
324
candidate_nodes = dict((key, self._nodes[key]) for key in keys)
325
if revision.NULL_REVISION in candidate_nodes:
326
# NULL_REVISION is only a head if it is the only entry
327
candidate_nodes.pop(revision.NULL_REVISION)
328
if not candidate_nodes:
329
return set([revision.NULL_REVISION])
330
if len(candidate_nodes) < 2:
331
return frozenset(candidate_nodes)
332
heads_key = frozenset(candidate_nodes)
333
if heads_key != frozenset(keys):
334
note('%s != %s', heads_key, frozenset(keys))
336
heads = self._known_heads[heads_key]
339
pass # compute it ourselves
340
dom_to_node = self._get_dominators_to_nodes(candidate_nodes)
341
if len(candidate_nodes) < 2:
342
# We shrunk candidate_nodes and determined a new head
343
return frozenset(candidate_nodes)
345
# Check the linear dominators of these keys, to see if we already
346
# know the heads answer
347
dom_heads_key = frozenset([node.linear_dominator
348
for node in candidate_nodes.itervalues()])
349
if dom_heads_key in self._known_heads:
350
# map back into the original keys
351
heads = self._known_heads[dom_heads_key]
352
heads = frozenset([dom_to_node[key].key for key in heads])
354
heads = self._heads_from_candidate_nodes(candidate_nodes, dom_to_node)
356
self._known_heads[heads_key] = heads
357
# Cache the dominator heads
358
if dom_heads_key is not None:
359
dom_heads = frozenset([candidate_nodes[key].linear_dominator
361
self._known_heads[dom_heads_key] = dom_heads
364
def _heads_from_candidate_nodes(self, candidate_nodes, dom_to_node):
367
to_cleanup_append = to_cleanup.append
368
for node in candidate_nodes.itervalues():
369
node.ancestor_of = (node.key,)
370
queue.append((-node.gdfo, node))
371
to_cleanup_append(node)
373
# These are nodes that we determined are 'common' that we are no longer
375
# Now we walk nodes until all nodes that are being walked are 'common'
376
num_candidates = len(candidate_nodes)
378
heappop = heapq.heappop
379
heappush = heapq.heappush
380
while queue and len(candidate_nodes) > 1:
381
_, node = heappop(queue)
382
next_ancestor_of = node.ancestor_of
383
if len(next_ancestor_of) == num_candidates:
384
# This node is now considered 'common'
385
# Make sure all parent nodes are marked as such
386
for parent_key in node.parent_keys:
387
parent_node = nodes[parent_key]
388
if parent_node.ancestor_of is not None:
389
parent_node.ancestor_of = next_ancestor_of
390
if node.linear_dominator != node.key:
391
parent_node = nodes[node.linear_dominator]
392
if parent_node.ancestor_of is not None:
393
parent_node.ancestor_of = next_ancestor_of
395
if node.parent_keys is None:
398
# Now project the current nodes ancestor list to the parent nodes,
399
# and queue them up to be walked
400
# Note: using linear_dominator speeds things up quite a bit
401
# enough that we actually start to be slightly faster
402
# than the default heads() implementation
403
if node.linear_dominator != node.key:
404
# We are at the tip of a long linear region
405
# We know that there is nothing between here and the tail
406
# that is interesting, so skip to the end
407
parent_keys = [node.linear_dominator]
409
parent_keys = node.parent_keys
410
for parent_key in parent_keys:
411
if parent_key in candidate_nodes:
412
candidate_nodes.pop(parent_key)
413
if len(candidate_nodes) <= 1:
415
elif parent_key in dom_to_node:
416
orig_node = dom_to_node[parent_key]
417
if orig_node is not node:
418
if orig_node.key in candidate_nodes:
419
candidate_nodes.pop(orig_node.key)
420
if len(candidate_nodes) <= 1:
422
parent_node = nodes[parent_key]
423
ancestor_of = parent_node.ancestor_of
424
if ancestor_of is None:
425
# This node hasn't been walked yet
426
parent_node.ancestor_of = next_ancestor_of
428
heappush(queue, (-parent_node.gdfo, parent_node))
429
to_cleanup_append(parent_node)
430
elif ancestor_of != next_ancestor_of:
431
# Combine to get the full set of parents
432
all_ancestors = set(ancestor_of)
433
all_ancestors.update(next_ancestor_of)
434
parent_node.ancestor_of = tuple(sorted(all_ancestors))
436
for node in to_cleanup:
437
node.ancestor_of = None
439
return frozenset(candidate_nodes)