~abentley/bzrtools/bzrtools.dev

140 by Aaron Bentley
Mapped some email addresses to names
1
from dotgraph import Node, dot_output, invoke_dot, NoDot, mail_map
128 by Aaron Bentley
Got initial graphing functionality working
2
from bzrlib.branch import Branch
139 by Aaron Bentley
Tweaked missing-dot handling
3
from bzrlib.errors import BzrCommandError
128 by Aaron Bentley
Got initial graphing functionality working
4
import bzrlib.errors
130 by Aaron Bentley
Added committer to revisions
5
import re
142 by Aaron Bentley
Clustered the branch revision history
6
import os.path
128 by Aaron Bentley
Got initial graphing functionality working
7
140 by Aaron Bentley
Mapped some email addresses to names
8
mail_map.update({'aaron.bentley@utoronto.ca'     : 'Aaron Bentley',
9
                 'abentley@panoramicfeedback.com': 'Aaron Bentley',
10
                 'john@arbash-meinel.com'        : 'John A. Meinel',
11
                 'mbp@sourcefrog.net'            : 'Martin Pool'
12
                })
13
128 by Aaron Bentley
Got initial graphing functionality working
14
def add_relations(rev_id):
15
    if rev_id in ancestors:
16
        return
17
    print rev_id
18
    if rev_id not in nodes:
19
        nodes[rev_id] = Node("n%d" % counter, label = rev_id)
20
        counter += 1
21
    revision = branch.get_revision(rev_id)
22
    ancestors [rev_id] = []
23
    for p in (p.revision_id for p in revision.parents):
24
        add_relations(p)
25
        if p not in descendants:
26
            descendants[p] = []
27
        descendants[p].append(rev_id)
28
        ancestors [rev_id].append(rev_id)
29
130 by Aaron Bentley
Added committer to revisions
30
def short_committer(committer):
135 by Aaron Bentley
Enhanced revision-crediting
31
    new_committer = re.sub('<.*>', '', committer).strip(' ')
130 by Aaron Bentley
Added committer to revisions
32
    if len(new_committer) < 2:
33
        return committer
34
    return new_committer
136 by Aaron Bentley
Allowed disabling ancestry collapsing
35
36
37
def compact_descendants(descendants, ancestors):
38
    new_descendants={}
39
    skip = set()
40
    for me, my_descendants in descendants.iteritems():
41
        if me in skip:
42
            continue
43
        new_descendants[me] = []
44
        for descendant in my_descendants:
45
            new_descendant = descendant
46
            while new_descendant in descendants and \
47
                len(ancestors[new_descendant]) == 1 and \
48
                len(descendants[new_descendant]) == 1:
49
                skip.add(new_descendant)
50
                if new_descendant in new_descendants:
51
                    del new_descendants[new_descendant]
52
                new_descendant = descendants[new_descendant][0]
53
            new_descendants[me].append(new_descendant)
54
    return new_descendants    
55
56
57
def graph_ancestry(branch, collapse=True):
128 by Aaron Bentley
Got initial graphing functionality working
58
    nodes = {}
59
    q = ((i+1, n) for (i, n) in enumerate(branch.revision_history()))
60
    r = 1
142 by Aaron Bentley
Clustered the branch revision history
61
    try:
62
        branch_name = os.path.basename(branch.base)
63
    except AttributeError:
64
        branch_name = "main"
128 by Aaron Bentley
Got initial graphing functionality working
65
    for (revno, rev_id) in q:
142 by Aaron Bentley
Clustered the branch revision history
66
        nodes[rev_id] = Node("R%d" % revno, color="#ffff00", rev_id=rev_id, 
67
                             cluster=branch_name)
128 by Aaron Bentley
Got initial graphing functionality working
68
69
    ancestors = {} 
70
    descendants = {}
71
    counter = 0
72
    lines = [branch.last_patch()]
73
    while len(lines) > 0:
74
        new_lines = set()
75
        for rev_id in lines:
76
            if rev_id not in nodes:
135 by Aaron Bentley
Enhanced revision-crediting
77
                nodes[rev_id] = Node("n%d" % counter, label=rev_id, 
78
                                     rev_id=rev_id)
128 by Aaron Bentley
Got initial graphing functionality working
79
                counter+=1
80
                
81
            try:
82
                revision = branch.get_revision(rev_id)
83
            except bzrlib.errors.NoSuchRevision:
137 by Aaron Bentley
Put dotted outlines on missing revisions
84
                nodes[rev_id].node_style.append('dotted')
128 by Aaron Bentley
Got initial graphing functionality working
85
                continue
130 by Aaron Bentley
Added committer to revisions
86
            if nodes[rev_id].committer is None:
87
                nodes[rev_id].committer = short_committer(revision.committer)
128 by Aaron Bentley
Got initial graphing functionality working
88
            parent_ids = [r.revision_id for r in revision.parents]
89
            ancestors [rev_id] = parent_ids
90
            for parent in parent_ids:
91
                if parent not in ancestors:
92
                    new_lines.add(parent)
93
                    descendants[parent] = []
94
                descendants[parent].append(rev_id)
95
        lines = new_lines
96
    node_relations = []
136 by Aaron Bentley
Allowed disabling ancestry collapsing
97
98
    if collapse:
99
        visible_descendants = compact_descendants(descendants, ancestors)
100
    else:
101
        visible_descendants = descendants
129 by Aaron Bentley
Added collapsing for lines of development.
102
                
136 by Aaron Bentley
Allowed disabling ancestry collapsing
103
    for key, values in visible_descendants.iteritems():
128 by Aaron Bentley
Got initial graphing functionality working
104
        for value in values:
105
            node_relations.append((nodes[key], nodes[value]))
106
    return node_relations
131 by Aaron Bentley
Added required filename parameter
107
136 by Aaron Bentley
Allowed disabling ancestry collapsing
108
def write_ancestry_file(branch, filename, collapse=True):
131 by Aaron Bentley
Added required filename parameter
109
    b = Branch(branch)
136 by Aaron Bentley
Allowed disabling ancestry collapsing
110
    relations = graph_ancestry(b, collapse)
134 by Aaron Bentley
support multiple image formats for graph-ancestry
111
    ext = filename.split('.')[-1]
112
    if ext in ('svg', 'svgz', 'gif', 'jpg', 'ps', 'fig', 'mif', 'png'):
139 by Aaron Bentley
Tweaked missing-dot handling
113
        try:
114
            invoke_dot(dot_output(relations), filename, ext)
115
        except NoDot, e:
116
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
117
                " is installed correctly.")
134 by Aaron Bentley
support multiple image formats for graph-ancestry
118
    elif ext=='dot':
119
        file(filename, 'wb').write("".join(list(dot_output(relations))))
120
    else:
121
        print "Unknown file extension: %s" % ext
131 by Aaron Bentley
Added required filename parameter
122