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