~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to graph.py

  • Committer: Aaron Bentley
  • Date: 2008-05-12 18:06:07 UTC
  • Revision ID: aaron@aaronbentley.com-20080512180607-dn6a55if3pk4zdju
Update to avoid deprecated API

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2005 Aaron Bentley
2
 
# <aaron.bentley@utoronto.ca>
 
2
# <aaron@aaronbentley.com>
3
3
#
4
4
#    This program is free software; you can redistribute it and/or modify
5
5
#    it under the terms of the GNU General Public License as published by
20
20
from bzrlib.branch import Branch
21
21
from bzrlib.errors import BzrCommandError, NoCommonRoot, NoSuchRevision
22
22
from bzrlib.graph import node_distances, select_farthest
23
 
from bzrlib.revision import combined_graph, revision_graph
 
23
from bzrlib.revision import combined_graph, revision_graph, NULL_REVISION
24
24
from bzrlib.revision import MultipleRevisionSources
25
25
import bzrlib.errors
26
26
import re
56
56
    for me, my_parents in ancestors.iteritems():
57
57
        if me in skip:
58
58
            continue
59
 
        new_ancestors[me] = {} 
 
59
        new_ancestors[me] = {}
60
60
        for parent in my_parents:
61
 
            new_parent = parent 
 
61
            new_parent = parent
62
62
            distance = 0
63
63
            while can_skip(new_parent, descendants, ancestors):
64
64
                if new_parent in exceptions:
69
69
                new_parent = list(ancestors[new_parent])[0]
70
70
                distance += 1
71
71
            new_ancestors[me][new_parent] = distance
72
 
    return new_ancestors    
 
72
    return new_ancestors
73
73
 
74
74
def get_rev_info(rev_id, source):
75
75
    """Return the committer, message, and date of a revision."""
77
77
    message = None
78
78
    date = None
79
79
    if rev_id == 'null:':
80
 
        return None, 'Null Revision', None
 
80
        return None, 'Null Revision', None, None
81
81
    try:
82
82
        rev = source.get_revision(rev_id)
83
83
    except NoSuchRevision:
84
84
        try:
85
85
            committer = '-'.join(rev_id.split('-')[:-2]).strip(' ')
86
86
            if committer == '':
87
 
                return None, None, None
 
87
                return None, None, None, None
88
88
        except ValueError:
89
 
            return None, None, None
 
89
            return None, None, None, None
90
90
    else:
91
91
        committer = short_committer(rev.committer)
92
92
        if rev.message is not None:
93
93
            message = rev.message.split('\n')[0]
94
94
        gmtime = time.gmtime(rev.timestamp + (rev.timezone or 0))
95
95
        date = time.strftime('%Y/%m/%d', gmtime)
 
96
        nick = rev.properties.get('branch-nick')
96
97
    if '@' in committer:
97
98
        try:
98
99
            committer = mail_map[committer]
102
103
        committer = committer_alias[committer]
103
104
    except KeyError:
104
105
        pass
105
 
    return committer, message, date
 
106
    return committer, message, nick, date
106
107
 
107
108
class Grapher(object):
 
109
 
108
110
    def __init__(self, branch, other_branch=None):
109
111
        object.__init__(self)
110
112
        self.branch = branch
111
113
        self.other_branch = other_branch
 
114
        if other_branch is not None:
 
115
            other_repo = other_branch.repository
 
116
            revision_b = self.other_branch.last_revision()
 
117
        else:
 
118
            other_repo = None
 
119
            revision_b = None
 
120
        self.graph = self.branch.repository.get_graph(other_repo)
112
121
        revision_a = self.branch.last_revision()
113
 
        if other_branch is not None:
114
 
            branch.fetch(other_branch)
115
 
            revision_b = self.other_branch.last_revision()
116
 
            try:
117
 
                self.root, self.ancestors, self.descendants, self.common = \
118
 
                    combined_graph(revision_a, revision_b,
119
 
                                   self.branch.repository)
120
 
            except bzrlib.errors.NoCommonRoot:
121
 
                raise bzrlib.errors.NoCommonAncestor(revision_a, revision_b)
122
 
        else:
123
 
            self.root, self.ancestors, self.descendants = \
124
 
                revision_graph(revision_a, branch.repository)
125
 
            self.common = []
126
 
 
 
122
        self.scan_graph(revision_a, revision_b)
127
123
        self.n_history = branch.revision_history()
128
 
        self.distances = node_distances(self.descendants, self.ancestors, 
 
124
        self.n_revnos = branch.get_revision_id_to_revno_map()
 
125
        self.distances = node_distances(self.descendants, self.ancestors,
129
126
                                        self.root)
130
127
        if other_branch is not None:
131
128
            self.base = select_farthest(self.distances, self.common)
132
 
            self.m_history = other_branch.revision_history() 
 
129
            self.m_history = other_branch.revision_history()
 
130
            self.m_revnos = other_branch.get_revision_id_to_revno_map()
 
131
            self.new_base = self.graph.find_unique_lca(revision_a,
 
132
                                                       revision_b)
 
133
            self.lcas = self.graph.find_lca(revision_a, revision_b)
133
134
        else:
134
135
            self.base = None
 
136
            self.new_base = None
 
137
            self.lcas = set()
135
138
            self.m_history = []
 
139
            self.m_revnos = {}
 
140
 
 
141
    def scan_graph(self, revision_a, revision_b):
 
142
        a_ancestors = dict(self.graph.iter_ancestry([revision_a]))
 
143
        self.ancestors = a_ancestors
 
144
        self.root = NULL_REVISION
 
145
        if revision_b is not None:
 
146
            b_ancestors = dict(self.graph.iter_ancestry([revision_b]))
 
147
            self.common = set(a_ancestors.keys())
 
148
            self.common.intersection_update(b_ancestors)
 
149
            self.ancestors.update(b_ancestors)
 
150
        else:
 
151
            self.common = []
 
152
            revision_b = None
 
153
        self.descendants = {}
 
154
        ghosts = set()
 
155
        for revision, parents in self.ancestors.iteritems():
 
156
            self.descendants.setdefault(revision, [])
 
157
            if parents is None:
 
158
                ghosts.add(revision)
 
159
                parents = [NULL_REVISION]
 
160
            for parent in parents:
 
161
                self.descendants.setdefault(parent, []).append(revision)
 
162
        for ghost in ghosts:
 
163
            self.ancestors[ghost] = [NULL_REVISION]
 
164
 
 
165
    @staticmethod
 
166
    def _get_revno_str(prefix, revno_map, revision_id):
 
167
        try:
 
168
            revno = revno_map[revision_id]
 
169
        except KeyError:
 
170
            return None
 
171
        return '%s%s' % (prefix, '.'.join(str(n) for n in revno))
136
172
 
137
173
    def dot_node(self, node, num):
138
174
        try:
144
180
        except ValueError:
145
181
            m_rev = None
146
182
        if (n_rev, m_rev) == (None, None):
147
 
            name = node[-5:]
 
183
            name = self._get_revno_str('r', self.n_revnos, node)
 
184
            if name is None:
 
185
                name = self._get_revno_str('R', self.m_revnos, node)
 
186
            if name is None:
 
187
                name = node[-5:]
148
188
            cluster = None
149
189
        elif n_rev == m_rev:
150
190
            name = "rR%d" % n_rev
170
210
            assert m_rev is not None
171
211
            cluster = "other_history"
172
212
            color = "#ff0000"
 
213
        if node in self.lcas:
 
214
            color = "#9933cc"
173
215
        if node == self.base:
174
 
            color = "#33ff99"
 
216
            color = "#669933"
 
217
            if node == self.new_base:
 
218
                color = "#33ff33"
 
219
        if node == self.new_base:
 
220
            color = '#33cc99'
175
221
 
176
222
        label = [name]
177
 
        committer, message, date = get_rev_info(node, self.branch.repository)
 
223
        committer, message, nick, date = get_rev_info(node,
 
224
                                                      self.branch.repository)
178
225
        if committer is not None:
179
226
            label.append(committer)
180
227
 
 
228
        if nick is not None:
 
229
            label.append(nick)
 
230
 
181
231
        if date is not None:
182
232
            label.append(date)
183
233
 
187
237
        else:
188
238
            rank = None
189
239
 
190
 
        d_node = Node("n%d" % num, color=color, label="\\n".join(label), 
 
240
        d_node = Node("n%d" % num, color=color, label="\\n".join(label),
191
241
                    rev_id=node, cluster=cluster, message=message,
192
242
                    date=date)
193
243
        d_node.rank = rank
196
246
            d_node.node_style.append('dotted')
197
247
 
198
248
        return d_node
199
 
        
200
 
    def get_relations(self, collapse=False):
 
249
 
 
250
    def get_relations(self, collapse=False, max_distance=None):
201
251
        dot_nodes = {}
202
252
        node_relations = []
203
253
        num = 0
204
254
        if collapse:
205
 
            visible_ancestors = compact_ancestors(self.descendants, 
206
 
                                                  self.ancestors, (self.base,))
 
255
            exceptions = self.lcas.union([self.base, self.new_base])
 
256
            visible_ancestors = compact_ancestors(self.descendants,
 
257
                                                  self.ancestors,
 
258
                                                  exceptions)
207
259
        else:
208
 
            visible_ancestors = self.ancestors
 
260
            visible_ancestors = {}
 
261
            for revision, parents in self.ancestors.iteritems():
 
262
                visible_ancestors[revision] = dict((p, 0) for p in parents)
 
263
        if max_distance is not None:
 
264
            min_distance = max(self.distances.values()) - max_distance
 
265
            visible_ancestors = dict((n, p) for n, p in
 
266
                                     visible_ancestors.iteritems() if
 
267
                                     self.distances[n] >= min_distance)
209
268
        for node, parents in visible_ancestors.iteritems():
210
269
            if node not in dot_nodes:
211
270
                dot_nodes[node] = self.dot_node(node, num)
212
271
                num += 1
213
 
            if visible_ancestors is self.ancestors:
214
 
                parent_iter = ((f, 0) for f in parents)
215
 
            else:
216
 
                parent_iter = (f for f in parents.iteritems())
217
 
            for parent, skipped in parent_iter:
 
272
            for parent, skipped in parents.iteritems():
218
273
                if parent not in dot_nodes:
219
274
                    dot_nodes[parent] = self.dot_node(parent, num)
220
275
                    num += 1
226
281
 
227
282
 
228
283
def write_ancestry_file(branch, filename, collapse=True, antialias=True,
229
 
                        merge_branch=None, ranking="forced"):
 
284
                        merge_branch=None, ranking="forced", max_distance=None):
230
285
    b = Branch.open_containing(branch)[0]
231
286
    if merge_branch is not None:
232
287
        m = Branch.open_containing(merge_branch)[0]
238
293
            m.lock_read()
239
294
        try:
240
295
            grapher = Grapher(b, m)
241
 
            relations = grapher.get_relations(collapse)
 
296
            relations = grapher.get_relations(collapse, max_distance)
242
297
        finally:
243
298
            if m is not None:
244
299
                m.unlock()
250
305
    done = False
251
306
    if ext not in RSVG_OUTPUT_TYPES:
252
307
        antialias = False
253
 
    if antialias: 
 
308
    if antialias:
254
309
        output = list(output)
255
310
        try:
256
311
            invoke_dot_aa(output, filename, ext)
268
323
            done = True
269
324
        except NoDot, e:
270
325
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
271
 
                " is installed correctly, or use --noantialias")
 
326
                " is installed correctly.")
272
327
    elif ext == 'dot' and not done:
273
328
        my_file = file(filename, 'wb')
274
329
        for fragment in output:
278
333
            invoke_dot_html(output, filename)
279
334
        except NoDot, e:
280
335
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
281
 
                " is installed correctly, or use --noantialias")
 
336
                " is installed correctly.")
282
337
    elif not done:
283
338
        print "Unknown file extension: %s" % ext
284