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