/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
1
# -*- coding: UTF-8 -*-
2
"""Directed graph production.
3
4
This module contains the code to produce an ordered directed graph of a
5
bzr branch, such as we display in the tree view at the top of the bzrk
6
window.
7
"""
8
9
__copyright__ = "Copyright © 2005 Canonical Ltd."
10
__author__    = "Scott James Remnant <scott@ubuntu.com>"
11
256.2.25 by Gary van der Merwe
Use Merge sort
12
from bzrlib.tsort import merge_sort
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
13
452.4.1 by Jelmer Vernooij
Support displaying multiple tips in viz.
14
def linegraph(repository, start_revs, maxnum, broken_line_length = None,
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
15
              graph_data = True, mainline_only = False):
328 by Jelmer Vernooij
Use repository instead of branch in more places, to make it easier to support multiple branches in viz.
16
    """Produce a directed graph of a bzr repository.
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
17
256.4.1 by Gary van der Merwe
* Set a width and use ellips on Revision No column.
18
    Returns a tuple of (line_graph, revid_index, columns_len) where
19
    * line_graph is a list of tuples of (revid,
20
                                         node,
21
                                         lines,
22
                                         parents,
23
                                         children,
24
                                         revno_sequence),
25
    * revid_index is a dict of each revision with the key being the revid, and
26
      the value the row index, and
27
    * columns_len is the number of columns need to draw the line graph.
28
    
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
29
30
    Node is a tuple of (column, colour) with column being a zero-indexed
31
    column number of the graph that this revision represents and colour
32
    being a zero-indexed colour (which doesn't specify any actual colour
33
    in particular) to draw the node in.
34
35
    Lines is a list of tuples which represent lines you should draw away
36
    from the revision, if you also need to draw lines into the revision
37
    you should use the lines list from the previous iteration.  Each
38
    typle in the list is in the form (start, end, colour) with start and
39
    end being zero-indexed column numbers and colour as in node.
40
41
    It's up to you how to actually draw the nodes and lines (straight,
42
    curved, kinked, etc.) and to pick the actual colours for each index.
43
    """
256.2.46 by Gary van der Merwe
Store branch lines as list rather than dict.
44
    
452.4.1 by Jelmer Vernooij
Support displaying multiple tips in viz.
45
    graph = repository.get_graph()
46
    graph_parents = {}
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
47
    graph_children = {}
452.4.1 by Jelmer Vernooij
Support displaying multiple tips in viz.
48
    for (revid, parent_revids) in graph.iter_ancestry(start_revs):
49
        graph_parents[revid] = parent_revids
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
50
        graph_children[revid] = []
256.2.28 by Gary van der Merwe
Better line drawing with info from merge_sort
51
452.4.1 by Jelmer Vernooij
Support displaying multiple tips in viz.
52
    graph_parents["top:"] = start_revs
53
421.1.1 by Gary van der Merwe
Make viz not throw errors when there are 0 commits.
54
    if len(graph_parents)>0:
55
        merge_sorted_revisions = merge_sort(
56
            graph_parents,
452.4.1 by Jelmer Vernooij
Support displaying multiple tips in viz.
57
            "top:",
421.1.1 by Gary van der Merwe
Make viz not throw errors when there are 0 commits.
58
            generate_revno=True)
59
    else:
60
        merge_sorted_revisions = ()
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
61
    
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
62
    if mainline_only:
63
        merge_sorted_revisions = [elem for elem in merge_sorted_revisions \
64
                                  if len(elem[3])==1 ]
452.4.1 by Jelmer Vernooij
Support displaying multiple tips in viz.
65
66
    assert merge_sorted_revisions[0][1] == "top:"
67
    merge_sorted_revisions = merge_sorted_revisions[1:]
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
68
    
256.2.28 by Gary van der Merwe
Better line drawing with info from merge_sort
69
    revid_index = {}
256.2.48 by Gary van der Merwe
Make the line to a direct parent in the same column as the child branch line.
70
    revno_index = {}
256.2.46 by Gary van der Merwe
Store branch lines as list rather than dict.
71
    
72
    # This will hold an item for each "branch". For a revisions, the revsion
73
    # number less the least significant digit is the branch_id, and used as the
74
    # key for the dict. Hence revision with the same revsion number less the
75
    # least significant digit are considered to be in the same branch line.
76
    # e.g.: for revisions 290.12.1 and 290.12.2, the branch_id would be 290.12,
77
    # and these two revisions will be in the same branch line. Each value is
256.2.50 by Gary van der Merwe
First implementation of broken lines.
78
    # a list of rev_indexes in the branch.
