# HG changeset patch # User Alpar Juttner # Date 1227306431 0 # Node ID df0962f6c54e4578eac4482e83dad873eb6d86c7 # Parent 14ed6662af4891df2f67b448f9db62529fee6261 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. diff -r 14ed6662af48 -r df0962f6c54e hgext/graphlog.py --- a/hgext/graphlog.py Sun Nov 23 20:16:29 2008 +0100 +++ b/hgext/graphlog.py Fri Nov 21 22:27:11 2008 +0000 @@ -4,15 +4,22 @@ # # This software may be used and distributed according to the terms of # the GNU General Public License, incorporated herein by reference. -'''show revision graphs in terminal windows''' +'''show revision graphs in terminal windows + +This extension adds a --graph option to the incoming, outgoing and log +commands. When this options is given, an ascii representation of the +revision graph is also shown. +''' import os import sys from mercurial.cmdutil import revrange, show_changeset -from mercurial.commands import templateopts +from mercurial.commands import templateopts, logopts, remoteopts from mercurial.i18n import _ from mercurial.node import nullrev from mercurial.util import Abort, canonpath +from mercurial import bundlerepo, changegroup, cmdutil, commands, extensions +from mercurial import hg, ui, url def revisions(repo, start, stop): """cset DAG generator yielding (rev, node, [parents]) tuples @@ -257,6 +264,14 @@ else: return (len(repo) - 1, 0) +def check_unsupported_flags(opts): + for op in ["follow", "follow_first", "date", "copies", "keyword", "remove", + "only_merges", "user", "only_branch", "prune", "newest_first", + "no_merges", "include", "exclude"]: + if op in opts and opts[op]: + raise Abort(_("--graph option is incompatible with --%s") % op) + + def graphlog(ui, repo, path=None, **opts): """show revision history alongside an ASCII revision graph @@ -267,6 +282,7 @@ directory. """ + check_unsupported_flags(opts) limit = get_limit(opts["limit"]) start, stop = get_revs(repo, opts["rev"]) stop = max(stop, start - limit + 1) @@ -293,6 +309,165 @@ ascii(ui, grapher(graphabledag())) +def outgoing_revs(ui, repo, dest, opts): + """cset DAG generator yielding (node, [parents]) tuples + + This generator function walks through the revisions not found + in the destination + """ + limit = cmdutil.loglimit(opts) + dest, revs, checkout = hg.parseurl( + ui.expandpath(dest or 'default-push', dest or 'default'), + opts.get('rev')) + cmdutil.setremoteconfig(ui, opts) + if revs: + revs = [repo.lookup(rev) for rev in revs] + other = hg.repository(ui, dest) + ui.status(_('comparing with %s\n') % url.hidepassword(dest)) + o = repo.findoutgoing(other, force=opts.get('force')) + if not o: + ui.status(_("no changes found\n")) + return + o = repo.changelog.nodesbetween(o, revs)[0] + o.reverse() + revdict = {} + for n in o: + revdict[repo.changectx(n).rev()]=True + count = 0 + for n in o: + if count >= limit: + break + ctx = repo.changectx(n) + parents = [p.rev() for p in ctx.parents() if p.rev() in revdict] + parents.sort() + yield (ctx, parents) + count += 1 + +def goutgoing(ui, repo, dest=None, **opts): + """show the outgoing changesets alongside an ASCII revision graph + + Print the outgoing changesets alongside a revision graph drawn with + ASCII characters. + + Nodes printed as an @ character are parents of the working + directory. + """ + check_unsupported_flags(opts) + revdag = outgoing_revs(ui, repo, dest, opts) + repo_parents = repo.dirstate.parents() + displayer = show_changeset(ui, repo, opts, buffered=True) + def graphabledag(): + for (ctx, parents) in revdag: + # log_strings is the list of all log strings to draw alongside + # the graph. + displayer.show(ctx) + lines = displayer.hunk.pop(ctx.rev()).split("\n")[:-1] + char = ctx.node() in repo_parents and '@' or 'o' + yield (ctx.rev(), parents, char, lines) + + ascii(ui, grapher(graphabledag())) + +def incoming_revs(other, chlist, opts): + """cset DAG generator yielding (node, [parents]) tuples + + This generator function walks through the revisions of the destination + not found in repo + """ + limit = cmdutil.loglimit(opts) + chlist.reverse() + revdict = {} + for n in chlist: + revdict[other.changectx(n).rev()]=True + count = 0 + for n in chlist: + if count >= limit: + break + ctx = other.changectx(n) + parents = [p.rev() for p in ctx.parents() if p.rev() in revdict] + parents.sort() + yield (ctx, parents) + count += 1 + +def gincoming(ui, repo, source="default", **opts): + """show the incoming changesets alongside an ASCII revision graph + + Print the incoming changesets alongside a revision graph drawn with + ASCII characters. + + Nodes printed as an @ character are parents of the working + directory. + """ + + check_unsupported_flags(opts) + source, revs, checkout = hg.parseurl(ui.expandpath(source), opts.get('rev')) + cmdutil.setremoteconfig(ui, opts) + + other = hg.repository(ui, source) + ui.status(_('comparing with %s\n') % url.hidepassword(source)) + if revs: + revs = [other.lookup(rev) for rev in revs] + incoming = repo.findincoming(other, heads=revs, force=opts["force"]) + if not incoming: + try: + os.unlink(opts["bundle"]) + except: + pass + ui.status(_("no changes found\n")) + return + + cleanup = None + try: + fname = opts["bundle"] + if fname or not other.local(): + # create a bundle (uncompressed if other repo is not local) + if revs is None: + cg = other.changegroup(incoming, "incoming") + else: + cg = other.changegroupsubset(incoming, revs, 'incoming') + bundletype = other.local() and "HG10BZ" or "HG10UN" + fname = cleanup = changegroup.writebundle(cg, fname, bundletype) + # keep written bundle? + if opts["bundle"]: + cleanup = None + if not other.local(): + # use the created uncompressed bundlerepo + other = bundlerepo.bundlerepository(ui, repo.root, fname) + + chlist = other.changelog.nodesbetween(incoming, revs)[0] + revdag = incoming_revs(other, chlist, opts) + other_parents = other.dirstate.parents() + displayer = show_changeset(ui, other, opts, buffered=True) + def graphabledag(): + for (ctx, parents) in revdag: + # log_strings is the list of all log strings to draw alongside + # the graph. + displayer.show(ctx) + lines = displayer.hunk.pop(ctx.rev()).split("\n")[:-1] + char = ctx.node() in other_parents and '@' or 'o' + yield (ctx.rev(), parents, char, lines) + + ascii(ui, grapher(graphabledag())) + finally: + if hasattr(other, 'close'): + other.close() + if cleanup: + os.unlink(cleanup) + +def uisetup(ui): + '''Initialize the extension.''' + _wrapcmd(ui, 'log', commands.table, graphlog) + _wrapcmd(ui, 'incoming', commands.table, gincoming) + _wrapcmd(ui, 'outgoing', commands.table, goutgoing) + +def _wrapcmd(ui, cmd, table, wrapfn): + '''wrap the command''' + def graph(orig, *args, **kwargs): + if kwargs['graph']: + return wrapfn(*args, **kwargs) + return orig(*args, **kwargs) + entry = extensions.wrapcommand(table, cmd, graph) + entry[1].append(('g', 'graph', None, _("show the revision DAG"))) + cmdtable = { "glog": (graphlog,