~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
143 by Aaron Bentley
Used rsvga for nice antialiasing
2
from dotgraph import mail_map
128 by Aaron Bentley
Got initial graphing functionality working
3
from bzrlib.branch import Branch
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
4
from bzrlib.errors import BzrCommandError, NoCommonRoot
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
7
from bzrlib.revision import combined_graph
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
128 by Aaron Bentley
Got initial graphing functionality working
20
def add_relations(rev_id):
21
    if rev_id in ancestors:
22
        return
23
    print rev_id
24
    if rev_id not in nodes:
25
        nodes[rev_id] = Node("n%d" % counter, label = rev_id)
26
        counter += 1
27
    revision = branch.get_revision(rev_id)
28
    ancestors [rev_id] = []
29
    for p in (p.revision_id for p in revision.parents):
30
        add_relations(p)
31
        if p not in descendants:
32
            descendants[p] = []
33
        descendants[p].append(rev_id)
34
        ancestors [rev_id].append(rev_id)
35
130 by Aaron Bentley
Added committer to revisions
36
def short_committer(committer):
135 by Aaron Bentley
Enhanced revision-crediting
37
    new_committer = re.sub('<.*>', '', committer).strip(' ')
130 by Aaron Bentley
Added committer to revisions
38
    if len(new_committer) < 2:
39
        return committer
40
    return new_committer
136 by Aaron Bentley
Allowed disabling ancestry collapsing
41
145 by aaron.bentley at utoronto
Reduced graph-collapsing aggressivness
42
def can_skip(rev_id, descendants, ancestors):
43
    if rev_id not in descendants:
44
        return False
45
    elif len(ancestors[rev_id]) != 1:
46
        return False
47
    elif len(descendants[ancestors[rev_id][0]]) != 1:
48
        return False
49
    elif len(descendants[rev_id]) != 1:
50
        return False
51
    else:
52
        return True
136 by Aaron Bentley
Allowed disabling ancestry collapsing
53
54
def compact_descendants(descendants, ancestors):
55
    new_descendants={}
56
    skip = set()
57
    for me, my_descendants in descendants.iteritems():
58
        if me in skip:
59
            continue
60
        new_descendants[me] = []
61
        for descendant in my_descendants:
62
            new_descendant = descendant
145 by aaron.bentley at utoronto
Reduced graph-collapsing aggressivness
63
            while can_skip(new_descendant, descendants, ancestors):
136 by Aaron Bentley
Allowed disabling ancestry collapsing
64
                skip.add(new_descendant)
65
                if new_descendant in new_descendants:
66
                    del new_descendants[new_descendant]
67
                new_descendant = descendants[new_descendant][0]
68
            new_descendants[me].append(new_descendant)
69
    return new_descendants    
70
71
72
def graph_ancestry(branch, collapse=True):
128 by Aaron Bentley
Got initial graphing functionality working
73
    nodes = {}
74
    q = ((i+1, n) for (i, n) in enumerate(branch.revision_history()))
75
    r = 1
142 by Aaron Bentley
Clustered the branch revision history
76
    try:
77
        branch_name = os.path.basename(branch.base)
78
    except AttributeError:
79
        branch_name = "main"
128 by Aaron Bentley
Got initial graphing functionality working
80
    for (revno, rev_id) in q:
142 by Aaron Bentley
Clustered the branch revision history
81
        nodes[rev_id] = Node("R%d" % revno, color="#ffff00", rev_id=rev_id, 
82
                             cluster=branch_name)
128 by Aaron Bentley
Got initial graphing functionality working
83
84
    ancestors = {} 
85
    descendants = {}
86
    counter = 0
87
    lines = [branch.last_patch()]
88
    while len(lines) > 0:
89
        new_lines = set()
90
        for rev_id in lines:
91
            if rev_id not in nodes:
135 by Aaron Bentley
Enhanced revision-crediting
92
                nodes[rev_id] = Node("n%d" % counter, label=rev_id, 
93
                                     rev_id=rev_id)
128 by Aaron Bentley
Got initial graphing functionality working
94
                counter+=1
95
                
96
            try:
97
                revision = branch.get_revision(rev_id)
98
            except bzrlib.errors.NoSuchRevision:
137 by Aaron Bentley
Put dotted outlines on missing revisions
99
                nodes[rev_id].node_style.append('dotted')
128 by Aaron Bentley
Got initial graphing functionality working
100
                continue
130 by Aaron Bentley
Added committer to revisions
101
            if nodes[rev_id].committer is None:
102
                nodes[rev_id].committer = short_committer(revision.committer)
128 by Aaron Bentley
Got initial graphing functionality working
103
            parent_ids = [r.revision_id for r in revision.parents]
104
            ancestors [rev_id] = parent_ids
105
            for parent in parent_ids:
106
                if parent not in ancestors:
107
                    new_lines.add(parent)
108
                    descendants[parent] = []
109
                descendants[parent].append(rev_id)
110
        lines = new_lines
111
    node_relations = []
136 by Aaron Bentley
Allowed disabling ancestry collapsing
112
113
    if collapse:
