Mercurial > hg
comparison hgext/graphlog.py @ 7426:df0962f6c54e
Graphlog extension adds a --graph option to log/in/out
The --graph option shows the ascii revision graph when used in conjunction
with the incoming, outgoing or log commands.
It also makes sure that incompatible options (e.g. --newest-first) are not
used.
author | Alpar Juttner <alpar@cs.elte.hu> |
---|---|
date | Fri, 21 Nov 2008 22:27:11 +0000 |
parents | b501c7f3c2ad |
children | 5e13df32fb74 |
comparison
equal
deleted
inserted
replaced
7425:14ed6662af48 | 7426:df0962f6c54e |
---|---|
2 # | 2 # |
3 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net> | 3 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net> |
4 # | 4 # |
5 # This software may be used and distributed according to the terms of | 5 # This software may be used and distributed according to the terms of |
6 # the GNU General Public License, incorporated herein by reference. | 6 # the GNU General Public License, incorporated herein by reference. |
7 '''show revision graphs in terminal windows''' | 7 '''show revision graphs in terminal windows |
8 | |
9 This extension adds a --graph option to the incoming, outgoing and log | |
10 commands. When this options is given, an ascii representation of the | |
11 revision graph is also shown. | |
12 ''' | |
8 | 13 |
9 import os | 14 import os |
10 import sys | 15 import sys |
11 from mercurial.cmdutil import revrange, show_changeset | 16 from mercurial.cmdutil import revrange, show_changeset |
12 from mercurial.commands import templateopts | 17 from mercurial.commands import templateopts, logopts, remoteopts |
13 from mercurial.i18n import _ | 18 from mercurial.i18n import _ |
14 from mercurial.node import nullrev | 19 from mercurial.node import nullrev |
15 from mercurial.util import Abort, canonpath | 20 from mercurial.util import Abort, canonpath |
21 from mercurial import bundlerepo, changegroup, cmdutil, commands, extensions | |
22 from mercurial import hg, ui, url | |
16 | 23 |
17 def revisions(repo, start, stop): | 24 def revisions(repo, start, stop): |
18 """cset DAG generator yielding (rev, node, [parents]) tuples | 25 """cset DAG generator yielding (rev, node, [parents]) tuples |
19 | 26 |
20 This generator function walks through the revision history from revision | 27 This generator function walks through the revision history from revision |
255 revs = revrange(repo, rev_opt) | 262 revs = revrange(repo, rev_opt) |
256 return (max(revs), min(revs)) | 263 return (max(revs), min(revs)) |
257 else: | 264 else: |
258 return (len(repo) - 1, 0) | 265 return (len(repo) - 1, 0) |
259 | 266 |
267 def check_unsupported_flags(opts): | |
268 for op in ["follow", "follow_first", "date", "copies", "keyword", "remove", | |
269 "only_merges", "user", "only_branch", "prune", "newest_first", | |
270 "no_merges", "include", "exclude"]: | |
271 if op in opts and opts[op]: | |
272 raise Abort(_("--graph option is incompatible with --%s") % op) | |
273 | |
274 | |
260 def graphlog(ui, repo, path=None, **opts): | 275 def graphlog(ui, repo, path=None, **opts): |
261 """show revision history alongside an ASCII revision graph | 276 """show revision history alongside an ASCII revision graph |
262 | 277 |
263 Print a revision history alongside a revision graph drawn with | 278 Print a revision history alongside a revision graph drawn with |
264 ASCII characters. | 279 ASCII characters. |
265 | 280 |
266 Nodes printed as an @ character are parents of the working | 281 Nodes printed as an @ character are parents of the working |
267 directory. | 282 directory. |
268 """ | 283 """ |
269 | 284 |
285 check_unsupported_flags(opts) | |
270 limit = get_limit(opts["limit"]) | 286 limit = get_limit(opts["limit"]) |
271 start, stop = get_revs(repo, opts["rev"]) | 287 start, stop = get_revs(repo, opts["rev"]) |
272 stop = max(stop, start - limit + 1) | 288 stop = max(stop, start - limit + 1) |
273 if start == nullrev: | 289 if start == nullrev: |
274 return | 290 return |
291 char = ctx.node() in repo_parents and '@' or 'o' | 307 char = ctx.node() in repo_parents and '@' or 'o' |
292 yield (ctx.rev(), parents, char, lines) | 308 yield (ctx.rev(), parents, char, lines) |
293 | 309 |
294 ascii(ui, grapher(graphabledag())) | 310 ascii(ui, grapher(graphabledag())) |
295 | 311 |
312 def outgoing_revs(ui, repo, dest, opts): | |
313 """cset DAG generator yielding (node, [parents]) tuples | |
314 | |
315 This generator function walks through the revisions not found | |
316 in the destination | |
317 """ | |
318 limit = cmdutil.loglimit(opts) | |
319 dest, revs, checkout = hg.parseurl( | |
320 ui.expandpath(dest or 'default-push', dest or 'default'), | |
321 opts.get('rev')) | |
322 cmdutil.setremoteconfig(ui, opts) | |
323 if revs: | |
324 revs = [repo.lookup(rev) for rev in revs] | |
325 other = hg.repository(ui, dest) | |
326 ui.status(_('comparing with %s\n') % url.hidepassword(dest)) | |
327 o = repo.findoutgoing(other, force=opts.get('force')) | |
328 if not o: | |
329 ui.status(_("no changes found\n")) | |
330 return | |
331 o = repo.changelog.nodesbetween(o, revs)[0] | |
332 o.reverse() | |
333 revdict = {} | |
334 for n in o: | |
335 revdict[repo.changectx(n).rev()]=True | |
336 count = 0 | |
337 for n in o: | |
338 if count >= limit: | |
339 break | |
340 ctx = repo.changectx(n) | |
341 parents = [p.rev() for p in ctx.parents() if p.rev() in revdict] | |
342 parents.sort() | |
343 yield (ctx, parents) | |
344 count += 1 | |
345 | |
346 def goutgoing(ui, repo, dest=None, **opts): | |
347 """show the outgoing changesets alongside an ASCII revision graph | |
348 | |
349 Print the outgoing changesets alongside a revision graph drawn with | |
350 ASCII characters. | |
351 | |
352 Nodes printed as an @ character are parents of the working | |
353 directory. | |
354 """ | |
355 check_unsupported_flags(opts) | |
356 revdag = outgoing_revs(ui, repo, dest, opts) | |
357 repo_parents = repo.dirstate.parents() | |
358 displayer = show_changeset(ui, repo, opts, buffered=True) | |
359 def graphabledag(): | |
360 for (ctx, parents) in revdag: | |
361 # log_strings is the list of all log strings to draw alongside | |
362 # the graph. | |
363 displayer.show(ctx) | |
364 lines = displayer.hunk.pop(ctx.rev()).split("\n")[:-1] | |
365 char = ctx.node() in repo_parents and '@' or 'o' | |
366 yield (ctx.rev(), parents, char, lines) | |
367 | |
368 ascii(ui, grapher(graphabledag())) | |
369 | |
370 def incoming_revs(other, chlist, opts): | |
371 """cset DAG generator yielding (node, [parents]) tuples | |
372 | |
373 This generator function walks through the revisions of the destination | |
374 not found in repo | |
375 """ | |
376 limit = cmdutil.loglimit(opts) | |
377 chlist.reverse() | |
378 revdict = {} | |
379 for n in chlist: | |
380 revdict[other.changectx(n).rev()]=True | |
381 count = 0 | |
382 for n in chlist: | |
383 if count >= limit: | |
384 break | |
385 ctx = other.changectx(n) | |
386 parents = [p.rev() for p in ctx.parents() if p.rev() in revdict] | |
387 parents.sort() | |
388 yield (ctx, parents) | |
389 count += 1 | |
390 | |
391 def gincoming(ui, repo, source="default", **opts): | |
392 """show the incoming changesets alongside an ASCII revision graph | |
393 | |
394 Print the incoming changesets alongside a revision graph drawn with | |
395 ASCII characters. | |
396 | |
397 Nodes printed as an @ character are parents of the working | |
398 directory. | |
399 """ | |
400 | |
401 check_unsupported_flags(opts) | |
402 source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev')) | |
403 cmdutil.setremoteconfig(ui, opts) | |
404 | |
405 other = hg.repository(ui, source) | |
406 ui.status(_('comparing with %s\n') % url.hidepassword(source)) | |
407 if revs: | |
408 revs = [other.lookup(rev) for rev in revs] | |
409 incoming = repo.findincoming(other, heads=revs, force=opts["force"]) | |
410 if not incoming: | |
411 try: | |
412 os.unlink(opts["bundle"]) | |
413 except: | |
414 pass | |
415 ui.status(_("no changes found\n")) | |
416 return | |
417 | |
418 cleanup = None | |
419 try: | |
420 fname = opts["bundle"] | |
421 if fname or not other.local(): | |
422 # create a bundle (uncompressed if other repo is not local) | |
423 if revs is None: | |
424 cg = other.changegroup(incoming, "incoming") | |
425 else: | |
426 cg = other.changegroupsubset(incoming, revs, 'incoming') | |
427 bundletype = other.local() and "HG10BZ" or "HG10UN" | |
428 fname = cleanup = changegroup.writebundle(cg, fname, bundletype) | |
429 # keep written bundle? | |
430 if opts["bundle"]: | |
431 cleanup = None | |
432 if not other.local(): | |
433 # use the created uncompressed bundlerepo | |
434 other = bundlerepo.bundlerepository(ui, repo.root, fname) | |
435 | |
436 chlist = other.changelog.nodesbetween(incoming, revs)[0] | |
437 revdag = incoming_revs(other, chlist, opts) | |
438 other_parents = other.dirstate.parents() | |
439 displayer = show_changeset(ui, other, opts, buffered=True) | |
440 def graphabledag(): | |
441 for (ctx, parents) in revdag: | |
442 # log_strings is the list of all log strings to draw alongside | |
443 # the graph. | |
444 displayer.show(ctx) | |
445 lines = displayer.hunk.pop(ctx.rev()).split("\n")[:-1] | |
446 char = ctx.node() in other_parents and '@' or 'o' | |
447 yield (ctx.rev(), parents, char, lines) | |
448 | |
449 ascii(ui, grapher(graphabledag())) | |
450 finally: | |
451 if hasattr(other, 'close'): | |
452 other.close() | |
453 if cleanup: | |
454 os.unlink(cleanup) | |
455 | |
456 def uisetup(ui): | |
457 '''Initialize the extension.''' | |
458 _wrapcmd(ui, 'log', commands.table, graphlog) | |
459 _wrapcmd(ui, 'incoming', commands.table, gincoming) | |
460 _wrapcmd(ui, 'outgoing', commands.table, goutgoing) | |
461 | |
462 def _wrapcmd(ui, cmd, table, wrapfn): | |
463 '''wrap the command''' | |
464 def graph(orig, *args, **kwargs): | |
465 if kwargs['graph']: | |
466 return wrapfn(*args, **kwargs) | |
467 return orig(*args, **kwargs) | |
468 entry = extensions.wrapcommand(table, cmd, graph) | |
469 entry[1].append(('g', 'graph', None, _("show the revision DAG"))) | |
470 | |
296 cmdtable = { | 471 cmdtable = { |
297 "glog": | 472 "glog": |
298 (graphlog, | 473 (graphlog, |
299 [('l', 'limit', '', _('limit number of changes displayed')), | 474 [('l', 'limit', '', _('limit number of changes displayed')), |
300 ('p', 'patch', False, _('show patch')), | 475 ('p', 'patch', False, _('show patch')), |