/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to branchview/linegraph.py

  • Committer: Jelmer Vernooij
  • Date: 2008-04-25 20:03:44 UTC
  • mfrom: (463.3.1 bug.215350)
  • Revision ID: jelmer@samba.org-20080425200344-1s2gp5qnoq15fu1o
Merge fix for View Changes menu option.

Show diffs side-by-side

added added

removed removed

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