[PATCH 09 of 10] glog: add --collapse-linear or -c option for branching overview

Peter Arrenbrecht peter.arrenbrecht at gmail.com
Sat May 16 00:16:48 CDT 2009


# HG changeset patch
# User Peter Arrenbrecht <peter.arrenbrecht at gmail.com>
# Date 1242450864 -7200
glog: add --collapse-linear or -c option for branching overview

This can be used to get an overview of branching and merging in a repo.
It collapses linear runs of nodes, listing only the first <n> nodes of
such runs. For example:

$ hg glog -c1
@  (22) four
|
.  ... (2 skipped)
|
| o  (19) three
| |
| .  ... (4 skipped)
| |
| o    (14) merge
| |\
| | o  (13) two
| | |
| | .  ... (3 skipped)
| | |
| o |  (9) one
| | |
| . |  ... (3 skipped)
| |/
| o  (5) branch
| |



diff --git a/hgext/graphlog.py b/hgext/graphlog.py
--- a/hgext/graphlog.py
+++ b/hgext/graphlog.py
@@ -13,6 +13,7 @@
 '''
 
 import os, sys
+from itertools import islice
 from mercurial.cmdutil import revrange, show_changeset
 from mercurial.commands import templateopts
 from mercurial.i18n import _
@@ -32,6 +33,11 @@
             lines = displayer.hunk.pop(ctx.rev()).split('\n')[:-1]
             char = ctx.node() in showparents and '@' or 'o'
             yield (id, ASCIIDATA, (char, lines), parentids)
+        elif type == graphmod.SKIPPEDRUN:
+            skipped = ctx
+            lines = [_('... (%i skipped)') % skipped]
+            char = '.'
+            yield (id, ASCIIDATA, (char, lines), parentids)
 
 def asciiedges(nodes):
     """adds edge info to changelog DAG walk suitable for ascii()"""
@@ -232,6 +238,18 @@
         if op in opts and opts[op]:
             raise util.Abort(_("--graph option is incompatible with --%s") % op)
 
+def getlimit(limit):
+    """get a limit from an option value"""
+    if limit:
+        try:
+            limit = int(limit)
+        except ValueError:
+            raise util.Abort(_('limit must be a positive integer'))
+        if limit <= 0: raise util.Abort(_('limit must be positive'))
+    else:
+        limit = sys.maxint
+    return limit
+
 def graphlog(ui, repo, path=None, **opts):
     """show revision history alongside an ASCII revision graph
 
@@ -244,8 +262,10 @@
 
     check_unsupported_flags(opts)
     limit = cmdutil.loglimit(opts)
+    collapse = getlimit(opts["collapse_linear"])
     start, stop = get_revs(repo, opts["rev"])
-    stop = max(stop, start - limit + 1)
+    if collapse == sys.maxint:
+        stop = max(stop, start - limit + 1)
     if start == nullrev:
         return
 
@@ -256,15 +276,16 @@
     else:
         revdag = graphmod.changelogwalk(repo, start, stop)
 
+    if collapse < sys.maxint:
+        revdag = islice(graphmod.collapseruns(revdag, collapse), limit)
+
     fmtdag = asciiformat(ui, repo, revdag, opts)
     ascii(ui, asciiedges(fmtdag))
 
 def graphrevs(repo, nodes, opts):
+    nodes.reverse()
     limit = cmdutil.loglimit(opts)
-    nodes.reverse()
-    if limit < sys.maxint:
-        nodes = nodes[:limit]
-    return graphmod.nodelistwalk(repo, nodes)
+    return islice(graphmod.nodelistwalk(repo, nodes), limit)
 
 def goutgoing(ui, repo, dest=None, **opts):
     """show the outgoing changesets alongside an ASCII revision graph
@@ -370,6 +391,7 @@
          [('l', 'limit', '', _('limit number of changes displayed')),
           ('p', 'patch', False, _('show patch')),
           ('r', 'rev', [], _('show the specified revision or range')),
+          ('c', 'collapse-linear', '', _('limit number of nodes for linear runs')),
          ] + templateopts,
          _('hg glog [OPTION]... [FILE]')),
 }
diff --git a/mercurial/graphmod.py b/mercurial/graphmod.py
--- a/mercurial/graphmod.py
+++ b/mercurial/graphmod.py
@@ -12,7 +12,7 @@
 
   (id, type, data, [parentids])
 
-The node and parent ids are arbitrary integers which identify a node in the
+The node and parent ids are arbitrary values which identify a node in the
 context of the graph returned. Type is a constant specifying the node type.
 Data depends on type.
 """
@@ -20,6 +20,7 @@
 from mercurial.node import nullrev
 
 CHANGESET = 'C'
+SKIPPEDRUN = 'S'
 
 def changelogwalk(repo, start, stop):
     """cset DAG generator yielding (id, CHANGESET, ctx, [parentids]) tuples
@@ -65,6 +66,72 @@
         parents = [p.rev() for p in ctx.parents() if p.node() in include]
         yield (ctx.rev(), CHANGESET, ctx, sorted(parents))
 
+def collapseruns(dag, runslongerthan):
+    """cset DAG transformer which collapses linear runs of nodes"""
+    expecting = {}
+    def expect(id, parents):
+        for p in parents:
+            if not expecting.get(p):
+                expecting[p] = id
+
+    skipfirstid = None
+    skipfirsttype = None
+    skipfirstdata = None
+    skiplastparents = []
+    skiplen = 0
+    runlen = 0
+    previd = None
+    prevloneparentid = None
+    for (id, type, data, parents) in dag:
+        skip = False
+        expectedby = expecting.get(id)
+        if expectedby:
+            del expecting[id]
+        if len(parents) > 1:
+            prevloneparentid = None
+        else:
+            skip = (id == prevloneparentid
+                    and (not expectedby or expectedby == previd))
+            if len(parents) > 0:
+                prevloneparentid = parents[0]
+            else:
+                prevloneparentid = None
+        if skip:
+            if skipfirstid:
+                skiplastparents = parents
+                skiplen += 1
+            else:
+                runlen += 1
+                if runlen >= runslongerthan:
+                    skipfirstid = id
+                    skipfirsttype = type
+                    skipfirstdata = data
+                    skiplastparents = parents
+                    skiplen = 1
+        else:
+            runlen = 0
+            if skipfirstid:
+                if skiplen >= 2:
+                    yield (skipfirstid, SKIPPEDRUN, skiplen, skiplastparents)
+                else:
+                    yield (skipfirstid, skipfirsttype, skipfirstdata,
+                           skiplastparents)
+                expect(skipfirstid, skiplastparents)
+                skipfirstid = None
+        if not skipfirstid:
+            yield (id, type, data, parents)
+            expect(id, parents)
+        previd = id
+
+    # final
+    if skipfirstid:
+        if skiplen >= 2:
+            yield (skipfirstid, SKIPPEDRUN, skiplen, skiplastparents)
+        else:
+            yield (skipfirstid, skipfirsttype, skipfirstdata,
+                   skiplastparents)
+
+
 def colorededges(dag):
     """annotates a DAG with colored edge information
 
diff --git a/tests/test-glog b/tests/test-glog
--- a/tests/test-glog
+++ b/tests/test-glog
@@ -173,3 +173,44 @@
 hg incoming -q --graph
 cd ..
 hg -R repo outgoing --graph repo2
+
+echo % collapse linear runs
+hg init runs
+cd runs
+echo % building tree with runs
+commit 0 "root"
+commit 1 "run" 0
+commit 2 "run" 1
+commit 3 "run" 2
+commit 4 "run" 3
+commit 5 "branch" 4
+commit 6 "one" 5
+commit 7 "one" 6
+commit 8 "one" 7
+commit 9 "one" 8
+commit 10 "two" 5
+commit 11 "two" 10
+commit 12 "two" 11
+commit 13 "two" 12
+commit 14 "merge" 9 13
+commit 15 "three" 14
+commit 16 "three" 15
+commit 17 "three" 16
+commit 18 "three" 17
+commit 19 "three" 18
+commit 20 "four" 3
+commit 21 "four" 20
+commit 22 "four" 21
+echo % unfiltered
+hg glog --template "{desc}\n"
+echo % c1
+hg glog -c1 --template "{desc}\n"
+echo % c2
+hg glog -c2 --template "{desc}\n"
+echo % c3
+hg glog -c3 --template "{desc}\n"
+echo % c4
+hg glog -c4 --template "{desc}\n"
+echo % c1 l10
+hg glog -c1 -l10 --template "{desc}\n"
+
diff --git a/tests/test-glog.out b/tests/test-glog.out
--- a/tests/test-glog.out
+++ b/tests/test-glog.out
@@ -423,3 +423,252 @@
    date:        Thu Jan 01 00:00:27 1970 +0000
    summary:     (27) collapse
 
+% collapse linear runs
+% building tree with runs
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+(the rawcommit command is deprecated)
+% unfiltered
+@  (22) four
+|
+o  (21) four
+|
+o  (20) four
+|
+| o  (19) three
+| |
+| o  (18) three
+| |
+| o  (17) three
+| |
+| o  (16) three
+| |
+| o  (15) three
+| |
+| o    (14) merge
+| |\
+| | o  (13) two
+| | |
+| | o  (12) two
+| | |
+| | o  (11) two
+| | |
+| | o  (10) two
+| | |
+| o |  (9) one
+| | |
+| o |  (8) one
+| | |
+| o |  (7) one
+| | |
+| o |  (6) one
+| |/
+| o  (5) branch
+| |
+| o  (4) run
+|/
+o  (3) run
+|
+o  (2) run
+|
+o  (1) run
+|
+o  (0) root
+
+% c1
+@  (22) four
+|
+.  ... (2 skipped)
+|
+| o  (19) three
+| |
+| .  ... (4 skipped)
+| |
+| o    (14) merge
+| |\
+| | o  (13) two
+| | |
+| | .  ... (3 skipped)
+| | |
+| o |  (9) one
+| | |
+| . |  ... (3 skipped)
+| |/
+| o  (5) branch
+| |
+| o  (4) run
+|/
+o  (3) run
+|
+.  ... (3 skipped)
+
+% c2
+@  (22) four
+|
+o  (21) four
+|
+o  (20) four
+|
+| o  (19) three
+| |
+| o  (18) three
+| |
+| .  ... (3 skipped)
+| |
+| o    (14) merge
+| |\
+| | o  (13) two
+| | |
+| | o  (12) two
+| | |
+| | .  ... (2 skipped)
+| | |
+| o |  (9) one
+| | |
+| o |  (8) one
+| | |
+| . |  ... (2 skipped)
+| |/
+| o  (5) branch
+| |
+| o  (4) run
+|/
+o  (3) run
+|
+o  (2) run
+|
+.  ... (2 skipped)
+
+% c3
+@  (22) four
+|
+o  (21) four
+|
+o  (20) four
+|
+| o  (19) three
+| |
+| o  (18) three
+| |
+| o  (17) three
+| |
+| .  ... (2 skipped)
+| |
+| o    (14) merge
+| |\
+| | o  (13) two
+| | |
+| | o  (12) two
+| | |
+| | o  (11) two
+| | |
+| | o  (10) two
+| | |
+| o |  (9) one
+| | |
+| o |  (8) one
+| | |
+| o |  (7) one
+| | |
+| o |  (6) one
+| |/
+| o  (5) branch
+| |
+| o  (4) run
+|/
+o  (3) run
+|
+o  (2) run
+|
+o  (1) run
+|
+o  (0) root
+
+% c4
+@  (22) four
+|
+o  (21) four
+|
+o  (20) four
+|
+| o  (19) three
+| |
+| o  (18) three
+| |
+| o  (17) three
+| |
+| o  (16) three
+| |
+| o  (15) three
+| |
+| o    (14) merge
+| |\
+| | o  (13) two
+| | |
+| | o  (12) two
+| | |
+| | o  (11) two
+| | |
+| | o  (10) two
+| | |
+| o |  (9) one
+| | |
+| o |  (8) one
+| | |
+| o |  (7) one
+| | |
+| o |  (6) one
+| |/
+| o  (5) branch
+| |
+| o  (4) run
+|/
+o  (3) run
+|
+o  (2) run
+|
+o  (1) run
+|
+o  (0) root
+
+% c1 l10
+@  (22) four
+|
+.  ... (2 skipped)
+|
+| o  (19) three
+| |
+| .  ... (4 skipped)
+| |
+| o    (14) merge
+| |\
+| | o  (13) two
+| | |
+| | .  ... (3 skipped)
+| | |
+| o |  (9) one
+| | |
+| . |  ... (3 skipped)
+| |/
+| o  (5) branch
+| |


More information about the Mercurial-devel mailing list