changeset 7370:7bc62ebe7693

graphlog: refactor common grapher code Extracts the column and edge determination code into a separate function usable on generic DAGs with at most 2 parents per node. grapher() is very similar to graphmod.graph(). I shall look into merging them when I try visualizing patch branches in hgweb. Started using contexts and renamed a bunch of variables (fewer underscores).
author Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
date Fri, 14 Nov 2008 13:44:10 +0100
parents 87158be081b8
children 6ad2b914acbd
files hgext/graphlog.py
diffstat 1 files changed, 74 insertions(+), 115 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/graphlog.py	Fri Nov 14 13:59:25 2008 +0100
+++ b/hgext/graphlog.py	Fri Nov 14 13:44:10 2008 +0100
@@ -13,115 +13,76 @@
 from mercurial.i18n import _
 from mercurial.node import nullrev
 from mercurial.util import Abort, canonpath
-from mercurial import util
-
-def get_rev_parents(repo, rev):
-    return [x for x in repo.changelog.parentrevs(rev) if x != nullrev]
-
-def revision_grapher(repo, start_rev, stop_rev):
-    """incremental revision grapher
-
-    This generator function walks through the revision history from
-    revision start_rev to revision stop_rev (which must be less than
-    or equal to start_rev) and for each revision emits tuples with the
-    following elements:
-
-      - Current revision.
-      - Current node.
-      - Column of the current node in the set of ongoing edges.
-      - Edges; a list of (col, next_col) indicating the edges between
-        the current node and its parents.
-      - Number of columns (ongoing edges) in the current revision.
-      - The difference between the number of columns (ongoing edges)
-        in the next revision and the number of columns (ongoing edges)
-        in the current revision. That is: -1 means one column removed;
-        0 means no columns added or removed; 1 means one column added.
-    """
-
-    assert start_rev >= stop_rev
-    curr_rev = start_rev
-    revs = []
-    while curr_rev >= stop_rev:
-        node = repo.changelog.node(curr_rev)
-
-        # Compute revs and next_revs.
-        if curr_rev not in revs:
-            # New head.
-            revs.append(curr_rev)
-        rev_index = revs.index(curr_rev)
-        next_revs = revs[:]
 
-        # Add parents to next_revs.
-        parents = get_rev_parents(repo, curr_rev)
-        parents_to_add = []
-        for parent in parents:
-            if parent not in next_revs:
-                parents_to_add.append(parent)
-        next_revs[rev_index:rev_index + 1] = util.sort(parents_to_add)
-
-        edges = []
-        for parent in parents:
-            edges.append((rev_index, next_revs.index(parent)))
-
-        n_columns_diff = len(next_revs) - len(revs)
-        yield (curr_rev, node, rev_index, edges, len(revs), n_columns_diff)
-
-        revs = next_revs
-        curr_rev -= 1
+def revisions(repo, start, stop):
+    """cset DAG generator yielding (rev, node, [parents]) tuples
+    
+    This generator function walks through the revision history from revision
+    start to revision stop (which must be less than or equal to start).
+    """
+    assert start >= stop
+    cur = start
+    while cur >= stop:
+        ctx = repo[cur]
+        parents = sorted(p.rev() for p in ctx.parents() if p.rev() != nullrev)
+        yield (ctx, parents)
+        cur -= 1
 
