~abentley/bzrtools/bzrtools.dev

146 by aaron.bentley at utoronto
Patch for missing NoRsvg
1
from dotgraph import Node, dot_output, invoke_dot, invoke_dot_aa, NoDot, NoRsvg
167 by Aaron Bentley
Moved extention lists to dotgraph
2
from dotgraph import mail_map, RSVG_OUTPUT_TYPES, DOT_OUTPUT_TYPES
128 by Aaron Bentley
Got initial graphing functionality working
3
from bzrlib.branch import Branch
161 by Aaron Bentley
Added committer names to nodes
4
from bzrlib.errors import BzrCommandError, NoCommonRoot, NoSuchRevision
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
5
from bzrlib.fetch import greedy_fetch
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
6
from bzrlib.graph import node_distances, select_farthest
161 by Aaron Bentley
Added committer names to nodes
7
from bzrlib.revision import combined_graph, MultipleRevisionSources
128 by Aaron Bentley
Got initial graphing functionality working
8
import bzrlib.errors
130 by Aaron Bentley
Added committer to revisions
9
import re
142 by Aaron Bentley
Clustered the branch revision history
10
import os.path
128 by Aaron Bentley
Got initial graphing functionality working
11
140 by Aaron Bentley
Mapped some email addresses to names
12
mail_map.update({'aaron.bentley@utoronto.ca'     : 'Aaron Bentley',
13
                 'abentley@panoramicfeedback.com': 'Aaron Bentley',
151 by Aaron Bentley
Added another address to the table
14
                 'abentley@lappy'                : 'Aaron Bentley',
152.1.1 by Aaron Bentley
Matched John's name to his commit name
15
                 'john@arbash-meinel.com'        : 'John Arbash Meinel',
148 by abentley
Updated email addresses translation in graph-ancestry
16
                 'mbp@sourcefrog.net'            : 'Martin Pool',
17
                 'robertc@robertcollins.net'     : 'Robert Collins',
140 by Aaron Bentley
Mapped some email addresses to names
18
                })
19
166 by Aaron Bentley
Fixed username-in-ancestry-graph issue
20
committer_alias = {'abentley': 'Aaron Bentley'}
128 by Aaron Bentley
Got initial graphing functionality working
21
def add_relations(rev_id):
22
    if rev_id in ancestors:
23
        return
24
    print rev_id
25
    if rev_id not in nodes:
26
        nodes[rev_id] = Node("n%d" % counter, label = rev_id)
27
        counter += 1
28
    revision = branch.get_revision(rev_id)
29
    ancestors [rev_id] = []
30
    for p in (p.revision_id for p in revision.parents):
31
        add_relations(p)
32
        if p not in descendants:
33
            descendants[p] = []
34
        descendants[p].append(rev_id)
35
        ancestors [rev_id].append(rev_id)
36
130 by Aaron Bentley
Added committer to revisions
37
def short_committer(committer):
135 by Aaron Bentley
Enhanced revision-crediting
38
    new_committer = re.sub('<.*>', '', committer).strip(' ')
130 by Aaron Bentley
Added committer to revisions
39
    if len(new_committer) < 2:
40
        return committer
41
    return new_committer
136 by Aaron Bentley
Allowed disabling ancestry collapsing
42
145 by aaron.bentley at utoronto
Reduced graph-collapsing aggressivness
43
def can_skip(rev_id, descendants, ancestors):
44
    if rev_id not in descendants:
45
        return False
46
    elif len(ancestors[rev_id]) != 1:
47
        return False
48
    elif len(descendants[ancestors[rev_id][0]]) != 1:
49
        return False
50
    elif len(descendants[rev_id]) != 1:
51
        return False
52
    else:
53
        return True
136 by Aaron Bentley
Allowed disabling ancestry collapsing
54
55
def compact_descendants(descendants, ancestors):
56
    new_descendants={}
57
    skip = set()
58
    for me, my_descendants in descendants.iteritems():
59
        if me in skip:
60
            continue
61
        new_descendants[me] = []
62
        for descendant in my_descendants:
63
            new_descendant = descendant
145 by aaron.bentley at utoronto
Reduced graph-collapsing aggressivness
64
            while can_skip(new_descendant, descendants, ancestors):
136 by Aaron Bentley
Allowed disabling ancestry collapsing
65
                skip.add(new_descendant)
66
                if new_descendant in new_descendants:
67
                    del new_descendants[new_descendant]
68
                new_descendant = descendants[new_descendant][0]
69
            new_descendants[me].append(new_descendant)
70
    return new_descendants    
71
72
73
def graph_ancestry(branch, collapse=True):
128 by Aaron Bentley
Got initial graphing functionality working
74
    nodes = {}
