##// END OF EJS Templates
add graph page to hgweb
Dirkjan Ochtman -
r6691:0dba955c default
parent child Browse files
Show More
@@ -0,0 +1,74 b''
1 # Revision graph generator for Mercurial
2 #
3 # Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
4 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
5 #
6 # This software may be used and distributed according to the terms of
7 # the GNU General Public License, incorporated herein by reference.
8
9 from node import nullrev, short
10 import ui, hg, util, templatefilters
11
12 def graph(repo, start_rev, stop_rev):
13 """incremental revision grapher
14
15 This generator function walks through the revision history from
16 revision start_rev to revision stop_rev (which must be less than
17 or equal to start_rev) and for each revision emits tuples with the
18 following elements:
19
20 - Current node
21 - Column and color for the current node
22 - Edges; a list of (col, next_col, color) indicating the edges between
23 the current node and its parents.
24 - First line of the changeset description
25 - The changeset author
26 - The changeset date/time
27 """
28
29 assert start_rev >= stop_rev
30 curr_rev = start_rev
31 revs = []
32 cl = repo.changelog
33 colors = {}
34 new_color = 1
35
36 while curr_rev >= stop_rev:
37 node = cl.node(curr_rev)
38
39 # Compute revs and next_revs
40 if curr_rev not in revs:
41 revs.append(curr_rev) # new head
42 colors[curr_rev] = new_color
43 new_color += 1
44
45 idx = revs.index(curr_rev)
46 color = colors.pop(curr_rev)
47 next = revs[:]
48
49 # Add parents to next_revs
50 parents = [x for x in cl.parentrevs(curr_rev) if x != nullrev]
51 addparents = [p for p in parents if p not in next]
52 next[idx:idx + 1] = addparents
53
54 # Set colors for the parents
55 for i, p in enumerate(addparents):
56 if not i:
57 colors[p] = color
58 else:
59 colors[p] = new_color
60 new_color += 1
61
62 # Add edges to the graph
63 edges = []
64 for col, r in enumerate(revs):
65 if r in next:
66 edges.append((col, next.index(r), colors[r]))
67 elif r == curr_rev:
68 for p in parents:
69 edges.append((col, next.index(p), colors[p]))
70
71 # Yield and move on
72 yield (repo.changectx(curr_rev), (idx, color), edges)
73 revs = next
74 curr_rev -= 1
@@ -0,0 +1,114 b''
1 {header}
2 <title>{repo|escape}: revision graph</title>
3 <link rel="alternate" type="application/atom+xml"
4 href="{url}atom-log" title="Atom feed for {repo|escape}: log">
5 <link rel="alternate" type="application/rss+xml"
6 href="{url}rss-log" title="RSS feed for {repo|escape}: log">
7 <!--[if IE]><script type="text/javascript" src="{staticurl}excanvas.js"></script><![endif]-->
8 </head>
9 <body>
10
11 <div class="container">
12 <div class="menu">
13 <div class="logo">
14 <a href="http://www.selenic.com/mercurial/">
15 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
16 </div>
17 <ul>
18 <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
19 <li class="active">graph</li>
20 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
21 </ul>
22 <ul>
23 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
24 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
25 </ul>
26 </div>
27
28 <div class="main">
29 <h2>{repo|escape}</h2>
30 <h3>graph</h3>
31
32 <form class="search" action="{url}log">
33 {sessionvars%hiddenformentry}
34 <p><input name="rev" id="search1" type="text" size="30"></p>
35 </form>
36
37 <div class="navigate">
38 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountless}">less</a>
39 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountmore}">more</a>
40 | {changenav%navshortentry}
41 </div>
42
43 <div id="noscript">The revision graph only works with JavaScript-enabled browsers.</div>
44
45 <div id="wrapper">
46 <ul id="nodebgs"></ul>
47 <canvas id="graph" width="224" height="{canvasheight}"></canvas>
48 <ul id="graphnodes"></ul>
49 </div>
50
51 <script type="text/javascript" src="{staticurl}graph.js"></script>
52 <script>
53
54 document.getElementById('noscript').style.visibility = 'hidden';
55
56 data = {jsdata|json};
57 graph = new Graph();
58 graph.scale({bg_height});
59
60 graph.edge = function(x0, y0, x1, y1, color) {
61
62 this.setColor(color, 0.0, 0.65);
63 this.ctx.beginPath();
64 this.ctx.moveTo(x0, y0);
65 this.ctx.lineTo(x1, y1);
66 this.ctx.stroke();
67
68 }
69
70 var nodes = document.getElementById('graphnodes');
71 var nodebgs = document.getElementById('nodebgs');
72
73 var revlink = '<li style="_STYLE"><span class="desc">';
74 revlink += '<a href="{url}rev/_NODEID{sessionvars%urlparameter}" title="_NODEID">_DESC</a>';
75 revlink += '</span><span class="tag">_TAGS</span>';
76 revlink += '<span class="info">_DATE ago, by _USER</span></li>';
77
78 graph.vertex = function(x, y, color, parity, cur) {
79
80 this.ctx.beginPath();
81 color = this.setColor(color, 0.25, 0.75);
82 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
83 this.ctx.fill();
84
85 var bg = '<li class="bg parity' + parity + '"></li>';
86 nodebgs.innerHTML += bg;
87
88 var left = (this.columns + 1) * this.bg_height;
89 var nstyle = 'padding-left: ' + left + 'px;';
90 var item = revlink.replace(/_STYLE/, nstyle);
91 item = item.replace(/_PARITY/, 'parity' + parity);
92 item = item.replace(/_NODEID/, cur[0]);
93 item = item.replace(/_NODEID/, cur[0]);
94 item = item.replace(/_DESC/, cur[3]);
95 item = item.replace(/_USER/, cur[4]);
96 item = item.replace(/_DATE/, cur[5]);
97 item = item.replace(/_TAGS/, cur[6].join('&nbsp; '));
98 nodes.innerHTML += item;
99
100 }
101
102 graph.render(data);
103 </script>
104
105 <div class="navigate">
106 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountless}">less</a>
107 <a href="{url}graph/{uprev}{sessionvars%urlparameter}?revcount={revcountmore}">more</a>
108 | {changenav%navshortentry}
109 </div>
110
111 </div>
112 </div>
113
114 {footer}
@@ -0,0 +1,19 b''
1 if(!window.CanvasRenderingContext2D){(function(){var I=Math,i=I.round,L=I.sin,M=I.cos,m=10,A=m/2,Q={init:function(a){var b=a||document;if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=this;b.attachEvent("onreadystatechange",function(){c.r(b)})}},r:function(a){if(a.readyState=="complete"){if(!a.namespaces["s"]){a.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml")}var b=a.createStyleSheet();b.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}";
2 var c=a.getElementsByTagName("canvas");for(var d=0;d<c.length;d++){if(!c[d].getContext){this.initElement(c[d])}}}},q:function(a){var b=a.outerHTML,c=a.ownerDocument.createElement(b);if(b.slice(-2)!="/>"){var d="/"+a.tagName,e;while((e=a.nextSibling)&&e.tagName!=d){e.removeNode()}if(e){e.removeNode()}}a.parentNode.replaceChild(c,a);return c},initElement:function(a){a=this.q(a);a.getContext=function(){if(this.l){return this.l}return this.l=new K(this)};a.attachEvent("onpropertychange",V);a.attachEvent("onresize",
3 W);var b=a.attributes;if(b.width&&b.width.specified){a.style.width=b.width.nodeValue+"px"}else{a.width=a.clientWidth}if(b.height&&b.height.specified){a.style.height=b.height.nodeValue+"px"}else{a.height=a.clientHeight}return a}};function V(a){var b=a.srcElement;switch(a.propertyName){case "width":b.style.width=b.attributes.width.nodeValue+"px";b.getContext().clearRect();break;case "height":b.style.height=b.attributes.height.nodeValue+"px";b.getContext().clearRect();break}}function W(a){var b=a.srcElement;
4 if(b.firstChild){b.firstChild.style.width=b.clientWidth+"px";b.firstChild.style.height=b.clientHeight+"px"}}Q.init();var R=[];for(var E=0;E<16;E++){for(var F=0;F<16;F++){R[E*16+F]=E.toString(16)+F.toString(16)}}function J(){return[[1,0,0],[0,1,0],[0,0,1]]}function G(a,b){var c=J();for(var d=0;d<3;d++){for(var e=0;e<3;e++){var g=0;for(var h=0;h<3;h++){g+=a[d][h]*b[h][e]}c[d][e]=g}}return c}function N(a,b){b.fillStyle=a.fillStyle;b.lineCap=a.lineCap;b.lineJoin=a.lineJoin;b.lineWidth=a.lineWidth;b.miterLimit=
5 a.miterLimit;b.shadowBlur=a.shadowBlur;b.shadowColor=a.shadowColor;b.shadowOffsetX=a.shadowOffsetX;b.shadowOffsetY=a.shadowOffsetY;b.strokeStyle=a.strokeStyle;b.d=a.d;b.e=a.e}function O(a){var b,c=1;a=String(a);if(a.substring(0,3)=="rgb"){var d=a.indexOf("(",3),e=a.indexOf(")",d+1),g=a.substring(d+1,e).split(",");b="#";for(var h=0;h<3;h++){b+=R[Number(g[h])]}if(g.length==4&&a.substr(3,1)=="a"){c=g[3]}}else{b=a}return[b,c]}function S(a){switch(a){case "butt":return"flat";case "round":return"round";
6 case "square":default:return"square"}}function K(a){this.a=J();this.m=[];this.k=[];this.c=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=m*1;this.globalAlpha=1;this.canvas=a;var b=a.ownerDocument.createElement("div");b.style.width=a.clientWidth+"px";b.style.height=a.clientHeight+"px";b.style.overflow="hidden";b.style.position="absolute";a.appendChild(b);this.j=b;this.d=1;this.e=1}var j=K.prototype;j.clearRect=function(){this.j.innerHTML=
7 "";this.c=[]};j.beginPath=function(){this.c=[]};j.moveTo=function(a,b){this.c.push({type:"moveTo",x:a,y:b});this.f=a;this.g=b};j.lineTo=function(a,b){this.c.push({type:"lineTo",x:a,y:b});this.f=a;this.g=b};j.bezierCurveTo=function(a,b,c,d,e,g){this.c.push({type:"bezierCurveTo",cp1x:a,cp1y:b,cp2x:c,cp2y:d,x:e,y:g});this.f=e;this.g=g};j.quadraticCurveTo=function(a,b,c,d){var e=this.f+0.6666666666666666*(a-this.f),g=this.g+0.6666666666666666*(b-this.g),h=e+(c-this.f)/3,l=g+(d-this.g)/3;this.bezierCurveTo(e,
8 g,h,l,c,d)};j.arc=function(a,b,c,d,e,g){c*=m;var h=g?"at":"wa",l=a+M(d)*c-A,n=b+L(d)*c-A,o=a+M(e)*c-A,f=b+L(e)*c-A;if(l==o&&!g){l+=0.125}this.c.push({type:h,x:a,y:b,radius:c,xStart:l,yStart:n,xEnd:o,yEnd:f})};j.rect=function(a,b,c,d){this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath()};j.strokeRect=function(a,b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.stroke()};j.fillRect=function(a,
9 b,c,d){this.beginPath();this.moveTo(a,b);this.lineTo(a+c,b);this.lineTo(a+c,b+d);this.lineTo(a,b+d);this.closePath();this.fill()};j.createLinearGradient=function(a,b,c,d){var e=new H("gradient");return e};j.createRadialGradient=function(a,b,c,d,e,g){var h=new H("gradientradial");h.n=c;h.o=g;h.i.x=a;h.i.y=b;return h};j.drawImage=function(a,b){var c,d,e,g,h,l,n,o,f=a.runtimeStyle.width,k=a.runtimeStyle.height;a.runtimeStyle.width="auto";a.runtimeStyle.height="auto";var q=a.width,r=a.height;a.runtimeStyle.width=
10 f;a.runtimeStyle.height=k;if(arguments.length==3){c=arguments[1];d=arguments[2];h=(l=0);n=(e=q);o=(g=r)}else if(arguments.length==5){c=arguments[1];d=arguments[2];e=arguments[3];g=arguments[4];h=(l=0);n=q;o=r}else if(arguments.length==9){h=arguments[1];l=arguments[2];n=arguments[3];o=arguments[4];c=arguments[5];d=arguments[6];e=arguments[7];g=arguments[8]}else{throw"Invalid number of arguments";}var s=this.b(c,d),t=[],v=10,w=10;t.push(" <g_vml_:group",' coordsize="',m*v,",",m*w,'"',' coordorigin="0,0"',
11 ' style="width:',v,";height:",w,";position:absolute;");if(this.a[0][0]!=1||this.a[0][1]){var x=[];x.push("M11='",this.a[0][0],"',","M12='",this.a[1][0],"',","M21='",this.a[0][1],"',","M22='",this.a[1][1],"',","Dx='",i(s.x/m),"',","Dy='",i(s.y/m),"'");var p=s,y=this.b(c+e,d),z=this.b(c,d+g),B=this.b(c+e,d+g);p.x=Math.max(p.x,y.x,z.x,B.x);p.y=Math.max(p.y,y.y,z.y,B.y);t.push("padding:0 ",i(p.x/m),"px ",i(p.y/m),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",x.join(""),", sizingmethod='clip');")}else{t.push("top:",
12 i(s.y/m),"px;left:",i(s.x/m),"px;")}t.push(' ">','<g_vml_:image src="',a.src,'"',' style="width:',m*e,";"," height:",m*g,';"',' cropleft="',h/q,'"',' croptop="',l/r,'"',' cropright="',(q-h-n)/q,'"',' cropbottom="',(r-l-o)/r,'"'," />","</g_vml_:group>");this.j.insertAdjacentHTML("BeforeEnd",t.join(""))};j.stroke=function(a){var b=[],c=O(a?this.fillStyle:this.strokeStyle),d=c[0],e=c[1]*this.globalAlpha,g=10,h=10;b.push("<g_vml_:shape",' fillcolor="',d,'"',' filled="',Boolean(a),'"',' style="position:absolute;width:',
13 g,";height:",h,';"',' coordorigin="0 0" coordsize="',m*g," ",m*h,'"',' stroked="',!a,'"',' strokeweight="',this.lineWidth,'"',' strokecolor="',d,'"',' path="');var l={x:null,y:null},n={x:null,y:null};for(var o=0;o<this.c.length;o++){var f=this.c[o];if(f.type=="moveTo"){b.push(" m ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="lineTo"){b.push(" l ");var k=this.b(f.x,f.y);b.push(i(k.x),",",i(k.y))}else if(f.type=="close"){b.push(" x ")}else if(f.type=="bezierCurveTo"){b.push(" c ");
14 var k=this.b(f.x,f.y),q=this.b(f.cp1x,f.cp1y),r=this.b(f.cp2x,f.cp2y);b.push(i(q.x),",",i(q.y),",",i(r.x),",",i(r.y),",",i(k.x),",",i(k.y))}else if(f.type=="at"||f.type=="wa"){b.push(" ",f.type," ");var k=this.b(f.x,f.y),s=this.b(f.xStart,f.yStart),t=this.b(f.xEnd,f.yEnd);b.push(i(k.x-this.d*f.radius),",",i(k.y-this.e*f.radius)," ",i(k.x+this.d*f.radius),",",i(k.y+this.e*f.radius)," ",i(s.x),",",i(s.y)," ",i(t.x),",",i(t.y))}if(k){if(l.x==null||k.x<l.x){l.x=k.x}if(n.x==null||k.x>n.x){n.x=k.x}if(l.y==
15 null||k.y<l.y){l.y=k.y}if(n.y==null||k.y>n.y){n.y=k.y}}}b.push(' ">');if(typeof this.fillStyle=="object"){var v={x:"50%",y:"50%"},w=n.x-l.x,x=n.y-l.y,p=w>x?w:x;v.x=i(this.fillStyle.i.x/w*100+50)+"%";v.y=i(this.fillStyle.i.y/x*100+50)+"%";var y=[];if(this.fillStyle.p=="gradientradial"){var z=this.fillStyle.n/p*100,B=this.fillStyle.o/p*100-z}else{var z=0,B=100}var C={offset:null,color:null},D={offset:null,color:null};this.fillStyle.h.sort(function(T,U){return T.offset-U.offset});for(var o=0;o<this.fillStyle.h.length;o++){var u=
16 this.fillStyle.h[o];y.push(u.offset*B+z,"% ",u.color,",");if(u.offset>C.offset||C.offset==null){C.offset=u.offset;C.color=u.color}if(u.offset<D.offset||D.offset==null){D.offset=u.offset;D.color=u.color}}y.pop();b.push("<g_vml_:fill",' color="',D.color,'"',' color2="',C.color,'"',' type="',this.fillStyle.p,'"',' focusposition="',v.x,", ",v.y,'"',' colors="',y.join(""),'"',' opacity="',e,'" />')}else if(a){b.push('<g_vml_:fill color="',d,'" opacity="',e,'" />')}else{b.push("<g_vml_:stroke",' opacity="',
17 e,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',S(this.lineCap),'"',' weight="',this.lineWidth,'px"',' color="',d,'" />')}b.push("</g_vml_:shape>");this.j.insertAdjacentHTML("beforeEnd",b.join(""));this.c=[]};j.fill=function(){this.stroke(true)};j.closePath=function(){this.c.push({type:"close"})};j.b=function(a,b){return{x:m*(a*this.a[0][0]+b*this.a[1][0]+this.a[2][0])-A,y:m*(a*this.a[0][1]+b*this.a[1][1]+this.a[2][1])-A}};j.save=function(){var a={};N(this,a);
18 this.k.push(a);this.m.push(this.a);this.a=G(J(),this.a)};j.restore=function(){N(this.k.pop(),this);this.a=this.m.pop()};j.translate=function(a,b){var c=[[1,0,0],[0,1,0],[a,b,1]];this.a=G(c,this.a)};j.rotate=function(a){var b=M(a),c=L(a),d=[[b,c,0],[-c,b,0],[0,0,1]];this.a=G(d,this.a)};j.scale=function(a,b){this.d*=a;this.e*=b;var c=[[a,0,0],[0,b,0],[0,0,1]];this.a=G(c,this.a)};j.clip=function(){};j.arcTo=function(){};j.createPattern=function(){return new P};function H(a){this.p=a;this.n=0;this.o=
19 0;this.h=[];this.i={x:0,y:0}}H.prototype.addColorStop=function(a,b){b=O(b);this.h.push({offset:1-a,color:b})};function P(){}G_vmlCanvasManager=Q;CanvasRenderingContext2D=K;CanvasGradient=H;CanvasPattern=P})()};
@@ -0,0 +1,127 b''
1 // branch_renderer.js - Rendering of branch DAGs on the client side
2 //
3 // Copyright 2008 Dirkjan Ochtman <dirkjan AT ochtman DOT nl>
4 // Copyright 2006 Alexander Schremmer <alex AT alexanderweb DOT de>
5 //
6 // derived from code written by Scott James Remnant <scott@ubuntu.com>
7 // Copyright 2005 Canonical Ltd.
8 //
9 // This software may be used and distributed according to the terms
10 // of the GNU General Public License, incorporated herein by reference.
11
12 var colors = [
13 [ 1.0, 0.0, 0.0 ],
14 [ 1.0, 1.0, 0.0 ],
15 [ 0.0, 1.0, 0.0 ],
16 [ 0.0, 1.0, 1.0 ],
17 [ 0.0, 0.0, 1.0 ],
18 [ 1.0, 0.0, 1.0 ]
19 ];
20
21 function Graph() {
22
23 this.canvas = document.getElementById('graph');
24 if (navigator.userAgent.indexOf('MSIE') >= 0) this.canvas = window.G_vmlCanvasManager.initElement(this.canvas);
25 this.ctx = this.canvas.getContext('2d');
26 this.ctx.strokeStyle = 'rgb(0, 0, 0)';
27 this.ctx.fillStyle = 'rgb(0, 0, 0)';
28 this.cur = [0, 0];
29 this.line_width = 3;
30 this.bg = [0, 4];
31 this.cell = [2, 0];
32 this.columns = 0;
33 this.revlink = '';
34
35 this.scale = function(height) {
36 this.bg_height = height;
37 this.box_size = Math.floor(this.bg_height / 1.2);
38 this.cell_height = this.box_size;
39 }
40
41 function colorPart(num) {
42 num *= 255
43 num = num < 0 ? 0 : num;
44 num = num > 255 ? 255 : num;
45 var digits = Math.round(num).toString(16);
46 if (num < 16) {
47 return '0' + digits;
48 } else {
49 return digits;
50 }
51 }
52
53 this.setColor = function(color, bg, fg) {
54
55 // Set the colour.
56 //
57 // Picks a distinct colour based on an internal wheel; the bg
58 // parameter provides the value that should be assigned to the 'zero'
59 // colours and the fg parameter provides the multiplier that should be
60 // applied to the foreground colours.
61
62 color %= colors.length;
63 var red = (colors[color][0] * fg) || bg;
64 var green = (colors[color][1] * fg) || bg;
65 var blue = (colors[color][2] * fg) || bg;
66 red = Math.round(red * 255);
67 green = Math.round(green * 255);
68 blue = Math.round(blue * 255);
69 var s = 'rgb(' + red + ', ' + green + ', ' + blue + ')';
70 this.ctx.strokeStyle = s;
71 this.ctx.fillStyle = s;
72 return s;
73
74 }
75
76 this.render = function(data) {
77
78 for (var i in data) {
79
80 var parity = i % 2;
81 this.cell[1] += this.bg_height;
82 this.bg[1] += this.bg_height;
83
84 var cur = data[i];
85 var node = cur[1];
86 var edges = cur[2];
87 var fold = false;
88
89 for (var j in edges) {
90
91 line = edges[j];
92 start = line[0];
93 end = line[1];
94 color = line[2];
95
96 if (end > this.columns) {
97 this.columns += 1;
98 } else if (start == this.columns && start > end) {
99 var fold = true;
100 }
101
102 x0 = this.cell[0] + this.box_size * start + this.box_size / 2;
103 y0 = this.bg[1] - this.bg_height / 2;
104 x1 = this.cell[0] + this.box_size * end + this.box_size / 2;
105 y1 = this.bg[1] + this.bg_height / 2;
106
107 this.edge(x0, y0, x1, y1, color);
108
109 }
110
111 // Draw the revision node in the right column
112
113 column = node[0]
114 color = node[1]
115
116 radius = this.box_size / 8;
117 x = this.cell[0] + this.box_size * column + this.box_size / 2;
118 y = this.bg[1] - this.bg_height / 2;
119 this.vertex(x, y, color, parity, cur);
120
121 if (fold) this.columns -= 1;
122
123 }
124
125 }
126
127 }
@@ -5,14 +5,15 b''
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 import os, mimetypes, re
8 import os, mimetypes, re, cgi
9 9 import webutil
10 from mercurial import revlog, archival
10 from mercurial import revlog, archival, templatefilters
11 11 from mercurial.node import short, hex, nullid
12 from mercurial.util import binary
12 from mercurial.util import binary, datestr
13 13 from mercurial.repo import RepoError
14 14 from common import paritygen, staticfile, get_contact, ErrorResponse
15 15 from common import HTTP_OK, HTTP_NOT_FOUND
16 from mercurial import graphmod
16 17
17 18 # __all__ is populated with the allowed commands. Be sure to add to it if
18 19 # you're adding a new command, or the new command won't work.
@@ -20,7 +21,7 b' from common import HTTP_OK, HTTP_NOT_FOU'
20 21 __all__ = [
21 22 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
22 23 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
23 'archive', 'static',
24 'archive', 'static', 'graph',
24 25 ]
25 26
26 27 def log(web, req, tmpl):
@@ -574,3 +575,37 b' def static(web, req, tmpl):'
574 575 os.path.join(web.templatepath, "static"),
575 576 untrusted=False)
576 577 return [staticfile(static, fname, req)]
578
579 def graph(web, req, tmpl):
580 rev = webutil.changectx(web.repo, req).rev()
581 revcount = int(req.form.get('revcount', [25])[0])
582 bg_height = 39
583
584 max_rev = web.repo.changelog.count() - 1
585 revnode = web.repo.changelog.node(rev)
586 revnode_hex = hex(revnode)
587 uprev = min(max_rev, rev + revcount)
588 downrev = max(0, rev - revcount)
589 lessrev = max(0, rev - revcount / 2)
590
591 maxchanges = web.maxshortchanges or web.maxchanges
592 count = web.repo.changelog.count()
593 changenav = webutil.revnavgen(rev, maxchanges, count, web.repo.changectx)
594
595 tree = list(graphmod.graph(web.repo, rev, rev - revcount))
596 canvasheight = (len(tree) + 1) * bg_height - 27;
597
598 data = []
599 for i, (ctx, vtx, edges) in enumerate(tree):
600 node = short(ctx.node())
601 age = templatefilters.age(ctx.date())
602 desc = templatefilters.firstline(ctx.description())
603 desc = cgi.escape(desc)
604 user = cgi.escape(templatefilters.person(ctx.user()))
605 data.append((node, vtx, edges, desc, user, age, ctx.tags()))
606
607 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
608 lessrev=lessrev, revcountmore=revcount and 2 * revcount or 1,
609 revcountless=revcount / 2, downrev=downrev,
610 canvasheight=canvasheight, bg_height=bg_height,
611 jsdata=data, node=revnode_hex, changenav=changenav)
@@ -122,6 +122,36 b' def xmlescape(text):'
122 122 .replace("'", '&#39;')) # &apos; invalid in HTML
123 123 return re.sub('[\x00-\x08\x0B\x0C\x0E-\x1F]', ' ', text)
124 124
125 _escapes = [
126 ('\\', '\\\\'), ('"', '\\"'), ('\t', '\\t'), ('\n', '\\n'),
127 ('\r', '\\r'), ('\f', '\\f'), ('\b', '\\b'),
128 ]
129
130 def json(obj):
131 if obj is None or obj is False or obj is True:
132 return {None: 'null', False: 'false', True: 'true'}[obj]
133 elif isinstance(obj, int) or isinstance(obj, float):
134 return str(obj)
135 elif isinstance(obj, str):
136 for k, v in _escapes:
137 obj = obj.replace(k, v)
138 return '"%s"' % obj
139 elif isinstance(obj, unicode):
140 return json(obj.encode('utf-8'))
141 elif hasattr(obj, 'keys'):
142 out = []
143 for k, v in obj.iteritems():
144 s = '%s: %s' % (json(k), json(v))
145 out.append(s)
146 return '{' + ', '.join(out) + '}'
147 elif hasattr(obj, '__iter__'):
148 out = []
149 for i in obj:
150 out.append(json(i))
151 return '[' + ', '.join(out) + ']'
152 else:
153 raise TypeError('cannot encode type %s' % obj.__class__.__name__)
154
125 155 filters = {
126 156 "addbreaks": nl2br,
127 157 "basename": os.path.basename,
@@ -150,5 +180,5 b' filters = {'
150 180 "user": lambda x: util.shortuser(x),
151 181 "stringescape": lambda x: x.encode('string_escape'),
152 182 "xmlescape": xmlescape,
153 }
154
183 "json": json,
184 }
@@ -10,6 +10,7 b''
10 10 </div>
11 11 <ul>
12 12 <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
13 <li><a href="{url}graph{sessionvars%urlparameter}">graph</a></li>
13 14 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
14 15 </ul>
15 16 <ul>
@@ -11,6 +11,7 b''
11 11 </div>
12 12 <ul>
13 13 <li><a href="{url}log{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph{sessionvars%urlparameter}">graph</a></li>
14 15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
15 16 </ul>
16 17 </div>
@@ -11,6 +11,7 b''
11 11 </div>
12 12 <ul>
13 13 <li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
14 15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
15 16 </ul>
16 17
@@ -11,6 +11,7 b''
11 11 </div>
12 12 <ul>
13 13 <li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
14 15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
15 16 </ul>
16 17 <ul>
@@ -16,6 +16,7 b''
16 16 </div>
17 17 <ul>
18 18 <li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
19 <li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
19 20 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
20 21 </ul>
21 22 <ul>
@@ -11,6 +11,7 b''
11 11 </div>
12 12 <ul>
13 13 <li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
14 15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
15 16 </ul>
16 17 <ul>
@@ -11,6 +11,7 b''
11 11 </div>
12 12 <ul>
13 13 <li><a href="{url}shortlog/{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph/{sessionvars%urlparameter}">graph</a></li>
14 15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
15 16 </ul>
16 17 <ul>
@@ -8,6 +8,7 b' search = search.tmpl'
8 8 changelog = shortlog.tmpl
9 9 shortlog = shortlog.tmpl
10 10 shortlogentry = shortlogentry.tmpl
11 graph = graph.tmpl
11 12
12 13 naventry = '<a href="{url}log/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
13 14 navshortentry = '<a href="{url}shortlog/{node|short}{sessionvars%urlparameter}">{label|escape}</a> '
@@ -11,6 +11,7 b''
11 11 </div>
12 12 <ul>
13 13 <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
14 <li><a href="{url}graph{sessionvars%urlparameter}">graph</a></li>
14 15 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
15 16 </ul>
16 17 </div>
@@ -15,6 +15,7 b''
15 15 </div>
16 16 <ul>
17 17 <li class="active">log</li>
18 <li><a href="{url}graph{sessionvars%urlparameter}">graph</a></li>
18 19 <li><a href="{url}tags{sessionvars%urlparameter}">tags</a></li>
19 20 </ul>
20 21 <ul>
@@ -15,6 +15,7 b''
15 15 </div>
16 16 <ul>
17 17 <li><a href="{url}shortlog{sessionvars%urlparameter}">log</a></li>
18 <li><a href="{url}graph{sessionvars%urlparameter}">graph</a></li>
18 19 <li class="active">tags</li>
19 20 </ul>
20 21 </div>
@@ -154,3 +154,44 b' div.description {'
154 154 margin: 1em 0 1em 0;
155 155 padding: .3em;
156 156 }
157
158 div#wrapper {
159 position: relative;
160 border-top: 1px solid black;
161 border-bottom: 1px solid black;
162 margin: 0;
163 padding: 0;
164 }
165
166 canvas {
167 position: absolute;
168 z-index: 5;
169 top: -0.7em;
170 margin: 0;
171 }
172
173 ul#graphnodes {
174 position: absolute;
175 z-index: 10;
176 top: -1.0em;
177 list-style: none inside none;
178 padding: 0;
179 }
180
181 ul#nodebgs {
182 list-style: none inside none;
183 padding: 0;
184 margin: 0;
185 top: -0.7em;
186 }
187
188 ul#graphnodes li, ul#nodebgs li {
189 height: 39px;
190 }
191
192 ul#graphnodes li .info {
193 display: block;
194 font-size: 70%;
195 position: relative;
196 top: -3px;
197 }
General Comments 0
You need to be logged in to leave comments. Login now