Author: julianfoad Date: Thu Feb 2 14:48:15 2012 New Revision: 1239652 URL: http://svn.apache.org/viewvc?rev=1239652&view=rev Log: Teach merge-graph.py to allow multiple annotations on the same node, and make some minor formatting changes.
Modified: subversion/trunk/tools/dev/merge-graph.py Modified: subversion/trunk/tools/dev/merge-graph.py URL: http://svn.apache.org/viewvc/subversion/trunk/tools/dev/merge-graph.py?rev=1239652&r1=1239651&r2=1239652&view=diff ============================================================================== --- subversion/trunk/tools/dev/merge-graph.py (original) +++ subversion/trunk/tools/dev/merge-graph.py Thu Feb 2 14:48:15 2012 @@ -22,6 +22,8 @@ args_message = 'GRAPH_CONFIG_FILE...' help_message = """Produce pretty graphs representing branches and merging. For each config file specified, construct a graph and write it as a PNG file.""" + +# Config file format: example = """ [graph] filename = merge-sync-1.png @@ -93,19 +95,21 @@ class MergeGraph(pydot.Graph): """Base class, not intended for direct use. Use MergeDot for the main graph and MergeSubgraph for a subgraph.""" - def mk_origin_node(graph, name): + def mk_origin_node(graph, name, label): """Add a node to the graph""" - graph.add_node(Node(name + '0', label=name, shape='plaintext')) + graph.add_node(Node(name, label=label, shape='plaintext')) def mk_invis_node(graph, name): """Add a node to the graph""" graph.add_node(Node(name, style='invis')) - def mk_node(graph, name): + def mk_node(graph, name, label=None): """Add a node to the graph, if not already present""" if not graph.get_node(name): + if not label: + label = name if name in graph.changes: - graph.add_node(Node(name)) + graph.add_node(Node(name, label=label)) else: graph.add_node(Node(name, color='grey', label='')) @@ -138,8 +142,6 @@ class MergeGraph(pydot.Graph): style='bold') if kind == 'cherry': e.set_style('dashed') - e.set_dir('both') - e.set_arrowtail('tee') graph.add_edge(e) def mk_mergeinfo_edge(graph, base_node, src_node, important): @@ -160,7 +162,7 @@ class MergeGraph(pydot.Graph): """Add a merge""" base_node, src_node, tgt_node, kind = merge - if base_node and src_node and kind != 'cherry': + if base_node and src_node: # and kind != 'cherry': graph.mk_mergeinfo_edge(base_node, src_node, important) # Merge target node @@ -170,12 +172,29 @@ class MergeGraph(pydot.Graph): graph.mk_merge_edge(src_node, tgt_node, kind, kind, important) def add_annotation(graph, node, label, color='red'): - """""" - ann_node = node + '_annotation' - g = pydot.Subgraph(rank='same') + """Add a graph node that serves as an annotation to a normal node. + More than one annotation can be added to the same normal node.""" + subg_name = node + '_annotations' + + def get_subgraph(graph, name): + """Equivalent to pydot.Graph.get_subgraph() when there is no more than + one subgraph of the given name, but working aroung a bug in + pydot.Graph.get_subgraph().""" + for subg in graph.get_subgraph_list(): + if subg.get_name() == name: + return subg + return None + + g = get_subgraph(graph, subg_name) + if not g: + g = pydot.Subgraph(subg_name, rank='same') + graph.add_subgraph(g) + + ann_node = node + '_' + while g.get_node(ann_node): + ann_node = ann_node + '_' g.add_node(Node(ann_node, shape='box', color=color, label='"' + label + '"')) g.add_edge(Edge(ann_node, node, style='dotted', color=color, dir='none', constraint='false')) - graph.add_subgraph(g) class MergeSubgraph(MergeGraph, pydot.Subgraph): """""" @@ -189,7 +208,7 @@ class MergeDot(MergeGraph, pydot.Dot): # TODO: In the 'merges' input, find the predecessor automatically. """ def __init__(graph, config_filename, **attrs): - """Return a new MergeDot graph generated from the specified config file.""" + """Return a new MergeDot graph generated from a config file.""" MergeGraph.__init__(graph) pydot.Dot.__init__(graph, **attrs) @@ -198,15 +217,15 @@ class MergeDot(MergeGraph, pydot.Dot): graph.construct() def read_config(graph, config_filename): - """""" + """Initialize a MergeDot graph's input data from a config file.""" import ConfigParser config = ConfigParser.SafeConfigParser() files_read = config.read(config_filename) if len(files_read) == 0: print >> sys.stderr, 'graph: unable to read graph config from "' + config_filename + '"' sys.exit(1) - graph.title = config.get('graph', 'title') graph.filename = config.get('graph', 'filename') + graph.title = config.get('graph', 'title') graph.branches = eval(config.get('graph', 'branches')) graph.changes = eval(config.get('graph', 'changes')) graph.merges = eval(config.get('graph', 'merges')) @@ -216,7 +235,11 @@ class MergeDot(MergeGraph, pydot.Dot): """""" # Origin nodes (done first, in an attempt to set the order) for br, orig, r1, head in graph.branches: - graph.mk_origin_node(br) + name = br + '0' + if r1 > 0: + graph.mk_origin_node(name, br) + else: + graph.mk_node(name, label=br) # Edges and target nodes for merges for merge in graph.merges: @@ -224,7 +247,7 @@ class MergeDot(MergeGraph, pydot.Dot): important = (merge == graph.merges[-1]) graph.add_merge(merge, important) - # Edges for basic lines of descent + # Parallel edges for basic lines of descent for br, orig, r1, head in graph.branches: sub_g = MergeSubgraph(ordering='out') for i in range(1, head + 1): @@ -252,7 +275,8 @@ class MergeDot(MergeGraph, pydot.Dot): graph.add_annotation(node, label) # A title for the graph (added last so it goes at the top) - #graph.add_node(Node('title', shape='plaintext', label='"' + graph.title + '"')) + #if graph.title: + # graph.add_node(Node('title', shape='plaintext', label='"' + graph.title + '"')) # If run as a program, process each input filename as a graph config file.