##// END OF EJS Templates
graphlog: do not swallow all TypeError exceptions (fix 29c800ee54cf)
Markus F.X.J. Oberhumer -
r13669:12f60626 default
parent child Browse files
Show More
@@ -1,341 +1,342
1 1 # ASCII graph log extension for Mercurial
2 2 #
3 3 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''command to view revision graphs from a shell
9 9
10 10 This extension adds a --graph option to the incoming, outgoing and log
11 11 commands. When this options is given, an ASCII representation of the
12 12 revision graph is also shown.
13 13 '''
14 14
15 15 import os
16 16 from mercurial.cmdutil import revrange, show_changeset
17 17 from mercurial.commands import templateopts
18 18 from mercurial.i18n import _
19 19 from mercurial.node import nullrev
20 20 from mercurial import cmdutil, commands, extensions
21 21 from mercurial import hg, util, graphmod
22 22
23 23 ASCIIDATA = 'ASC'
24 24
25 25 def asciiedges(seen, rev, parents):
26 26 """adds edge info to changelog DAG walk suitable for ascii()"""
27 27 if rev not in seen:
28 28 seen.append(rev)
29 29 nodeidx = seen.index(rev)
30 30
31 31 knownparents = []
32 32 newparents = []
33 33 for parent in parents:
34 34 if parent in seen:
35 35 knownparents.append(parent)
36 36 else:
37 37 newparents.append(parent)
38 38
39 39 ncols = len(seen)
40 40 seen[nodeidx:nodeidx + 1] = newparents
41 41 edges = [(nodeidx, seen.index(p)) for p in knownparents]
42 42
43 43 if len(newparents) > 0:
44 44 edges.append((nodeidx, nodeidx))
45 45 if len(newparents) > 1:
46 46 edges.append((nodeidx, nodeidx + 1))
47 47
48 48 nmorecols = len(seen) - ncols
49 49 return nodeidx, edges, ncols, nmorecols
50 50
51 51 def fix_long_right_edges(edges):
52 52 for (i, (start, end)) in enumerate(edges):
53 53 if end > start:
54 54 edges[i] = (start, end + 1)
55 55
56 56 def get_nodeline_edges_tail(
57 57 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
58 58 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
59 59 # Still going in the same non-vertical direction.
60 60 if n_columns_diff == -1:
61 61 start = max(node_index + 1, p_node_index)
62 62 tail = ["|", " "] * (start - node_index - 1)
63 63 tail.extend(["/", " "] * (n_columns - start))
64 64 return tail
65 65 else:
66 66 return ["\\", " "] * (n_columns - node_index - 1)
67 67 else:
68 68 return ["|", " "] * (n_columns - node_index - 1)
69 69
70 70 def draw_edges(edges, nodeline, interline):
71 71 for (start, end) in edges:
72 72 if start == end + 1:
73 73 interline[2 * end + 1] = "/"
74 74 elif start == end - 1:
75 75 interline[2 * start + 1] = "\\"
76 76 elif start == end:
77 77 interline[2 * start] = "|"
78 78 else:
79 79 nodeline[2 * end] = "+"
80 80 if start > end:
81 81 (start, end) = (end, start)
82 82 for i in range(2 * start + 1, 2 * end):
83 83 if nodeline[i] != "+":
84 84 nodeline[i] = "-"
85 85
86 86 def get_padding_line(ni, n_columns, edges):
87 87 line = []
88 88 line.extend(["|", " "] * ni)
89 89 if (ni, ni - 1) in edges or (ni, ni) in edges:
90 90 # (ni, ni - 1) (ni, ni)
91 91 # | | | | | | | |
92 92 # +---o | | o---+
93 93 # | | c | | c | |
94 94 # | |/ / | |/ /
95 95 # | | | | | |
96 96 c = "|"
97 97 else:
98 98 c = " "
99 99 line.extend([c, " "])
100 100 line.extend(["|", " "] * (n_columns - ni - 1))
101 101 return line
102 102
103 103 def asciistate():
104 104 """returns the initial value for the "state" argument to ascii()"""
105 105 return [0, 0]
106 106
107 107 def ascii(ui, state, type, char, text, coldata):
108 108 """prints an ASCII graph of the DAG
109 109
110 110 takes the following arguments (one call per node in the graph):
111 111
112 112 - ui to write to
113 113 - Somewhere to keep the needed state in (init to asciistate())
114 114 - Column of the current node in the set of ongoing edges.
115 115 - Type indicator of node data == ASCIIDATA.
116 116 - Payload: (char, lines):
117 117 - Character to use as node's symbol.
118 118 - List of lines to display as the node's text.
119 119 - Edges; a list of (col, next_col) indicating the edges between
120 120 the current node and its parents.
121 121 - Number of columns (ongoing edges) in the current revision.
122 122 - The difference between the number of columns (ongoing edges)
123 123 in the next revision and the number of columns (ongoing edges)
124 124 in the current revision. That is: -1 means one column removed;
125 125 0 means no columns added or removed; 1 means one column added.
126 126 """
127 127
128 128 idx, edges, ncols, coldiff = coldata
129 129 assert -2 < coldiff < 2
130 130 if coldiff == -1:
131 131 # Transform
132 132 #
133 133 # | | | | | |
134 134 # o | | into o---+
135 135 # |X / |/ /
136 136 # | | | |
137 137 fix_long_right_edges(edges)
138 138
139 139 # add_padding_line says whether to rewrite
140 140 #
141 141 # | | | | | | | |
142 142 # | o---+ into | o---+
143 143 # | / / | | | # <--- padding line
144 144 # o | | | / /
145 145 # o | |
146 146 add_padding_line = (len(text) > 2 and coldiff == -1 and
147 147 [x for (x, y) in edges if x + 1 < y])
148 148
149 149 # fix_nodeline_tail says whether to rewrite
150 150 #
151 151 # | | o | | | | o | |
152 152 # | | |/ / | | |/ /
153 153 # | o | | into | o / / # <--- fixed nodeline tail
154 154 # | |/ / | |/ /
155 155 # o | | o | |
156 156 fix_nodeline_tail = len(text) <= 2 and not add_padding_line
157 157
158 158 # nodeline is the line containing the node character (typically o)
159 159 nodeline = ["|", " "] * idx
160 160 nodeline.extend([char, " "])
161 161
162 162 nodeline.extend(
163 163 get_nodeline_edges_tail(idx, state[1], ncols, coldiff,
164 164 state[0], fix_nodeline_tail))
165 165
166 166 # shift_interline is the line containing the non-vertical
167 167 # edges between this entry and the next
168 168 shift_interline = ["|", " "] * idx
169 169 if coldiff == -1:
170 170 n_spaces = 1
171 171 edge_ch = "/"
172 172 elif coldiff == 0:
173 173 n_spaces = 2
174 174 edge_ch = "|"
175 175 else:
176 176 n_spaces = 3
177 177 edge_ch = "\\"
178 178 shift_interline.extend(n_spaces * [" "])
179 179 shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
180 180
181 181 # draw edges from the current node to its parents
182 182 draw_edges(edges, nodeline, shift_interline)
183 183
184 184 # lines is the list of all graph lines to print
185 185 lines = [nodeline]
186 186 if add_padding_line:
187 187 lines.append(get_padding_line(idx, ncols, edges))
188 188 lines.append(shift_interline)
189 189
190 190 # make sure that there are as many graph lines as there are
191 191 # log strings
192 192 while len(text) < len(lines):
193 193 text.append("")
194 194 if len(lines) < len(text):
195 195 extra_interline = ["|", " "] * (ncols + coldiff)
196 196 while len(lines) < len(text):
197 197 lines.append(extra_interline)
198 198
199 199 # print lines
200 200 indentation_level = max(ncols, ncols + coldiff)
201 201 for (line, logstr) in zip(lines, text):
202 202 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
203 203 ui.write(ln.rstrip() + '\n')
204 204
205 205 # ... and start over
206 206 state[0] = coldiff
207 207 state[1] = idx
208 208
209 209 def get_revs(repo, rev_opt):
210 210 if rev_opt:
211 211 revs = revrange(repo, rev_opt)
212 212 if len(revs) == 0:
213 213 return (nullrev, nullrev)
214 214 return (max(revs), min(revs))
215 215 else:
216 216 return (len(repo) - 1, 0)
217 217
218 218 def check_unsupported_flags(opts):
219 219 for op in ["follow", "follow_first", "date", "copies", "keyword", "remove",
220 220 "only_merges", "user", "branch", "only_branch", "prune",
221 221 "newest_first", "no_merges", "include", "exclude"]:
222 222 if op in opts and opts[op]:
223 223 raise util.Abort(_("--graph option is incompatible with --%s")
224 224 % op.replace("_", "-"))
225 225
226 226 def generate(ui, dag, displayer, showparents, edgefn):
227 227 seen, state = [], asciistate()
228 228 for rev, type, ctx, parents in dag:
229 229 char = ctx.node() in showparents and '@' or 'o'
230 230 displayer.show(ctx)
231 231 lines = displayer.hunk.pop(rev).split('\n')[:-1]
232 232 displayer.flush(rev)
233 233 ascii(ui, state, type, char, lines, edgefn(seen, rev, parents))
234 234 displayer.close()
235 235
236 236 def graphlog(ui, repo, path=None, **opts):
237 237 """show revision history alongside an ASCII revision graph
238 238
239 239 Print a revision history alongside a revision graph drawn with
240 240 ASCII characters.
241 241
242 242 Nodes printed as an @ character are parents of the working
243 243 directory.
244 244 """
245 245
246 246 check_unsupported_flags(opts)
247 247 limit = cmdutil.loglimit(opts)
248 248 start, stop = get_revs(repo, opts["rev"])
249 249 if start == nullrev:
250 250 return
251 251
252 252 if path:
253 253 path = util.canonpath(repo.root, os.getcwd(), path)
254 254 if path: # could be reset in canonpath
255 255 revdag = graphmod.filerevs(repo, path, start, stop, limit)
256 256 else:
257 257 if limit is not None:
258 258 stop = max(stop, start - limit + 1)
259 259 revdag = graphmod.revisions(repo, start, stop)
260 260
261 261 displayer = show_changeset(ui, repo, opts, buffered=True)
262 262 showparents = [ctx.node() for ctx in repo[None].parents()]
263 263 generate(ui, revdag, displayer, showparents, asciiedges)
264 264
265 265 def graphrevs(repo, nodes, opts):
266 266 limit = cmdutil.loglimit(opts)
267 267 nodes.reverse()
268 268 if limit is not None:
269 269 nodes = nodes[:limit]
270 270 return graphmod.nodes(repo, nodes)
271 271
272 272 def goutgoing(ui, repo, dest=None, **opts):
273 273 """show the outgoing changesets alongside an ASCII revision graph
274 274
275 275 Print the outgoing changesets alongside a revision graph drawn with
276 276 ASCII characters.
277 277
278 278 Nodes printed as an @ character are parents of the working
279 279 directory.
280 280 """
281 281
282 282 check_unsupported_flags(opts)
283 283 o = hg._outgoing(ui, repo, dest, opts)
284 284 if o is None:
285 285 return
286 286
287 287 revdag = graphrevs(repo, o, opts)
288 288 displayer = show_changeset(ui, repo, opts, buffered=True)
289 289 showparents = [ctx.node() for ctx in repo[None].parents()]
290 290 generate(ui, revdag, displayer, showparents, asciiedges)
291 291
292 292 def gincoming(ui, repo, source="default", **opts):
293 293 """show the incoming changesets alongside an ASCII revision graph
294 294
295 295 Print the incoming changesets alongside a revision graph drawn with
296 296 ASCII characters.
297 297
298 298 Nodes printed as an @ character are parents of the working
299 299 directory.
300 300 """
301 301 def subreporecurse():
302 302 return 1
303 303
304 304 check_unsupported_flags(opts)
305 305 def display(other, chlist, displayer):
306 306 revdag = graphrevs(other, chlist, opts)
307 307 showparents = [ctx.node() for ctx in repo[None].parents()]
308 308 generate(ui, revdag, displayer, showparents, asciiedges)
309 309
310 310 hg._incoming(display, subreporecurse, ui, repo, source, opts, buffered=True)
311 311
312 312 def uisetup(ui):
313 313 '''Initialize the extension.'''
314 314 _wrapcmd(ui, 'log', commands.table, graphlog)
315 315 _wrapcmd(ui, 'incoming', commands.table, gincoming)
316 316 _wrapcmd(ui, 'outgoing', commands.table, goutgoing)
317 317
318 318 def _wrapcmd(ui, cmd, table, wrapfn):
319 319 '''wrap the command'''
320 320 def graph(orig, *args, **kwargs):
321 321 if kwargs['graph']:
322 322 try:
323 323 return wrapfn(*args, **kwargs)
324 324 except TypeError, e:
325 325 if len(args) > wrapfn.func_code.co_argcount:
326 326 raise util.Abort(_('--graph option allows at most one file'))
327 raise
327 328 return orig(*args, **kwargs)
328 329 entry = extensions.wrapcommand(table, cmd, graph)
329 330 entry[1].append(('G', 'graph', None, _("show the revision DAG")))
330 331
331 332 cmdtable = {
332 333 "glog":
333 334 (graphlog,
334 335 [('l', 'limit', '',
335 336 _('limit number of changes displayed'), _('NUM')),
336 337 ('p', 'patch', False, _('show patch')),
337 338 ('r', 'rev', [],
338 339 _('show the specified revision or range'), _('REV')),
339 340 ] + templateopts,
340 341 _('hg glog [OPTION]... [FILE]')),
341 342 }
General Comments 0
You need to be logged in to leave comments. Login now