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