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 @@ + + 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) +@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/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 @@ 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/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} -{repo|escape}: {node|short} +{repo|escape}: {node|short} obsolescence graph
@@ -16,7 +16,8 @@
  • branches
  • @@ -69,26 +70,57 @@ files {files} - - diffstat - - {diffsummary} - [+] - - - -
    -
    line wrap: on
    -
    line diff
    -
    -{diff} + + +
    + + + + + + + + + + + + + normal commit + + + + branch-closing commit + + + + unstable commit + + + + obsolete commit + + + + working directory parent + + + + + + + + +
      {obsgraphentries%obsgraphentry}
    -
    + +
    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 @@
  • - {desc|strip|firstline|escape|nonempty} + {if(missing, '{node|short}', '{desc|strip|firstline|escape|nonempty}')} - {alltags} -
    {date|rfc822date}, by {author|person}
    + {if(missing, 'missing', '{alltags}')} +
    {if(missing, 'not available locally', '{date|rfc822date}, by {author|person}')}
  • 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/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/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 @@ @@ -964,6 +965,8 @@ + $ 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/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 @@ @@ -362,6 +363,7 @@ 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 @@ + $ (get-with-headers.py localhost:$HGPORT 'obsgraph') + 200 Script output follows + + + + + + + + + + test: 000000000000 obsolescence graph + + +
    + + +
    + + +

    + changeset -1:000000000000 + tip +

    + + + + +
    (none)
    + + + + + + + + + + + + + + + + + + + + + + + + +
    author
    dateThu, 01 Jan 1970 00:00:00 +0000
    parents
    children
    files
    + + + +
    + + + + + + + + + + + + + normal commit + + + + branch-closing commit + + + + unstable commit + + + + obsolete commit + + + + working directory parent + + + + + + + + +
    • +
      + + (none) + + tip +
      Thu, 01 Jan 1970 00:00:00 +0000, by
      +
      +
    • +
    +
    + + + +
    +
    + + + + + $ (get-with-headers.py localhost:$HGPORT 'file') 200 Script output follows 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 @@ 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
  • log
  • graph
  • +
  • obsgraph
  • raw
  • browse
  • zip @@ -297,6 +298,7 @@ $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=coal' | egrep $REVLINKS
  • log
  • graph
  • +
  • obsgraph
  • raw
  • browse
  • zip