D5538: hgweb: add /obsgraph page that shows obsolescence history of a changeset (PoC)

av6 (Anton Shestakov) phabricator at mercurial-scm.org
Wed Jan 9 11:12:19 UTC 2019


av6 created this revision.
Herald added subscribers: mercurial-devel, mjpieters.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  Feel free to ask questions, I suspect some chunks would need a comment (either
  in review or in code) to explain what's going on there.
  
  I'll start working on the other hgweb themes later, when I get feedback on this
  series.
  
  Color choices are from the current canvas-based graph, but that probably will
  change when I find a better color scheme.
  
  I'm revisiting the idea of making graph in hgweb be SVG as opposed to canvas,
  and this new page, that is separate from /graph, looks like a good testing
  field: locally I have replaced canvas graph with this new SVG graph on /graph
  page and it works fine and looks better, and I hope to improve the visuals even
  more. The data structures are the same (for now, at least), and SVGGraph in
  mercurial.js is mostly a copy of the current Graph class.
  
  Tooltips for different graph nodes currently work only in Chromium and similar
  browsers, but it's still better than no tooltips (on cavnas).
  
  You can see obsgraph live on http://hg-test.dwimlabs.net/

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D5538

AFFECTED FILES
  contrib/wix/templates.wxs
  mercurial/hgweb/webcommands.py
  mercurial/templates/paper/changeset.tmpl
  mercurial/templates/paper/graphentry.tmpl
  mercurial/templates/paper/map
  mercurial/templates/paper/obsgraph.tmpl
  mercurial/templates/paper/obsgraphentry.tmpl
  mercurial/templates/static/mercurial.js
  mercurial/templates/static/style-paper.css
  tests/test-hgweb-commands.t
  tests/test-hgweb-diffs.t
  tests/test-hgweb-empty.t
  tests/test-hgweb-removed.t
  tests/test-hgweb-symrev.t

CHANGE DETAILS

diff --git a/tests/test-hgweb-symrev.t b/tests/test-hgweb-symrev.t
--- a/tests/test-hgweb-symrev.t
+++ b/tests/test-hgweb-symrev.t
@@ -97,6 +97,7 @@
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=paper' | egrep $REVLINKS
    <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
    <li><a href="/graph/xyzzy?style=paper">graph</a></li>
+   <li><a href="/obsgraph/xyzzy?style=paper">obsgraph</a></li>
    <li><a href="/raw-rev/xyzzy?style=paper">raw</a></li>
    <li><a href="/file/xyzzy?style=paper">browse</a></li>
   <a href="/archive/xyzzy.zip">zip</a>
@@ -297,6 +298,7 @@
   $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=coal' | egrep $REVLINKS
    <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
    <li><a href="/graph/xyzzy?style=coal">graph</a></li>
+   <li><a href="/obsgraph/xyzzy?style=coal">obsgraph</a></li>
    <li><a href="/raw-rev/xyzzy?style=coal">raw</a></li>
    <li><a href="/file/xyzzy?style=coal">browse</a></li>
   <a href="/archive/xyzzy.zip">zip</a>
diff --git a/tests/test-hgweb-removed.t b/tests/test-hgweb-removed.t
--- a/tests/test-hgweb-removed.t
+++ b/tests/test-hgweb-removed.t
@@ -46,6 +46,7 @@
   </ul>
   <ul>
    <li class="active">changeset</li>
+   <li><a href="/obsgraph/tip">obsgraph</a></li>
    <li><a href="/raw-rev/tip">raw</a></li>
    <li><a href="/file/tip">browse</a></li>
   </ul>
diff --git a/tests/test-hgweb-empty.t b/tests/test-hgweb-empty.t
--- a/tests/test-hgweb-empty.t
+++ b/tests/test-hgweb-empty.t
@@ -335,6 +335,156 @@
   </body>
   </html>
   
