示例#1
0
    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
示例#2
0
 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
示例#3
0
    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))
示例#4
0
    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