~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to tools/dev/mergegraph/mergegraph.py

  • Committer: Package Import Robot
  • Author(s): James McCoy, Peter Samuelson, James McCoy
  • Date: 2014-01-12 19:48:33 UTC
  • mfrom: (0.2.10)
  • Revision ID: package-import@ubuntu.com-20140112194833-w3axfwksn296jn5x
Tags: 1.8.5-1
[ Peter Samuelson ]
* New upstream release.  (Closes: #725787) Rediff patches:
  - Remove apr-abi1 (applied upstream), rename apr-abi2 to apr-abi
  - Remove loosen-sqlite-version-check (shouldn't be needed)
  - Remove java-osgi-metadata (applied upstream)
  - svnmucc prompts for a changelog if none is provided. (Closes: #507430)
  - Remove fix-bdb-version-detection, upstream uses "apu-config --dbm-libs"
  - Remove ruby-test-wc (applied upstream)
  - Fix “svn diff -r N file” when file has svn:mime-type set.
    (Closes: #734163)
  - Support specifying an encoding for mod_dav_svn's environment in which
    hooks are run.  (Closes: #601544)
  - Fix ordering of “svnadmin dump” paths with certain APR versions.
    (Closes: #687291)
  - Provide a better error message when authentication fails with an
    svn+ssh:// URL.  (Closes: #273874)
  - Updated Polish translations.  (Closes: #690815)

[ James McCoy ]
* Remove all traces of libneon, replaced by libserf.
* patches/sqlite_3.8.x_workaround: Upstream fix for wc-queries-test test
  failurse.
* Run configure with --with-apache-libexecdir, which allows removing part of
  patches/rpath.
* Re-enable auth-test as upstream has fixed the problem of picking up
  libraries from the environment rather than the build tree.
  (Closes: #654172)
* Point LD_LIBRARY_PATH at the built auth libraries when running the svn
  command during the build.  (Closes: #678224)
* Add a NEWS entry describing how to configure mod_dav_svn to understand
  UTF-8.  (Closes: #566148)
* Remove ancient transitional package, libsvn-ruby.
* Enable compatibility with Sqlite3 versions back to Wheezy.
* Enable hardening flags.  (Closes: #734918)
* patches/build-fixes: Enable verbose build logs.
* Build against the default ruby version.  (Closes: #722393)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
 
 
3
# ====================================================================
 
4
#    Licensed to the Apache Software Foundation (ASF) under one
 
5
#    or more contributor license agreements.  See the NOTICE file
 
6
#    distributed with this work for additional information
 
7
#    regarding copyright ownership.  The ASF licenses this file
 
8
#    to you under the Apache License, Version 2.0 (the
 
9
#    "License"); you may not use this file except in compliance
 
10
#    with the License.  You may obtain a copy of the License at
 
11
#
 
12
#      http://www.apache.org/licenses/LICENSE-2.0
 
13
#
 
14
#    Unless required by applicable law or agreed to in writing,
 
15
#    software distributed under the License is distributed on an
 
16
#    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 
17
#    KIND, either express or implied.  See the License for the
 
18
#    specific language governing permissions and limitations
 
19
#    under the License.
 
20
# ====================================================================
 
21
 
 
22
# Config file format:
 
23
example = """
 
24
  [graph]
 
25
  filename = merge-sync-1.png
 
26
  title = Sync Merge: CC vs SVN
 
27
  # Branches: (branch name, branched from node, first rev, last rev).
 
28
  branches = [
 
29
    ('A', 'O0', 1, 4),
 
30
    ('O', None, 0, 0),
 
31
    ('B', 'O0', 1, 5)
 
32
    ]
 
33
  # Changes: nodes in which a change was committed; merge targets need not
 
34
  # be listed here.
 
35
  changes = [
 
36
    'A1', 'A2', 'A3', 'A4',
 
37
    'B1', 'B2', 'B3', 'B4', 'B5'
 
38
    ]
 
39
  # Merges: (base node, source-right node, target node, label).
 
40
  # Base is also known as source-left.
 
41
  merges = [
 
42
    ('O0', 'A:1', 'B3', 'sync'),
 
43
    ('A2', 'A:3', 'B5', 'sync'),
 
44
    ]
 
45
  # Annotations for nodes: (node, annotation text).
 
46
  annotations = [
 
47
    ('A2', 'cc:YCA')
 
48
    ]
 
49
"""
 
50
 
 
51
# Notes about different kinds of merge.
 
52
#
 
53
# A basic 3-way merge is ...
 
54
#
 
55
# The ClearCase style of merge is a 3-way merge.
 
56
#
 
57
# The Subversion style of merge (that is, one phase of a Subversion merge)
 
58
# is a three-way merge with its base (typically the YCA) on the source branch.
 
59
 
 
60
 
 
61
import sys
 
62
import pydot
 
63
from pydot import Node, Edge
 
64
 
 
65
 
 
66
def mergeinfo_to_node_list(mi):
 
67
  """Convert a mergeinfo string such as '/foo:1,3-5*' into a list of
 
68
     node names such as ['foo1', 'foo3', 'foo4', 'foo5'].
 
69
  """
 
70
  ### Doesn't yet strip the leading slash.
 
71
  l = []
 
72
  if mi:
 
73
    for mi_str in mi.split(' '):
 
74
      path, ranges = mi_str.split(':')
 
75
      for r in ranges.split(','):
 
76
        if r.endswith('*'):
 
77
          # TODO: store & use this 'non-inheritable' flag
 
78
          # Remove the flag
 
79
          r = r[:-1]
 
80
        rlist = r.split('-')
 
81
        r1 = int(rlist[0])
 
82
        if len(rlist) == 2:
 
83
          r2 = int(rlist[1])
 
84
        else:
 
85
          r2 = r1
 
86
        for rev in range(r1, r2 + 1):
 
87
          l.append(path + str(rev))
 
88
  return l
 
89
 
 
90
 
 
91
class MergeGraph(pydot.Graph):
 
92
  """Base class, not intended for direct use.  Use MergeDot for the main
 
93
     graph and MergeSubgraph for a subgraph.
 
94
  """
 
95
 
 
96
  def mk_origin_node(graph, name, label):
 
97
    """Add a node to the graph"""
 
98
    graph.add_node(Node(name, label=label, shape='plaintext'))
 
99
 
 
100
  def mk_invis_node(graph, name):
 
101
    """Add a node to the graph"""
 
102
    graph.add_node(Node(name, style='invis'))
 
103
 
 
104
  def mk_node(graph, name, label=None):
 
105
    """Add a node to the graph, if not already present"""
 
106
    if not graph.get_node(name):
 
107
      if not label:
 
108
        label = name
 
109
      if name in graph.changes:
 
110
        graph.add_node(Node(name, label=label))
 
111
      else:
 
112
        graph.add_node(Node(name, color='grey', label=''))
 
113
 
 
114
  def mk_merge_target(graph, target_node, important):
 
115
    """Add a merge target node to the graph."""
 
116
    if important:
 
117
      color = 'red'
 
118
    else:
 
119
      color = 'black'
 
120
    graph.add_node(Node(target_node, color=color, fontcolor=color, style='bold'))
 
121
 
 
122
  def mk_edge(graph, name1, name2, **attrs):
 
123
    """Add an ordinary edge to the graph"""
 
124
    graph.add_edge(Edge(name1, name2, dir='none', style='dotted', color='grey', **attrs))
 
125
 
 
126
  def mk_br_edge(graph, name1, name2):
 
127
    """Add a branch-creation edge to the graph"""
 
128
    # Constraint=false to avoid the Y-shape skewing the nice parallel branch lines
 
129
    graph.mk_edge(name1, name2, constraint='false')
 
130
 
 
131
  def mk_merge_edge(graph, src_node, tgt_node, kind, label, important):
 
132
    """Add a merge edge to the graph"""
 
133
    if important:
 
134
      color = 'red'
 
135
    else:
 
136
      color = 'grey'
 
137
    e = Edge(src_node, tgt_node, constraint='false',
 
138
             label='"' + label + '"',
 
139
             color=color, fontcolor=color,
 
140
             style='bold')
 
141
    if kind.startswith('cherry'):
 
142
      e.set_style('dashed')
 
143
    graph.add_edge(e)
 
144
 
 
145
  def mk_mergeinfo_edge(graph, base_node, src_node, important):
 
146
    """"""
 
147
    if important:
 
148
      color = 'red'
 
149
    else:
 
150
      color = 'grey'
 
151
    graph.add_edge(Edge(base_node, src_node,
 
152
                        dir='both', arrowtail='odot', arrowhead='tee',
 
153
                        color=color, constraint='false'))
 
154
 
 
155
  def mk_invis_edge(graph, name1, name2):
 
156
    """Add an invisible edge to the graph"""
 
157
    graph.add_edge(Edge(name1, name2, style='invis'))
 
158
 
 
159
  def add_merge(graph, merge, important):
 
160
    """Add a merge"""
 
161
    base_node, src_node, tgt_node, kind = merge
 
162
 
 
163
    if base_node and src_node:  # and not kind.startwith('cherry'):
 
164
      graph.mk_mergeinfo_edge(base_node, src_node, important)
 
165
 
 
166
    # Merge target node
 
167
    graph.mk_merge_target(tgt_node, important)
 
168
 
 
169
    # Merge edge
 
170
    graph.mk_merge_edge(src_node, tgt_node, kind, kind, important)
 
171
 
 
172
  def add_annotation(graph, node, label, color='lightblue'):
 
173
    """Add a graph node that serves as an annotation to a normal node.
 
174
       More than one annotation can be added to the same normal node.
 
175
    """
 
176
    subg_name = node + '_annotations'
 
177
 
 
178
    def get_subgraph(graph, name):
 
179
      """Equivalent to pydot.Graph.get_subgraph() when there is no more than
 
180
         one subgraph of the given name, but working aroung a bug in
 
181
         pydot.Graph.get_subgraph().
 
182
      """
 
183
      for subg in graph.get_subgraph_list():
 
184
        if subg.get_name() == name:
 
185
          return subg
 
186
      return None
 
187
 
 
188
    g = get_subgraph(graph, subg_name)
 
189
    if not g:
 
190
      g = pydot.Subgraph(subg_name, rank='same')
 
191
      graph.add_subgraph(g)
 
192
 
 
193
    ann_node = node + '_'
 
194
    while g.get_node(ann_node):
 
195
      ann_node = ann_node + '_'
 
196
    g.add_node(Node(ann_node, shape='box', style='filled', color=color,
 
197
                    label='"' + label + '"'))
 
198
    g.add_edge(Edge(ann_node, node, style='solid', color=color,
 
199
                    dir='none', constraint='false'))
 
200
 
 
201
class MergeSubgraph(MergeGraph, pydot.Subgraph):
 
202
  """"""
 
203
  def __init__(graph, **attrs):
 
204
    """"""
 
205
    MergeGraph.__init__(graph)
 
206
    pydot.Subgraph.__init__(graph, **attrs)
 
207
 
 
208
class MergeDot(MergeGraph, pydot.Dot):
 
209
  """
 
210
  # TODO: In the 'merges' input, find the predecessor automatically.
 
211
  """
 
212
  def __init__(graph, config_filename=None,
 
213
               filename=None, title=None, branches=None, changes=None,
 
214
               merges=[], annotations=[], **attrs):
 
215
    """Return a new MergeDot graph generated from a config file or args."""
 
216
    MergeGraph.__init__(graph)
 
217
    pydot.Dot.__init__(graph, **attrs)
 
218
 
 
219
    if config_filename:
 
220
      graph.read_config(config_filename)
 
221
    else:
 
222
      graph.filename = filename
 
223
      graph.title = title
 
224
      graph.branches = branches
 
225
      graph.changes = changes
 
226
      graph.merges = merges
 
227
      graph.annotations = annotations
 
228
 
 
229
    graph.construct()
 
230
 
 
231
  def read_config(graph, config_filename):
 
232
    """Initialize a MergeDot graph's input data from a config file."""
 
233
    import ConfigParser
 
234
    if config_filename.endswith('.txt'):
 
235
      default_basename = config_filename[:-4]
 
236
    else:
 
237
      default_basename = config_filename
 
238
 
 
239
    config = ConfigParser.SafeConfigParser({ 'basename': default_basename,
 
240
                                             'title': None,
 
241
                                             'merges': '[]',
 
242
                                             'annotations': '[]' })
 
243
    files_read = config.read(config_filename)
 
244
    if len(files_read) == 0:
 
245
      print >> sys.stderr, 'graph: unable to read graph config from "' + config_filename + '"'
 
246
      sys.exit(1)
 
247
    graph.basename = config.get('graph', 'basename')
 
248
    graph.title = config.get('graph', 'title')
 
249
    graph.branches = eval(config.get('graph', 'branches'))
 
250
    graph.changes = eval(config.get('graph', 'changes'))
 
251
    graph.merges = eval(config.get('graph', 'merges'))
 
252
    graph.annotations = eval(config.get('graph', 'annotations'))
 
253
 
 
254
  def construct(graph):
 
255
    """"""
 
256
    # Origin nodes (done first, in an attempt to set the order)
 
257
    for br, orig, r1, head in graph.branches:
 
258
      name = br + '0'
 
259
      if r1 > 0:
 
260
        graph.mk_origin_node(name, br)
 
261
      else:
 
262
        graph.mk_node(name, label=br)
 
263
 
 
264
    # Edges and target nodes for merges
 
265
    for merge in graph.merges:
 
266
      # Emphasize the last merge, as it's the important one
 
267
      important = (merge == graph.merges[-1])
 
268
      graph.add_merge(merge, important)
 
269
 
 
270
    # Parallel edges for basic lines of descent
 
271
    for br, orig, r1, head in graph.branches:
 
272
      sub_g = MergeSubgraph(ordering='out')
 
273
      for i in range(1, head + 1):
 
274
        prev_n = br + str(i - 1)
 
275
        this_n = br + str(i)
 
276
 
 
277
        # Normal edges and nodes
 
278
        if i < r1:
 
279
          graph.mk_invis_node(this_n)
 
280
        else:
 
281
          graph.mk_node(this_n)
 
282
        if i <= r1:
 
283
          graph.mk_invis_edge(prev_n, this_n)
 
284
        else:
 
285
          graph.mk_edge(prev_n, this_n)
 
286
 
 
287
      # Branch creation edges
 
288
      if orig:
 
289
        sub_g.mk_br_edge(orig, br + str(r1))
 
290
 
 
291
      graph.add_subgraph(sub_g)
 
292
 
 
293
    # Annotations
 
294
    for node, label in graph.annotations:
 
295
      graph.add_annotation(node, label)
 
296
 
 
297
    # A title for the graph (added last so it goes at the top)
 
298
    if graph.title:
 
299
      graph.add_node(Node('title', shape='plaintext', label='"' + graph.title + '"'))
 
300
 
 
301
  def save(graph, format='png', filename=None):
 
302
    """Save this merge graph to the given file format. If filename is None,
 
303
       construct a filename from the basename of the original file (as passed
 
304
       to the constructor and then stored in graph.basename) and the suffix
 
305
       according to the given format.
 
306
    """
 
307
    if not filename:
 
308
      filename = graph.basename + '.' + format
 
309
    if format == 'sh':
 
310
      import save_as_sh
 
311
      save_as_sh.write_sh_file(graph, filename)
 
312
    else:
 
313
      pydot.Dot.write(graph, filename, format=format)