+  $ (get-with-headers.py localhost:$HGPORT 'obsgraph')
+  200 Script output follows
+  
+  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
+  <head>
+  <link rel="icon" href="/static/hgicon.png" type="image/png" />
+  <meta name="robots" content="index, nofollow" />
+  <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
+  <script type="text/javascript" src="/static/mercurial.js"></script>
+  
+  <title>test: 000000000000 obsolescence graph</title>
+  </head>
+  <body>
+  <div class="container">
+  <div class="menu">
+  <div class="logo">
+  <a href="https://mercurial-scm.org/">
+  <img src="/static/hglogo.png" alt="mercurial" /></a>
+  </div>
+  <ul>
+   <li><a href="/shortlog/000000000000">log</a></li>
+   <li><a href="/graph/000000000000">graph</a></li>
+   <li><a href="/tags">tags</a></li>
+   <li><a href="/bookmarks">bookmarks</a></li>
+   <li><a href="/branches">branches</a></li>
+  </ul>
+  <ul>
+   <li><a href="/rev/000000000000">changeset</a></li>
+   <li class="active">obsgraph</li>
+   <li><a href="/raw-rev/000000000000">raw</a></li>
+   <li><a href="/file/000000000000">browse</a></li>
+  </ul>
+  <ul>
+   
+  </ul>
+  <ul>
+   <li><a href="/help">help</a></li>
+  </ul>
+  </div>
+  
+  <div class="main">
+  
+  <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
+  <h3>
+   changeset -1:<a href="/rev/000000000000">000000000000</a>
+   <span class="tag">tip</span> 
+  </h3>
+  
+  
+  <form class="search" action="/log">
+  
+  <p><input name="rev" id="search1" type="text" size="30" value="" /></p>
+  <div id="hint">Find changesets by keywords (author, files, the commit message), revision
+  number or hash, or <a href="/help/revsets">revset expression</a>.</div>
+  </form>
+  
+  <div class="description">(none)</div>
+  
+  <table id="changesetEntry">
+  <tr>
+   <th class="author">author</th>
+   <td class="author"></td>
+  </tr>
+  <tr>
+   <th class="date">date</th>
+   <td class="date age">Thu, 01 Jan 1970 00:00:00 +0000</td>
+  </tr>
+  
+  
+  <tr>
+   <th class="author">parents</th>
+   <td class="author"></td>
+  </tr>
+  <tr>
+   <th class="author">children</th>
+   <td class="author"></td>
+  </tr>
+  <tr>
+   <th class="files">files</th>
+   <td class="files"></td>
+  </tr>
+  </table>
+  
+  <noscript><p>The revision graph only works with JavaScript-enabled browsers.</p></noscript>
+  
+  <div id="wrapper">
+  <svg id="svg-graph" xmlns="http://www.w3.org/2000/svg">
+   <defs>
+    <linearGradient id="outline" gradientUnits="userSpaceOnUse"><stop stop-color="white"/></linearGradient>
+  
+    <linearGradient id="color-old-0" gradientUnits="userSpaceOnUse"><stop stop-color="#bfbf40"/></linearGradient>
+    <linearGradient id="color-old-1" gradientUnits="userSpaceOnUse"><stop stop-color="#40bf40"/></linearGradient>
+    <linearGradient id="color-old-2" gradientUnits="userSpaceOnUse"><stop stop-color="#40bfbf"/></linearGradient>
+    <linearGradient id="color-old-3" gradientUnits="userSpaceOnUse"><stop stop-color="#4040bf"/></linearGradient>
+    <linearGradient id="color-old-4" gradientUnits="userSpaceOnUse"><stop stop-color="#bf40bf"/></linearGradient>
+    <linearGradient id="color-old-5" gradientUnits="userSpaceOnUse"><stop stop-color="#bf4040"/></linearGradient>
+  
+    <g id="graph-node-normal">
+     <title>normal commit</title>
+     <circle r="6"/>
+    </g>
+    <g id="graph-node-closing">
+     <title>branch-closing commit</title>
+     <rect x="-6" y="-2.5" width="12" height="5"/>
+    </g>
+    <g id="graph-node-unstable">
+     <title>unstable commit</title>
+     <path d="M 6.1961524,1.2679492 4.0006429,3.711753e-4 6.1961524,-1.2679492 l -2,-3.4641016 L 2,-3.4641016 V -6 h -4 l -6.429e-4,2.5355272 -2.1955095,-1.267578 -2,3.4641016 2.1955095,1.2675780247 -2.1955095,1.2683203753 2,3.4641016 L -2,3.4641016 V 6 h 4 l 6.429e-4,-2.5355272 2.1955095,1.267578 Z"/>
+    </g>
+    <g id="graph-node-obsolete">
+     <title>obsolete commit</title>
+     <path d="M -6.0104076,-2.4748737 -3.5355339,0 -6.0104076,2.4748737 -2.4748737,6.0104076 0,3.5355339 2.4748737,6.0104076 6.0104076,2.4748737 3.5355339,0 6.0104076,-2.4748737 2.4748737,-6.0104076 0,-3.5355339 -2.4748737,-6.0104076 Z"/>
+    </g>
+    <g id="graph-node-current">
+     <title>working directory parent</title>
+     <path d="M -11.232422,-5.7675781 -14.767578,-2.2324219 -12.535156,0 -14.767578,2.2324219 -11.232422,5.7675781 -5.4648438,0 Z"/>
+    </g>
+   </defs>
+   <g id="transformer" shape-rendering="geometricPrecision">
+    <g id="lines" stroke-width="2" fill="transparent" opacity="0.75"></g>
+    <g id="nodes" stroke-width="2" stroke="url(#outline)"></g>
+   </g>
+  </svg>
+  <ul id="graphnodes" class="stripes2"><li data-node="000000000000">
+   <div class="fg">
+    <span class="desc">
+     <a href="/rev/000000000000">(none)</a>
+    </span>
+    <span class="tag">tip</span> 
+    <div class="info"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>, by </div>
+   </div>
+  </li>
+  </ul>
+  </div>
+  
+  <script type="text/javascript">
+  var data = [{"edges": [], "graphnode": "@o", "node": "000000000000", "vertex": [0, 1]}];
+  var graph = new SVGGraph();
+  graph.scale(39);
+  graph.render(data);
+  </script>
+  
+  </div>
+  </div>
+  
+  
+  </body>
+  </html>
+  
   $ (get-with-headers.py localhost:$HGPORT 'file')
   200 Script output follows
   
