##// END OF EJS Templates
graphlog: move common code into function again, change function types
Dirkjan Ochtman -
r9369:20140c24 default
parent child Browse files
Show More
@@ -1,380 +1,376 b''
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, incorporated herein by reference.
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, sys
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 bundlerepo, changegroup, cmdutil, commands, extensions
21 21 from mercurial import hg, url, util, graphmod
22 22
23 23 ASCIIDATA = 'ASC'
24 24
25 def asciiformat(revdag, displayer, parents):
26 """formats a changelog DAG walk for ASCII output"""
27 for (id, type, ctx, parentids) in revdag:
28 if type != graphmod.CHANGESET:
29 continue
30 displayer.show(ctx)
31 lines = displayer.hunk.pop(ctx.rev()).split('\n')[:-1]
32 char = ctx.node() in parents and '@' or 'o'
33 yield (id, ASCIIDATA, (char, lines), parentids)
34
35 def asciiedges(nodes):
25 def asciiedges(seen, rev, parents):
36 26 """adds edge info to changelog DAG walk suitable for ascii()"""
37 seen = []
38 for node, type, data, parents in nodes:
39 if node not in seen:
40 seen.append(node)
41 nodeidx = seen.index(node)
27 if rev not in seen:
28 seen.append(rev)
29 nodeidx = seen.index(rev)
42 30
43 knownparents = []
44 newparents = []
45 for parent in parents:
46 if parent in seen:
47 knownparents.append(parent)
48 else:
49 newparents.append(parent)
31 knownparents = []
32 newparents = []
33 for parent in parents:
34 if parent in seen:
35 knownparents.append(parent)
36 else:
37 newparents.append(parent)
50 38
51 ncols = len(seen)
52 nextseen = seen[:]
53 nextseen[nodeidx:nodeidx + 1] = newparents
54 edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
39 ncols = len(seen)
40 seen[nodeidx:nodeidx + 1] = newparents
41 edges = [(nodeidx, seen.index(p)) for p in knownparents]
55 42
56 if len(newparents) > 0:
57 edges.append((nodeidx, nodeidx))
58 if len(newparents) > 1:
59 edges.append((nodeidx, nodeidx + 1))
60 nmorecols = len(nextseen) - ncols
61 seen = nextseen
62 yield (nodeidx, type, data, edges, ncols, nmorecols)
43 if len(newparents) > 0:
44 edges.append((nodeidx, nodeidx))
45 if len(newparents) > 1:
46 edges.append((nodeidx, nodeidx + 1))
47
48 nmorecols = len(seen) - ncols
49 return nodeidx, edges, ncols, nmorecols
63 50
64 51 def fix_long_right_edges(edges):
65 52 for (i, (start, end)) in enumerate(edges):
66 53 if end > start:
67 54 edges[i] = (start, end + 1)
68 55
69 56 def get_nodeline_edges_tail(
70 57 node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
71 58 if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
72 59 # Still going in the same non-vertical direction.
73 60 if n_columns_diff == -1:
74 61 start = max(node_index + 1, p_node_index)
75 62 tail = ["|", " "] * (start - node_index - 1)
76 63 tail.extend(["/", " "] * (n_columns - start))
77 64 return tail
78 65 else:
79 66 return ["\\", " "] * (n_columns - node_index - 1)
80 67 else:
81 68 return ["|", " "] * (n_columns - node_index - 1)
82 69
83 70 def draw_edges(edges, nodeline, interline):
84 71 for (start, end) in edges:
85 72 if start == end + 1:
86 73 interline[2 * end + 1] = "/"
87 74 elif start == end - 1:
88 75 interline[2 * start + 1] = "\\"
89 76 elif start == end:
90 77 interline[2 * start] = "|"
91 78 else:
92 79 nodeline[2 * end] = "+"
93 80 if start > end:
94 81 (start, end) = (end, start)
95 82 for i in range(2 * start + 1, 2 * end):
96 83 if nodeline[i] != "+":
97 84 nodeline[i] = "-"
98 85
99 86 def get_padding_line(ni, n_columns, edges):
100 87 line = []
101 88 line.extend(["|", " "] * ni)
102 89 if (ni, ni - 1) in edges or (ni, ni) in edges:
103 90 # (ni, ni - 1) (ni, ni)
104 91 # | | | | | | | |
105 92 # +---o | | o---+
106 93 # | | c | | c | |
107 94 # | |/ / | |/ /
108 95 # | | | | | |
109 96 c = "|"
110 97 else:
111 98 c = " "
112 99 line.extend([c, " "])
113 100 line.extend(["|", " "] * (n_columns - ni - 1))
114 101 return line
115 102
116 103 def ascii(ui, dag):
117 104 """prints an ASCII graph of the DAG
118 105
119 106 dag is a generator that emits tuples with the following elements:
120 107
121 108 - Column of the current node in the set of ongoing edges.
122 109 - Type indicator of node data == ASCIIDATA.
123 110 - Payload: (char, lines):
124 111 - Character to use as node's symbol.
125 112 - List of lines to display as the node's text.
126 113 - Edges; a list of (col, next_col) indicating the edges between
127 114 the current node and its parents.
128 115 - Number of columns (ongoing edges) in the current revision.
129 116 - The difference between the number of columns (ongoing edges)
130 117 in the next revision and the number of columns (ongoing edges)
131 118 in the current revision. That is: -1 means one column removed;
132 119 0 means no columns added or removed; 1 means one column added.
133 120 """
134 121 prev_n_columns_diff = 0
135 122 prev_node_index = 0
136 123 for (node_index, type, (node_ch, node_lines), edges, n_columns, n_columns_diff) in dag:
137 124
138 125 assert -2 < n_columns_diff < 2
139 126 if n_columns_diff == -1:
140 127 # Transform
141 128 #
142 129 # | | | | | |
143 130 # o | | into o---+
144 131 # |X / |/ /
145 132 # | | | |
146 133 fix_long_right_edges(edges)
147 134
148 135 # add_padding_line says whether to rewrite
149 136 #
150 137 # | | | | | | | |
151 138 # | o---+ into | o---+
152 139 # | / / | | | # <--- padding line
153 140 # o | | | / /
154 141 # o | |
155 142 add_padding_line = (len(node_lines) > 2 and
156 143 n_columns_diff == -1 and
157 144 [x for (x, y) in edges if x + 1 < y])
158 145
159 146 # fix_nodeline_tail says whether to rewrite
160 147 #
161 148 # | | o | | | | o | |
162 149 # | | |/ / | | |/ /
163 150 # | o | | into | o / / # <--- fixed nodeline tail
164 151 # | |/ / | |/ /
165 152 # o | | o | |
166 153 fix_nodeline_tail = len(node_lines) <= 2 and not add_padding_line
167 154
168 155 # nodeline is the line containing the node character (typically o)
169 156 nodeline = ["|", " "] * node_index
170 157 nodeline.extend([node_ch, " "])
171 158
172 159 nodeline.extend(
173 160 get_nodeline_edges_tail(
174 161 node_index, prev_node_index, n_columns, n_columns_diff,
175 162 prev_n_columns_diff, fix_nodeline_tail))
176 163
177 164 # shift_interline is the line containing the non-vertical
178 165 # edges between this entry and the next
179 166 shift_interline = ["|", " "] * node_index
180 167 if n_columns_diff == -1:
181 168 n_spaces = 1
182 169 edge_ch = "/"
183 170 elif n_columns_diff == 0:
184 171 n_spaces = 2
185 172 edge_ch = "|"
186 173 else:
187 174 n_spaces = 3
188 175 edge_ch = "\\"
189 176 shift_interline.extend(n_spaces * [" "])
190 177 shift_interline.extend([edge_ch, " "] * (n_columns - node_index - 1))
191 178
192 179 # draw edges from the current node to its parents
193 180 draw_edges(edges, nodeline, shift_interline)
194 181
195 182 # lines is the list of all graph lines to print
196 183 lines = [nodeline]
197 184 if add_padding_line:
198 185 lines.append(get_padding_line(node_index, n_columns, edges))
199 186 lines.append(shift_interline)
200 187
201 188 # make sure that there are as many graph lines as there are
202 189 # log strings
203 190 while len(node_lines) < len(lines):
204 191 node_lines.append("")
205 192 if len(lines) < len(node_lines):
206 193 extra_interline = ["|", " "] * (n_columns + n_columns_diff)
207 194 while len(lines) < len(node_lines):
208 195 lines.append(extra_interline)
209 196
210 197 # print lines
211 198 indentation_level = max(n_columns, n_columns + n_columns_diff)
212 199 for (line, logstr) in zip(lines, node_lines):
213 200 ln = "%-*s %s" % (2 * indentation_level, "".join(line), logstr)
214 201 ui.write(ln.rstrip() + '\n')
215 202
216 203 # ... and start over
217 204 prev_node_index = node_index
218 205 prev_n_columns_diff = n_columns_diff
219 206
220 207 def get_revs(repo, rev_opt):
221 208 if rev_opt:
222 209 revs = revrange(repo, rev_opt)
223 210 return (max(revs), min(revs))
224 211 else:
225 212 return (len(repo) - 1, 0)
226 213
227 214 def check_unsupported_flags(opts):
228 215 for op in ["follow", "follow_first", "date", "copies", "keyword", "remove",
229 216 "only_merges", "user", "only_branch", "prune", "newest_first",
230 217 "no_merges", "include", "exclude"]:
231 218 if op in opts and opts[op]:
232 219 raise util.Abort(_("--graph option is incompatible with --%s") % op)
233 220
221 def generate(dag, displayer, showparents, edgefn):
222 seen = []
223 for rev, type, ctx, parents in dag:
224 char = ctx.node() in showparents and '@' or 'o'
225 displayer.show(ctx)
226 lines = displayer.hunk.pop(rev).split('\n')[:-1]
227 cols = edgefn(seen, rev, parents)
228 yield cols[0], type, (char, lines), cols[1], cols[2], cols[3]
229
234 230 def graphlog(ui, repo, path=None, **opts):
235 231 """show revision history alongside an ASCII revision graph
236 232
237 233 Print a revision history alongside a revision graph drawn with
238 234 ASCII characters.
239 235
240 236 Nodes printed as an @ character are parents of the working
241 237 directory.
242 238 """
243 239
244 240 check_unsupported_flags(opts)
245 241 limit = cmdutil.loglimit(opts)
246 242 start, stop = get_revs(repo, opts["rev"])
247 243 stop = max(stop, start - limit + 1)
248 244 if start == nullrev:
249 245 return
250 246
251 247 if path:
252 248 path = util.canonpath(repo.root, os.getcwd(), path)
253 249 if path: # could be reset in canonpath
254 250 revdag = graphmod.filerevs(repo, path, start, stop)
255 251 else:
256 252 revdag = graphmod.revisions(repo, start, stop)
257 253
258 254 displayer = show_changeset(ui, repo, opts, buffered=True)
259 255 showparents = [ctx.node() for ctx in repo[None].parents()]
260 fmtdag = asciiformat(revdag, displayer, showparents)
261 ascii(ui, asciiedges(fmtdag))
256 gen = generate(revdag, displayer, showparents, asciiedges)
257 ascii(ui, gen)
262 258
263 259 def graphrevs(repo, nodes, opts):
264 260 limit = cmdutil.loglimit(opts)
265 261 nodes.reverse()
266 262 if limit < sys.maxint:
267 263 nodes = nodes[:limit]
268 264 return graphmod.nodes(repo, nodes)
269 265
270 266 def goutgoing(ui, repo, dest=None, **opts):
271 267 """show the outgoing changesets alongside an ASCII revision graph
272 268
273 269 Print the outgoing changesets alongside a revision graph drawn with
274 270 ASCII characters.
275 271
276 272 Nodes printed as an @ character are parents of the working
277 273 directory.
278 274 """
279 275
280 276 check_unsupported_flags(opts)
281 277 dest, revs, checkout = hg.parseurl(
282 278 ui.expandpath(dest or 'default-push', dest or 'default'),
283 279 opts.get('rev'))
284 280 if revs:
285 281 revs = [repo.lookup(rev) for rev in revs]
286 282 other = hg.repository(cmdutil.remoteui(ui, opts), dest)
287 283 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
288 284 o = repo.findoutgoing(other, force=opts.get('force'))
289 285 if not o:
290 286 ui.status(_("no changes found\n"))
291 287 return
292 288
293 289 o = repo.changelog.nodesbetween(o, revs)[0]
294 290 revdag = graphrevs(repo, o, opts)
295 291 displayer = show_changeset(ui, repo, opts, buffered=True)
296 292 showparents = [ctx.node() for ctx in repo[None].parents()]
297 fmtdag = asciiformat(revdag, displayer, showparents)
298 ascii(ui, asciiedges(fmtdag))
293 gen = generate(revdag, displayer, showparents, asciiedges)
294 ascii(ui, gen)
299 295
300 296 def gincoming(ui, repo, source="default", **opts):
301 297 """show the incoming changesets alongside an ASCII revision graph
302 298
303 299 Print the incoming changesets alongside a revision graph drawn with
304 300 ASCII characters.
305 301
306 302 Nodes printed as an @ character are parents of the working
307 303 directory.
308 304 """
309 305
310 306 check_unsupported_flags(opts)
311 307 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev'))
312 308 other = hg.repository(cmdutil.remoteui(repo, opts), source)
313 309 ui.status(_('comparing with %s\n') % url.hidepassword(source))
314 310 if revs:
315 311 revs = [other.lookup(rev) for rev in revs]
316 312 incoming = repo.findincoming(other, heads=revs, force=opts["force"])
317 313 if not incoming:
318 314 try:
319 315 os.unlink(opts["bundle"])
320 316 except:
321 317 pass
322 318 ui.status(_("no changes found\n"))
323 319 return
324 320
325 321 cleanup = None
326 322 try:
327 323
328 324 fname = opts["bundle"]
329 325 if fname or not other.local():
330 326 # create a bundle (uncompressed if other repo is not local)
331 327 if revs is None:
332 328 cg = other.changegroup(incoming, "incoming")
333 329 else:
334 330 cg = other.changegroupsubset(incoming, revs, 'incoming')
335 331 bundletype = other.local() and "HG10BZ" or "HG10UN"
336 332 fname = cleanup = changegroup.writebundle(cg, fname, bundletype)
337 333 # keep written bundle?
338 334 if opts["bundle"]:
339 335 cleanup = None
340 336 if not other.local():
341 337 # use the created uncompressed bundlerepo
342 338 other = bundlerepo.bundlerepository(ui, repo.root, fname)
343 339
344 340 chlist = other.changelog.nodesbetween(incoming, revs)[0]
345 341 revdag = graphrevs(other, chlist, opts)
346 342 displayer = show_changeset(ui, other, opts, buffered=True)
347 343 showparents = [ctx.node() for ctx in repo[None].parents()]
348 fmtdag = asciiformat(revdag, displayer, showparents)
349 ascii(ui, asciiedges(fmtdag))
344 gen = generate(revdag, displayer, showparents, asciiedges)
345 ascii(ui, gen)
350 346
351 347 finally:
352 348 if hasattr(other, 'close'):
353 349 other.close()
354 350 if cleanup:
355 351 os.unlink(cleanup)
356 352
357 353 def uisetup(ui):
358 354 '''Initialize the extension.'''
359 355 _wrapcmd(ui, 'log', commands.table, graphlog)
360 356 _wrapcmd(ui, 'incoming', commands.table, gincoming)
361 357 _wrapcmd(ui, 'outgoing', commands.table, goutgoing)
362 358
363 359 def _wrapcmd(ui, cmd, table, wrapfn):
364 360 '''wrap the command'''
365 361 def graph(orig, *args, **kwargs):
366 362 if kwargs['graph']:
367 363 return wrapfn(*args, **kwargs)
368 364 return orig(*args, **kwargs)
369 365 entry = extensions.wrapcommand(table, cmd, graph)
370 366 entry[1].append(('G', 'graph', None, _("show the revision DAG")))
371 367
372 368 cmdtable = {
373 369 "glog":
374 370 (graphlog,
375 371 [('l', 'limit', '', _('limit number of changes displayed')),
376 372 ('p', 'patch', False, _('show patch')),
377 373 ('r', 'rev', [], _('show the specified revision or range')),
378 374 ] + templateopts,
379 375 _('hg glog [OPTION]... [FILE]')),
380 376 }
General Comments 0
You need to be logged in to leave comments. Login now