114
        visible_descendants = compact_descendants(descendants, ancestors)
115
    else:
116
        visible_descendants = descendants
129 by Aaron Bentley
Added collapsing for lines of development.
117
                
136 by Aaron Bentley
Allowed disabling ancestry collapsing
118
    for key, values in visible_descendants.iteritems():
128 by Aaron Bentley
Got initial graphing functionality working
119
        for value in values:
120
            node_relations.append((nodes[key], nodes[value]))
121
    return node_relations
131 by Aaron Bentley
Added required filename parameter
122
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
123
def graph_merge_pick(branch, other_branch):
124
    greedy_fetch(branch, other_branch)
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
125
    revision_a = branch.last_patch()
126
    revision_b = other_branch.last_patch()
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
127
    try:
128
        root, ancestors, descendants, common = \
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
129
            combined_graph(revision_a, revision_b, branch)
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
130
    except bzrlib.errors.NoCommonRoot:
131
        raise bzrlib.errors.NoCommonAncestor(revision_a, revision_b)
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
132
    distances = node_distances(descendants, ancestors, root)
133
    base = select_farthest(distances, common)
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
134
    n_history = branch.revision_history()
135
    m_history = other_branch.revision_history()
136
    dot_nodes = {}
137
    def dot_node(node, num):
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
138
        try:
139
            n_rev = n_history.index(node) + 1
140
        except ValueError:
141
            n_rev = None
142
        try:
143
            m_rev = m_history.index(node) + 1
144
        except ValueError:
145
            m_rev = None
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
146
        if (n_rev, m_rev) == (None, None):
157 by Aaron Bentley
Got graph showing merge selection decently
147
            name = node[-4:]
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
148
            cluster = None
149
        elif n_rev == m_rev:
150
            name = "rR%d" % n_rev
151
        else:
152
            namelist = []
153
            for prefix, num in (('r', n_rev), ('R', m_rev)):
154
                if num is not None:
155
                    namelist.append("%s%d" % (prefix, num))
156
            name = ' '.join(namelist)
157
        if None not in (n_rev, m_rev):
158
            cluster = "common_history"
159
            color = "#ff9900"
160
        elif (None, None) == (n_rev, m_rev):
161
            cluster = None
162
            if node in common:
163
                color = "#6699ff"
164
            else:
165
                color = None
166
        elif n_rev is not None:
167
            cluster = "my_history"
168
            color = "#ffff00"
169
        else:
170
            assert m_rev is not None
171
            cluster = "other_history"
172
            color = "#ff0000"
173
        if node == base:
174
            color = "#33ff99"
175
176
        label = [name]
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
177
        if node in distances:
157 by Aaron Bentley
Got graph showing merge selection decently
178
            label.append('d%d' % distances[node])
179
        return Node("n%d" % num, color=color, label="\\n".join(label), 
180
                    rev_id=node, cluster=cluster)
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
181
            
182
            
183
    for num,node in enumerate(descendants):
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
184
        dot_nodes[node] = dot_node(node, num)
185
186
    node_relations = []
157 by Aaron Bentley
Got graph showing merge selection decently
187
    for node, parents in ancestors.iteritems():
188
        if node not in dot_nodes:
189
            dot_nodes[node] = dot_node(node, 100000)
190
        for parent in parents:
191
            node_relations.append((dot_nodes[parent], dot_nodes[node]))
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
192
    return node_relations
155 by Aaron Bentley
Did a bunch of work on merge-base graphs
193
194
143 by Aaron Bentley
Used rsvga for nice antialiasing
195
def write_ancestry_file(branch, filename, collapse=True, antialias=True):
158 by Aaron Bentley
Updated to match API changes
196
    b = Branch.open_containing(branch)
156 by Aaron Bentley
Switched to merge base pick graphing temporarily
197
    relations = graph_merge_pick(b, b)
134 by Aaron Bentley
support multiple image formats for graph-ancestry
198
    ext = filename.split('.')[-1]
143 by Aaron Bentley
Used rsvga for nice antialiasing
199
    if antialias and ext in ('png', 'jpg'):
200
        try:
201
            invoke_dot_aa(dot_output(relations), filename, ext)
202
        except NoDot, e:
203
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
204
                " is installed correctly.")
205
        except NoRsvg, e:
206
            raise BzrCommandError("Can't find 'rsvg'.  Please ensure "\
207
                "librsvg-bin is installed correctly, or use --noantialias.")
208
    elif ext in ('svg', 'svgz', 'gif', 'jpg', 'ps', 'fig', 'mif', 'png'):
139 by Aaron Bentley
Tweaked missing-dot handling
209
        try:
210
            invoke_dot(dot_output(relations), filename, ext)
211
        except NoDot, e:
212
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
143 by Aaron Bentley
Used rsvga for nice antialiasing
213
                " is installed correctly, or use --noantialias")
134 by Aaron Bentley
support multiple image formats for graph-ancestry
214
    elif ext=='dot':
215
        file(filename, 'wb').write("".join(list(dot_output(relations))))
216
    else:
217
        print "Unknown file extension: %s" % ext
131 by Aaron Bentley
Added required filename parameter
218