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