~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to graph.py

  • Committer: Aaron Bentley
  • Date: 2007-03-07 17:34:37 UTC
  • mfrom: (517.1.2 bzrtools-0.15)
  • Revision ID: abentley@panoramicfeedback.com-20070307173437-mr9o9u21rxm94dq2
Merge 0.15.1 update

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2008 Aaron Bentley
2
 
# <aaron@aaronbentley.com>
 
1
# Copyright (C) 2005 Aaron Bentley
 
2
# <aaron.bentley@utoronto.ca>
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
 
 
18
 
 
 
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
 
20
from bzrlib.branch import Branch
 
21
from bzrlib.errors import BzrCommandError, NoCommonRoot, NoSuchRevision
 
22
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
19
28
import time
20
29
 
21
 
from bzrlib.branch import Branch
22
 
from bzrlib.errors import BzrCommandError, NoSuchRevision
23
 
from bzrlib.deprecated_graph import node_distances, select_farthest
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
 
 
40
 
 
41
30
mail_map = {'aaron.bentley@utoronto.ca'     : 'Aaron Bentley',
42
31
            'abentley@panoramicfeedback.com': 'Aaron Bentley',
43
32
            'abentley@lappy'                : 'Aaron Bentley',
67
56
    for me, my_parents in ancestors.iteritems():
68
57
        if me in skip:
69
58
            continue
70
 
        new_ancestors[me] = {}
 
59
        new_ancestors[me] = {} 
71
60
        for parent in my_parents:
72
 
            new_parent = parent
 
61
            new_parent = parent 
73
62
            distance = 0
74
63
            while can_skip(new_parent, descendants, ancestors):
75
64
                if new_parent in exceptions:
80
69
                new_parent = list(ancestors[new_parent])[0]
81
70
                distance += 1
82
71
            new_ancestors[me][new_parent] = distance
83
 
    return new_ancestors
 
72
    return new_ancestors    
84
73
 
85
74
def get_rev_info(rev_id, source):
86
75
    """Return the committer, message, and date of a revision."""
117
106
    return committer, message, nick, date
118
107
 
119
108
class Grapher(object):
120
 
 
121
109
    def __init__(self, branch, other_branch=None):
122
110
        object.__init__(self)
123
111
        self.branch = branch
124
112
        self.other_branch = other_branch
 
113
        revision_a = self.branch.last_revision()
125
114
        if other_branch is not None:
126
 
            other_repo = other_branch.repository
 
115
            branch.fetch(other_branch)
127
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)
128
123
        else:
129
 
            other_repo = None
130
 
            revision_b = None
131
 
        self.graph = self.branch.repository.get_graph(other_repo)
132
 
        revision_a = self.branch.last_revision()
133
 
        self.scan_graph(revision_a, revision_b)
 
124
            self.root, self.ancestors, self.descendants = \
 
125
                revision_graph(revision_a, branch.repository)
 
126
            self.common = []
 
127
 
134
128
        self.n_history = branch.revision_history()
135
 
        self.n_revnos = branch.get_revision_id_to_revno_map()
136
 
        self.distances = node_distances(self.descendants, self.ancestors,
 
129
        self.distances = node_distances(self.descendants, self.ancestors, 
137
130
                                        self.root)
138
131
        if other_branch is not None:
139
132
            self.base = select_farthest(self.distances, self.common)
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)
 
133
            self.m_history = other_branch.revision_history() 
145
134
        else:
146
135
            self.base = None
147
 
            self.new_base = None
148
 
            self.lcas = set()
149
136
            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))
183
137
 
184
138
    def dot_node(self, node, num):
185
139
        try:
191
145
        except ValueError:
192
146
            m_rev = None
193
147
        if (n_rev, m_rev) == (None, None):
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:]
 
148
            name = node[-5:]
199
149
            cluster = None
200
150
        elif n_rev == m_rev:
201
151
            name = "rR%d" % n_rev
221
171
            assert m_rev is not None
222
172
            cluster = "other_history"
223
173
            color = "#ff0000"
224
 
        if node in self.lcas:
225
 
            color = "#9933cc"
226
174
        if node == self.base:
227
 
            color = "#669933"
228
 
            if node == self.new_base:
229
 
                color = "#33ff33"
230
 
        if node == self.new_base:
231
 
            color = '#33cc99'
 
175
            color = "#33ff99"
232
176
 
233
177
        label = [name]
234
 
        committer, message, nick, date = get_rev_info(node,
 
178
        committer, message, nick, date = get_rev_info(node, 
235
179
                                                      self.branch.repository)
236
180
        if committer is not None:
237
181
            label.append(committer)
248
192
        else:
249
193
            rank = None
250
194
 
251
 
        d_node = Node("n%d" % num, color=color, label="\\n".join(label),
 
195
        d_node = Node("n%d" % num, color=color, label="\\n".join(label), 
252
196
                    rev_id=node, cluster=cluster, message=message,
253
197
                    date=date)
254
198
        d_node.rank = rank
257
201
            d_node.node_style.append('dotted')
258
202
 
259
203
        return d_node
260
 
 
 
204
       
261
205
    def get_relations(self, collapse=False, max_distance=None):
262
206
        dot_nodes = {}
263
207
        node_relations = []
264
208
        num = 0
265
209
        if collapse:
266
 
            exceptions = self.lcas.union([self.base, self.new_base])
267
 
            visible_ancestors = compact_ancestors(self.descendants,
268
 
                                                  self.ancestors,
269
 
                                                  exceptions)
 
210
            visible_ancestors = compact_ancestors(self.descendants, 
 
211
                                                  self.ancestors, (self.base,))
270
212
        else:
271
 
            visible_ancestors = {}
272
 
            for revision, parents in self.ancestors.iteritems():
273
 
                visible_ancestors[revision] = dict((p, 0) for p in parents)
 
213
            visible_ancestors = self.ancestors
274
214
        if max_distance is not None:
275
215
            min_distance = max(self.distances.values()) - max_distance
276
 
            visible_ancestors = dict((n, p) for n, p in
277
 
                                     visible_ancestors.iteritems() if
278
 
                                     self.distances[n] >= min_distance)
 
216
            visible_ancestors = dict((n, p) for n, p in visible_ancestors.iteritems() if
 
217
                    self.distances[n] >= min_distance)
279
218
        for node, parents in visible_ancestors.iteritems():
280
219
            if node not in dot_nodes:
281
220
                dot_nodes[node] = self.dot_node(node, num)
282
221
                num += 1
283
 
            for parent, skipped in parents.iteritems():
 
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:
284
227
                if parent not in dot_nodes:
285
228
                    dot_nodes[parent] = self.dot_node(parent, num)
286
229
                    num += 1
316
259
    done = False
317
260
    if ext not in RSVG_OUTPUT_TYPES:
318
261
        antialias = False
319
 
    if antialias:
 
262
    if antialias: 
320
263
        output = list(output)
321
264
        try:
322
265
            invoke_dot_aa(output, filename, ext)
334
277
            done = True
335
278
        except NoDot, e:
336
279
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
337
 
                " is installed correctly.")
 
280
                " is installed correctly, or use --noantialias")
338
281
    elif ext == 'dot' and not done:
339
282
        my_file = file(filename, 'wb')
340
283
        for fragment in output:
344
287
            invoke_dot_html(output, filename)
345
288
        except NoDot, e:
346
289
            raise BzrCommandError("Can't find 'dot'.  Please ensure Graphviz"\
347
 
                " is installed correctly.")
 
290
                " is installed correctly, or use --noantialias")
348
291
    elif not done:
349
292
        print "Unknown file extension: %s" % ext
 
293