-def filelog_grapher(repo, path, start_rev, stop_rev):
-    """incremental file log grapher
-
-    This generator function walks through the revision history of a
-    single file from revision start_rev to revision stop_rev (which must
-    be less than or equal to start_rev) and for each revision emits
-    tuples with the following elements:
-
-      - Current revision.
-      - Current node.
-      - Column of the current node in the set of ongoing edges.
-      - Edges; a list of (col, next_col) indicating the edges between
-        the current node and its parents.
-      - Number of columns (ongoing edges) in the current revision.
-      - The difference between the number of columns (ongoing edges)
-        in the next revision and the number of columns (ongoing edges)
-        in the current revision. That is: -1 means one column removed;
-        0 means no columns added or removed; 1 means one column added.
+def filerevs(repo, path, start, stop):
+    """file cset DAG generator yielding (rev, node, [parents]) tuples
+    
+    This generator function walks through the revision history of a single
+    file from revision start to revision stop (which must be less than or
+    equal to start).
     """
-
-    assert start_rev >= stop_rev
-    revs = []
+    assert start >= stop
     filerev = len(repo.file(path)) - 1
     while filerev >= 0:
         fctx = repo.filectx(path, fileid=filerev)
+        parents = [f.filerev() for f in fctx.parents() if f.path() == path]
+        parents.sort()
+        if fctx.rev() <= start:
+            yield (fctx, parents)
+        if fctx.rev() <= stop:
+            break
+        filerev -= 1
 
-        # Compute revs and next_revs.
-        if filerev not in revs:
-            revs.append(filerev)
-        rev_index = revs.index(filerev)
-        next_revs = revs[:]
+def grapher(nodes):
+    """grapher for asciigraph on a list of nodes and their parents
+    
+    nodes must generate tuples (node, parents, char, lines) where
+    
+     - parents must generate the parents of node, in sorted order,
+       and max length 2,
+     - char is the char to print as the node symbol, and
+     - lines are the lines to display next to the node.  
+    """
+    seen = []
+    for node, parents, char, lines in nodes:
+        if node not in seen:
+            seen.append(node)
+        nodeidx = seen.index(node)
 
-        # Add parents to next_revs.
-        parents = [f.filerev() for f in fctx.parents() if f.path() == path]
-        parents_to_add = []
+        knownparents = []
+        newparents = []
         for parent in parents:
-            if parent not in next_revs:
-                parents_to_add.append(parent)
-        next_revs[rev_index:rev_index + 1] = util.sort(parents_to_add)
-
-        edges = []
-        for parent in parents:
-            edges.append((rev_index, next_revs.index(parent)))
+            if parent in seen:
+                knownparents.append(parent)
+            else:
+                newparents.append(parent)
 
-        changerev = fctx.linkrev()
-        if changerev <= start_rev:
-            node = repo.changelog.node(changerev)
-            n_columns_diff = len(next_revs) - len(revs)
-            yield (changerev, node, rev_index, edges, len(revs), n_columns_diff)
-        if changerev <= stop_rev:
-            break
-        revs = next_revs
-        filerev -= 1
+        ncols = len(seen)
+        nextseen = seen[:]
+        nextseen[nodeidx:nodeidx + 1] = newparents
+        edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
+
+        if len(newparents) > 0:
+            edges.append((nodeidx, nodeidx))
+        if len(newparents) > 1:
+            edges.append((nodeidx, nodeidx + 1))
+        nmorecols = len(nextseen) - ncols
+        seen = nextseen
+        yield (char, lines, nodeidx, edges, ncols, nmorecols)
 
 def fix_long_right_edges(edges):
     for (i, (start, end)) in enumerate(edges):
@@ -307,33 +268,31 @@
     """
 
     limit = get_limit(opts["limit"])
-    (start_rev, stop_rev) = get_revs(repo, opts["rev"])
-    stop_rev = max(stop_rev, start_rev - limit + 1)
-    if start_rev == nullrev:
+    start, stop = get_revs(repo, opts["rev"])
+    stop = max(stop, start - limit + 1)
+    if start == nullrev:
         return
+
     if path:
         path = canonpath(repo.root, os.getcwd(), path)
-    if path:
-        revgrapher = filelog_grapher(repo, path, start_rev, stop_rev)
+    if path: # could be reset in canonpath
+        revdag = filerevs(repo, path, start, stop)
     else:
-        revgrapher = revision_grapher(repo, start_rev, stop_rev)
+        revdag = revisions(repo, start, stop)
 
     repo_parents = repo.dirstate.parents()
-    cs_printer = show_changeset(ui, repo, opts)
-    def grapher():
-        for (rev, node, node_index, edges, n_columns, n_columns_diff) in revgrapher:
+    displayer = show_changeset(ui, repo, opts)
+    def graphabledag():
+        for (ctx, parents) in revdag:
             # log_strings is the list of all log strings to draw alongside
             # the graph.
             ui.pushbuffer()
-            cs_printer.show(repo[rev])
+            displayer.show(ctx)
             log_strings = ui.popbuffer().split("\n")[:-1]
-            if node in repo_parents:
-                node_ch = "@"
-            else:
-                node_ch = "o"
-            yield (node_ch, log_strings, node_index, edges, n_columns, n_columns_diff)
+            char = ctx.node() in repo_parents and '@' or 'o'
+            yield (ctx.rev(), parents, char, log_strings)
 
-    ascii(ui, grapher())
+    ascii(ui, grapher(graphabledag()))
 
 cmdtable = {
     "glog":