changeset 2380:0c7dc3402efa beta

Unified DAG generation for hg and git - also fixes issue #470
author Marcin Kuzminski <marcin@python-works.com>
date Mon, 04 Jun 2012 01:21:15 +0200
parents 7ac09514a178
children e487d2a6aa38
files rhodecode/controllers/changelog.py rhodecode/lib/graphmod.py
diffstat 2 files changed, 132 insertions(+), 15 deletions(-) [+]
line wrap: on
line diff
--- a/rhodecode/controllers/changelog.py	Sun Jun 03 20:35:13 2012 +0200
+++ b/rhodecode/controllers/changelog.py	Mon Jun 04 01:21:15 2012 +0200
@@ -26,7 +26,6 @@
 import logging
 import traceback
 
-from mercurial import graphmod
 from pylons import request, url, session, tmpl_context as c
 from pylons.controllers.util import redirect
 from pylons.i18n.translation import _
@@ -36,7 +35,7 @@
 from rhodecode.lib.base import BaseRepoController, render
 from rhodecode.lib.helpers import RepoPage
 from rhodecode.lib.compat import json
-
+from rhodecode.lib.graphmod import _colored, _dagwalker
 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
 
 log = logging.getLogger(__name__)
@@ -117,18 +116,9 @@
         data = []
         revs = [x.revision for x in collection]
 
-        if repo.alias == 'git':
-            for _ in revs:
-                vtx = [0, 1]
-                edges = [[0, 0, 1]]
-                data.append(['', vtx, edges])
-
-        elif repo.alias == 'hg':
-            dag = graphmod.dagwalker(repo._repo, revs)
-            c.dag = graphmod.colored(dag, repo._repo)
-            for (id, type, ctx, vtx, edges) in c.dag:
-                if type != graphmod.CHANGESET:
-                    continue
-                data.append(['', vtx, edges])
+        dag = _dagwalker(repo, revs, repo.alias)
+        dag = _colored(dag)
+        for (id, type, ctx, vtx, edges) in dag:
+            data.append(['', vtx, edges])
 
         c.jsdata = json.dumps(data)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rhodecode/lib/graphmod.py	Mon Jun 04 01:21:15 2012 +0200
@@ -0,0 +1,127 @@
+"""
+Modified mercurial DAG graph functions that re-uses VCS structure
+
+It allows to have a shared codebase for DAG generation for hg and git repos
+"""
+
+nullrev = -1
+
+
+def grandparent(parentrev_func, lowestrev, roots, head):
+    """
+    Return all ancestors of head in roots which revision is
+    greater or equal to lowestrev.
+    """
+    pending = set([head])
+    seen = set()
+    kept = set()
+    llowestrev = max(nullrev, lowestrev)
+    while pending:
+        r = pending.pop()
+        if r >= llowestrev and r not in seen:
+            if r in roots:
+                kept.add(r)
+            else:
+                pending.update([p for p in parentrev_func(r)])
+            seen.add(r)
+    return sorted(kept)
+
+
+def _dagwalker(repo, revs, alias):
+    if not revs:
+        return
+
+    if alias == 'hg':
+        cl = repo._repo.changelog.parentrevs
+        repo = repo
+    elif alias == 'git':
+        def cl(rev):
+            return [x.revision for x in repo[rev].parents()]
+        repo = repo
+
+    lowestrev = min(revs)
+    gpcache = {}
+
+    knownrevs = set(revs)
+    for rev in revs:
+        ctx = repo[rev]
+        parents = sorted(set([p.revision for p in ctx.parents
+                              if p.revision in knownrevs]))
+        mpars = [p.revision for p in ctx.parents if
+                 p.revision != nullrev and p.revision not in parents]
+
+        for mpar in mpars:
+            gp = gpcache.get(mpar)
+            if gp is None:
+                gp = gpcache[mpar] = grandparent(cl, lowestrev, revs, mpar)
+            if not gp:
+                parents.append(mpar)
+            else:
+                parents.extend(g for g in gp if g not in parents)
+
+        yield (ctx.revision, 'C', ctx, parents)
+
+
+def _colored(dag):
+    """annotates a DAG with colored edge information
+
+    For each DAG node this function emits tuples::
+
+      (id, type, data, (col, color), [(col, nextcol, color)])
+
+    with the following new elements:
+
+      - Tuple (col, color) with column and color index for the current node
+      - A list of tuples indicating the edges between the current node and its
+        parents.
+    """
+    seen = []
+    colors = {}
+    newcolor = 1
+
+    getconf = lambda rev: {}
+
+    for (cur, type, data, parents) in dag:
+
+        # Compute seen and next
+        if cur not in seen:
+            seen.append(cur)  # new head
+            colors[cur] = newcolor
+            newcolor += 1
+
+        col = seen.index(cur)
+        color = colors.pop(cur)
+        next = seen[:]
+
+        # Add parents to next
+        addparents = [p for p in parents if p not in next]
+        next[col:col + 1] = addparents
+
+        # Set colors for the parents
+        for i, p in enumerate(addparents):
+            if not i:
+                colors[p] = color
+            else:
+                colors[p] = newcolor
+                newcolor += 1
+
+        # Add edges to the graph
+        edges = []
+        for ecol, eid in enumerate(seen):
+            if eid in next:
+                bconf = getconf(eid)
+                edges.append((
+                    ecol, next.index(eid), colors[eid],
+                    bconf.get('width', -1),
+                    bconf.get('color', '')))
+            elif eid == cur:
+                for p in parents:
+                    bconf = getconf(p)
+                    edges.append((
+                        ecol, next.index(p), color,
+                        bconf.get('width', -1),
+                        bconf.get('color', '')))
+
+        # Yield and move on
+        yield (cur, type, data, (col, color), edges)
+        seen = next