141
142
Names in this stack will get emitted into the output as they are popped
145
self._node_name_stack.append(node_name)
146
self._pending_parents_stack.append(list(parents))
149
"""Pop the top node off the stack
151
The node is appended to the sorted output.
153
# we are returning from the flattened call frame:
154
# pop off the local variables
155
node_name = self._node_name_stack.pop()
156
self._pending_parents_stack.pop()
158
self._completed_node_names.add(node_name)
145
self._node_name_stack.append(node_name)
146
self._pending_parents_stack.append(list(parents))
149
"""Pop the top node off the stack
151
The node is appended to the sorted output.
153
# we are returning from the flattened call frame:
154
# pop off the local variables
155
node_name = self._node_name_stack.pop()
156
self._pending_parents_stack.pop()
158
self._completed_node_names.add(node_name)
162
def merge_sort(graph, branch_tip):
163
"""Topological sort a graph which groups merges.
165
:param graph: sequence of pairs of node->parents_list.
166
:param branch_tip: the tip of the branch to graph. Revisions not
167
reachable from branch_tip are not included in the
170
The result is a list of node names, such that all parents come before
173
node identifiers can be any hashable object, and are typically strings.
175
return MergeSorter(graph, branch_tip).sorted()
178
class MergeSorter(object):
180
def __init__(self, graph, branch_tip):
181
"""Merge-aware topological sorting of a graph.
183
:param graph: sequence of pairs of node_name->parent_names_list.
184
i.e. [('C', ['B']), ('B', ['A']), ('A', [])]
185
For this input the output from the sort or
186
iter_topo_order routines will be:
189
node identifiers can be any hashable object, and are typically strings.
191
If you have a graph like [('a', ['b']), ('a', ['c'])] this will only use
192
one of the two values for 'a'.
194
The graph is sorted lazily: until you iterate or sort the input is
195
not processed other than to create an internal representation.
197
iteration or sorting may raise GraphCycleError if a cycle is present
200
Background information on the design:
201
-------------------------------------
202
definition: the end of any cluster or 'merge' occurs when:
203
1 - the next revision has a lower merge depth than we do.
210
C, D are the ends of clusters, E might be but we need more data.
211
2 - or the next revision at our merge depth is not our left most
213
This is required to handle multiple-merges in one commit.
221
C is the end of a cluster due to rule 1.
222
D is not the end of a cluster from rule 1, but is from rule 2: E
223
is not its left most ancestor
224
E is the end of a cluster due to rule 1
225
F might be but we need more data.
227
we show connecting lines to a parent when:
228
- The parent is the start of a merge within this cluster.
229
That is, the merge was not done to the mainline before this cluster
230
was merged to the mainline.
231
This can be detected thus:
232
* The parent has a higher merge depth and is the next revision in
235
The next revision in the list constraint is needed for this case:
237
B 1 [C, F] # we do not want to show a line to F which is depth 2
239
C 1 [H] # note that this is a long line to show back to the
240
ancestor - see the end of merge rules.
246
- Part of this merges 'branch':
247
The parent has the same merge depth and is our left most parent and we
248
are not the end of the cluster.
249
A 0 [C, B] lines: [B, C]
250
B 1 [E, C] lines: [C]
252
D 0 [F, E] lines: [E, F]
255
- The end of this merge/cluster:
256
we can ONLY have multiple parents at the end of a cluster if this
257
branch was previously merged into the 'mainline'.
258
- if we have one and only one parent, show it
259
Note that this may be to a greater merge depth - for instance if
260
this branch continued from a deeply nested branch to add something
262
- if we have more than one parent - show the second oldest (older ==
263
further down the list) parent with
264
an equal or lower merge depth
265
XXXX revisit when awake. ddaa asks about the relevance of each one
266
- maybe more than one parent is relevant
268
# a dict of the graph.
269
self._graph = dict(graph)
270
# we need to do a check late in the process to detect end-of-merges
271
# which requires the parents to be accessible: its easier for now
272
# to just keep the original graph around.
273
self._original_graph = dict(graph)
275
# this is a stack storing the depth first search into the graph.
276
self._node_name_stack = []
277
# at each level of recursion we need the merge depth this node is at:
278
self._node_merge_depth_stack = []
279
# at each level of 'recursion' we have to check each parent. This
280
# stack stores the parents we have not yet checked for the node at the
281
# matching depth in _node_name_stack
282
self._pending_parents_stack = []
283
# this is a set of the nodes who have been completely analysed for fast
284
# membership checking
285
self._completed_node_names = set()
286
# this is the scheduling of nodes list.
287
# Nodes are scheduled
288
# from the bottom left of the tree: in the tree
295
# the scheduling order is: F, E, D, C, B, A
296
# that is - 'left subtree, right subtree, node'
297
# which would mean that when we schedule A we can emit the entire tree.
298
self._scheduled_nodes = []
299
# This records for each node when we have processed its left most
300
# unmerged subtree. After this subtree is scheduled, all other subtrees
301
# have their merge depth increased by one from this nodes merge depth.
302
self._left_subtree_done_stack = []
304
# seed the search with the tip of the branch
305
if branch_tip is not None:
306
parents = self._graph.pop(branch_tip)
307
self._push_node(branch_tip, 0, parents)
310
"""Sort the graph and return as a list.
312
After calling this the sorter is empty and you must create a new one.
314
return list(self.iter_topo_order())
316
def iter_topo_order(self):
317
"""Yield the nodes of the graph in a topological order.
319
After finishing iteration the sorter is empty and you cannot continue
322
while self._node_name_stack:
323
# loop until this call completes.
324
parents_to_visit = self._pending_parents_stack[-1]
325
# if all parents are done, the revision is done
326
if not parents_to_visit:
327
# append the revision to the topo sorted scheduled list:
328
# all the nodes parents have been scheduled added, now
329
# we can add it to the output.
332
while self._pending_parents_stack[-1]:
333
if not self._left_subtree_done_stack[-1]:
334
# recurse depth first into the primary parent
335
next_node_name = self._pending_parents_stack[-1].pop(0)
337
# place any merges in right-to-left order for scheduling
338
# which gives us left-to-right order after we reverse
339
# the scheduled queue. XXX: This has the effect of
340
# allocating common-new revisions to the right-most
341
# subtree rather than the left most, which will
342
# display nicely (you get smaller trees at the top
343
# of the combined merge).
344
next_node_name = self._pending_parents_stack[-1].pop()
345
if next_node_name in self._completed_node_names:
346
# this parent was completed by a child on the
347
# call stack. skip it.
349
# otherwise transfer it from the source graph into the
350
# top of the current depth first search stack.
352
parents = self._graph.pop(next_node_name)
354
# if the next node is not in the source graph it has
355
# already been popped from it and placed into the
356
# current search stack (but not completed or we would
357
# have hit the continue 4 lines up.
358
# this indicates a cycle.
359
raise errors.GraphCycleError(self._node_name_stack)
361
if self._left_subtree_done_stack[-1]:
365
self._left_subtree_done_stack[-1] = True
367
self._node_merge_depth_stack[-1] + next_merge_depth)
372
# and do not continue processing parents until this 'call'
375
# We have scheduled the graph. Now deliver the ordered output:
377
while self._scheduled_nodes:
378
node_name, merge_depth = self._scheduled_nodes.pop()
379
if not len(self._scheduled_nodes):
381
elif self._scheduled_nodes[-1][1] < merge_depth:
382
# the next node is to our left
384
elif (self._scheduled_nodes[-1][1] == merge_depth and
385
(self._scheduled_nodes[-1][0] not in
386
self._original_graph[node_name])):
387
# the next node was part of a multiple-merge.
391
yield (sequence_number, node_name, merge_depth, end_of_merge)
394
def _push_node(self, node_name, merge_depth, parents):
395
"""Add node_name to the pending node stack.
397
Names in this stack will get emitted into the output as they are popped
400
self._node_name_stack.append(node_name)
401
self._node_merge_depth_stack.append(merge_depth)
402
self._left_subtree_done_stack.append(False)
403
self._pending_parents_stack.append(list(parents))
406
"""Pop the top node off the stack
408
The node is appended to the sorted output.
410
# we are returning from the flattened call frame:
411
# pop off the local variables
412
node_name = self._node_name_stack.pop()
413
merge_depth = self._node_merge_depth_stack.pop()
414
self._left_subtree_done_stack.pop()
415
self._pending_parents_stack.pop()
417
self._completed_node_names.add(node_name)
418
self._scheduled_nodes.append((node_name, merge_depth))