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