~bzr/bzr-gtk/trunk

unknown 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
unknown by Gary van der Merwe
Use Merge sort
12
from bzrlib.tsort import merge_sort
unknown 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
unknown 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
    
unknown 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
    """
unknown by Gary van der Merwe
Store branch lines as list rather than dict.
43
    
unknown by Gary van der Merwe
First implementation of broken lines.
44
    # FIXME: This should be configurable
45
    BROKEN_LINE_LENGTH = 32
46
    
unknown 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.
unknown 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 = {}
unknown by Gary van der Merwe
Performance improvements.
52
    for revid in graph_parents.iterkeys():
unknown by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
53
        graph_children[revid] = []
unknown by Gary van der Merwe
Better line drawing with info from merge_sort
54
unknown 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
    
unknown by Gary van der Merwe
Better line drawing with info from merge_sort
61
    revid_index = {}
unknown by Gary van der Merwe
Make the line to a direct parent in the same column as the child branch line.
62
    revno_index = {}
unknown 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
unknown by Gary van der Merwe
First implementation of broken lines.
70
    # a list of rev_indexes in the branch.
unknown by Gary van der Merwe
Better line drawing with info from merge_sort
71
    branch_lines = {}
unknown by Gary van der Merwe
Store branch lines as list rather than dict.
72
    
unknown by Gary van der Merwe
Better line drawing with info from merge_sort
73
    linegraph = []    
unknown by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
74
    
unknown 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):
unknown by Gary van der Merwe
Merge vizchanges
80
        if maxnum and rev_index >= maxnum:
81
            break
unknown by Gary van der Merwe
Take out a whole bunch of code for better performance.
82
        revid_index[revid] = rev_index
unknown 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
unknown by Gary van der Merwe
Better line drawing with info from merge_sort
84
        
85
        branch_id = revno_sequence[0:-1]
86
        
unknown by Gary van der Merwe
Revert fast test
87
        branch_line = None
88
        if branch_id not in branch_lines:
unknown by Gary van der Merwe
First implementation of broken lines.
89
            branch_line = []
unknown by Gary van der Merwe
Revert fast test
90
            branch_lines[branch_id] = branch_line
91
        else:
92
            branch_line = branch_lines[branch_id]
unknown by Gary van der Merwe
Store branch lines as list rather than dict.
93
        
unknown by Gary van der Merwe
First implementation of broken lines.
94
        branch_line.append(rev_index)
unknown by Gary van der Merwe
Revert fast test
95
        
unknown by Gary van der Merwe
Better line drawing with info from merge_sort
96
        parents = graph_parents[revid]
unknown 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
        
unknown by Gary van der Merwe
Revert fast test
100
        linegraph.append([revid,
101
                          None,
unknown by Gary van der Merwe
Show Revision No in tree
102
                          [],
103
                          parents,
unknown by Gary van der Merwe
Revert fast test
104
                          None,
105
                          revno_sequence])
106
107
    branch_ids = branch_lines.keys()
unknown by Gary van der Merwe
Store branch lines as list rather than dict.
108
    
unknown by Gary van der Merwe
Tweek order that branch lines are processed for more understandable results.
109
    def branch_id_cmp(x, y):
unknown 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"""
unknown 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)
unknown by Gary van der Merwe
Store branch lines as list rather than dict.
117
    
unknown by Gary van der Merwe
Tweek order that branch lines are processed for more understandable results.
118
    branch_ids.sort(branch_id_cmp)
unknown 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.
unknown by Gary van der Merwe
Take out a whole bunch of code for better performance.
124
    lines = []
unknown by Gary van der Merwe
Performance improvements.
125
    empty_column = [False for i in range(len(graph_parents))]
unknown 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.
unknown by Gary van der Merwe
Chose what column to place nodes and lines in better.
129
    columns = [list(empty_column)]
unknown by Gary van der Merwe
Performance improvements.
130
    
unknown 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
        
unknown 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.
unknown by Gary van der Merwe
Chose what column to place nodes and lines in better.
137
        parent_col_index = 0
unknown by Gary van der Merwe
First implementation of broken lines.
138
        parent_index = None
unknown by Gary van der Merwe
Chose what column to place nodes and lines in better.
139
        if len(branch_id) > 1:
unknown 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]
unknown 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
                
unknown 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)
unknown by Gary van der Merwe
Revert fast test
150
        color = reduce(lambda x, y: x+y, branch_id, 0)
unknown by Gary van der Merwe
First implementation of broken lines.
151
        cur_cont_line = []
152
        
unknown by Gary van der Merwe
Make branch lines that have broken lines exist only on one line.
153
        line_range = []
unknown 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:
unknown 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:
unknown 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)
unknown by Gary van der Merwe
First implementation of broken lines.
165
            last_rev_index = rev_index
unknown by Gary van der Merwe
Make branch lines that have broken lines exist only on one line.
166
        
167
        if parent_index:
unknown 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:
unknown 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
unknown by Gary van der Merwe
First implementation of broken lines.
182
        
