~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to graph.py

  • Committer: Aaron Bentley
  • Date: 2008-11-05 00:11:09 UTC
  • mto: This revision was merged to the branch mainline in revision 678.
  • Revision ID: aaron@aaronbentley.com-20081105001109-yt2dp0h5h3ssb7xt
Restore runtime ignore for .shelf

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Aaron Bentley
2
 
# <aaron.bentley@utoronto.ca>
 
1
# Copyright (C) 2005, 2008 Aaron Bentley
 
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
14
14
#    You should have received a copy of the GNU General Public License
15
15
#    along with this program; if not, write to the Free Software
16
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
 
from bzrtools import short_committer
18
 
from dotgraph import Node, dot_output, invoke_dot, invoke_dot_aa, NoDot, NoRsvg
19
 
from dotgraph import RSVG_OUTPUT_TYPES, DOT_OUTPUT_TYPES, Edge, invoke_dot_html
 
17
 
 
18
 
 
19
import time
 
20
 
20
21
from bzrlib.branch import Branch
21
 
from bzrlib.errors import BzrCommandError, NoCommonRoot, NoSuchRevision
 
22
from bzrlib.errors import BzrCommandError, NoSuchRevision
22
23
from bzrlib.graph import node_distances, select_farthest
23
 
from bzrlib.revision import combined_graph, revision_graph
24
 
from bzrlib.revision import MultipleRevisionSources
25
 
import bzrlib.errors
26
 
import re
27
 
import os.path
28
 
import time
 
24
from bzrlib.revision import NULL_REVISION
 
25
 
 
26
from bzrtools import short_committer
 
27
from dotgraph import (
 
28
    dot_output,
 
29
    DOT_OUTPUT_TYPES,
 
30
    Edge,
 
31
    invoke_dot,
 
32
    invoke_dot_aa,
 
33
    invoke_dot_html,
 
34
    Node,
 
35
    NoDot,
 
36
    NoRsvg,
 
37
    RSVG_OUTPUT_TYPES,
 
38
    )
 
39
 
29
40
 