256.2.28 by Gary van der Merwe
Better line drawing with info from merge_sort
79
    branch_lines = {}
256.2.46 by Gary van der Merwe
Store branch lines as list rather than dict.
80
    
256.2.28 by Gary van der Merwe
Better line drawing with info from merge_sort
81
    linegraph = []    
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
82
    
256.2.40 by Gary van der Merwe
Take out a whole bunch of code for better performance.
83
    for (rev_index, (sequence_number,
84
                     revid,
85
                     merge_depth,
86
                     revno_sequence,
87
                     end_of_merge)) in enumerate(merge_sorted_revisions):
256.2.55 by Gary van der Merwe
Merge vizchanges
88
        if maxnum and rev_index >= maxnum:
89
            break
256.2.40 by Gary van der Merwe
Take out a whole bunch of code for better performance.
90
        revid_index[revid] = rev_index
256.2.33 by Gary van der Merwe
Revert fast test
91
        
256.2.28 by Gary van der Merwe
Better line drawing with info from merge_sort
92
        parents = graph_parents[revid]
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
93
        for parent_revid in parents:
94
            graph_children[parent_revid].append(revid)
95
        
256.2.33 by Gary van der Merwe
Revert fast test
96
        linegraph.append([revid,
97
                          None,
256.2.27 by Gary van der Merwe
Show Revision No in tree
98
                          [],
99
                          parents,
256.2.33 by Gary van der Merwe
Revert fast test
100
                          None,
101
                          revno_sequence])
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
102
        
103
        if graph_data:
104
            revno_index[revno_sequence] = rev_index
105
            
106
            branch_id = revno_sequence[0:-1]
107
            
108
            branch_line = None
109
            if branch_id not in branch_lines:
110
                branch_line = []
111
                branch_lines[branch_id] = branch_line
112
            else:
113
                branch_line = branch_lines[branch_id]
114
            
115
            branch_line.append(rev_index)        
256.2.33 by Gary van der Merwe
Revert fast test
116
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
117
    if graph_data:
118
        branch_ids = branch_lines.keys()
119
    
120
        def branch_id_cmp(x, y):
121
            """Compaire branch_id's first by the number of digits, then reversed
122
            by their value"""
123
            len_x = len(x)
124
            len_y = len(y)
125
            if len_x == len_y:
126
                return -cmp(x, y)
127
            return cmp(len_x, len_y)
128
        
129
        branch_ids.sort(branch_id_cmp)
130
        # This will hold a tuple of (child_index, parent_index, col_index) for each
131
        # line that needs to be drawn. If col_index is not none, then the line is
132
        # drawn along that column, else the the line can be drawn directly between
133
        # the child and parent because either the child and parent are in the same
134
        # branch line, or the child and parent are 1 row apart.
135
        lines = []
136
        empty_column = [False for i in range(len(graph_parents))]
137
        # This will hold a bit map for each cell. If the cell is true, then the
138
        # cell allready contains a node or line. This use when deciding what column
139
        # to place a branch line or line in, without it overlaping something else.
140
        columns = [list(empty_column)]
141
        
142
        
143
        for branch_id in branch_ids:
144
            branch_line = branch_lines[branch_id]
145
            
146
            # Find the col_index for the direct parent branch. This will be the
147
            # starting point when looking for a free column.
148
            parent_col_index = 0
149
            parent_index = None
150
            if len(branch_id) > 1:
151
                parent_revno = branch_id[0:-1]
152
                if parent_revno in revno_index:
153
                    parent_index = revno_index[parent_revno]
256.2.50 by Gary van der Merwe
First implementation of broken lines.
154
                    parent_node = linegraph[parent_index][1]
155
                    if parent_node:
156
                        parent_col_index = parent_node[0]
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
157
                    
158
            
159
            col_search_order = _branch_line_col_search_order(columns,
160
                                                             parent_col_index)
161
            color = reduce(lambda x, y: x+y, branch_id, 0)
162
            cur_cont_line = []
163
            
164
            line_range = []
165
            last_rev_index = None
166
            for rev_index in branch_line:
167
                if last_rev_index:
168
                    if broken_line_length and \
169
                       rev_index - last_rev_index > broken_line_length:
170
                        line_range.append(last_rev_index+1)
171
                        line_range.append(rev_index-1)
256.2.50 by Gary van der Merwe
First implementation of broken lines.
172
                    else:
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
173
                        line_range.extend(range(last_rev_index+1, rev_index))
174
                
175
                line_range.append(rev_index)
176
                last_rev_index = rev_index
177
            
178
            if parent_index:
179
                if broken_line_length and \
180
                   parent_index - last_rev_index > broken_line_length:
181
                    line_range.append(last_rev_index+1)
182
                else:
183
                    line_range.extend(range(last_rev_index+1, parent_index))
184
            
185
            col_index = _find_free_column(columns,
186
                                          empty_column,
187
                                          col_search_order,
188
                                          line_range)
189
            node = (col_index, color)
190
            for rev_index in branch_line:
191
                linegraph[rev_index][1] = node
192
                columns[col_index][rev_index] = True
193
            
194
            for rev_index in branch_line:
195
                (sequence_number,
196
                     revid,
197
                     merge_depth,
198
                     revno_sequence,
199
                     end_of_merge) = merge_sorted_revisions[rev_index]
200
                
201
                linegraph[rev_index][4] = graph_children[revid]
202
                col_index = linegraph[rev_index][1][0]
203
                
204
                for parent_revid in graph_parents[revid]:
205
                    if parent_revid in revid_index:
206
                        
207
                        parent_index = revid_index[parent_revid]                            
208
                        parent_node = linegraph[parent_index][1]
209
                        if parent_node:
210
                            parent_col_index = parent_node[0]
211
                        else:
212
                            parent_col_index = None
256.2.60 by Gary van der Merwe
Fix a bug with the selection of a column for a broken line.
213
                        col_search_order = \
214
                                _line_col_search_order(columns,
215
                                                       parent_col_index,
216
                                                       col_index)
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
217
                            
218
                        # If this line is really long, break it.
219
                        if len(branch_id) > 0 and \
220
                           broken_line_length and \
221
                           parent_index - rev_index > broken_line_length:
222
                            child_line_col_index = \
223
                                _find_free_column(columns,
224
                                                  empty_column,
225
                                                  col_search_order,
226
                                                  (rev_index + 1,))
227
                            _mark_column_as_used(columns,
228
                                                 child_line_col_index,
229
                                                 (rev_index + 1,))
230
                            
231
                            # Recall _line_col_search_order to reset it back to
232
                            # the beging.
233
                            col_search_order = \
234
                                    _line_col_search_order(columns,
235
                                                           parent_col_index,
236
                                                           col_index)
237
                            parent_col_line_index = \
238
                                _find_free_column(columns,
239
                                                  empty_column,
240
                                                  col_search_order,
241
                                                  (parent_index - 1,))
242
                            _mark_column_as_used(columns,
243
                                                 parent_col_line_index,
244
                                                 (parent_index - 1,))
245
                            lines.append((rev_index,
246
                                          parent_index,
247
                                          (child_line_col_index,
248
                                           parent_col_line_index)))
249
                        else :
250
                            line_col_index = col_index
251
                            if parent_index - rev_index >1:
252
                                line_range = range(rev_index + 1, parent_index)
253
                                line_col_index = \
254
                                    _find_free_column(columns,
255
                                                      empty_column,
256
                                                      col_search_order,
257
                                                      line_range)
258
                                _mark_column_as_used(columns,
259
                                                     line_col_index,
260
                                                     line_range)
261
                            lines.append((rev_index,
262
                                          parent_index,
263
                                          (line_col_index,)))
256.2.40 by Gary van der Merwe
Take out a whole bunch of code for better performance.
264
        
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
265
        for (child_index, parent_index, line_col_indexes) in lines:
266
            (child_col_index, child_color) = linegraph[child_index][1]
267
            (parent_col_index, parent_color) = linegraph[parent_index][1]
268
            
269
            if len(line_col_indexes) == 1:
270
                if parent_index - child_index == 1:
271
                    linegraph[child_index][2].append(
272
                        (child_col_index,
273
                         parent_col_index,
274
                         parent_color))
275
                else:
276
                    # line from the child's column to the lines column
277
                    linegraph[child_index][2].append(
278
                        (child_col_index,
279
                         line_col_indexes[0],
280
                         parent_color))
281
                    # lines down the line's column
282
                    for line_part_index in range(child_index+1, parent_index-1):
283
                        linegraph[line_part_index][2].append(
284
                            (line_col_indexes[0],   
285
                             line_col_indexes[0],
286
                             parent_color))
287
                    # line from the line's column to the parent's column
288
                    linegraph[parent_index-1][2].append(
289
                        (line_col_indexes[0],
290
                         parent_col_index,
291
                         parent_color))
256.2.50 by Gary van der Merwe
First implementation of broken lines.
292
            else:
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
293
                # Broken line
256.2.50 by Gary van der Merwe
First implementation of broken lines.
294
                # line from the child's column to the lines column
295
                linegraph[child_index][2].append(
296
                    (child_col_index,
297
                     line_col_indexes[0],
256.2.51 by Gary van der Merwe
Make the color of the bottom of a broken the color of the child.
298
                     parent_color))
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
299
                # Broken line end