183
        for rev_index in branch_line:
unknown 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
            
unknown by Gary van der Merwe
Take out a whole bunch of code for better performance.
190
            linegraph[rev_index][4] = graph_children[revid]
unknown by Gary van der Merwe
First implementation of broken lines.
191
            col_index = linegraph[rev_index][1][0]
unknown 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:
unknown by Gary van der Merwe
Make the line to a direct parent in the same column as the child branch line.
195
                    
unknown 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 \
unknown by Gary van der Merwe
Make it possible to set BROKEN_LINE_LENGTH = None to specify that
209
                       BROKEN_LINE_LENGTH and \
unknown 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,
unknown by Gary van der Merwe
* Revert using child color for bottom of broken line.
215
                                              (rev_index + 1,))
unknown by Gary van der Merwe
First implementation of broken lines.
216
                        _mark_column_as_used(columns,
217
                                             child_line_col_index,
unknown by Gary van der Merwe
* Revert using child color for bottom of broken line.
218
                                             (rev_index + 1,))
unknown 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)
unknown 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,
unknown by Gary van der Merwe
* Revert using child color for bottom of broken line.
230
                                              (parent_index - 1,))
unknown by Gary van der Merwe
First implementation of broken lines.
231
                        _mark_column_as_used(columns,
232
                                             parent_col_line_index,
unknown by Gary van der Merwe
* Revert using child color for bottom of broken line.
233
                                             (parent_index - 1,))
unknown 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,)))
unknown by Gary van der Merwe
Take out a whole bunch of code for better performance.
253
    
unknown by Gary van der Merwe
First implementation of broken lines.
254
    for (child_index, parent_index, line_col_indexes) in lines:
unknown 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]
unknown by Gary van der Merwe
Take out a whole bunch of code for better performance.
257
        
unknown 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,
unknown by Gary van der Merwe
Make the color of the bottom of a broken the color of the child.
263
                     parent_color))
unknown 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],
unknown by Gary van der Merwe
Make the color of the bottom of a broken the color of the child.
269
                     parent_color))
unknown 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],
unknown by Gary van der Merwe
Make the color of the bottom of a broken the color of the child.
275
                         parent_color))
unknown 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,
unknown by Gary van der Merwe
Make the color of the bottom of a broken the color of the child.
280
                     parent_color))
unknown by Gary van der Merwe
First implementation of broken lines.
281
        else:
282
            # Broken line
unknown 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
unknown 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,
unknown by Gary van der Merwe
First implementation of broken lines.
286
                 line_col_indexes[0],
unknown by Gary van der Merwe
Make the color of the bottom of a broken the color of the child.
287
                 parent_color))
unknown 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,
unknown by Gary van der Merwe
Make the color of the bottom of a broken the color of the child.
292
                 parent_color))
unknown 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],
unknown by Gary van der Merwe
* Revert using child color for bottom of broken line.
298
                 parent_color))
unknown 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
unknown by Gary van der Merwe
Take out a whole bunch of code for better performance.
300
            linegraph[parent_index-1][2].append(
unknown by Gary van der Merwe
First implementation of broken lines.
301
                (line_col_indexes[1],
302
                 parent_col_index,
unknown by Gary van der Merwe
* Revert using child color for bottom of broken line.
303
                 parent_color))
unknown by Gary van der Merwe
First implementation of broken lines.
304
            
unknown by Gary van der Merwe
Better line drawing with info from merge_sort
305
    
unknown by Gary van der Merwe
* Set a width and use ellips on Revision No column.
306
    return (linegraph, revid_index, len(columns))
unknown by Gary van der Merwe
Use info from merge sort to draw lines. Needs work
307
unknown 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):
unknown 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
unknown by Gary van der Merwe
Tweek the search order of columns.
313
unknown 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:
unknown 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
unknown by Gary van der Merwe
Make the line to a direct parent in the same column as the child branch line.
320
    else:
unknown 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
unknown by Gary van der Merwe
Tweek the search order of columns.
324
    i = 1
unknown 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
unknown by Gary van der Merwe
Tweek the search order of columns.
331
        i += 1
332
unknown by Gary van der Merwe
First implementation of broken lines.
333
def _find_free_column(columns, empty_column, col_search_order, line_range):
unknown by Gary van der Merwe
Tweek the search order of columns.
334
    for col_index in col_search_order:
unknown by Gary van der Merwe
Chose what column to place nodes and lines in better.
335
        column = columns[col_index]
unknown by Gary van der Merwe
Take out a whole bunch of code for better performance.
336
        has_overlaping_line = False
unknown by Gary van der Merwe
Performance improvements.
337
        for row_index in line_range:
338
            if column[row_index]:
unknown 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)
unknown by Gary van der Merwe
Performance improvements.
345
        column = list(empty_column)
346
        columns.append(column)
unknown 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]
unknown by Gary van der Merwe
Performance improvements.
351
    for row_index in line_range:
unknown by Gary van der Merwe
First implementation of broken lines.
352
        column[row_index] = True    
unknown 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