75
    q = ((i+1, n) for (i, n) in enumerate(branch.revision_history()))
76
    r = 1
142 by Aaron Bentley
Clustered the branch revision history
77
    try:
78
        branch_name = os.path.basename(branch.base)
79
    except AttributeError:
80
        branch_name = "main"
128 by Aaron Bentley
Got initial graphing functionality working
81
    for (revno, rev_id) in q:
142 by Aaron Bentley
Clustered the branch revision history
82
        nodes[rev_id] = Node("R%d" % revno, color="#ffff00", rev_id=rev_id, 
83
                             cluster=branch_name)
128 by Aaron Bentley
Got initial graphing functionality working
84
85
    ancestors = {} 
86
    descendants = {}
87
    counter = 0
88
    lines = [branch.last_patch()]
89
    while len(lines) > 0:
90
        new_lines = set()
91
        for rev_id in lines:
92
            if rev_id not in nodes:
135 by Aaron Bentley
Enhanced revision-crediting
93
                nodes[rev_id] = Node("n%d" % counter, label=rev_id, 
94
                                     rev_id=rev_id)
128 by Aaron Bentley
Got initial graphing functionality working
95
                counter+=1
96
                
97
            try:
98
                revision = branch.get_revision(rev_id)
99
            except bzrlib.errors.NoSuchRevision:
137 by Aaron Bentley
Put dotted outlines on missing revisions
100
                nodes[rev_id].node_style.append('dotted')
128 by Aaron Bentley
Got initial graphing functionality working
101
                continue
130 by Aaron Bentley
Added committer to revisions
102
            if nodes[rev_id].committer is None:
103
                nodes[rev_id].committer = short_committer(revision.committer)
128 by Aaron Bentley
Got initial graphing functionality working
104
            parent_ids = [r.revision_id for r in revision.parents]
105
            ancestors [rev_id] = parent_ids
106
            for parent in parent_ids:
107
                if parent not in ancestors:
108
                    new_lines.add(parent)
109
                    descendants[parent] = []
110
                descendants[parent].append(rev_id)
111
        lines = new_lines
112
    node_relations = []
136 by Aaron Bentley
Allowed disabling ancestry collapsing
113
160 by Aaron Bentley
Restored old graph-ancestry functionality
114
    for node in nodes.itervalues():
115
        node.label = node.get_label()
136 by Aaron Bentley
Allowed disabling ancestry collapsing
116
    if collapse:
117
        visible_descendants = compact_descendants(descendants, ancestors)
118
    else:
119
        visible_descendants = descendants
129 by Aaron Bentley
Added collapsing for lines of development.
120
                
136 by Aaron Bentley
Allowed disabling ancestry collapsing
121
    for key, values in visible_descendants.iteritems():
128 by Aaron Bentley
Got initial graphing functionality working
122
        for value in values:
123
            node_relations.append((nodes[key], nodes[value]))
124
    return node_relations
131 by Aaron Bentley
Added required filename parameter
125
161 by Aaron Bentley
Added committer names to nodes
126
def get_committer(rev_id, source):
127
    try:
128
        committer = short_committer(source.get_revision(rev_id).committer)
129
    except NoSuchRevision:
130
        try:
166 by Aaron Bentley
Fixed username-in-ancestry-graph issue
131
            committer = '-'.join(rev_id.split('-')[:-2])\
161 by Aaron Bentley
Added committer names to nodes
132
                .strip(' ')
133
        except ValueError:
166 by Aaron Bentley
Fixed username-in-ancestry-graph issue
134
            committer = '' 
135
    if '@' in committer:
136
        try:
137
            committer = mail_map[committer]
138
        except KeyError:
139
            pass
140
    try:
141
        committer = committer_alias[committer]
142
    except KeyError:
143
        pass
144
    return committer
161 by Aaron Bentley
Added committer names to nodes
145
146
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
147
def graph_merge_pick(branch, other_branch):
148
    greedy_fetch(branch, other_branch)
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
149
    revision_a = branch.last_patch()
150
    revision_b = other_branch.last_patch()
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
151
    try:
152
        root, ancestors, descendants, common = \
162 by Aaron Bentley
reverted to using branch
153
            combined_graph(revision_a, revision_b, branch)
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
154
    except bzrlib.errors.NoCommonRoot:
155
        raise bzrlib.errors.NoCommonAncestor(revision_a, revision_b)
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
156
    distances = node_distances(descendants, ancestors, root)
157
    base = select_farthest(distances, common)
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
158
    n_history = branch.revision_history()
