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