Graphlog extension adds a --graph option to log/in/out
authorAlpar Juttner <alpar@cs.elte.hu>
Fri, 21 Nov 2008 22:27:11 +0000
changeset 7426 df0962f6c54e
parent 7425 14ed6662af48
child 7433 c4ce828e8074
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.
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,