30
41
mail_map = {'aaron.bentley@utoronto.ca'     : 'Aaron Bentley',
31
42
            'abentley@panoramicfeedback.com': 'Aaron Bentley',
56
67
    for me, my_parents in ancestors.iteritems():
57
68
        if me in skip:
58
69
            continue
59
 
        new_ancestors[me] = {} 
 
70
        new_ancestors[me] = {}
60
71
        for parent in my_parents:
61
 
            new_parent = parent 
 
72
            new_parent = parent
62
73
            distance = 0
63
74
            while can_skip(new_parent, descendants, ancestors):
64
75
                if new_parent in exceptions:
69
80
                new_parent = list(ancestors[new_parent])[0]
70
81
                distance += 1
71
82
            new_ancestors[me][new_parent] = distance
72
 
    return new_ancestors    
 
83
    return new_ancestors
73
84
 
74
85
def get_rev_info(rev_id, source):
75
86
    """Return the committer, message, and date of a revision."""
106
117
    return committer, message, nick, date
107
118
 
108
119
class Grapher(object):
 
120
 
109
121
    def __init__(self, branch, other_branch=None):
110
122
        object.__init__(self)
111
123
        self.branch = branch
112
124
        self.other_branch = other_branch
 
125
        if other_branch is not None:
 
126
            other_repo = other_branch.repository
 
127
            revision_b = self.other_branch.last_revision()
 
128
        else:
 
129
            other_repo = None
 
130
            revision_b = None
 
131
        self.graph = self.branch.repository.get_graph(other_repo)
113
132
        revision_a = self.branch.last_revision()
114
 
        if other_branch is not None:
115
 
            branch.fetch(other_branch)
116
 
            revision_b = self.other_branch.last_revision()
117
 
            try:
118
 
                self.root, self.ancestors, self.descendants, self.common = \
119
 
                    combined_graph(revision_a, revision_b,
120
 
                                   self.branch.repository)
121
 
            except bzrlib.errors.NoCommonRoot:
122
 
                raise bzrlib.errors.NoCommonAncestor(revision_a, revision_b)
123
 
        else:
124
 
            self.root, self.ancestors, self.descendants = \
125
 
                revision_graph(revision_a, branch.repository)
126
 
            self.common = []
127
 
 
 
133
        self.scan_graph(revision_a, revision_b)
128
134
        self.n_history = branch.revision_history()
129
 
        self.distances = node_distances(self.descendants, self.ancestors, 
 
135
        self.n_revnos = branch.get_revision_id_to_revno_map()
 
136
        self.distances = node_distances(self.descendants, self.ancestors,
130
137
                                        self.root)
131
138
        if other_branch is not None:
132
139
            self.base = select_farthest(self.distances, self.common)
133
 
            self.m_history = other_branch.revision_history() 
 
140
            self.m_history = other_branch.revision_history()
 
141
            self.m_revnos = other_branch.get_revision_id_to_revno_map()
 
142
            self.new_base = self.graph.find_unique_lca(revision_a,
 
143
                                                       revision_b)
 
144
            self.lcas = self.graph.find_lca(revision_a, revision_b)
134
145
        else:
135
146
            self.base = None
 
147
            self.new_base = None
 
148
            self.lcas = set()
136
149
            self.m_history = []
 
150
            self.m_revnos = {}
 
151
 
 
152
    def scan_graph(self, revision_a, revision_b):
 
153
        a_ancestors = dict(self.graph.iter_ancestry([revision_a]))
 
154
        self.ancestors = a_ancestors
 
155
        self.root = NULL_REVISION
 
156
        if revision_b is not None:
 
157
            b_ancestors = dict(self.graph.iter_ancestry([revision_b]))
 
158
            self.common = set(a_ancestors.keys())
 
159
            self.common.intersection_update(b_ancestors)
 
160
            self.ancestors.update(b_ancestors)
 
161
        else:
 
162
            self.common = []
 
163
            revision_b = None
 
164
        self.descendants = {}
 
165
        ghosts = set()
 
166
        for revision, parents in self.ancestors.iteritems():
 
167
            self.descendants.setdefault(revision, [])
 
168
            if parents is None:
 
169
                ghosts.add(revision)
 
170
                parents = [NULL_REVISION]
 
171
            for parent in parents:
 
172
                self.descendants.setdefault(parent, []).append(revision)
 
173
        for ghost in ghosts:
 
174
            self.ancestors[ghost] = [NULL_REVISION]
 
175
 
 
176
    @staticmethod
 
177
    def _get_revno_str(prefix, revno_map, revision_id):
 
178
        try:
 
179
            revno = revno_map[revision_id]
 
180
        except KeyError:
 
181
            return None
 
182
        return '%s%s' % (prefix, '.'.join(str(n) for n in revno))
137
183
 
138
184
    def dot_node(self, node, num):
139
185
        try:
145
191
        except ValueError:
146
192
            m_rev = None
147
193
        if (n_rev, m_rev) == (None, None):
148
 
            name = node[-5:]
 
194
            name = self._get_revno_str('r', self.n_revnos, node)
 
195
            if name is None:
 
196
                name = self._get_revno_str('R', self.m_revnos, node)
 
197
            if name is None:
 
198
                name = node[-5:]
149
199
            cluster = None
150
200
        elif n_rev == m_rev:
151
201
            name = "rR%d" % n_rev
171
221
            assert m_rev is not None
172
222
            cluster = "other_history"
173
223
            color = "#ff0000"
 
224
        if node in self.lcas:
 
225
            color = "#9933cc"
174
226
        if node == self.base:
175
 
            color = "#33ff99"
 
227
            color = "#669933"
 
228
            if node == self.new_base:
 
229
                color = "#33ff33"
 
230
        if node == self.new_base:
 
231
            color = '#33cc99'
176
232
 
177
233
        label = [name]
178
 
        committer, message, nick, date = get_rev_info(node, 
 
234
        committer, message, nick, date = get_rev_info(node,
179
235
                                                      self.branch.repository)
180
236
        if committer is not None:
181
237
            label.append(committer)
192
248
        else:
193
249
            rank = None
194
250
 
195
 
        d_node = Node("n%d" % num, color=color, label="\\n".join(label), 
 
251
        d_node = Node("n%d" % num, color=color, label="\\n".join(label),
196
252
                    rev_id=node, cluster=cluster, message=message,
197
253
                    date=date)
198
254
        d_node.rank = rank
201
257
            d_node.node_style.append('dotted')
202
258
 
203
259
        return d_node
204
 
       
 
260
 
205
261
    def get_relations(self, collapse=False, max_distance=None):
206
262
        dot_nodes = {}
207
263
        node_relations = []
208
264
        num = 0
209
265
        if collapse:
210
 
            visible_ancestors = compact_ancestors(self.descendants, 
211
 
                                                  self.ancestors, (self.base,))
 
266
            exceptions = self.lcas.union([self.base, self.new_base])
 
267
            visible_ancestors = compact_ancestors(self.descendants,
 
268
                                                  self.ancestors,
 
269
                                                  exceptions)
212
270
        else:
213
 
            visible_ancestors = self.ancestors
 
271
            visible_ancestors = {}
 
272
            for revision, parents in self.ancestors.iteritems():
 
273
                visible_ancestors[revision] = dict((p, 0) for p in parents)
214
274
        if max_distance is not None:
215
275
            min_distance = max(self.distances.values()) - max_distance
216
 
            visible_ancestors = dict((n, p) for n, p in visible_ancestors.iteritems() if
217
 
                    self.distances[n] >= min_distance)
 
276
            visible_ancestors = dict((n, p) for n, p in
 
277
                                     visible_ancestors.iteritems() if
 
278
                                     self.distances[n] >= min_distance)
218
279
        for node, parents in visible_ancestors.iteritems():
219
280
            if node not in dot_nodes:
220
281
                dot_nodes[node] = self.dot_node(node, num)
221
282
                num += 1
222
 
            if visible_ancestors is self.ancestors:
223
 
                parent_iter = ((f, 0) for f in parents)
224
 
            else:
225
 
                parent_iter = (f for f in parents.iteritems())
226
 
            for parent, skipped in parent_iter:
 
283
            for parent, skipped in parents.iteritems():
227
284
                if parent not in dot_nodes:
228
285
                    dot_nodes[parent] = self.dot_node(parent, num)
229
286
                    num += 1
259
316
    done = False
260
317
    if ext not in RSVG_OUTPUT_TYPES:
261
318
        antialias = False
262
 
    if antialias: 
 
319
    if antialias:
263
320
        output = list(output)
264
321
        try:
265
322
            invoke_dot_aa(output, filename, ext)
277
334
            done = True
278
335
        except NoDot, e:
279
336
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
280
 
                " is installed correctly, or use --noantialias")
 
337
                " is installed correctly.")
281
338
    elif ext == 'dot' and not done:
282
339
        my_file = file(filename, 'wb')
283
340
        for fragment in output:
287
344
            invoke_dot_html(output, filename)
288
345
        except NoDot, e:
289
346
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
290
 
                " is installed correctly, or use --noantialias")
 
347
                " is installed correctly.")
291
348
    elif not done:
292
349
        print "Unknown file extension: %s" % ext
293