/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz
1 by Scott James Remnant
Commit the first version of bzrk.
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
37.1.2 by Robert Collins
Make revision sorting and linking use merge_sorted from latest bzr.dev. This
12
from bzrlib.tsort import merge_sort
1 by Scott James Remnant
Commit the first version of bzrk.
13
256.4.3 by Gary van der Merwe
Merge trunk.
14
def linegraph(branch, start, maxnum):
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
15
    """Produce a directed graph of a bzr branch.
16
256.4.3 by Gary van der Merwe
Merge trunk.
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
    
3 by Scott James Remnant
Split the display in two with a pane, we'll use the bottom half to show
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.4.3 by Gary van der Merwe
Merge trunk.
43
    
44
    # We get the mainline so we can pass it to merge_sort to make merge_sort
45
    # run faster.
46
    mainline = branch.revision_history()
47
    graph_parents = branch.repository.get_revision_graph(start)
48
    graph_children = {}
49
    for revid in graph_parents.iterkeys():
50
        graph_children[revid] = []
51
52
    merge_sorted_revisions = merge_sort(
53
        graph_parents,
54
        start,
55
        mainline,
56
        generate_revno=True)
57
    
58
    revid_index = {}
59
    revno_index = {}
60
    
61
    # This will hold an item for each "branch". For a revisions, the revsion
62
    # number less the least significant digit is the branch_id, and used as the
63
    # key for the dict. Hence revision with the same revsion number less the
64
    # least significant digit are considered to be in the same branch line.
65
    # e.g.: for revisions 290.12.1 and 290.12.2, the branch_id would be 290.12,
66
    # and these two revisions will be in the same branch line. Each value is
67
    # a list of [rev_indexes, min_index, max_index, col_index].
68
    branch_lines = {}
69
    BL_REV_INDEXES = 0
70
    BL_MIN_INDEX = 1
71
    BL_MAX_INDEX = 2
72
    BL_COL_INDEX = 3
73
    
74
    linegraph = []    
75
    
76
    for (rev_index, (sequence_number,
77
                     revid,
78
                     merge_depth,
79
                     revno_sequence,
80
                     end_of_merge)) in enumerate(merge_sorted_revisions):
81
        if maxnum and rev_index >= maxnum:
82
            break
83
        revid_index[revid] = rev_index
84
        revno_index[revno_sequence] = rev_index
85
        
86
        branch_id = revno_sequence[0:-1]
87
        
88
        branch_line = None
89
        if branch_id not in branch_lines:
90
            branch_line = [[],        # BL_REV_INDEXES
91
                           rev_index, # BL_MIN_INDEX
92
                           0,         # BL_MAX_INDEX
93
                           None]      # BL_COL_INDEX
94
            branch_lines[branch_id] = branch_line
95
        else:
96
            branch_line = branch_lines[branch_id]
97
        
98
        branch_line[BL_REV_INDEXES].append(rev_index)
99
        if rev_index > branch_line[BL_MAX_INDEX]:
100
            branch_line[BL_MAX_INDEX] = rev_index
101
        
102
        parents = graph_parents[revid]
103
        for parent_revid in parents:
104
            graph_children[parent_revid].append(revid)
105
        
106
        linegraph.append([revid,
107
                          None,
108
                          [],
109
                          parents,
110
                          None,
111
                          revno_sequence])
112
113
    branch_ids = branch_lines.keys()
114
    
115
    def branch_id_cmp(x, y):
116
        """Compaire branch_id's first by the number of digits, then reversed
