hgweb: make different kinds of commits look differently on /graph
authorAnton Shestakov <av6@dwimlabs.net>
Tue, 19 Dec 2017 20:41:25 +0800
changeset 35566 baca93a47992
parent 35565 bdae51a83dfb
child 35567 07769a04bc66
hgweb: make different kinds of commits look differently on /graph Regular hg log -G uses different symbols for some graph nodes, such as commits that close branches and hidden commits. It also marks the currently checked out commit with "@". Since hg serve is sometimes used/recommended as a more visual alternative to CLI, it makes sense to port these features to hgweb. "graphnode" includes the style of a particular node and also if it's currently checked out or not, both at the same time. This is different from hg log -G (which uses templatekw.showgraphnode), where there's only place for one character, but hgweb doesn't have this limitation, since it uses <canvas> and not plain text. I'm using one string of 1 or 2 characters in this patch, it's not the most self-explanatory format, but it's concise, uses the same characters as hg log -G, and is internal to hgweb (i.e. not used for json-graph). I'm more or less fine with how things look visually, but there's still room for improvement. Feel free to criticise or point me to good-looking graphs of this kind for inspiration.
mercurial/hgweb/webcommands.py
mercurial/templates/static/mercurial.js
tests/test-hgweb-commands.t
--- a/mercurial/hgweb/webcommands.py	Thu Dec 21 13:58:11 2017 +0100
+++ b/mercurial/hgweb/webcommands.py	Tue Dec 19 20:41:25 2017 +0800
@@ -13,7 +13,7 @@
 import re
 
 from ..i18n import _
-from ..node import hex, short
+from ..node import hex, nullid, short
 
 from .common import (
     ErrorResponse,
@@ -1248,6 +1248,24 @@
         tree = list(item for item in graphmod.colored(dag, web.repo)
                     if item[1] == graphmod.CHANGESET)
 
+    def nodecurrent(ctx):
+        wpnodes = web.repo.dirstate.parents()
+        if wpnodes[1] == nullid:
+            wpnodes = wpnodes[:1]
+        if ctx.node() in wpnodes:
+            return '@'
+        return ''
+
+    def nodesymbol(ctx):
+        if ctx.obsolete():
+            return 'x'
+        elif ctx.isunstable():
+            return '*'
+        elif ctx.closesbranch():
+            return '_'
+        else:
+            return 'o'
+
     def fulltree():
         pos = web.repo[graphtop].rev()
         tree = []
@@ -1260,6 +1278,7 @@
 
     def jsdata():
         return [{'node': pycompat.bytestr(ctx),
+                 'graphnode': nodecurrent(ctx) + nodesymbol(ctx),
                  'vertex': vtx,
                  'edges': edges}
                 for (id, type, ctx, vtx, edges) in fulltree()]
--- a/mercurial/templates/static/mercurial.js	Thu Dec 21 13:58:11 2017 +0100
+++ b/mercurial/templates/static/mercurial.js	Tue Dec 19 20:41:25 2017 +0800
@@ -90,11 +90,68 @@
 
 	},
 
-	vertex: function(x, y, radius, color, parity, cur) {
+	graphNodeCurrent: function(x, y, radius) {
+		this.ctx.lineWidth = 2;
+		this.ctx.beginPath();
+		this.ctx.arc(x, y, radius * 1.75, 0, Math.PI * 2, true);
+		this.ctx.stroke();
+	},
+
+	graphNodeClosing: function(x, y, radius) {
+		this.ctx.fillRect(x - radius, y - 1.5, radius * 2, 3);
+	},
+
+	graphNodeUnstable: function(x, y, radius) {
+		var x30 = radius * Math.cos(Math.PI / 6);
+		var y30 = radius * Math.sin(Math.PI / 6);
+		this.ctx.lineWidth = 2;
 		this.ctx.beginPath();
-		this.setColor(color, 0.25, 0.75);
+		this.ctx.moveTo(x, y - radius);
+		this.ctx.lineTo(x, y + radius);
+		this.ctx.moveTo(x - x30, y - y30);
+		this.ctx.lineTo(x + x30, y + y30);
+		this.ctx.moveTo(x - x30, y + y30);
+		this.ctx.lineTo(x + x30, y - y30);
+		this.ctx.stroke();
+	},
+
+	graphNodeObsolete: function(x, y, radius) {
+		var p45 = radius * Math.cos(Math.PI / 4);
+		this.ctx.lineWidth = 3;
+		this.ctx.beginPath();
+		this.ctx.moveTo(x - p45, y - p45);
+		this.ctx.lineTo(x + p45, y + p45);
+		this.ctx.moveTo(x - p45, y + p45);
+		this.ctx.lineTo(x + p45, y - p45);
+		this.ctx.stroke();
+	},
+
+	graphNodeNormal: function(x, y, radius) {
+		this.ctx.beginPath();
 		this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
 		this.ctx.fill();
+	},
+
+	vertex: function(x, y, radius, color, parity, cur) {
+		this.ctx.save();
+		this.setColor(color, 0.25, 0.75);
+		if (cur.graphnode[0] === '@') {
+			this.graphNodeCurrent(x, y, radius);
+		}
+		switch (cur.graphnode.substr(-1)) {
+			case '_':
+				this.graphNodeClosing(x, y, radius);
+				break;
+			case '*':
+				this.graphNodeUnstable(x, y, radius);
+				break;
+			case 'x':
+				this.graphNodeObsolete(x, y, radius);
+				break;
+			default:
+				this.graphNodeNormal(x, y, radius);
+		}
+		this.ctx.restore();
 
 		var left = (this.bg_height - this.box_size) + (this.columns + 1) * this.box_size;
 		var item = document.querySelector('[data-node="' + cur.node + '"]');
--- a/tests/test-hgweb-commands.t	Thu Dec 21 13:58:11 2017 +0100
+++ b/tests/test-hgweb-commands.t	Tue Dec 19 20:41:25 2017 +0800
@@ -1822,7 +1822,7 @@
   </div>
   
   <script>
-  var data = [{"edges": [[0, 0, 1, 3, "FF0000"]], "node": "cad8025a2e87", "vertex": [0, 1]}, {"edges": [[0, 0, 1, 3, ""]], "node": "1d22e65f027e", "vertex": [0, 1]}, {"edges": [[0, 0, 1, 3, ""]], "node": "a4f92ed23982", "vertex": [0, 1]}, {"edges": [], "node": "2ef0ac749a14", "vertex": [0, 1]}];
+  var data = [{"edges": [[0, 0, 1, 3, "FF0000"]], "graphnode": "@o", "node": "cad8025a2e87", "vertex": [0, 1]}, {"edges": [[0, 0, 1, 3, ""]], "graphnode": "o", "node": "1d22e65f027e", "vertex": [0, 1]}, {"edges": [[0, 0, 1, 3, ""]], "graphnode": "o", "node": "a4f92ed23982", "vertex": [0, 1]}, {"edges": [], "graphnode": "o", "node": "2ef0ac749a14", "vertex": [0, 1]}];
   var graph = new Graph();
   graph.scale(39);
   graph.render(data);