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