117
        by their value"""
118
        len_x = len(x)
119
        len_y = len(y)
120
        if len_x == len_y:
121
            return -cmp(x, y)
122
        return cmp(len_x, len_y)
123
    
124
    branch_ids.sort(branch_id_cmp)
125
    # This will hold a tuple of (child_index, parent_index, col_index) for each
126
    # line that needs to be drawn. If col_index is not none, then the line is
127
    # drawn along that column, else the the line can be drawn directly between
128
    # the child and parent because either the child and parent are in the same
129
    # branch line, or the child and parent are 1 row apart.
130
    lines = []
131
    empty_column = [False for i in range(len(graph_parents))]
132
    # This will hold a bit map for each cell. If the cell is true, then the
133
    # cell allready contains a node or line. This use when deciding what column
134
    # to place a branch line or line in, without it overlaping something else.
135
    columns = [list(empty_column)]
136
    
137
    
138
    for branch_id in branch_ids:
139
        branch_line = branch_lines[branch_id]
140
        
141
        # Find the col_index for the direct parent branch. This will be the
142
        # starting point when looking for a free column.
143
        parent_col_index = 0
144
        if len(branch_id) > 1:
145
            parent_branch_id = branch_id[0:-2]
146
            parent_col_index = branch_lines[parent_branch_id][BL_COL_INDEX]
147
            parent_revno = branch_id[0:-1]
148
            if parent_revno in revno_index:
149
                parent_index = revno_index[parent_revno]
150
                branch_line[BL_MAX_INDEX] = parent_index - 1
151
        
152
        col_search_order = _branch_line_col_search_order(columns,
153
                                                         parent_col_index)
154
        branch_line[BL_COL_INDEX] = _append_line(columns,
155
                                                (branch_line[BL_MIN_INDEX],
156
                                                 branch_line[BL_MAX_INDEX]),
157
                                                empty_column,
158
                                                col_search_order)
159
        color = reduce(lambda x, y: x+y, branch_id, 0)
160
        col_index = branch_line[BL_COL_INDEX]
161
        node = (col_index, color)        
162
        
163
        for rev_index in branch_line[BL_REV_INDEXES]:
164
            (sequence_number,
165
                 revid,
166
                 merge_depth,
167
                 revno_sequence,
168
                 end_of_merge) = merge_sorted_revisions[rev_index]
169
            
170
            linegraph[rev_index][1] = node
171
            linegraph[rev_index][4] = graph_children[revid]
172
            
173
            for parent_revid in graph_parents[revid]:
174
                if parent_revid in revid_index:
175
                    parent_index = revid_index[parent_revid]
176
                    parent_revno = merge_sorted_revisions[parent_index][3]
177
                    parent_branch_id = parent_revno[0:-1]
178
                    col_index = None
179
                    is_direct_parent_line = False
180
                    if len(branch_id) > 1:
181
                        if parent_revno == branch_id[0:-1]:
182
                            is_direct_parent_line = True
183
                    
184
                    # A line only needs it's own column if it is going from
185
                    # one branch line to another, it's not the line to the
186
                    # direct parent, and if it is longer than one row.
187
                    if branch_id != parent_branch_id and \
188
                       parent_index - rev_index > 1 and \
189
                       not is_direct_parent_line:
190
                        parent_node = linegraph[parent_index][1]
191
                        if parent_node:
192
                            parent_col_index = parent_node[0]
193
                        else:
194
                            parent_col_index = None
195
                        col_search_order = \
196
                                _line_col_search_order(columns,
197
                                                       parent_col_index,
198
                                                       branch_line[BL_COL_INDEX])
199
                        col_index = _append_line(columns,
200
                                                 (rev_index+1, parent_index-1),
201
                                                 empty_column,
202
                                                 col_search_order)
203
                    lines.append((rev_index, parent_index, col_index))
204
    
205
    for (child_index, parent_index, line_col_index) in lines:
206
        child_col_index = linegraph[child_index][1][0]
207
        
208
        parent_node = linegraph[parent_index][1]
209
        parent_col_index = parent_node[0]
210
        color = parent_node[1]
211
        
212
        if line_col_index:
213
            # line from the child's column to the lines column
214
            linegraph[child_index][2].append(
215
                (child_col_index,
216
                 line_col_index,
217
                 color))
218
            # lines down the line's column
219
            for line_part_index in range(child_index+1, parent_index-1):
220
                linegraph[line_part_index][2].append(
221
                    (line_col_index,   
222
                     line_col_index,
223
                     color))
224
            # line from the line's column to the parent's column
225
            linegraph[parent_index-1][2].append(
226
                (line_col_index,
227
                 parent_col_index,
228
                 color))
229
        else:
230
            # lines down the child's column
231
            for line_part_index in range(child_index, parent_index-1):
232
                linegraph[line_part_index][2].append(
233
                    (child_col_index,   
234
                     child_col_index,
235
                     color))
236
            # line from the child's column to the parent's column
237
            linegraph[parent_index-1][2].append(
238
                (child_col_index,
239
                 parent_col_index,
240
                 color))
241
    
242
    return (linegraph, revid_index, len(columns))
243
244
def _branch_line_col_search_order(columns, parent_col_index):
245
    return range(parent_col_index, len(columns)) + \
246
           range(parent_col_index-1, -1, -1)
247
248
def _line_col_search_order(columns, parent_col_index, child_col_index):
249
    dest_col_indexes = []
250
    if parent_col_index is not None:
251
        dest_col_indexes.append(parent_col_index)
252
    else:
253
        dest_col_indexes.append(child_col_index)
254
    dest_col_indexes.append(child_col_index)
255
    dest_col_indexes.sort()
256
    col_search_order = range(dest_col_indexes[1], dest_col_indexes[0] -1, -1) 
257
    i = 1
258
    while dest_col_indexes[1] + i < len(columns) or \
259
          dest_col_indexes[0] - i > -1:
260
        if dest_col_indexes[1] + i < len(columns):
261
            col_search_order.append(dest_col_indexes[1] + i)
262
        if dest_col_indexes[0] - i > -1:
263
            col_search_order.append(dest_col_indexes[0] - i)
264
        i += 1
265
    return col_search_order
266
267
def _append_line(columns, line, empty_column, col_search_order):
268
    line_range = range(line[0], line[1]+1)
269
    
270
    for col_index in col_search_order:
271
        column = columns[col_index]
272
        has_overlaping_line = False
273
        for row_index in line_range:
274
            if column[row_index]:
275
                has_overlaping_line = True
276
                break
277
        if not has_overlaping_line:
278
            break
279
    else:
280
        col_index = len(columns)
281
        column = list(empty_column)
282
        columns.append(column)
283
    
284
    for row_index in line_range:
285
        column[row_index] = True
286
    return col_index
27 by David Allouche
refactor distances
287
2 by Scott James Remnant
Split the same branch functionality out into a separate function so
288
def same_branch(a, b):
289
    """Return whether we think revisions a and b are on the same branch."""
290
    if len(a.parent_ids) == 1:
291
        # Defacto same branch if only parent
292
        return True
293
    elif a.committer == b.committer:
294
        # Same committer so may as well be
295
        return True
296
    else:
297
        return False