169 by Aaron Bentley
Got ancestry-graph showing common and revision-history nodes properly
159
    m_history = other_branch.revision_history() 
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
160
    dot_nodes = {}
161
    def dot_node(node, num):
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
162
        try:
163
            n_rev = n_history.index(node) + 1
164
        except ValueError:
165
            n_rev = None
166
        try:
167
            m_rev = m_history.index(node) + 1
168
        except ValueError:
169
            m_rev = None
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
170
        if (n_rev, m_rev) == (None, None):
168 by Aaron Bentley
lengthened graph node name to include null:
171
            name = node[-5:]
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
172
            cluster = None
173
        elif n_rev == m_rev:
174
            name = "rR%d" % n_rev
175
        else:
176
            namelist = []
164 by Aaron Bentley
Fixed varname reuse generating nodes
177
            for prefix, revno in (('r', n_rev), ('R', m_rev)):
178
                if revno is not None:
179
                    namelist.append("%s%d" % (prefix, revno))
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
180
            name = ' '.join(namelist)
181
        if None not in (n_rev, m_rev):
182
            cluster = "common_history"
183
            color = "#ff9900"
184
        elif (None, None) == (n_rev, m_rev):
185
            cluster = None
186
            if node in common:
187
                color = "#6699ff"
188
            else:
189
                color = None
190
        elif n_rev is not None:
191
            cluster = "my_history"
192
            color = "#ffff00"
193
        else:
194
            assert m_rev is not None
195
            cluster = "other_history"
196
            color = "#ff0000"
197
        if node == base:
198
            color = "#33ff99"
199
200
        label = [name]
162 by Aaron Bentley
reverted to using branch
201
        committer = get_committer(node, branch)
161 by Aaron Bentley
Added committer names to nodes
202
        if committer is not None:
203
            label.append(committer)
204
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
205
        if node in distances:
157 by Aaron Bentley
Got graph showing merge selection decently
206
            label.append('d%d' % distances[node])
207
        return Node("n%d" % num, color=color, label="\\n".join(label), 
208
                    rev_id=node, cluster=cluster)
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
209
            
210
            
211
    for num,node in enumerate(descendants):
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
212
        dot_nodes[node] = dot_node(node, num)
213
214
    node_relations = []
157 by Aaron Bentley
Got graph showing merge selection decently
215
    for node, parents in ancestors.iteritems():
216
        if node not in dot_nodes:
217
            dot_nodes[node] = dot_node(node, 100000)
218
        for parent in parents:
219
            node_relations.append((dot_nodes[parent], dot_nodes[node]))
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
220
    return node_relations
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
221
222
160 by Aaron Bentley
Restored old graph-ancestry functionality
223
def write_ancestry_file(branch, filename, collapse=True, antialias=True,
224
                        merge_branch=None):
158 by Aaron Bentley
Updated to match API changes
225
    b = Branch.open_containing(branch)
160 by Aaron Bentley
Restored old graph-ancestry functionality
226
    if merge_branch is None:
227
        relations = graph_ancestry(b, collapse)
228
    else:
165 by Aaron Bentley
Got ancestry graph working properly for merges
229
        m = Branch.open_containing(merge_branch)
160 by Aaron Bentley
Restored old graph-ancestry functionality
230
        relations = graph_merge_pick(b, m)
231
134 by Aaron Bentley
support multiple image formats for graph-ancestry
232
    ext = filename.split('.')[-1]
167 by Aaron Bentley
Moved extention lists to dotgraph
233
    if antialias and ext in RSVG_OUTPUT_TYPES:
143 by Aaron Bentley
Used rsvga for nice antialiasing
234
        try:
235
            invoke_dot_aa(dot_output(relations), filename, ext)
236
        except NoDot, e:
237
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
238
                " is installed correctly.")
239
        except NoRsvg, e:
240
            raise BzrCommandError("Can't find 'rsvg'.  Please ensure "\
241
                "librsvg-bin is installed correctly, or use --noantialias.")
167 by Aaron Bentley
Moved extention lists to dotgraph
242
    elif ext in DOT_OUTPUT_TYPES:
139 by Aaron Bentley
Tweaked missing-dot handling
243
        try:
244
            invoke_dot(dot_output(relations), filename, ext)
245
        except NoDot, e:
246
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
143 by Aaron Bentley
Used rsvga for nice antialiasing
247
                " is installed correctly, or use --noantialias")
134 by Aaron Bentley
support multiple image formats for graph-ancestry
248
    elif ext=='dot':
249
        file(filename, 'wb').write("".join(list(dot_output(relations))))
250
    else:
251
        print "Unknown file extension: %s" % ext
131 by Aaron Bentley
Added required filename parameter
252