##// 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 }
@@ -1,576 +1,611 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
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.
19 20
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):
27 28 if 'file' in req.form and req.form['file'][0]:
28 29 return filelog(web, req, tmpl)
29 30 else:
30 31 return changelog(web, req, tmpl)
31 32
32 33 def rawfile(web, req, tmpl):
33 34 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
34 35 if not path:
35 36 content = manifest(web, req, tmpl)
36 37 req.respond(HTTP_OK, web.ctype)
37 38 return content
38 39
39 40 try:
40 41 fctx = webutil.filectx(web.repo, req)
41 42 except revlog.LookupError, inst:
42 43 try:
43 44 content = manifest(web, req, tmpl)
44 45 req.respond(HTTP_OK, web.ctype)
45 46 return content
46 47 except ErrorResponse:
47 48 raise inst
48 49
49 50 path = fctx.path()
50 51 text = fctx.data()
51 52 mt = mimetypes.guess_type(path)[0]
52 53 if mt is None or binary(text):
53 54 mt = mt or 'application/octet-stream'
54 55
55 56 req.respond(HTTP_OK, mt, path, len(text))
56 57 return [text]
57 58
58 59 def _filerevision(web, tmpl, fctx):
59 60 f = fctx.path()
60 61 text = fctx.data()
61 62 fl = fctx.filelog()
62 63 n = fctx.filenode()
63 64 parity = paritygen(web.stripecount)
64 65
65 66 if binary(text):
66 67 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
67 68 text = '(binary:%s)' % mt
68 69
69 70 def lines():
70 71 for lineno, t in enumerate(text.splitlines(1)):
71 72 yield {"line": t,
72 73 "lineid": "l%d" % (lineno + 1),
73 74 "linenumber": "% 6d" % (lineno + 1),
74 75 "parity": parity.next()}
75 76
76 77 return tmpl("filerevision",
77 78 file=f,
78 79 path=webutil.up(f),
79 80 text=lines(),
80 81 rev=fctx.rev(),
81 82 node=hex(fctx.node()),
82 83 author=fctx.user(),
83 84 date=fctx.date(),
84 85 desc=fctx.description(),
85 86 branch=webutil.nodebranchnodefault(fctx),
86 87 parent=webutil.siblings(fctx.parents()),
87 88 child=webutil.siblings(fctx.children()),
88 89 rename=webutil.renamelink(fctx),
89 90 permissions=fctx.manifest().flags(f))
90 91
91 92 def file(web, req, tmpl):
92 93 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
93 94 if path:
94 95 try:
95 96 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
96 97 except revlog.LookupError, inst:
97 98 pass
98 99
99 100 try:
100 101 return manifest(web, req, tmpl)
101 102 except ErrorResponse:
102 103 raise inst
103 104
104 105 def _search(web, tmpl, query):
105 106
106 107 def changelist(**map):
107 108 cl = web.repo.changelog
108 109 count = 0
109 110 qw = query.lower().split()
110 111
111 112 def revgen():
112 113 for i in xrange(cl.count() - 1, 0, -100):
113 114 l = []
114 115 for j in xrange(max(0, i - 100), i + 1):
115 116 ctx = web.repo.changectx(j)
116 117 l.append(ctx)
117 118 l.reverse()
118 119 for e in l:
119 120 yield e
120 121
121 122 for ctx in revgen():
122 123 miss = 0
123 124 for q in qw:
124 125 if not (q in ctx.user().lower() or
125 126 q in ctx.description().lower() or
126 127 q in " ".join(ctx.files()).lower()):
127 128 miss = 1
128 129 break
129 130 if miss:
130 131 continue
131 132
132 133 count += 1
133 134 n = ctx.node()
134 135 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
135 136
136 137 yield tmpl('searchentry',
137 138 parity=parity.next(),
138 139 author=ctx.user(),
139 140 parent=webutil.siblings(ctx.parents()),
140 141 child=webutil.siblings(ctx.children()),
141 142 changelogtag=showtags,
142 143 desc=ctx.description(),
143 144 date=ctx.date(),
144 145 files=web.listfilediffs(tmpl, ctx.files(), n),
145 146 rev=ctx.rev(),
146 147 node=hex(n),
147 148 tags=webutil.nodetagsdict(web.repo, n),
148 149 inbranch=webutil.nodeinbranch(web.repo, ctx),
149 150 branches=webutil.nodebranchdict(web.repo, ctx))
150 151
151 152 if count >= web.maxchanges:
152 153 break
153 154
154 155 cl = web.repo.changelog
155 156 parity = paritygen(web.stripecount)
156 157
157 158 return tmpl('search',
158 159 query=query,
159 160 node=hex(cl.tip()),
160 161 entries=changelist,
161 162 archives=web.archivelist("tip"))
162 163
163 164 def changelog(web, req, tmpl, shortlog = False):
164 165 if 'node' in req.form:
165 166 ctx = webutil.changectx(web.repo, req)
166 167 else:
167 168 if 'rev' in req.form:
168 169 hi = req.form['rev'][0]
169 170 else:
170 171 hi = web.repo.changelog.count() - 1
171 172 try:
172 173 ctx = web.repo.changectx(hi)
173 174 except RepoError:
174 175 return _search(web, tmpl, hi) # XXX redirect to 404 page?
175 176
176 177 def changelist(limit=0, **map):
177 178 cl = web.repo.changelog
178 179 l = [] # build a list in forward order for efficiency
179 180 for i in xrange(start, end):
180 181 ctx = web.repo.changectx(i)
181 182 n = ctx.node()
182 183 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
183 184
184 185 l.insert(0, {"parity": parity.next(),
185 186 "author": ctx.user(),
186 187 "parent": webutil.siblings(ctx.parents(), i - 1),
187 188 "child": webutil.siblings(ctx.children(), i + 1),
188 189 "changelogtag": showtags,
189 190 "desc": ctx.description(),
190 191 "date": ctx.date(),
191 192 "files": web.listfilediffs(tmpl, ctx.files(), n),
192 193 "rev": i,
193 194 "node": hex(n),
194 195 "tags": webutil.nodetagsdict(web.repo, n),
195 196 "inbranch": webutil.nodeinbranch(web.repo, ctx),
196 197 "branches": webutil.nodebranchdict(web.repo, ctx)
197 198 })
198 199
199 200 if limit > 0:
200 201 l = l[:limit]
201 202
202 203 for e in l:
203 204 yield e
204 205
205 206 maxchanges = shortlog and web.maxshortchanges or web.maxchanges
206 207 cl = web.repo.changelog
207 208 count = cl.count()
208 209 pos = ctx.rev()
209 210 start = max(0, pos - maxchanges + 1)
210 211 end = min(count, start + maxchanges)
211 212 pos = end - 1
212 213 parity = paritygen(web.stripecount, offset=start-end)
213 214
214 215 changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx)
215 216
216 217 return tmpl(shortlog and 'shortlog' or 'changelog',
217 218 changenav=changenav,
218 219 node=hex(ctx.node()),
219 220 rev=pos, changesets=count,
220 221 entries=lambda **x: changelist(limit=0,**x),
221 222 latestentry=lambda **x: changelist(limit=1,**x),
222 223 archives=web.archivelist("tip"))
223 224
224 225 def shortlog(web, req, tmpl):
225 226 return changelog(web, req, tmpl, shortlog = True)
226 227
227 228 def changeset(web, req, tmpl):
228 229 ctx = webutil.changectx(web.repo, req)
229 230 n = ctx.node()
230 231 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', n)
231 232 parents = ctx.parents()
232 233 p1 = parents[0].node()
233 234
234 235 files = []
235 236 parity = paritygen(web.stripecount)
236 237 for f in ctx.files():
237 238 files.append(tmpl("filenodelink",
238 239 node=hex(n), file=f,
239 240 parity=parity.next()))
240 241
241 242 diffs = web.diff(tmpl, p1, n, None)
242 243 return tmpl('changeset',
243 244 diff=diffs,
244 245 rev=ctx.rev(),
245 246 node=hex(n),
246 247 parent=webutil.siblings(parents),
247 248 child=webutil.siblings(ctx.children()),
248 249 changesettag=showtags,
249 250 author=ctx.user(),
250 251 desc=ctx.description(),
251 252 date=ctx.date(),
252 253 files=files,
253 254 archives=web.archivelist(hex(n)),
254 255 tags=webutil.nodetagsdict(web.repo, n),
255 256 branch=webutil.nodebranchnodefault(ctx),
256 257 inbranch=webutil.nodeinbranch(web.repo, ctx),
257 258 branches=webutil.nodebranchdict(web.repo, ctx))
258 259
259 260 rev = changeset
260 261
261 262 def manifest(web, req, tmpl):
262 263 ctx = webutil.changectx(web.repo, req)
263 264 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
264 265 mf = ctx.manifest()
265 266 node = ctx.node()
266 267
267 268 files = {}
268 269 parity = paritygen(web.stripecount)
269 270
270 271 if path and path[-1] != "/":
271 272 path += "/"
272 273 l = len(path)
273 274 abspath = "/" + path
274 275
275 276 for f, n in mf.items():
276 277 if f[:l] != path:
277 278 continue
278 279 remain = f[l:]
279 280 if "/" in remain:
280 281 short = remain[:remain.index("/") + 1] # bleah
281 282 files[short] = (f, None)
282 283 else:
283 284 short = os.path.basename(remain)
284 285 files[short] = (f, n)
285 286
286 287 if not files:
287 288 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
288 289
289 290 def filelist(**map):
290 291 fl = files.keys()
291 292 fl.sort()
292 293 for f in fl:
293 294 full, fnode = files[f]
294 295 if not fnode:
295 296 continue
296 297
297 298 fctx = ctx.filectx(full)
298 299 yield {"file": full,
299 300 "parity": parity.next(),
300 301 "basename": f,
301 302 "date": fctx.changectx().date(),
302 303 "size": fctx.size(),
303 304 "permissions": mf.flags(full)}
304 305
305 306 def dirlist(**map):
306 307 fl = files.keys()
307 308 fl.sort()
308 309 for f in fl:
309 310 full, fnode = files[f]
310 311 if fnode:
311 312 continue
312 313
313 314 yield {"parity": parity.next(),
314 315 "path": "%s%s" % (abspath, f),
315 316 "basename": f[:-1]}
316 317
317 318 return tmpl("manifest",
318 319 rev=ctx.rev(),
319 320 node=hex(node),
320 321 path=abspath,
321 322 up=webutil.up(abspath),
322 323 upparity=parity.next(),
323 324 fentries=filelist,
324 325 dentries=dirlist,
325 326 archives=web.archivelist(hex(node)),
326 327 tags=webutil.nodetagsdict(web.repo, node),
327 328 inbranch=webutil.nodeinbranch(web.repo, ctx),
328 329 branches=webutil.nodebranchdict(web.repo, ctx))
329 330
330 331 def tags(web, req, tmpl):
331 332 i = web.repo.tagslist()
332 333 i.reverse()
333 334 parity = paritygen(web.stripecount)
334 335
335 336 def entries(notip=False,limit=0, **map):
336 337 count = 0
337 338 for k, n in i:
338 339 if notip and k == "tip":
339 340 continue
340 341 if limit > 0 and count >= limit:
341 342 continue
342 343 count = count + 1
343 344 yield {"parity": parity.next(),
344 345 "tag": k,
345 346 "date": web.repo.changectx(n).date(),
346 347 "node": hex(n)}
347 348
348 349 return tmpl("tags",
349 350 node=hex(web.repo.changelog.tip()),
350 351 entries=lambda **x: entries(False,0, **x),
351 352 entriesnotip=lambda **x: entries(True,0, **x),
352 353 latestentry=lambda **x: entries(True,1, **x))
353 354
354 355 def summary(web, req, tmpl):
355 356 i = web.repo.tagslist()
356 357 i.reverse()
357 358
358 359 def tagentries(**map):
359 360 parity = paritygen(web.stripecount)
360 361 count = 0
361 362 for k, n in i:
362 363 if k == "tip": # skip tip
363 364 continue
364 365
365 366 count += 1
366 367 if count > 10: # limit to 10 tags
367 368 break
368 369
369 370 yield tmpl("tagentry",
370 371 parity=parity.next(),
371 372 tag=k,
372 373 node=hex(n),
373 374 date=web.repo.changectx(n).date())
374 375
375 376 def branches(**map):
376 377 parity = paritygen(web.stripecount)
377 378
378 379 b = web.repo.branchtags()
379 380 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()]
380 381 l.sort()
381 382
382 383 for r,n,t in l:
383 384 ctx = web.repo.changectx(n)
384 385 yield {'parity': parity.next(),
385 386 'branch': t,
386 387 'node': hex(n),
387 388 'date': ctx.date()}
388 389
389 390 def changelist(**map):
390 391 parity = paritygen(web.stripecount, offset=start-end)
391 392 l = [] # build a list in forward order for efficiency
392 393 for i in xrange(start, end):
393 394 ctx = web.repo.changectx(i)
394 395 n = ctx.node()
395 396 hn = hex(n)
396 397
397 398 l.insert(0, tmpl(
398 399 'shortlogentry',
399 400 parity=parity.next(),
400 401 author=ctx.user(),
401 402 desc=ctx.description(),
402 403 date=ctx.date(),
403 404 rev=i,
404 405 node=hn,
405 406 tags=webutil.nodetagsdict(web.repo, n),
406 407 inbranch=webutil.nodeinbranch(web.repo, ctx),
407 408 branches=webutil.nodebranchdict(web.repo, ctx)))
408 409
409 410 yield l
410 411
411 412 cl = web.repo.changelog
412 413 count = cl.count()
413 414 start = max(0, count - web.maxchanges)
414 415 end = min(count, start + web.maxchanges)
415 416
416 417 return tmpl("summary",
417 418 desc=web.config("web", "description", "unknown"),
418 419 owner=get_contact(web.config) or "unknown",
419 420 lastchange=cl.read(cl.tip())[2],
420 421 tags=tagentries,
421 422 branches=branches,
422 423 shortlog=changelist,
423 424 node=hex(cl.tip()),
424 425 archives=web.archivelist("tip"))
425 426
426 427 def filediff(web, req, tmpl):
427 428 fctx = webutil.filectx(web.repo, req)
428 429 n = fctx.node()
429 430 path = fctx.path()
430 431 parents = fctx.parents()
431 432 p1 = parents and parents[0].node() or nullid
432 433
433 434 diffs = web.diff(tmpl, p1, n, [path])
434 435 return tmpl("filediff",
435 436 file=path,
436 437 node=hex(n),
437 438 rev=fctx.rev(),
438 439 date=fctx.date(),
439 440 desc=fctx.description(),
440 441 author=fctx.user(),
441 442 rename=webutil.renamelink(fctx),
442 443 branch=webutil.nodebranchnodefault(fctx),
443 444 parent=webutil.siblings(parents),
444 445 child=webutil.siblings(fctx.children()),
445 446 diff=diffs)
446 447
447 448 diff = filediff
448 449
449 450 def annotate(web, req, tmpl):
450 451 fctx = webutil.filectx(web.repo, req)
451 452 f = fctx.path()
452 453 n = fctx.filenode()
453 454 fl = fctx.filelog()
454 455 parity = paritygen(web.stripecount)
455 456
456 457 def annotate(**map):
457 458 last = None
458 459 if binary(fctx.data()):
459 460 mt = (mimetypes.guess_type(fctx.path())[0]
460 461 or 'application/octet-stream')
461 462 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
462 463 '(binary:%s)' % mt)])
463 464 else:
464 465 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
465 466 for lineno, ((f, targetline), l) in lines:
466 467 fnode = f.filenode()
467 468
468 469 if last != fnode:
469 470 last = fnode
470 471
471 472 yield {"parity": parity.next(),
472 473 "node": hex(f.node()),
473 474 "rev": f.rev(),
474 475 "author": f.user(),
475 476 "desc": f.description(),
476 477 "file": f.path(),
477 478 "targetline": targetline,
478 479 "line": l,
479 480 "lineid": "l%d" % (lineno + 1),
480 481 "linenumber": "% 6d" % (lineno + 1)}
481 482
482 483 return tmpl("fileannotate",
483 484 file=f,
484 485 annotate=annotate,
485 486 path=webutil.up(f),
486 487 rev=fctx.rev(),
487 488 node=hex(fctx.node()),
488 489 author=fctx.user(),
489 490 date=fctx.date(),
490 491 desc=fctx.description(),
491 492 rename=webutil.renamelink(fctx),
492 493 branch=webutil.nodebranchnodefault(fctx),
493 494 parent=webutil.siblings(fctx.parents()),
494 495 child=webutil.siblings(fctx.children()),
495 496 permissions=fctx.manifest().flags(f))
496 497
497 498 def filelog(web, req, tmpl):
498 499 fctx = webutil.filectx(web.repo, req)
499 500 f = fctx.path()
500 501 fl = fctx.filelog()
501 502 count = fl.count()
502 503 pagelen = web.maxshortchanges
503 504 pos = fctx.filerev()
504 505 start = max(0, pos - pagelen + 1)
505 506 end = min(count, start + pagelen)
506 507 pos = end - 1
507 508 parity = paritygen(web.stripecount, offset=start-end)
508 509
509 510 def entries(limit=0, **map):
510 511 l = []
511 512
512 513 for i in xrange(start, end):
513 514 ctx = fctx.filectx(i)
514 515 n = fl.node(i)
515 516
516 517 l.insert(0, {"parity": parity.next(),
517 518 "filerev": i,
518 519 "file": f,
519 520 "node": hex(ctx.node()),
520 521 "author": ctx.user(),
521 522 "date": ctx.date(),
522 523 "rename": webutil.renamelink(fctx),
523 524 "parent": webutil.siblings(fctx.parents()),
524 525 "child": webutil.siblings(fctx.children()),
525 526 "desc": ctx.description()})
526 527
527 528 if limit > 0:
528 529 l = l[:limit]
529 530
530 531 for e in l:
531 532 yield e
532 533
533 534 nodefunc = lambda x: fctx.filectx(fileid=x)
534 535 nav = webutil.revnavgen(pos, pagelen, count, nodefunc)
535 536 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
536 537 entries=lambda **x: entries(limit=0, **x),
537 538 latestentry=lambda **x: entries(limit=1, **x))
538 539
539 540
540 541 def archive(web, req, tmpl):
541 542 type_ = req.form.get('type', [None])[0]
542 543 allowed = web.configlist("web", "allow_archive")
543 544 key = req.form['node'][0]
544 545
545 546 if not (type_ in web.archives and (type_ in allowed or
546 547 web.configbool("web", "allow" + type_, False))):
547 548 msg = 'Unsupported archive type: %s' % type_
548 549 raise ErrorResponse(HTTP_NOT_FOUND, msg)
549 550
550 551 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
551 552 cnode = web.repo.lookup(key)
552 553 arch_version = key
553 554 if cnode == key or key == 'tip':
554 555 arch_version = short(cnode)
555 556 name = "%s-%s" % (reponame, arch_version)
556 557 mimetype, artype, extension, encoding = web.archive_specs[type_]
557 558 headers = [
558 559 ('Content-Type', mimetype),
559 560 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
560 561 ]
561 562 if encoding:
562 563 headers.append(('Content-Encoding', encoding))
563 564 req.header(headers)
564 565 req.respond(HTTP_OK)
565 566 archival.archive(web.repo, req, cnode, artype, prefix=name)
566 567 return []
567 568
568 569
569 570 def static(web, req, tmpl):
570 571 fname = req.form['file'][0]
571 572 # a repo owner may set web.static in .hg/hgrc to get any file
572 573 # readable by the user running the CGI script
573 574 static = web.config("web", "static",
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)
@@ -1,154 +1,184 b''
1 1 # template-filters.py - common template expansion filters
2 2 #
3 3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
4 4 #
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 8 import cgi, re, os, time, urllib, textwrap
9 9 import util, templater
10 10
11 11 agescales = [("second", 1),
12 12 ("minute", 60),
13 13 ("hour", 3600),
14 14 ("day", 3600 * 24),
15 15 ("week", 3600 * 24 * 7),
16 16 ("month", 3600 * 24 * 30),
17 17 ("year", 3600 * 24 * 365)]
18 18
19 19 agescales.reverse()
20 20
21 21 def age(date):
22 22 '''turn a (timestamp, tzoff) tuple into an age string.'''
23 23
24 24 def plural(t, c):
25 25 if c == 1:
26 26 return t
27 27 return t + "s"
28 28 def fmt(t, c):
29 29 return "%d %s" % (c, plural(t, c))
30 30
31 31 now = time.time()
32 32 then = date[0]
33 33 delta = max(1, int(now - then))
34 34
35 35 for t, s in agescales:
36 36 n = delta / s
37 37 if n >= 2 or s == 1:
38 38 return fmt(t, n)
39 39
40 40 para_re = None
41 41 space_re = None
42 42
43 43 def fill(text, width):
44 44 '''fill many paragraphs.'''
45 45 global para_re, space_re
46 46 if para_re is None:
47 47 para_re = re.compile('(\n\n|\n\\s*[-*]\\s*)', re.M)
48 48 space_re = re.compile(r' +')
49 49
50 50 def findparas():
51 51 start = 0
52 52 while True:
53 53 m = para_re.search(text, start)
54 54 if not m:
55 55 w = len(text)
56 56 while w > start and text[w-1].isspace(): w -= 1
57 57 yield text[start:w], text[w:]
58 58 break
59 59 yield text[start:m.start(0)], m.group(1)
60 60 start = m.end(1)
61 61
62 62 return "".join([space_re.sub(' ', textwrap.fill(para, width)) + rest
63 63 for para, rest in findparas()])
64 64
65 65 def firstline(text):
66 66 '''return the first line of text'''
67 67 try:
68 68 return text.splitlines(1)[0].rstrip('\r\n')
69 69 except IndexError:
70 70 return ''
71 71
72 72 def nl2br(text):
73 73 '''replace raw newlines with xhtml line breaks.'''
74 74 return text.replace('\n', '<br/>\n')
75 75
76 76 def obfuscate(text):
77 77 text = unicode(text, util._encoding, 'replace')
78 78 return ''.join(['&#%d;' % ord(c) for c in text])
79 79
80 80 def domain(author):
81 81 '''get domain of author, or empty string if none.'''
82 82 f = author.find('@')
83 83 if f == -1: return ''
84 84 author = author[f+1:]
85 85 f = author.find('>')
86 86 if f >= 0: author = author[:f]
87 87 return author
88 88
89 89 def person(author):
90 90 '''get name of author, or else username.'''
91 91 f = author.find('<')
92 92 if f == -1: return util.shortuser(author)
93 93 return author[:f].rstrip()
94 94
95 95 def indent(text, prefix):
96 96 '''indent each non-empty line of text after first with prefix.'''
97 97 lines = text.splitlines()
98 98 num_lines = len(lines)
99 99 def indenter():
100 100 for i in xrange(num_lines):
101 101 l = lines[i]
102 102 if i and l.strip():
103 103 yield prefix
104 104 yield l
105 105 if i < num_lines - 1 or text.endswith('\n'):
106 106 yield '\n'
107 107 return "".join(indenter())
108 108
109 109 def permissions(flags):
110 110 if "l" in flags:
111 111 return "lrwxrwxrwx"
112 112 if "x" in flags:
113 113 return "-rwxr-xr-x"
114 114 return "-rw-r--r--"
115 115
116 116 def xmlescape(text):
117 117 text = (text
118 118 .replace('&', '&amp;')
119 119 .replace('<', '&lt;')
120 120 .replace('>', '&gt;')
121 121 .replace('"', '&quot;')
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,
128 158 "age": age,
129 159 "date": lambda x: util.datestr(x),
130 160 "domain": domain,
131 161 "email": util.email,
132 162 "escape": lambda x: cgi.escape(x, True),
133 163 "fill68": lambda x: fill(x, width=68),
134 164 "fill76": lambda x: fill(x, width=76),
135 165 "firstline": firstline,
136 166 "tabindent": lambda x: indent(x, '\t'),
137 167 "hgdate": lambda x: "%d %d" % x,
138 168 "isodate": lambda x: util.datestr(x, '%Y-%m-%d %H:%M %1%2'),
139 169 "isodatesec": lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2'),
140 170 "obfuscate": obfuscate,
141 171 "permissions": permissions,
142 172 "person": person,
143 173 "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S %1%2"),
144 174 "rfc3339date": lambda x: util.datestr(x, "%Y-%m-%dT%H:%M:%S%1:%2"),
145 175 "short": lambda x: x[:12],
146 176 "shortdate": util.shortdate,
147 177 "stringify": templater.stringify,
148 178 "strip": lambda x: x.strip(),
149 179 "urlescape": lambda x: urllib.quote(x),
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 }
@@ -1,71 +1,72 b''
1 1 {header}
2 2 <title>{repo|escape}: {node|short}</title>
3 3 </head>
4 4 <body>
5 5 <div class="container">
6 6 <div class="menu">
7 7 <div class="logo">
8 8 <a href="http://www.selenic.com/mercurial/">
9 9 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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>
16 17 <li class="active">changeset</li>
17 18 <li><a href="{url}file/{node|short}{sessionvars%urlparameter}">browse</a></li>
18 19 </ul>
19 20 <ul>
20 21 {archives%archiveentry}</ul>
21 22 </ul>
22 23 </div>
23 24
24 25 <div class="main">
25 26
26 27 <h2>{repo|escape}</h2>
27 28 <h3>changeset {rev}:{node|short} {changesettag}</h3>
28 29
29 30 <form class="search" action="{url}log">
30 31 {sessionvars%hiddenformentry}
31 32 <p><input name="rev" id="search1" type="text" size="30"></p>
32 33 </form>
33 34
34 35 <div class="description">{desc|strip|escape|addbreaks}</div>
35 36
36 37 <table id="changesetEntry">
37 38 <tr>
38 39 <th class="author">author</th>
39 40 <td class="author">{author|obfuscate}</td>
40 41 </tr>
41 42 <tr>
42 43 <th class="date">date</th>
43 44 <td class="date">{date|date} ({date|age} ago)</td></tr>
44 45 <tr>
45 46 <th class="author">parents</th>
46 47 <td class="author">{parent%changesetparent}</td>
47 48 </tr>
48 49 <tr>
49 50 <th class="author">children</th>
50 51 <td class="author">{child%changesetchild}</td>
51 52 </tr>
52 53 <tr>
53 54 <th class="files">files</th>
54 55 <td class="files">{files}</td></tr>
55 56 </tr>
56 57 </table>
57 58 <tr>
58 59
59 60 <div class="overflow">
60 61 <table class="bigtable">
61 62 <tr>
62 63 <th class="lineno">line</th>
63 64 <th class="source">diff</th>
64 65 </tr>
65 66 </table>
66 67 {diff}
67 68 </div>
68 69 </div>
69 70 {footer}
70 71
71 72
@@ -1,39 +1,40 b''
1 1 {header}
2 2 <title>{repo|escape}: error</title>
3 3 </head>
4 4 <body>
5 5
6 6 <div class="content">
7 7 <div class="menu">
8 8 <div class="logo">
9 9 <a href="http://www.selenic.com/mercurial/">
10 10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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>
17 18
18 19 <div class="main">
19 20
20 21 <h2>{repo|escape}</h2>
21 22 <h3>error</h3>
22 23
23 24 <form class="search" action="{url}log">
24 25 {sessionvars%hiddenformentry}
25 26 <p><input name="rev" id="search1" type="text" size="30"></p>
26 27 </form>
27 28
28 29 <div class="description">
29 30 <p>
30 31 An error occurred while processing your request:
31 32 </p>
32 33 <p>
33 34 {error|escape}
34 35 </p>
35 36 </div>
36 37 </div>
37 38 </div>
38 39
39 40 {footer}
@@ -1,76 +1,77 b''
1 1 {header}
2 2 <title>{repo|escape}: {file|escape} annotate</title>
3 3 </head>
4 4 <body>
5 5
6 6 <div class="container">
7 7 <div class="menu">
8 8 <div class="logo">
9 9 <a href="http://www.selenic.com/mercurial/">
10 10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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
17 18 <ul>
18 19 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
19 20 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
20 21 </ul>
21 22 <ul>
22 23 <li><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
23 24 <li><a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
24 25 <li class="active">annotate</li>
25 26 <li><a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
26 27 <li><a href="{url}raw-annotate/{node|short}/{file|urlescape}">raw</a></li>
27 28 </ul>
28 29 </div>
29 30
30 31 <div class="main">
31 32 <h2>{repo|escape}</h2>
32 33 <h3>annotate {file|escape} @ {rev}:{node|short}</h2>
33 34
34 35 <form class="search" action="{url}log">
35 36 {sessionvars%hiddenformentry}
36 37 <p><input name="rev" id="search1" type="text" size="30"></p>
37 38 </form>
38 39
39 40 <div class="description">{desc|strip|escape|addbreaks}</div>
40 41
41 42 <table id="changesetEntry">
42 43 <tr>
43 44 <th class="author">author</th>
44 45 <td class="author">{author|obfuscate}</td>
45 46 </tr>
46 47 <tr>
47 48 <th class="date">date</th>
48 49 <td class="date">{date|date} ({date|age} ago)</td>
49 50 </tr>
50 51 <tr>
51 52 <th class="author">parents</th>
52 53 <td class="author">{parent%filerevparent}</td>
53 54 </tr>
54 55 <tr>
55 56 <th class="author">children</th>
56 57 <td class="author">{child%filerevchild}</td>
57 58 </tr>
58 59 {changesettag}
59 60 </table>
60 61
61 62 <br/>
62 63
63 64 <div class="overflow">
64 65 <table class="bigtable">
65 66 <tr>
66 67 <th class="annotate">rev</th>
67 68 <th class="lineno">line</th>
68 69 <th class="line">source</th>
69 70 </tr>
70 71 {annotate%annotateline}
71 72 </table>
72 73 </div>
73 74 </div>
74 75 </div>
75 76
76 77 {footer}
@@ -1,74 +1,75 b''
1 1 {header}
2 2 <title>{repo|escape}: {file|escape} diff</title>
3 3 </head>
4 4 <body>
5 5
6 6 <div class="container">
7 7 <div class="menu">
8 8 <div class="logo">
9 9 <a href="http://www.selenic.com/mercurial/">
10 10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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>
17 18 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
18 19 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
19 20 </ul>
20 21 <ul>
21 22 <li><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
22 23 <li class="active">diff</li>
23 24 <li><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
24 25 <li><a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
25 26 <li><a href="{url}raw-file/{node|short}/{file|urlescape}">raw</a></li>
26 27 </ul>
27 28 </div>
28 29
29 30 <div class="main">
30 31 <h2>{repo|escape}</h2>
31 32 <h3>diff {file|escape} @ {rev}:{node|short}</h3>
32 33
33 34 <form class="search" action="{url}log">
34 35 {sessionvars%hiddenformentry}
35 36 <p><input name="rev" id="search1" type="text" size="30"></p>
36 37 </form>
37 38
38 39 <div class="description">{desc|strip|escape|addbreaks}</div>
39 40
40 41 <table id="changesetEntry">
41 42 <tr>
42 43 <th>author</th>
43 44 <td>{author|obfuscate}</td>
44 45 </tr>
45 46 <tr>
46 47 <th>date</th>
47 48 <td>{date|date} ({date|age} ago)</td>
48 49 </tr>
49 50 <tr>
50 51 <th>parents</th>
51 52 <td>{parent%filerevparent}</td>
52 53 </tr>
53 54 <tr>
54 55 <th>children</th>
55 56 <td>{child%filerevchild}</td>
56 57 </tr>
57 58 {changesettag}
58 59 </table>
59 60
60 61 <div class="overflow">
61 62 <table class="bigtable">
62 63 <tr>
63 64 <th class="lineno">line</th>
64 65 <th class="source">diff</th>
65 66 </tr>
66 67 <table>
67 68 {diff}
68 69 </div>
69 70 </div>
70 71 </div>
71 72
72 73 {footer}
73 74
74 75
@@ -1,58 +1,59 b''
1 1 {header}
2 2 <title>{repo|escape}: {file|escape} history</title>
3 3 <link rel="alternate" type="application/atom+xml"
4 4 href="{url}atom-log/tip/{file|urlescape}" title="Atom feed for {repo|escape}:{file}">
5 5 <link rel="alternate" type="application/rss+xml"
6 6 href="{url}rss-log/tip/{file|urlescape}" title="RSS feed for {repo|escape}:{file}">
7 7 </head>
8 8 </head>
9 9 <body>
10 10
11 11 <div class="container">
12 12 <div class="menu">
13 13 <div class="logo">
14 14 <a href="http://www.selenic.com/mercurial/">
15 15 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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>
22 23 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
23 24 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
24 25 </ul>
25 26 <ul>
26 27 <li><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
27 28 <li><a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
28 29 <li><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
29 30 <li class="active">file log</li>
30 31 <li><a href="{url}raw-file/{node|short}/{file|urlescape}">raw</a></li>
31 32 </ul>
32 33 </div>
33 34
34 35 <div class="main">
35 36
36 37 <h2>{repo|escape}</h2>
37 38 <h3>log {file|escape}</h3>
38 39
39 40 <form class="search" action="{url}log">
40 41 {sessionvars%hiddenformentry}
41 42 <p><input name="rev" id="search1" type="text" size="30"></p>
42 43 </form>
43 44
44 45 <div class="navigate">{nav%filenaventry}</div>
45 46
46 47 <table class="bigtable">
47 48 <tr>
48 49 <th class="age">age</td>
49 50 <th class="author">author</td>
50 51 <th class="description">description</td>
51 52 </tr>
52 53 {entries%filelogentry}
53 54 </table>
54 55
55 56 </div>
56 57 </div>
57 58
58 59 {footer}
@@ -1,73 +1,74 b''
1 1 {header}
2 2 <title>{repo|escape}: {node|short} {file|escape}</title>
3 3 </head>
4 4 <body>
5 5
6 6 <div class="container">
7 7 <div class="menu">
8 8 <div class="logo">
9 9 <a href="http://www.selenic.com/mercurial/">
10 10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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>
17 18 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
18 19 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
19 20 </ul>
20 21 <ul>
21 22 <li class="active">file</li>
22 23 <li><a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
23 24 <li><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
24 25 <li><a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">file log</a></li>
25 26 <li><a href="{url}raw-file/{node|short}/{file|urlescape}">raw</a></li>
26 27 </ul>
27 28 </div>
28 29
29 30 <div class="main">
30 31
31 32 <h2>{repo|escape}</h2>
32 33 <h3>view {file|escape} @ {rev}:{node|short}</h3>
33 34
34 35 <form class="search" action="{url}log">
35 36 {sessionvars%hiddenformentry}
36 37 <p><input name="rev" id="search1" type="text" size="30"></p>
37 38 </form>
38 39
39 40 <div class="description">{desc|strip|escape|addbreaks}</div>
40 41
41 42 <table id="changesetEntry">
42 43 <tr>
43 44 <th class="author">author</th>
44 45 <td class="author">{author|obfuscate}</td>
45 46 </tr>
46 47 <tr>
47 48 <th class="date">date</th>
48 49 <td class="date">{date|date} ({date|age} ago)</td>
49 50 </tr>
50 51 <tr>
51 52 <th class="author">parents</th>
52 53 <td class="author">{parent%filerevparent}</td>
53 54 </tr>
54 55 <tr>
55 56 <th class="author">children</th>
56 57 <td class="author">{child%filerevchild}</td>
57 58 </tr>
58 59 {changesettag}
59 60 </table>
60 61
61 62 <div class="overflow">
62 63 <table class="bigtable">
63 64 <tr>
64 65 <th class="lineno">line</th>
65 66 <th class="source">source</th>
66 67 </tr>
67 68 {text%fileline}
68 69 </table>
69 70 </div>
70 71 </div>
71 72 </div>
72 73
73 74 {footer}
@@ -1,51 +1,52 b''
1 1 {header}
2 2 <title>{repo|escape}: {node|short} {path|escape}</title>
3 3 </head>
4 4 <body>
5 5
6 6 <div class="container">
7 7 <div class="menu">
8 8 <div class="logo">
9 9 <a href="http://www.selenic.com/mercurial/">
10 10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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>
17 18 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
18 19 <li class="active">browse</li>
19 20 </ul>
20 21 <ul>
21 22 {archives%archiveentry}
22 23 </ul>
23 24 </div>
24 25
25 26 <div class="main">
26 27
27 28 <h2>{repo|escape}</h2>
28 29 <h3>directory {path|escape} @ {rev}:{node|short} {tags%changelogtag}</h3>
29 30
30 31 <form class="search" action="{url}log">
31 32 {sessionvars%hiddenformentry}
32 33 <p><input name="rev" id="search1" type="text" size="30"></p>
33 34 </form>
34 35
35 36 <table class="bigtable">
36 37 <tr>
37 38 <th class="name">name</th>
38 39 <th class="size">size</th>
39 40 <th class="permissions">permissions</th>
40 41 </tr>
41 42 <tr class="fileline parity{upparity}">
42 43 <td class="name"><a href="{url}file/{node|short}{up|urlescape}{sessionvars%urlparameter}">[up]</a></td>
43 44 <td class="size"></td>
44 45 <td class="permissions">drwxr-xr-x</td>
45 46 </tr>
46 47 {dentries%direntry}
47 48 {fentries%fileentry}
48 49 </table>
49 50 </div>
50 51 </div>
51 52 {footer}
@@ -1,69 +1,70 b''
1 1 default = 'shortlog'
2 2
3 3 mimetype = 'text/html; charset={encoding}'
4 4 header = header.tmpl
5 5 footer = footer.tmpl
6 6 search = search.tmpl
7 7
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> '
14 15 filenaventry = '<a href="{url}log/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{label|escape}</a> '
15 16 filedifflink = '<a href="{url}diff/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a> '
16 17 filenodelink = '<a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}</a> '
17 18 fileellipses = '...'
18 19 changelogentry = shortlogentry.tmpl
19 20 searchentry = shortlogentry.tmpl
20 21 changeset = changeset.tmpl
21 22 manifest = manifest.tmpl
22 23
23 24 direntry = '<tr class="fileline parity{parity}"><td class="name"><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}"><img src="{staticurl}coal-folder.png"> {basename|escape}/</a><td class="size"></td><td class="permissions">drwxr-xr-x</td></tr>'
24 25 fileentry = '<tr class="fileline parity{parity}"><td class="filename"><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}#l1"><img src="{staticurl}coal-file.png"> {basename|escape}</a></td><td class="size">{size}</td><td class="permissions">{permissions|permissions}</td></tr>'
25 26
26 27 filerevision = filerevision.tmpl
27 28 fileannotate = fileannotate.tmpl
28 29 filediff = filediff.tmpl
29 30 filelog = filelog.tmpl
30 31 fileline = '<tr class="parity{parity}"><td class="lineno"><a href="#{lineid}" id="{lineid}">{linenumber}</a></td><td class="source">{line|escape}</td></tr>'
31 32 filelogentry = filelogentry.tmpl
32 33
33 34 annotateline = '<tr class="parity{parity}"><td class="annotate"><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}#{targetline}" title="{node|short}: {desc|escape|firstline}">{author|user}@{rev}</a></td><td class="lineno"><a href="#{lineid}" id="{lineid}">{linenumber}</a></td><td class="source">{line|escape}</td></tr>'
34 35
35 36 diffblock = '<table class="bigtable parity{parity}">{lines}</table>'
36 37 difflineplus = '<tr><td class="lineno"><a href="#{lineid}" id="{lineid}">{linenumber}</a></td><td class="source plusline">{line|escape}</td></tr>'
37 38 difflineminus = '<tr><td class="lineno"><a href="#{lineid}" id="{lineid}">{linenumber}</a></td><td class="source minusline">{line|escape}</td></tr>'
38 39 difflineat = '<tr><td class="lineno"><a href="#{lineid}" id="{lineid}">{linenumber}</a></td><td class="source atline">{line|escape}</td></tr>'
39 40 diffline = '<tr><td class="lineno"><a href="#{lineid}" id="{lineid}">{linenumber}</a></td><td class="source">{line|escape}</td></tr>'
40 41
41 42 changelogparent = '<tr><th class="parent">parent {rev}:</th><td class="parent"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
42 43
43 44 changesetparent = '<a href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a> '
44 45
45 46 filerevparent = '<a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a> '
46 47 filerevchild = '<a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a> '
47 48
48 49 filerename = '{file|escape}@'
49 50 filelogrename = '<tr><th>base:</th><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{file|escape}@{node|short}</a></td></tr>'
50 51 fileannotateparent = '<tr><td class="metatag">parent:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{rename%filerename}{node|short}</a></td></tr>'
51 52 changesetchild = '<a href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>'
52 53 changelogchild = '<tr><th class="child">child</th><td class="child"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
53 54 fileannotatechild = '<tr><td class="metatag">child:</td><td><a href="{url}annotate/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
54 55 tags = tags.tmpl
55 56 tagentry = '<tr class="tagEntry parity{parity}"><td><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{tag|escape}</a></td><td class="node">{node|short}</td></tr>'
56 57 changelogtag = '<tr><th class="tag">tag:</th><td class="tag">{tag|escape}</td></tr>'
57 58 changelogtag = '<span class="tag">{name|escape}</span> '
58 59 changesettag = '<span class="tag">{tag|escape}</span> '
59 60 filediffparent = '<tr><th class="parent">parent {rev}:</th><td class="parent"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
60 61 filelogparent = '<tr><th>parent {rev}:</th><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
61 62 filediffchild = '<tr><th class="child">child {rev}:</th><td class="child"><a href="{url}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
62 63 filelogchild = '<tr><th>child {rev}:</th><td><a href="{url}file/{node|short}/{file|urlescape}{sessionvars%urlparameter}">{node|short}</a></td></tr>'
63 64 indexentry = '<tr class="parity{parity}"><td><a href="{url}{sessionvars%urlparameter}">{name|escape}</a></td><td>{description}</td><td>{contact|obfuscate}</td><td class="age">{lastchange|age} ago</td><td class="indexlinks"><a href="{url}rss-log">RSS</a> <a href="{url}atom-log">Atom</a> {archives%archiveentry}</td></tr>'
64 65 index = index.tmpl
65 66 archiveentry = '<li><a href="{url}archive/{node|short}{extension|urlescape}">{type|escape}</a></li>'
66 67 notfound = notfound.tmpl
67 68 error = error.tmpl
68 69 urlparameter = '{separator}{name}={value|urlescape}'
69 70 hiddenformentry = '<input type="hidden" name="{name}" value="{value|escape}" />'
@@ -1,40 +1,41 b''
1 1 {header}
2 2 <title>{repo|escape}: searching for {query|escape}</title>
3 3 </head>
4 4 <body>
5 5
6 6 <div class="container">
7 7 <div class="menu">
8 8 <div class="logo">
9 9 <a href="http://www.selenic.com/mercurial/">
10 10 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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>
17 18
18 19 <div class="main">
19 20
20 21 <h2>{repo|escape}</h2>
21 22 <h3>searching for '{query|escape}'</h3>
22 23
23 24 <form class="search" action="{url}log">
24 25 {sessionvars%hiddenformentry}
25 26 <p><input name="rev" id="search1" type="text" size="30"></p>
26 27 </form>
27 28
28 29 <table class="bigtable">
29 30 <tr>
30 31 <th class="age">age</td>
31 32 <th class="author">author</td>
32 33 <th class="description">description</td>
33 34 </tr>
34 35 {entries}
35 36 </table>
36 37
37 38 </div>
38 39 </div>
39 40
40 41 {footer}
@@ -1,54 +1,55 b''
1 1 {header}
2 2 <title>{repo|escape}: log</title>
3 3 <link rel="alternate" type="application/atom+xml"
4 4 href="{url}atom-log" title="Atom feed for {repo|escape}">
5 5 <link rel="alternate" type="application/rss+xml"
6 6 href="{url}rss-log" title="RSS feed for {repo|escape}">
7 7 </head>
8 8 <body>
9 9
10 10 <div class="container">
11 11 <div class="menu">
12 12 <div class="logo">
13 13 <a href="http://www.selenic.com/mercurial/">
14 14 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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>
21 22 <li><a href="{url}rev/{node|short}{sessionvars%urlparameter}">changeset</a></li>
22 23 <li><a href="{url}file/{node|short}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
23 24 </ul>
24 25 <ul>
25 26 {archives%archiveentry}
26 27 </ul>
27 28 </div>
28 29
29 30 <div class="main">
30 31
31 32 <h2>{repo|escape}</h2>
32 33 <h3>log</h3>
33 34
34 35 <form class="search" action="{url}log">
35 36 {sessionvars%hiddenformentry}
36 37 <p><input name="rev" id="search1" type="text" size="30"></p>
37 38 </form>
38 39
39 40 <div class="navigate">rev {rev}: {changenav%navshortentry}</div>
40 41
41 42 <table class="bigtable">
42 43 <tr>
43 44 <th class="age">age</td>
44 45 <th class="author">author</td>
45 46 <th class="description">description</td>
46 47 </tr>
47 48 {entries%shortlogentry}
48 49 </table>
49 50
50 51 <div class="navigate">rev {rev}: {changenav%navshortentry}</div>
51 52 </div>
52 53 </div>
53 54
54 55 {footer}
@@ -1,41 +1,42 b''
1 1 {header}
2 2 <title>{repo|escape}: tags</title>
3 3 <link rel="alternate" type="application/atom+xml"
4 4 href="{url}atom-tags" title="Atom feed for {repo|escape}: tags">
5 5 <link rel="alternate" type="application/rss+xml"
6 6 href="{url}rss-tags" title="RSS feed for {repo|escape}: tags">
7 7 </head>
8 8 <body>
9 9
10 10 <div class="container">
11 11 <div class="menu">
12 12 <div class="logo">
13 13 <a href="http://www.selenic.com/mercurial/">
14 14 <img src="{staticurl}hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
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>
21 22
22 23 <div class="main">
23 24 <h2>{repo|escape}</h2>
24 25 <h3>tags</h3>
25 26
26 27 <form class="search" action="{url}log">
27 28 {sessionvars%hiddenformentry}
28 29 <p><input name="rev" id="search1" type="text" size="30"></p>
29 30 </form>
30 31
31 32 <table class="bigtable">
32 33 <tr>
33 34 <th>tag</th>
34 35 <th>node</th>
35 36 </tr>
36 37 {entries%tagentry}
37 38 </table>
38 39 </div>
39 40 </div>
40 41
41 42 {footer}
@@ -1,156 +1,197 b''
1 1 body {
2 2 margin: 0;
3 3 padding: 0;
4 4 background: black url(background.png) repeat-x;
5 5 font-family: sans-serif;
6 6 }
7 7
8 8 .container {
9 9 padding-right: 150px;
10 10 }
11 11
12 12 .main {
13 13 position: relative;
14 14 background: white;
15 15 padding: 2em;
16 16 border-right: 15px solid black;
17 17 border-bottom: 15px solid black;
18 18 }
19 19
20 20 .overflow {
21 21 width: 100%;
22 22 overflow: auto;
23 23 }
24 24
25 25 .menu {
26 26 background: #999;
27 27 padding: 10px;
28 28 width: 75px;
29 29 margin: 0;
30 30 font-size: 80%;
31 31 text-align: left;
32 32 position: fixed;
33 33 top: 27px;
34 34 left: auto;
35 35 right: 27px;
36 36 }
37 37
38 38 .menu ul {
39 39 list-style: none;
40 40 padding: 0;
41 41 margin: 10px 0 0 0;
42 42 }
43 43
44 44 .menu li {
45 45 margin-bottom: 3px;
46 46 padding: 2px 4px;
47 47 background: white;
48 48 color: black;
49 49 font-weight: normal;
50 50 }
51 51
52 52 .menu li.active {
53 53 background: black;
54 54 color: white;
55 55 }
56 56
57 57 .menu a { color: black; display: block; }
58 58
59 59 .search {
60 60 position: absolute;
61 61 top: .7em;
62 62 right: 2em;
63 63 }
64 64
65 65 a { text-decoration:none; }
66 66 .age { white-space:nowrap; }
67 67 .date { white-space:nowrap; }
68 68 .indexlinks { white-space:nowrap; }
69 69 .parity0 { background-color: #f5f5f5; }
70 70 .parity1 { background-color: white; }
71 71 .plusline { color: green; }
72 72 .minusline { color: red; }
73 73 .atline { color: purple; }
74 74
75 75 .navigate {
76 76 text-align: right;
77 77 font-size: 60%;
78 78 margin: 1em 0 1em 0;
79 79 }
80 80
81 81 .tag {
82 82 color: #999;
83 83 font-size: 70%;
84 84 font-weight: normal;
85 85 margin-left: .5em;
86 86 vertical-align: baseline;
87 87 }
88 88
89 89 /* Common */
90 90 pre { margin: 0; }
91 91
92 92 h2 { font-size: 120%; border-bottom: 1px solid #999; }
93 93 h3 {
94 94 margin-top: -.7em;
95 95 font-size: 100%;
96 96 }
97 97
98 98 /* log and tags tables */
99 99 .bigtable {
100 100 border-bottom: 1px solid #999;
101 101 border-collapse: collapse;
102 102 font-size: 90%;
103 103 width: 100%;
104 104 font-weight: normal;
105 105 text-align: left;
106 106 }
107 107
108 108 .bigtable td {
109 109 padding: 1px 4px 1px 4px;
110 110 vertical-align: top;
111 111 }
112 112
113 113 .bigtable th {
114 114 padding: 1px 4px 1px 4px;
115 115 border-bottom: 1px solid #999;
116 116 font-size: smaller;
117 117 }
118 118 .bigtable tr { border: none; }
119 119 .bigtable .age { width: 6em; }
120 120 .bigtable .author { width: 10em; }
121 121 .bigtable .description { }
122 122 .bigtable .node { width: 5em; font-family: monospace;}
123 123 .bigtable .lineno { width: 2em; text-align: right;}
124 124 .bigtable .lineno a { color: #999; font-size: smaller; font-family: monospace;}
125 125 .bigtable td.source { font-family: monospace; white-space: pre; }
126 126 .bigtable .permissions { width: 8em; text-align: left;}
127 127 .bigtable .size { width: 5em; text-align: right; }
128 128 .bigtable .annotate { text-align: right; }
129 129 .bigtable td.annotate { font-size: smaller; }
130 130
131 131 .fileline { font-family: monospace; }
132 132 .fileline img { border: 0; }
133 133
134 134 /* Changeset entry */
135 135 #changesetEntry {
136 136 border-collapse: collapse;
137 137 font-size: 90%;
138 138 width: 100%;
139 139 margin-bottom: 1em;
140 140 }
141 141
142 142 #changesetEntry th {
143 143 padding: 1px 4px 1px 4px;
144 144 width: 4em;
145 145 text-align: right;
146 146 font-weight: normal;
147 147 color: #999;
148 148 margin-right: .5em;
149 149 vertical-align: top;
150 150 }
151 151
152 152 div.description {
153 153 border-left: 3px solid #999;
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