diff --git a/tests/test-hgweb-diffs.t b/tests/test-hgweb-diffs.t
--- a/tests/test-hgweb-diffs.t
+++ b/tests/test-hgweb-diffs.t
@@ -65,6 +65,7 @@
   </ul>
   <ul>
    <li class="active">changeset</li>
+   <li><a href="/obsgraph/0">obsgraph</a></li>
    <li><a href="/raw-rev/0">raw</a></li>
    <li><a href="/file/0">browse</a></li>
   </ul>
@@ -362,6 +363,7 @@
   </ul>
   <ul>
    <li class="active">changeset</li>
+   <li><a href="/obsgraph/0">obsgraph</a></li>
    <li><a href="/raw-rev/0">raw</a></li>
    <li><a href="/file/0">browse</a></li>
   </ul>
diff --git a/tests/test-hgweb-commands.t b/tests/test-hgweb-commands.t
--- a/tests/test-hgweb-commands.t
+++ b/tests/test-hgweb-commands.t
@@ -859,6 +859,7 @@
   </ul>
   <ul>
    <li class="active">changeset</li>
+   <li><a href="/obsgraph/0">obsgraph</a></li>
    <li><a href="/raw-rev/0">raw</a></li>
    <li><a href="/file/0">browse</a></li>
   </ul>
@@ -964,6 +965,8 @@
   </body>
   </html>
   
+  $ get-with-headers.py $LOCALIP:$HGPORT 'obsgraph' | grep 'var data ='
+  var data = [{"edges": [], "graphnode": "@o", "node": "cad8025a2e87", "vertex": [0, 1]}];
   $ get-with-headers.py $LOCALIP:$HGPORT 'rev/1/?style=raw'
   200 Script output follows
   
diff --git a/mercurial/templates/static/style-paper.css b/mercurial/templates/static/style-paper.css
--- a/mercurial/templates/static/style-paper.css
+++ b/mercurial/templates/static/style-paper.css
@@ -451,7 +451,7 @@
 	padding: 0;
 }
 
-canvas {
+canvas#graph, svg#svg-graph {
 	position: absolute;
 	z-index: 5;
 	top: -0.7em;
diff --git a/mercurial/templates/static/mercurial.js b/mercurial/templates/static/mercurial.js
--- a/mercurial/templates/static/mercurial.js
+++ b/mercurial/templates/static/mercurial.js
@@ -233,6 +233,168 @@
 
 };
 
