def topo_sort(self): """Return the nodes in topological order. All parents must occur before all children. """ for node in self._nodes.itervalues(): if node.gdfo is None: raise errors.GraphCycleError(self._nodes) pending = self._find_tails() pending_pop = pending.pop pending_append = pending.append topo_order = [] topo_order_append = topo_order.append num_seen_parents = dict.fromkeys(self._nodes, 0) while pending: node = pending_pop() if node.parent_keys is not None: # We don't include ghost parents topo_order_append(node.key) for child_key in node.child_keys: child_node = self._nodes[child_key] seen_parents = num_seen_parents[child_key] + 1 if seen_parents == len(child_node.parent_keys): # All parents have been processed, enqueue this child pending_append(child_node) # This has been queued up, stop tracking it del num_seen_parents[child_key] else: num_seen_parents[child_key] = seen_parents # We started from the parents, so we don't need to do anymore work return topo_order
def iter_topo_order(self): """Yield the nodes of the graph in a topological order. After finishing iteration the sorter is empty and you cannot continue iteration. """ while self._graph: # now pick a random node in the source graph, and transfer it to the # top of the depth first search stack. node_name, parents = self._graph.popitem() self._push_node(node_name, parents) while self._node_name_stack: # loop until this call completes. parents_to_visit = self._pending_parents_stack[-1] # if all parents are done, the revision is done if not parents_to_visit: # append the revision to the topo sorted list # all the nodes parents have been added to the output, now # we can add it to the output. yield self._pop_node() else: while self._pending_parents_stack[-1]: # recurse depth first into a single parent next_node_name = self._pending_parents_stack[-1].pop() if next_node_name in self._completed_node_names: # this parent was completed by a child on the # call stack. skip it. continue if next_node_name not in self._visitable: continue # otherwise transfer it from the source graph into the # top of the current depth first search stack. try: parents = self._graph.pop(next_node_name) except KeyError: # if the next node is not in the source graph it has # already been popped from it and placed into the # current search stack (but not completed or we would # have hit the continue 4 lines up. # this indicates a cycle. raise errors.GraphCycleError(self._node_name_stack) self._push_node(next_node_name, parents) # and do not continue processing parents until this 'call' # has recursed. break
def iter_topo_order(self): """Yield the nodes of the graph in a topological order. After finishing iteration the sorter is empty and you cannot continue iteration. """ graph = self._graph visitable = set(graph) # this is a stack storing the depth first search into the graph. pending_node_stack = [] # at each level of 'recursion' we have to check each parent. This # stack stores the parents we have not yet checked for the node at the # matching depth in pending_node_stack pending_parents_stack = [] # this is a set of the completed nodes for fast checking whether a # parent in a node we are processing on the stack has already been # emitted and thus can be skipped. completed_node_names = set() while graph: # now pick a random node in the source graph, and transfer it to the # top of the depth first search stack of pending nodes. node_name, parents = graph.popitem() pending_node_stack.append(node_name) pending_parents_stack.append(list(parents)) # loop until pending_node_stack is empty while pending_node_stack: parents_to_visit = pending_parents_stack[-1] # if there are no parents left, the revision is done if not parents_to_visit: # append the revision to the topo sorted list # all the nodes parents have been added to the output, # now we can add it to the output. popped_node = pending_node_stack.pop() pending_parents_stack.pop() completed_node_names.add(popped_node) yield popped_node else: # recurse depth first into a single parent next_node_name = parents_to_visit.pop() if next_node_name in completed_node_names: # parent was already completed by a child, skip it. continue if next_node_name not in visitable: # parent is not a node in the original graph, skip it. continue # transfer it along with its parents from the source graph # into the top of the current depth first search stack. try: parents = graph.pop(next_node_name) except KeyError: # if the next node is not in the source graph it has # already been popped from it and placed into the # current search stack (but not completed or we would # have hit the continue 6 lines up). this indicates a # cycle. raise errors.GraphCycleError(pending_node_stack) pending_node_stack.append(next_node_name) pending_parents_stack.append(list(parents))
def iter_topo_order(self): """Yield the nodes of the graph in a topological order. After finishing iteration the sorter is empty and you cannot continue iteration. """ # These are safe to offload to local variables, because they are used # as a stack and modified in place, never assigned to. node_name_stack = self._node_name_stack node_merge_depth_stack = self._node_merge_depth_stack pending_parents_stack = self._pending_parents_stack left_subtree_pushed_stack = self._left_subtree_pushed_stack completed_node_names = self._completed_node_names scheduled_nodes = self._scheduled_nodes graph_pop = self._graph.pop def push_node(node_name, merge_depth, parents, node_name_stack_append=node_name_stack.append, node_merge_depth_stack_append=node_merge_depth_stack.append, left_subtree_pushed_stack_append=left_subtree_pushed_stack.append, pending_parents_stack_append=pending_parents_stack.append, first_child_stack_append=self._first_child_stack.append, revnos=self._revnos, ): """Add node_name to the pending node stack. Names in this stack will get emitted into the output as they are popped off the stack. This inlines a lot of self._variable.append functions as local variables. """ node_name_stack_append(node_name) node_merge_depth_stack_append(merge_depth) left_subtree_pushed_stack_append(False) pending_parents_stack_append(list(parents)) # as we push it, check if it is the first child parent_info = None if parents: # node has parents, assign from the left most parent. try: parent_info = revnos[parents[0]] except KeyError: # Left-hand parent is a ghost, consider it not to exist pass if parent_info is not None: first_child = parent_info[1] parent_info[1] = False else: # We don't use the same algorithm here, but we need to keep the # stack in line first_child = None first_child_stack_append(first_child) def pop_node(node_name_stack_pop=node_name_stack.pop, node_merge_depth_stack_pop=node_merge_depth_stack.pop, first_child_stack_pop=self._first_child_stack.pop, left_subtree_pushed_stack_pop=left_subtree_pushed_stack.pop, pending_parents_stack_pop=pending_parents_stack.pop, original_graph=self._original_graph, revnos=self._revnos, completed_node_names_add=self._completed_node_names.add, scheduled_nodes_append=scheduled_nodes.append, revno_to_branch_count=self._revno_to_branch_count, ): """Pop the top node off the stack The node is appended to the sorted output. """ # we are returning from the flattened call frame: # pop off the local variables node_name = node_name_stack_pop() merge_depth = node_merge_depth_stack_pop() first_child = first_child_stack_pop() # remove this node from the pending lists: left_subtree_pushed_stack_pop() pending_parents_stack_pop() parents = original_graph[node_name] parent_revno = None if parents: # node has parents, assign from the left most parent. try: parent_revno = revnos[parents[0]][0] except KeyError: # left-hand parent is a ghost, treat it as not existing pass if parent_revno is not None: if not first_child: # not the first child, make a new branch base_revno = parent_revno[0] branch_count = revno_to_branch_count.get(base_revno, 0) branch_count += 1 revno_to_branch_count[base_revno] = branch_count revno = (parent_revno[0], branch_count, 1) # revno = (parent_revno[0], branch_count, parent_revno[-1]+1) else: # as the first child, we just increase the final revision # number revno = parent_revno[:-1] + (parent_revno[-1] + 1,) else: # no parents, use the root sequence root_count = revno_to_branch_count.get(0, -1) root_count += 1 if root_count: revno = (0, root_count, 1) else: revno = (1,) revno_to_branch_count[0] = root_count # store the revno for this node for future reference revnos[node_name][0] = revno completed_node_names_add(node_name) scheduled_nodes_append((node_name, merge_depth, revno)) return node_name while node_name_stack: # loop until this call completes. parents_to_visit = pending_parents_stack[-1] # if all parents are done, the revision is done if not parents_to_visit: # append the revision to the topo sorted scheduled list: # all the nodes parents have been scheduled added, now # we can add it to the output. pop_node() else: while pending_parents_stack[-1]: if not left_subtree_pushed_stack[-1]: # recurse depth first into the primary parent next_node_name = pending_parents_stack[-1].pop(0) is_left_subtree = True left_subtree_pushed_stack[-1] = True else: # place any merges in right-to-left order for scheduling # which gives us left-to-right order after we reverse # the scheduled queue. XXX: This has the effect of # allocating common-new revisions to the right-most # subtree rather than the left most, which will # display nicely (you get smaller trees at the top # of the combined merge). next_node_name = pending_parents_stack[-1].pop() is_left_subtree = False if next_node_name in completed_node_names: # this parent was completed by a child on the # call stack. skip it. continue # otherwise transfer it from the source graph into the # top of the current depth first search stack. try: parents = graph_pop(next_node_name) except KeyError: # if the next node is not in the source graph it has # already been popped from it and placed into the # current search stack (but not completed or we would # have hit the continue 4 lines up. # this indicates a cycle. if next_node_name in self._original_graph: raise errors.GraphCycleError(node_name_stack) else: # This is just a ghost parent, ignore it continue next_merge_depth = 0 if is_left_subtree: # a new child branch from name_stack[-1] next_merge_depth = 0 else: next_merge_depth = 1 next_merge_depth = ( node_merge_depth_stack[-1] + next_merge_depth) push_node( next_node_name, next_merge_depth, parents) # and do not continue processing parents until this 'call' # has recursed. break # We have scheduled the graph. Now deliver the ordered output: sequence_number = 0 stop_revision = self._stop_revision generate_revno = self._generate_revno original_graph = self._original_graph while scheduled_nodes: node_name, merge_depth, revno = scheduled_nodes.pop() if node_name == stop_revision: return if not len(scheduled_nodes): # last revision is the end of a merge end_of_merge = True elif scheduled_nodes[-1][1] < merge_depth: # the next node is to our left end_of_merge = True elif (scheduled_nodes[-1][1] == merge_depth and (scheduled_nodes[-1][0] not in original_graph[node_name])): # the next node was part of a multiple-merge. end_of_merge = True else: end_of_merge = False if generate_revno: yield (sequence_number, node_name, merge_depth, revno, end_of_merge) else: yield (sequence_number, node_name, merge_depth, end_of_merge) sequence_number += 1