300
                linegraph[child_index+1][2].append(
301
                    (line_col_indexes[0],
302
                     None,
303
                     parent_color))
304
                
305
                # Broken line end 
306
                linegraph[parent_index-2][2].append(
307
                    (None,
308
                     line_col_indexes[1],
309
                     parent_color))
256.2.50 by Gary van der Merwe
First implementation of broken lines.
310
                # line from the line's column to the parent's column
311
                linegraph[parent_index-1][2].append(
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
312
                    (line_col_indexes[1],
256.2.50 by Gary van der Merwe
First implementation of broken lines.
313
                     parent_col_index,
256.2.51 by Gary van der Merwe
Make the color of the bottom of a broken the color of the child.
314
                     parent_color))
423.1.18 by Gary van der Merwe
Add options to viz treeview to not show the line graph, and to only show the main line.
315
        return (linegraph, revid_index, len(columns))
316
    else:
317
        return (linegraph, revid_index, 0)
256.2.28 by Gary van der Merwe
Better line drawing with info from merge_sort
318
    
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
319
256.2.48 by Gary van der Merwe
Make the line to a direct parent in the same column as the child branch line.
320
def _branch_line_col_search_order(columns, parent_col_index):
256.2.58 by Gary van der Merwe
Improve the performance of the search order functions by making them
321
    for col_index in range(parent_col_index, len(columns)):
322
        yield col_index
323
    for col_index in range(parent_col_index-1, -1, -1):
324
        yield col_index
256.2.47 by Gary van der Merwe
Tweek the search order of columns.
325
256.2.48 by Gary van der Merwe
Make the line to a direct parent in the same column as the child branch line.
326
def _line_col_search_order(columns, parent_col_index, child_col_index):
327
    if parent_col_index is not None:
256.2.58 by Gary van der Merwe
Improve the performance of the search order functions by making them
328
        max_index = max(parent_col_index, child_col_index)
329
        min_index = min(parent_col_index, child_col_index)
330
        for col_index in range(max_index, min_index -1, -1):
331
            yield col_index
256.2.48 by Gary van der Merwe
Make the line to a direct parent in the same column as the child branch line.
332
    else:
256.2.58 by Gary van der Merwe
Improve the performance of the search order functions by making them
333
        max_index = child_col_index
334
        min_index = child_col_index
335
        yield child_col_index
256.2.47 by Gary van der Merwe
Tweek the search order of columns.
336
    i = 1
256.2.58 by Gary van der Merwe
Improve the performance of the search order functions by making them
337
    while max_index + i < len(columns) or \
338
          min_index - i > -1:
339
        if max_index + i < len(columns):
340
            yield max_index + i
341
        if min_index - i > -1:
342
            yield min_index - i
256.2.47 by Gary van der Merwe
Tweek the search order of columns.
343
        i += 1
344
256.2.50 by Gary van der Merwe
First implementation of broken lines.
345
def _find_free_column(columns, empty_column, col_search_order, line_range):
256.2.47 by Gary van der Merwe
Tweek the search order of columns.
346
    for col_index in col_search_order:
256.2.44 by Gary van der Merwe
Chose what column to place nodes and lines in better.
347
        column = columns[col_index]
256.2.40 by Gary van der Merwe
Take out a whole bunch of code for better performance.
348
        has_overlaping_line = False
256.2.42 by Gary van der Merwe
Performance improvements.
349
        for row_index in line_range:
350
            if column[row_index]:
256.2.40 by Gary van der Merwe
Take out a whole bunch of code for better performance.
351
                has_overlaping_line = True
352
                break
353
        if not has_overlaping_line:
354
            break
355
    else:
356
        col_index = len(columns)
256.2.42 by Gary van der Merwe
Performance improvements.
357
        column = list(empty_column)
358
        columns.append(column)
256.2.50 by Gary van der Merwe
First implementation of broken lines.
359
    return col_index
360
361
def _mark_column_as_used(columns, col_index, line_range):
362
    column = columns[col_index]
256.2.42 by Gary van der Merwe
Performance improvements.
363
    for row_index in line_range:
256.2.50 by Gary van der Merwe
First implementation of broken lines.
364
        column[row_index] = True    
256.2.26 by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
365
366
def same_branch(a, b):
367
    """Return whether we think revisions a and b are on the same branch."""
368
    if len(a.parent_ids) == 1:
369
        # Defacto same branch if only parent
370
        return True
371
    elif a.committer == b.committer:
372
        # Same committer so may as well be
373
        return True
374
    else:
375
        return False