+function SVGGraph() {
+    this.svg = document.getElementById('svg-graph');
+    var ctm = this.svg.getScreenCTM();
+    var transform = 'translate(' + (ctm.e % 1) + ',' + (ctm.f % 1) + ')';
+    this.svg.getElementById('transformer').setAttribute('transform', transform);
+    this.colors = this.svg.querySelectorAll('defs > [id^="color-"]');
+    this.bg = [0, 4];
+    this.cell = [2, 0];
+    this.columns = 0;
+}
+
+SVGGraph.prototype = {
+    reset: function() {
+        this.bg = [0, 4];
+        this.cell = [2, 0];
+        this.columns = 0;
+    },
+
+    scale: function(height) {
+        this.bgHeight = height;
+        this.boxSize = Math.floor(this.bgHeight / 1.2);
+    },
+
+    getColor: function(color) {
+        if (typeof color === "string") {
+            return "#" + color;
+        } else {
+            color %= this.colors.length;
+            return 'url(#' + this.colors[color].id + ')';
+        }
+    },
+
+    _line: function(x0, y0, x1, y1) {
+        var el = document.createElementNS(this.svg.getAttribute('xmlns'), 'line');
+        el.setAttribute('x1', x0);
+        el.setAttribute('y1', y0);
+        el.setAttribute('x2', x1);
+        el.setAttribute('y2', y1);
+        return el;
+    },
+
+    _curve: function(x0, y0, x1, y1) {
+        var el = document.createElementNS(this.svg.getAttribute('xmlns'), 'path');
+        var xmid = (x0 + x1) / 2;
+        var ymid = (y0 + y1) / 2;
+        var d = 'M ' + [x0, y0].join(' ');
+        d += ' Q ' + [x0, ymid, xmid, ymid].join(' ');
+        d += ' Q ' + [x1, ymid, x1, y1].join(' ');
+        el.setAttribute('d', d);
+        return el;
+    },
+
+    _el: function(x0, y0, x1, y1) {
+        if (x0 === x1) {
+            return this._line(x0, y0, x1, y1);
+        }
+        return this._curve(x0, y0, x1, y1);
+    },
+
+    edge: function(x0, y0, x1, y1, color, width) {
+        var c = this.getColor(color);
+        var line = this._el(x0, y0, x1, y1);
+        line.setAttribute('stroke', c);
+        if (width >= 0) {
+            line.setAttribute('stroke-width', width);
+        }
+        this.svg.getElementById('lines').appendChild(line);
+    },
+
+    _use: function(nodeType, x, y, fill) {
+        var el = document.createElementNS(this.svg.getAttribute('xmlns'), 'use');
+        el.setAttribute('href', '#graph-node-' + nodeType);
+        el.setAttribute('x', x);
+        el.setAttribute('y', y);
+        el.setAttribute('fill', fill);
+        return el;
+    },
+
+    vertex: function(x, y, color, parity, cur) {
+        var c = this.getColor(color);
+        if (cur.graphnode[0] === '@') {
+            var cnode = this._use('current', x, y, c);
+            this.svg.getElementById('nodes').appendChild(cnode);
+        }
+        var nodeType = 'normal';
+        switch (cur.graphnode.substr(-1)) {
+            case '_':
+                nodeType = 'closing';
+                break;
+            case '*':
+                nodeType = 'unstable';
+                break;
+            case 'x':
+                nodeType = 'obsolete';
+                break;
+        }
+        var node = this._use(nodeType, x, y, c);
+        this.svg.getElementById('nodes').appendChild(node);
+
+        var left = (this.bgHeight - this.boxSize) + (this.columns + 1) * this.boxSize;
+        var item = document.querySelector('[data-node="' + cur.node + '"]');
+        if (item) {
+            item.style.paddingLeft = left + 'px';
+        }
+    },
+
+    render: function(data) {
+        var i, j, cur, line, start, end, color, x, y, x0, y0, x1, y1, column;
+        var cols = 0;
+
+        for (i = 0; i < data.length; i++) {
+            var parity = i % 2;
+            this.cell[1] += this.bgHeight;
+            this.bg[1] += this.bgHeight;
+
+            cur = data[i];
+            var fold = false;
+
+            for (j = 0; j < cur.edges.length; j++) {
+                line = cur.edges[j];
+                start = line[0];
+                end = line[1];
+                color = line[2];
+                var width = line[3];
+                var branchcolor = line[4];
+                if (branchcolor) {
+                    color = branchcolor;
+                }
+
+                if (end > this.columns || start > this.columns) {
+                    this.columns += 1;
+                }
+
+                if (start === this.columns && start > end) {
+                    fold = true;
+                }
+
+                x0 = this.cell[0] + this.boxSize * start + this.boxSize / 2;
+                y0 = this.bg[1] - this.bgHeight / 2;
+                x1 = this.cell[0] + this.boxSize * end + this.boxSize / 2;
+                y1 = this.bg[1] + this.bgHeight / 2;
+
+                this.edge(x0, y0, x1, y1, color, width);
+
+                cols = Math.max(cols, start, end);
+            }
+
+            column = cur.vertex[0];
+            color = cur.vertex[1];
+
+            x = this.cell[0] + this.boxSize * column + this.boxSize / 2;
+            y = this.bg[1] - this.bgHeight / 2;
+            this.vertex(x, y, color, parity, cur);
+
+            if (fold) {
+                this.columns -= 1;
+            }
+        }
+        this.svg.setAttribute('width', (cols + 1) * this.bgHeight);
+        this.svg.setAttribute('height', (data.length + 1) * this.bgHeight - 27);
+    }
+};
 
 function process_dates(parentSelector){
 
diff --git a/mercurial/templates/paper/graphentry.tmpl b/mercurial/templates/paper/obsgraphentry.tmpl
copy from mercurial/templates/paper/graphentry.tmpl
copy to mercurial/templates/paper/obsgraphentry.tmpl
--- a/mercurial/templates/paper/graphentry.tmpl
+++ b/mercurial/templates/paper/obsgraphentry.tmpl
@@ -1,9 +1,9 @@
 <li data-node="{node|short}">
  <div class="fg">
   <span class="desc">
-   <a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>
+   {if(missing, '{node|short}', '<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{desc|strip|firstline|escape|nonempty}</a>')}
   </span>
-  {alltags}
-  <div class="info"><span class="age">{date|rfc822date}</span>, by {author|person}</div>
+  {if(missing, '<span class="obsolete">missing</span>', '{alltags}')}
+  <div class="info">{if(missing, 'not available locally', '<span class="age">{date|rfc822date}</span>, by {author|person}')}</div>
  </div>
 </li>
diff --git a/mercurial/templates/paper/changeset.tmpl b/mercurial/templates/paper/obsgraph.tmpl
copy from mercurial/templates/paper/changeset.tmpl
copy to mercurial/templates/paper/obsgraph.tmpl
--- a/mercurial/templates/paper/changeset.tmpl
+++ b/mercurial/templates/paper/obsgraph.tmpl
@@ -1,5 +1,5 @@
 {header}
-<title>{repo|escape}: {node|short}</title>
+<title>{repo|escape}: {node|short} obsolescence graph</title>
 </head>
 <body>
 <div class="container">
@@ -16,7 +16,8 @@
  <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
 </ul>
 <ul>
- <li class="active">changeset</li>
+ <li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
+ <li class="active">obsgraph</li>
  <li><a href="{url|urlescape}raw-rev/{symrev}{sessionvars%urlparameter}">raw</a></li>
  <li><a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">browse</a></li>
 </ul>
@@ -69,26 +70,57 @@
  <th class="files">files</th>
  <td class="files">{files}</td>
 </tr>
-<tr>
-  <th class="diffstat">diffstat</th>
-  <td class="diffstat">
-    {diffsummary}
-    <a id="diffstatexpand" class="diffstattoggle" href="#">[<tt>+</tt>]</a>
-    <div id="diffstatdetails" style="display:none;">
-      <a class="diffstattoggle" href="#">[<tt>-</tt>]</a>
-      <table class="diffstat-table stripes2">{diffstat}</table>
-    </div>
-  </td>
-</tr>
 </table>
 
-<div class="overflow">
-<div class="sourcefirst linewraptoggle">line wrap: <a class="linewraplink" href="#">on</a></div>
-<div class="sourcefirst"> line diff</div>
-<div class="stripes2 diffblocks">
-{diff}
+<noscript><p>The revision graph only works with JavaScript-enabled browsers.</p></noscript>
+
+<div id="wrapper">
+<svg id="svg-graph" xmlns="http://www.w3.org/2000/svg">
+ <defs>
+  <linearGradient id="outline" gradientUnits="userSpaceOnUse"><stop stop-color="white"/></linearGradient>
+
+  <linearGradient id="color-old-0" gradientUnits="userSpaceOnUse"><stop stop-color="#bf4040"/></linearGradient>
+  <linearGradient id="color-old-1" gradientUnits="userSpaceOnUse"><stop stop-color="#bfbf40"/></linearGradient>
+  <linearGradient id="color-old-2" gradientUnits="userSpaceOnUse"><stop stop-color="#40bf40"/></linearGradient>
+  <linearGradient id="color-old-3" gradientUnits="userSpaceOnUse"><stop stop-color="#40bfbf"/></linearGradient>
+  <linearGradient id="color-old-4" gradientUnits="userSpaceOnUse"><stop stop-color="#4040bf"/></linearGradient>
+  <linearGradient id="color-old-5" gradientUnits="userSpaceOnUse"><stop stop-color="#bf40bf"/></linearGradient>
+
+  <g id="graph-node-normal">
+   <title>normal commit</title>
+   <circle r="6"/>
+  </g>
+  <g id="graph-node-closing">
+   <title>branch-closing commit</title>
+   <rect x="-6" y="-2.5" width="12" height="5"/>
+  </g>
+  <g id="graph-node-unstable">
+   <title>unstable commit</title>
+   <path d="M 6.1961524,1.2679492 4.0006429,3.711753e-4 6.1961524,-1.2679492 l -2,-3.4641016 L 2,-3.4641016 V -6 h -4 l -6.429e-4,2.5355272 -2.1955095,-1.267578 -2,3.4641016 2.1955095,1.2675780247 -2.1955095,1.2683203753 2,3.4641016 L -2,3.4641016 V 6 h 4 l 6.429e-4,-2.5355272 2.1955095,1.267578 Z"/>
+  </g>
+  <g id="graph-node-obsolete">
+   <title>obsolete commit</title>
+   <path d="M -6.0104076,-2.4748737 -3.5355339,0 -6.0104076,2.4748737 -2.4748737,6.0104076 0,3.5355339 2.4748737,6.0104076 6.0104076,2.4748737 3.5355339,0 6.0104076,-2.4748737 2.4748737,-6.0104076 0,-3.5355339 -2.4748737,-6.0104076 Z"/>
+  </g>
+  <g id="graph-node-current">
+   <title>working directory parent</title>
+   <path d="M -11.232422,-5.7675781 -14.767578,-2.2324219 -12.535156,0 -14.767578,2.2324219 -11.232422,5.7675781 -5.4648438,0 Z"/>
+  </g>
+ </defs>
+ <g id="transformer" shape-rendering="geometricPrecision">
+  <g id="lines" stroke-width="2" fill="transparent" opacity="0.75"></g>
+  <g id="nodes" stroke-width="2" stroke="url(#outline)"></g>
+ </g>
+</svg>
+<ul id="graphnodes" class="stripes2">{obsgraphentries%obsgraphentry}</ul>
 </div>
-</div>
+
+<script type="text/javascript"{if(nonce, ' nonce="{nonce}"')}>
+var data = {jsdata|json};
+var graph = new SVGGraph();
+graph.scale(39);
+graph.render(data);
+</script>
 
 </div>
 </div>
diff --git a/mercurial/templates/paper/map b/mercurial/templates/paper/map
--- a/mercurial/templates/paper/map
+++ b/mercurial/templates/paper/map
@@ -10,6 +10,8 @@
 shortlogentry = shortlogentry.tmpl
 graph = graph.tmpl
 graphentry = graphentry.tmpl
+obsgraph = obsgraph.tmpl
+obsgraphentry = obsgraphentry.tmpl
 help = help.tmpl
 helptopics = helptopics.tmpl
 
diff --git a/mercurial/templates/paper/changeset.tmpl b/mercurial/templates/paper/changeset.tmpl
--- a/mercurial/templates/paper/changeset.tmpl
+++ b/mercurial/templates/paper/changeset.tmpl
@@ -17,6 +17,7 @@
 </ul>
 <ul>
  <li class="active">changeset</li>
+ <li><a href="{url|urlescape}obsgraph/{symrev}{sessionvars%urlparameter}">obsgraph</a></li>
  <li><a href="{url|urlescape}raw-rev/{symrev}{sessionvars%urlparameter}">raw</a></li>
  <li><a href="{url|urlescape}file/{symrev}{sessionvars%urlparameter}">browse</a></li>
 </ul>
diff --git a/mercurial/hgweb/webcommands.py b/mercurial/hgweb/webcommands.py
--- a/mercurial/hgweb/webcommands.py
+++ b/mercurial/hgweb/webcommands.py
@@ -30,6 +30,7 @@
     encoding,
     error,
     graphmod,
+    obsutil,
     pycompat,
     revset,
     revsetlang,
@@ -469,6 +470,60 @@
     """
     return changelog(web, shortlog=True)
 
+ at webcommand('obsgraph')
+def obsgraph(web):
+    """
+    /obsgraph[/{revision}]
+    -----------------------
+
+    Show obsolescence graph of a single changeset.
+    """
+    ctx = webutil.changectx(web.repo, web.req)
+    revs = smartset.baseset([ctx.rev()])
+
+    walker = obsutil.obshistorywalker(web.repo.unfiltered(), revs)
+    tree = [item for item in graphmod.colored(walker, web.repo)]
+
+    def jsdata(context):
+        for (id, type, ctx, vtx, edges) in tree:
+            yield {'node': pycompat.bytestr(ctx),
+                   'graphnode': webutil.getgraphnode(web.repo, ctx),
+                   'vertex': vtx,
+                   'edges': edges}
+
+    def obsgraphentries(context):
+        parity = paritygen(web.stripecount)
+        for row, (id, type, ctx, vtx, edges) in enumerate(tree):
+            if isinstance(ctx, obsutil.missingchangectx):
+                entry = {
+                    'node': hex(ctx.node()),
+                    'obsolete': True,
+                    'missing': True
+                }
+            else:
+                entry = webutil.commonentry(web.repo, ctx)
+            edgedata = [{'col': edge[0],
+                         'nextcol': edge[1],
+                         'color': edge[2] - 1,
+                         'width': edge[3],
+                         'bcolor': edge[4]}
+                        for edge in edges]
+
+            entry.update({'col': vtx[0],
+                          'color': vtx[1] - 1,
+                          'parity': next(parity),
+                          'edges': templateutil.mappinglist(edgedata),
+                          'row': row,
+                          'nextrow': row + 1})
+
+            yield entry
+
+    return web.sendtemplate(
+        'obsgraph',
+        jsdata=templateutil.mappinggenerator(jsdata),
+        obsgraphentries=templateutil.mappinggenerator(obsgraphentries),
+        **webutil.changesetentry(web, ctx))
+
 @webcommand('changeset')
 def changeset(web):
     """
diff --git a/contrib/wix/templates.wxs b/contrib/wix/templates.wxs
--- a/contrib/wix/templates.wxs
+++ b/contrib/wix/templates.wxs
@@ -147,6 +147,8 @@
             <File Id="paper.footer.tmpl"        Name="footer.tmpl" />
             <File Id="paper.graph.tmpl"         Name="graph.tmpl" />
             <File Id="paper.graphentry.tmpl"    Name="graphentry.tmpl" />
+            <File Id="paper.obsgraph.tmpl"      Name="obsgraph.tmpl" />
+            <File Id="paper.obsgraphentry.tmpl" Name="obsgraphentry.tmpl" />
             <File Id="paper.header.tmpl"        Name="header.tmpl" />
             <File Id="paper.index.tmpl"         Name="index.tmpl" />
             <File Id="paper.manifest.tmpl"      Name="manifest.tmpl" />



To: av6, #hg-reviewers
Cc: mjpieters, mercurial-devel


More information about the Mercurial-devel mailing list