##// END OF EJS Templates
hgweb: fix problems with empty repositories
Dirkjan Ochtman -
r7565:5f162f61 default
parent child Browse files
Show More
@@ -0,0 +1,12 b''
1 #!/bin/sh
2 # Some tests for hgweb in an empty repository
3
4 hg init test
5 cd test
6 hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
7 cat hg.pid >> $DAEMON_PIDS
8
9 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/shortlog')
10 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/log')
11 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/graph')
12 ("$TESTDIR/get-with-headers.py" localhost:$HGPORT '/file')
@@ -0,0 +1,348 b''
1 200 Script output follows
2
3 <!-- quirksmode -->
4 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
5 <html>
6 <head>
7 <link rel="icon" href="/static/hgicon.png" type="image/png">
8 <meta name="robots" content="index, nofollow" />
9 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
10
11 <title>test: log</title>
12 <link rel="alternate" type="application/atom+xml"
13 href="/atom-log" title="Atom feed for test">
14 <link rel="alternate" type="application/rss+xml"
15 href="/rss-log" title="RSS feed for test">
16 </head>
17 <body>
18
19 <div class="container">
20 <div class="menu">
21 <div class="logo">
22 <a href="http://www.selenic.com/mercurial/">
23 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
24 </div>
25 <ul>
26 <li class="active">log</li>
27 <li><a href="/graph/000000000000">graph</a></li>
28 <li><a href="/tags">tags</a></li>
29 </ul>
30 <ul>
31 <li><a href="/rev/000000000000">changeset</a></li>
32 <li><a href="/file/000000000000">browse</a></li>
33 </ul>
34 <ul>
35
36 </ul>
37 </div>
38
39 <div class="main">
40 <h2><a href="/">test</a></h2>
41 <h3>log</h3>
42
43 <form class="search" action="/log">
44
45 <p><input name="rev" id="search1" type="text" size="30"></p>
46 <span>find changesets by author, revision,
47 files, or words in the commit message</span>
48 </form>
49
50 <div class="navigate">rev -1: <a href="/shortlog/000000000000">(0)</a> <a href="/shortlog/tip">tip</a> </div>
51
52 <table class="bigtable">
53 <tr>
54 <th class="age">age</th>
55 <th class="author">author</th>
56 <th class="description">description</th>
57 </tr>
58
59 </table>
60
61 <div class="navigate">rev -1: <a href="/shortlog/000000000000">(0)</a> <a href="/shortlog/tip">tip</a> </div>
62 </div>
63 </div>
64
65
66
67 </body>
68 </html>
69
70 200 Script output follows
71
72 <!-- quirksmode -->
73 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
74 <html>
75 <head>
76 <link rel="icon" href="/static/hgicon.png" type="image/png">
77 <meta name="robots" content="index, nofollow" />
78 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
79
80 <title>test: log</title>
81 <link rel="alternate" type="application/atom+xml"
82 href="/atom-log" title="Atom feed for test">
83 <link rel="alternate" type="application/rss+xml"
84 href="/rss-log" title="RSS feed for test">
85 </head>
86 <body>
87
88 <div class="container">
89 <div class="menu">
90 <div class="logo">
91 <a href="http://www.selenic.com/mercurial/">
92 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
93 </div>
94 <ul>
95 <li class="active">log</li>
96 <li><a href="/graph/000000000000">graph</a></li>
97 <li><a href="/tags">tags</a></li>
98 </ul>
99 <ul>
100 <li><a href="/rev/000000000000">changeset</a></li>
101 <li><a href="/file/000000000000">browse</a></li>
102 </ul>
103 <ul>
104
105 </ul>
106 </div>
107
108 <div class="main">
109 <h2><a href="/">test</a></h2>
110 <h3>log</h3>
111
112 <form class="search" action="/log">
113
114 <p><input name="rev" id="search1" type="text" size="30"></p>
115 <span>find changesets by author, revision,
116 files, or words in the commit message</span>
117 </form>
118
119 <div class="navigate">rev -1: <a href="/shortlog/000000000000">(0)</a> <a href="/shortlog/tip">tip</a> </div>
120
121 <table class="bigtable">
122 <tr>
123 <th class="age">age</th>
124 <th class="author">author</th>
125 <th class="description">description</th>
126 </tr>
127
128 </table>
129
130 <div class="navigate">rev -1: <a href="/shortlog/000000000000">(0)</a> <a href="/shortlog/tip">tip</a> </div>
131 </div>
132 </div>
133
134
135
136 </body>
137 </html>
138
139 200 Script output follows
140
141 <!-- quirksmode -->
142 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
143 <html>
144 <head>
145 <link rel="icon" href="/static/hgicon.png" type="image/png">
146 <meta name="robots" content="index, nofollow" />
147 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
148
149 <title>test: revision graph</title>
150 <link rel="alternate" type="application/atom+xml"
151 href="/atom-log" title="Atom feed for test: log">
152 <link rel="alternate" type="application/rss+xml"
153 href="/rss-log" title="RSS feed for test: log">
154 <!--[if IE]><script type="text/javascript" src="/static/excanvas.js"></script><![endif]-->
155 </head>
156 <body>
157
158 <div class="container">
159 <div class="menu">
160 <div class="logo">
161 <a href="http://www.selenic.com/mercurial/">
162 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
163 </div>
164 <ul>
165 <li><a href="/shortlog/000000000000">log</a></li>
166 <li class="active">graph</li>
167 <li><a href="/tags">tags</a></li>
168 </ul>
169 <ul>
170 <li><a href="/rev/000000000000">changeset</a></li>
171 <li><a href="/file/000000000000">browse</a></li>
172 </ul>
173 </div>
174
175 <div class="main">
176 <h2><a href="/">test</a></h2>
177 <h3>graph</h3>
178
179 <form class="search" action="/log">
180
181 <p><input name="rev" id="search1" type="text" size="30"></p>
182 <span>find changesets by author, revision,
183 files, or words in the commit message</span>
184 </form>
185
186 <div class="navigate">
187 <a href="/graph/-1?revcount=12">less</a>
188 <a href="/graph/-1?revcount=50">more</a>
189 | rev -1: <a href="/graph/000000000000">(0)</a> <a href="/graph/tip">tip</a>
190 </div>
191
192 <noscript>The revision graph only works with JavaScript-enabled browsers.</noscript>
193
194 <div id="wrapper">
195 <ul id="nodebgs"></ul>
196 <canvas id="graph" width="224" height="12"></canvas>
197 <ul id="graphnodes"></ul>
198 </div>
199
200 <script type="text/javascript" src="/static/graph.js"></script>
201 <script type="text/javascript">
202 <!-- hide script content
203
204 var data = [];
205 var graph = new Graph();
206 graph.scale(39);
207
208 graph.edge = function(x0, y0, x1, y1, color) {
209
210 this.setColor(color, 0.0, 0.65);
211 this.ctx.beginPath();
212 this.ctx.moveTo(x0, y0);
213 this.ctx.lineTo(x1, y1);
214 this.ctx.stroke();
215
216 }
217
218 var revlink = '<li style="_STYLE"><span class="desc">';
219 revlink += '<a href="/rev/_NODEID" title="_NODEID">_DESC</a>';
220 revlink += '</span>_TAGS<span class="info">_DATE ago, by _USER</span></li>';
221
222 graph.vertex = function(x, y, color, parity, cur) {
223
224 this.ctx.beginPath();
225 color = this.setColor(color, 0.25, 0.75);
226 this.ctx.arc(x, y, radius, 0, Math.PI * 2, true);
227 this.ctx.fill();
228
229 var bg = '<li class="bg parity' + parity + '"></li>';
230 var left = (this.columns + 1) * this.bg_height;
231 var nstyle = 'padding-left: ' + left + 'px;';
232 var item = revlink.replace(/_STYLE/, nstyle);
233 item = item.replace(/_PARITY/, 'parity' + parity);
234 item = item.replace(/_NODEID/, cur[0]);
235 item = item.replace(/_NODEID/, cur[0]);
236 item = item.replace(/_DESC/, cur[3]);
237 item = item.replace(/_USER/, cur[4]);
238 item = item.replace(/_DATE/, cur[5]);
239
240 var tagspan = '';
241 if (cur[7].length || (cur[6][0] != 'default' || cur[6][1])) {
242 tagspan = '<span class="logtags">';
243 if (cur[6][1]) {
244 tagspan += '<span class="branchhead" title="' + cur[6][0] + '">';
245 tagspan += cur[6][0] + '</span> ';
246 } else if (!cur[6][1] && cur[6][0] != 'default') {
247 tagspan += '<span class="branchname" title="' + cur[6][0] + '">';
248 tagspan += cur[6][0] + '</span> ';
249 }
250 if (cur[7].length) {
251 for (var t in cur[7]) {
252 var tag = cur[7][t];
253 tagspan += '<span class="tag">' + tag + '</span> ';
254 }
255 }
256 tagspan += '</span>';
257 }
258
259 item = item.replace(/_TAGS/, tagspan);
260 return [bg, item];
261
262 }
263
264 graph.render(data);
265
266 // stop hiding script -->
267 </script>
268
269 <div class="navigate">
270 <a href="/graph/-1?revcount=12">less</a>
271 <a href="/graph/-1?revcount=50">more</a>
272 | rev -1: <a href="/graph/000000000000">(0)</a> <a href="/graph/tip">tip</a>
273 </div>
274
275 </div>
276 </div>
277
278
279
280 </body>
281 </html>
282
283 200 Script output follows
284
285 <!-- quirksmode -->
286 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
287 <html>
288 <head>
289 <link rel="icon" href="/static/hgicon.png" type="image/png">
290 <meta name="robots" content="index, nofollow" />
291 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
292
293 <title>test: 000000000000 /</title>
294 </head>
295 <body>
296
297 <div class="container">
298 <div class="menu">
299 <div class="logo">
300 <a href="http://www.selenic.com/mercurial/">
301 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
302 </div>
303 <ul>
304 <li><a href="/shortlog/000000000000">log</a></li>
305 <li><a href="/graph/000000000000">graph</a></li>
306 <li><a href="/tags">tags</a></li>
307 </ul>
308 <ul>
309 <li><a href="/rev/000000000000">changeset</a></li>
310 <li class="active">browse</li>
311 </ul>
312 <ul>
313
314 </ul>
315 </div>
316
317 <div class="main">
318 <h2><a href="/">test</a></h2>
319 <h3>directory / @ -1:000000000000 <span class="tag">tip</span> </h3>
320
321 <form class="search" action="/log">
322
323 <p><input name="rev" id="search1" type="text" size="30"></p>
324 <span>find changesets by author, revision,
325 files, or words in the commit message</span>
326 </form>
327
328 <table class="bigtable">
329 <tr>
330 <th class="name">name</th>
331 <th class="size">size</th>
332 <th class="permissions">permissions</th>
333 </tr>
334 <tr class="fileline parity0">
335 <td class="name"><a href="/file/000000000000/">[up]</a></td>
336 <td class="size"></td>
337 <td class="permissions">drwxr-xr-x</td>
338 </tr>
339
340
341 </table>
342 </div>
343 </div>
344
345
346 </body>
347 </html>
348
@@ -1,73 +1,76 b''
1 1 # Revision graph generator for Mercurial
2 2 #
3 3 # Copyright 2008 Dirkjan Ochtman <dirkjan@ochtman.nl>
4 4 # Copyright 2007 Joel Rosdahl <joel@rosdahl.net>
5 5 #
6 6 # This software may be used and distributed according to the terms of
7 7 # the GNU General Public License, incorporated herein by reference.
8 8
9 9 from node import nullrev, short
10 10 import ui, hg, util, templatefilters
11 11
12 12 def graph(repo, start_rev, stop_rev):
13 13 """incremental revision grapher
14 14
15 15 This generator function walks through the revision history from
16 16 revision start_rev to revision stop_rev (which must be less than
17 17 or equal to start_rev) and for each revision emits tuples with the
18 18 following elements:
19 19
20 20 - Current node
21 21 - Column and color for the current node
22 22 - Edges; a list of (col, next_col, color) indicating the edges between
23 23 the current node and its parents.
24 24 - First line of the changeset description
25 25 - The changeset author
26 26 - The changeset date/time
27 27 """
28 28
29 if start_rev == nullrev and not stop_rev:
30 return
31
29 32 assert start_rev >= stop_rev
30 33 assert stop_rev >= 0
31 34 curr_rev = start_rev
32 35 revs = []
33 36 cl = repo.changelog
34 37 colors = {}
35 38 new_color = 1
36 39
37 40 while curr_rev >= stop_rev:
38 41 # Compute revs and next_revs
39 42 if curr_rev not in revs:
40 43 revs.append(curr_rev) # new head
41 44 colors[curr_rev] = new_color
42 45 new_color += 1
43 46
44 47 idx = revs.index(curr_rev)
45 48 color = colors.pop(curr_rev)
46 49 next = revs[:]
47 50
48 51 # Add parents to next_revs
49 52 parents = [x for x in cl.parentrevs(curr_rev) if x != nullrev]
50 53 addparents = [p for p in parents if p not in next]
51 54 next[idx:idx + 1] = addparents
52 55
53 56 # Set colors for the parents
54 57 for i, p in enumerate(addparents):
55 58 if not i:
56 59 colors[p] = color
57 60 else:
58 61 colors[p] = new_color
59 62 new_color += 1
60 63
61 64 # Add edges to the graph
62 65 edges = []
63 66 for col, r in enumerate(revs):
64 67 if r in next:
65 68 edges.append((col, next.index(r), colors[r]))
66 69 elif r == curr_rev:
67 70 for p in parents:
68 71 edges.append((col, next.index(p), colors[p]))
69 72
70 73 # Yield and move on
71 74 yield (repo[curr_rev], (idx, color), edges)
72 75 revs = next
73 76 curr_rev -= 1
@@ -1,663 +1,663 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, mimetypes, re, cgi, copy
9 9 import webutil
10 10 from mercurial import revlog, archival, templatefilters
11 11 from mercurial.node import short, hex, nullid
12 12 from mercurial.util import binary, datestr
13 13 from mercurial.repo import RepoError
14 14 from common import paritygen, staticfile, get_contact, ErrorResponse
15 15 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
16 16 from mercurial import graphmod, util
17 17
18 18 # __all__ is populated with the allowed commands. Be sure to add to it if
19 19 # you're adding a new command, or the new command won't work.
20 20
21 21 __all__ = [
22 22 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
23 23 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
24 24 'archive', 'static', 'graph',
25 25 ]
26 26
27 27 def log(web, req, tmpl):
28 28 if 'file' in req.form and req.form['file'][0]:
29 29 return filelog(web, req, tmpl)
30 30 else:
31 31 return changelog(web, req, tmpl)
32 32
33 33 def rawfile(web, req, tmpl):
34 34 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
35 35 if not path:
36 36 content = manifest(web, req, tmpl)
37 37 req.respond(HTTP_OK, web.ctype)
38 38 return content
39 39
40 40 try:
41 41 fctx = webutil.filectx(web.repo, req)
42 42 except revlog.LookupError, inst:
43 43 try:
44 44 content = manifest(web, req, tmpl)
45 45 req.respond(HTTP_OK, web.ctype)
46 46 return content
47 47 except ErrorResponse:
48 48 raise inst
49 49
50 50 path = fctx.path()
51 51 text = fctx.data()
52 52 mt = mimetypes.guess_type(path)[0]
53 53 if mt is None:
54 54 mt = binary(text) and 'application/octet-stream' or 'text/plain'
55 55
56 56 req.respond(HTTP_OK, mt, path, len(text))
57 57 return [text]
58 58
59 59 def _filerevision(web, tmpl, fctx):
60 60 f = fctx.path()
61 61 text = fctx.data()
62 62 parity = paritygen(web.stripecount)
63 63
64 64 if binary(text):
65 65 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
66 66 text = '(binary:%s)' % mt
67 67
68 68 def lines():
69 69 for lineno, t in enumerate(text.splitlines(1)):
70 70 yield {"line": t,
71 71 "lineid": "l%d" % (lineno + 1),
72 72 "linenumber": "% 6d" % (lineno + 1),
73 73 "parity": parity.next()}
74 74
75 75 return tmpl("filerevision",
76 76 file=f,
77 77 path=webutil.up(f),
78 78 text=lines(),
79 79 rev=fctx.rev(),
80 80 node=hex(fctx.node()),
81 81 author=fctx.user(),
82 82 date=fctx.date(),
83 83 desc=fctx.description(),
84 84 branch=webutil.nodebranchnodefault(fctx),
85 85 parent=webutil.siblings(fctx.parents()),
86 86 child=webutil.siblings(fctx.children()),
87 87 rename=webutil.renamelink(fctx),
88 88 permissions=fctx.manifest().flags(f))
89 89
90 90 def file(web, req, tmpl):
91 91 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
92 92 if not path:
93 93 return manifest(web, req, tmpl)
94 94 try:
95 95 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
96 96 except revlog.LookupError, inst:
97 97 try:
98 98 return manifest(web, req, tmpl)
99 99 except ErrorResponse:
100 100 raise inst
101 101
102 102 def _search(web, tmpl, query):
103 103
104 104 def changelist(**map):
105 105 cl = web.repo.changelog
106 106 count = 0
107 107 qw = query.lower().split()
108 108
109 109 def revgen():
110 110 for i in xrange(len(cl) - 1, 0, -100):
111 111 l = []
112 112 for j in xrange(max(0, i - 100), i + 1):
113 113 ctx = web.repo[j]
114 114 l.append(ctx)
115 115 l.reverse()
116 116 for e in l:
117 117 yield e
118 118
119 119 for ctx in revgen():
120 120 miss = 0
121 121 for q in qw:
122 122 if not (q in ctx.user().lower() or
123 123 q in ctx.description().lower() or
124 124 q in " ".join(ctx.files()).lower()):
125 125 miss = 1
126 126 break
127 127 if miss:
128 128 continue
129 129
130 130 count += 1
131 131 n = ctx.node()
132 132 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
133 133 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
134 134
135 135 yield tmpl('searchentry',
136 136 parity=parity.next(),
137 137 author=ctx.user(),
138 138 parent=webutil.siblings(ctx.parents()),
139 139 child=webutil.siblings(ctx.children()),
140 140 changelogtag=showtags,
141 141 desc=ctx.description(),
142 142 date=ctx.date(),
143 143 files=files,
144 144 rev=ctx.rev(),
145 145 node=hex(n),
146 146 tags=webutil.nodetagsdict(web.repo, n),
147 147 inbranch=webutil.nodeinbranch(web.repo, ctx),
148 148 branches=webutil.nodebranchdict(web.repo, ctx))
149 149
150 150 if count >= web.maxchanges:
151 151 break
152 152
153 153 cl = web.repo.changelog
154 154 parity = paritygen(web.stripecount)
155 155
156 156 return tmpl('search',
157 157 query=query,
158 158 node=hex(cl.tip()),
159 159 entries=changelist,
160 160 archives=web.archivelist("tip"))
161 161
162 162 def changelog(web, req, tmpl, shortlog = False):
163 163 if 'node' in req.form:
164 164 ctx = webutil.changectx(web.repo, req)
165 165 else:
166 166 if 'rev' in req.form:
167 167 hi = req.form['rev'][0]
168 168 else:
169 169 hi = len(web.repo) - 1
170 170 try:
171 171 ctx = web.repo[hi]
172 172 except RepoError:
173 173 return _search(web, tmpl, hi) # XXX redirect to 404 page?
174 174
175 175 def changelist(limit=0, **map):
176 176 cl = web.repo.changelog
177 177 l = [] # build a list in forward order for efficiency
178 178 for i in xrange(start, end):
179 179 ctx = web.repo[i]
180 180 n = ctx.node()
181 181 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
182 182 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
183 183
184 184 l.insert(0, {"parity": parity.next(),
185 185 "author": ctx.user(),
186 186 "parent": webutil.siblings(ctx.parents(), i - 1),
187 187 "child": webutil.siblings(ctx.children(), i + 1),
188 188 "changelogtag": showtags,
189 189 "desc": ctx.description(),
190 190 "date": ctx.date(),
191 191 "files": files,
192 192 "rev": i,
193 193 "node": hex(n),
194 194 "tags": webutil.nodetagsdict(web.repo, n),
195 195 "inbranch": webutil.nodeinbranch(web.repo, ctx),
196 196 "branches": webutil.nodebranchdict(web.repo, ctx)
197 197 })
198 198
199 199 if limit > 0:
200 200 l = l[:limit]
201 201
202 202 for e in l:
203 203 yield e
204 204
205 205 maxchanges = shortlog and web.maxshortchanges or web.maxchanges
206 206 cl = web.repo.changelog
207 207 count = len(cl)
208 208 pos = ctx.rev()
209 209 start = max(0, pos - maxchanges + 1)
210 210 end = min(count, start + maxchanges)
211 211 pos = end - 1
212 212 parity = paritygen(web.stripecount, offset=start-end)
213 213
214 214 changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx)
215 215
216 216 return tmpl(shortlog and 'shortlog' or 'changelog',
217 217 changenav=changenav,
218 218 node=hex(ctx.node()),
219 219 rev=pos, changesets=count,
220 220 entries=lambda **x: changelist(limit=0,**x),
221 221 latestentry=lambda **x: changelist(limit=1,**x),
222 222 archives=web.archivelist("tip"))
223 223
224 224 def shortlog(web, req, tmpl):
225 225 return changelog(web, req, tmpl, shortlog = True)
226 226
227 227 def changeset(web, req, tmpl):
228 228 ctx = webutil.changectx(web.repo, req)
229 229 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
230 230 showbranch = webutil.nodebranchnodefault(ctx)
231 231 parents = ctx.parents()
232 232
233 233 files = []
234 234 parity = paritygen(web.stripecount)
235 235 for f in ctx.files():
236 236 template = f in ctx and 'filenodelink' or 'filenolink'
237 237 files.append(tmpl(template,
238 238 node=ctx.hex(), file=f,
239 239 parity=parity.next()))
240 240
241 241 parity = paritygen(web.stripecount)
242 242 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity)
243 243 return tmpl('changeset',
244 244 diff=diffs,
245 245 rev=ctx.rev(),
246 246 node=ctx.hex(),
247 247 parent=webutil.siblings(parents),
248 248 child=webutil.siblings(ctx.children()),
249 249 changesettag=showtags,
250 250 changesetbranch=showbranch,
251 251 author=ctx.user(),
252 252 desc=ctx.description(),
253 253 date=ctx.date(),
254 254 files=files,
255 255 archives=web.archivelist(ctx.hex()),
256 256 tags=webutil.nodetagsdict(web.repo, ctx.node()),
257 257 branch=webutil.nodebranchnodefault(ctx),
258 258 inbranch=webutil.nodeinbranch(web.repo, ctx),
259 259 branches=webutil.nodebranchdict(web.repo, ctx))
260 260
261 261 rev = changeset
262 262
263 263 def manifest(web, req, tmpl):
264 264 ctx = webutil.changectx(web.repo, req)
265 265 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
266 266 mf = ctx.manifest()
267 267 node = ctx.node()
268 268
269 269 files = {}
270 270 dirs = {}
271 271 parity = paritygen(web.stripecount)
272 272
273 273 if path and path[-1] != "/":
274 274 path += "/"
275 275 l = len(path)
276 276 abspath = "/" + path
277 277
278 278 for f, n in mf.items():
279 279 if f[:l] != path:
280 280 continue
281 281 remain = f[l:]
282 282 elements = remain.split('/')
283 283 if len(elements) == 1:
284 284 files[remain] = f
285 285 else:
286 286 h = dirs # need to retain ref to dirs (root)
287 287 for elem in elements[0:-1]:
288 288 if elem not in h:
289 289 h[elem] = {}
290 290 h = h[elem]
291 291 if len(h) > 1:
292 292 break
293 293 h[None] = None # denotes files present
294 294
295 if not files and not dirs:
295 if mf and not files and not dirs:
296 296 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
297 297
298 298 def filelist(**map):
299 299 for f in util.sort(files):
300 300 full = files[f]
301 301
302 302 fctx = ctx.filectx(full)
303 303 yield {"file": full,
304 304 "parity": parity.next(),
305 305 "basename": f,
306 306 "date": fctx.date(),
307 307 "size": fctx.size(),
308 308 "permissions": mf.flags(full)}
309 309
310 310 def dirlist(**map):
311 311 for d in util.sort(dirs):
312 312
313 313 emptydirs = []
314 314 h = dirs[d]
315 315 while isinstance(h, dict) and len(h) == 1:
316 316 k,v = h.items()[0]
317 317 if v:
318 318 emptydirs.append(k)
319 319 h = v
320 320
321 321 path = "%s%s" % (abspath, d)
322 322 yield {"parity": parity.next(),
323 323 "path": path,
324 324 "emptydirs": "/".join(emptydirs),
325 325 "basename": d}
326 326
327 327 return tmpl("manifest",
328 328 rev=ctx.rev(),
329 329 node=hex(node),
330 330 path=abspath,
331 331 up=webutil.up(abspath),
332 332 upparity=parity.next(),
333 333 fentries=filelist,
334 334 dentries=dirlist,
335 335 archives=web.archivelist(hex(node)),
336 336 tags=webutil.nodetagsdict(web.repo, node),
337 337 inbranch=webutil.nodeinbranch(web.repo, ctx),
338 338 branches=webutil.nodebranchdict(web.repo, ctx))
339 339
340 340 def tags(web, req, tmpl):
341 341 i = web.repo.tagslist()
342 342 i.reverse()
343 343 parity = paritygen(web.stripecount)
344 344
345 345 def entries(notip=False,limit=0, **map):
346 346 count = 0
347 347 for k, n in i:
348 348 if notip and k == "tip":
349 349 continue
350 350 if limit > 0 and count >= limit:
351 351 continue
352 352 count = count + 1
353 353 yield {"parity": parity.next(),
354 354 "tag": k,
355 355 "date": web.repo[n].date(),
356 356 "node": hex(n)}
357 357
358 358 return tmpl("tags",
359 359 node=hex(web.repo.changelog.tip()),
360 360 entries=lambda **x: entries(False,0, **x),
361 361 entriesnotip=lambda **x: entries(True,0, **x),
362 362 latestentry=lambda **x: entries(True,1, **x))
363 363
364 364 def summary(web, req, tmpl):
365 365 i = web.repo.tagslist()
366 366 i.reverse()
367 367
368 368 def tagentries(**map):
369 369 parity = paritygen(web.stripecount)
370 370 count = 0
371 371 for k, n in i:
372 372 if k == "tip": # skip tip
373 373 continue
374 374
375 375 count += 1
376 376 if count > 10: # limit to 10 tags
377 377 break
378 378
379 379 yield tmpl("tagentry",
380 380 parity=parity.next(),
381 381 tag=k,
382 382 node=hex(n),
383 383 date=web.repo[n].date())
384 384
385 385 def branches(**map):
386 386 parity = paritygen(web.stripecount)
387 387
388 388 b = web.repo.branchtags()
389 389 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()]
390 390 for r,n,t in util.sort(l):
391 391 yield {'parity': parity.next(),
392 392 'branch': t,
393 393 'node': hex(n),
394 394 'date': web.repo[n].date()}
395 395
396 396 def changelist(**map):
397 397 parity = paritygen(web.stripecount, offset=start-end)
398 398 l = [] # build a list in forward order for efficiency
399 399 for i in xrange(start, end):
400 400 ctx = web.repo[i]
401 401 n = ctx.node()
402 402 hn = hex(n)
403 403
404 404 l.insert(0, tmpl(
405 405 'shortlogentry',
406 406 parity=parity.next(),
407 407 author=ctx.user(),
408 408 desc=ctx.description(),
409 409 date=ctx.date(),
410 410 rev=i,
411 411 node=hn,
412 412 tags=webutil.nodetagsdict(web.repo, n),
413 413 inbranch=webutil.nodeinbranch(web.repo, ctx),
414 414 branches=webutil.nodebranchdict(web.repo, ctx)))
415 415
416 416 yield l
417 417
418 418 cl = web.repo.changelog
419 419 count = len(cl)
420 420 start = max(0, count - web.maxchanges)
421 421 end = min(count, start + web.maxchanges)
422 422
423 423 return tmpl("summary",
424 424 desc=web.config("web", "description", "unknown"),
425 425 owner=get_contact(web.config) or "unknown",
426 426 lastchange=cl.read(cl.tip())[2],
427 427 tags=tagentries,
428 428 branches=branches,
429 429 shortlog=changelist,
430 430 node=hex(cl.tip()),
431 431 archives=web.archivelist("tip"))
432 432
433 433 def filediff(web, req, tmpl):
434 434 fctx, ctx = None, None
435 435 try:
436 436 fctx = webutil.filectx(web.repo, req)
437 437 except LookupError:
438 438 ctx = webutil.changectx(web.repo, req)
439 439 path = webutil.cleanpath(web.repo, req.form['file'][0])
440 440 if path not in ctx.files():
441 441 raise
442 442
443 443 if fctx is not None:
444 444 n = fctx.node()
445 445 path = fctx.path()
446 446 parents = fctx.parents()
447 447 p1 = parents and parents[0].node() or nullid
448 448 else:
449 449 n = ctx.node()
450 450 # path already defined in except clause
451 451 parents = ctx.parents()
452 452
453 453 parity = paritygen(web.stripecount)
454 454 diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity)
455 455 rename = fctx and webutil.renamelink(fctx) or []
456 456 ctx = fctx and fctx or ctx
457 457 return tmpl("filediff",
458 458 file=path,
459 459 node=hex(n),
460 460 rev=ctx.rev(),
461 461 date=ctx.date(),
462 462 desc=ctx.description(),
463 463 author=ctx.user(),
464 464 rename=rename,
465 465 branch=webutil.nodebranchnodefault(ctx),
466 466 parent=webutil.siblings(parents),
467 467 child=webutil.siblings(ctx.children()),
468 468 diff=diffs)
469 469
470 470 diff = filediff
471 471
472 472 def annotate(web, req, tmpl):
473 473 fctx = webutil.filectx(web.repo, req)
474 474 f = fctx.path()
475 475 parity = paritygen(web.stripecount)
476 476
477 477 def annotate(**map):
478 478 last = None
479 479 if binary(fctx.data()):
480 480 mt = (mimetypes.guess_type(fctx.path())[0]
481 481 or 'application/octet-stream')
482 482 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
483 483 '(binary:%s)' % mt)])
484 484 else:
485 485 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
486 486 for lineno, ((f, targetline), l) in lines:
487 487 fnode = f.filenode()
488 488
489 489 if last != fnode:
490 490 last = fnode
491 491
492 492 yield {"parity": parity.next(),
493 493 "node": hex(f.node()),
494 494 "rev": f.rev(),
495 495 "author": f.user(),
496 496 "desc": f.description(),
497 497 "file": f.path(),
498 498 "targetline": targetline,
499 499 "line": l,
500 500 "lineid": "l%d" % (lineno + 1),
501 501 "linenumber": "% 6d" % (lineno + 1)}
502 502
503 503 return tmpl("fileannotate",
504 504 file=f,
505 505 annotate=annotate,
506 506 path=webutil.up(f),
507 507 rev=fctx.rev(),
508 508 node=hex(fctx.node()),
509 509 author=fctx.user(),
510 510 date=fctx.date(),
511 511 desc=fctx.description(),
512 512 rename=webutil.renamelink(fctx),
513 513 branch=webutil.nodebranchnodefault(fctx),
514 514 parent=webutil.siblings(fctx.parents()),
515 515 child=webutil.siblings(fctx.children()),
516 516 permissions=fctx.manifest().flags(f))
517 517
518 518 def filelog(web, req, tmpl):
519 519
520 520 try:
521 521 fctx = webutil.filectx(web.repo, req)
522 522 f = fctx.path()
523 523 fl = fctx.filelog()
524 524 except revlog.LookupError:
525 525 f = webutil.cleanpath(web.repo, req.form['file'][0])
526 526 fl = web.repo.file(f)
527 527 numrevs = len(fl)
528 528 if not numrevs: # file doesn't exist at all
529 529 raise
530 530 rev = webutil.changectx(web.repo, req).rev()
531 531 first = fl.linkrev(0)
532 532 if rev < first: # current rev is from before file existed
533 533 raise
534 534 frev = numrevs - 1
535 535 while fl.linkrev(frev) > rev:
536 536 frev -= 1
537 537 fctx = web.repo.filectx(f, fl.linkrev(frev))
538 538
539 539 count = fctx.filerev() + 1
540 540 pagelen = web.maxshortchanges
541 541 start = max(0, fctx.filerev() - pagelen + 1) # first rev on this page
542 542 end = min(count, start + pagelen) # last rev on this page
543 543 parity = paritygen(web.stripecount, offset=start-end)
544 544
545 545 def entries(limit=0, **map):
546 546 l = []
547 547
548 548 for i in xrange(start, end):
549 549 ctx = fctx.filectx(i)
550 550
551 551 l.insert(0, {"parity": parity.next(),
552 552 "filerev": i,
553 553 "file": f,
554 554 "node": hex(ctx.node()),
555 555 "author": ctx.user(),
556 556 "date": ctx.date(),
557 557 "rename": webutil.renamelink(fctx),
558 558 "parent": webutil.siblings(fctx.parents()),
559 559 "child": webutil.siblings(fctx.children()),
560 560 "desc": ctx.description(),
561 561 "tags": webutil.nodetagsdict(web.repo, ctx.node()),
562 562 "branch": webutil.nodebranchnodefault(ctx),
563 563 "inbranch": webutil.nodeinbranch(web.repo, ctx),
564 564 "branches": webutil.nodebranchdict(web.repo, ctx)})
565 565
566 566 if limit > 0:
567 567 l = l[:limit]
568 568
569 569 for e in l:
570 570 yield e
571 571
572 572 nodefunc = lambda x: fctx.filectx(fileid=x)
573 573 nav = webutil.revnavgen(end - 1, pagelen, count, nodefunc)
574 574 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
575 575 entries=lambda **x: entries(limit=0, **x),
576 576 latestentry=lambda **x: entries(limit=1, **x))
577 577
578 578
579 579 def archive(web, req, tmpl):
580 580 type_ = req.form.get('type', [None])[0]
581 581 allowed = web.configlist("web", "allow_archive")
582 582 key = req.form['node'][0]
583 583
584 584 if type_ not in web.archives:
585 585 msg = 'Unsupported archive type: %s' % type_
586 586 raise ErrorResponse(HTTP_NOT_FOUND, msg)
587 587
588 588 if not ((type_ in allowed or
589 589 web.configbool("web", "allow" + type_, False))):
590 590 msg = 'Archive type not allowed: %s' % type_
591 591 raise ErrorResponse(HTTP_FORBIDDEN, msg)
592 592
593 593 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
594 594 cnode = web.repo.lookup(key)
595 595 arch_version = key
596 596 if cnode == key or key == 'tip':
597 597 arch_version = short(cnode)
598 598 name = "%s-%s" % (reponame, arch_version)
599 599 mimetype, artype, extension, encoding = web.archive_specs[type_]
600 600 headers = [
601 601 ('Content-Type', mimetype),
602 602 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
603 603 ]
604 604 if encoding:
605 605 headers.append(('Content-Encoding', encoding))
606 606 req.header(headers)
607 607 req.respond(HTTP_OK)
608 608 archival.archive(web.repo, req, cnode, artype, prefix=name)
609 609 return []
610 610
611 611
612 612 def static(web, req, tmpl):
613 613 fname = req.form['file'][0]
614 614 # a repo owner may set web.static in .hg/hgrc to get any file
615 615 # readable by the user running the CGI script
616 616 static = web.config("web", "static", None, untrusted=False)
617 617 if not static:
618 618 tp = web.templatepath
619 619 if isinstance(tp, str):
620 620 tp = [tp]
621 621 static = [os.path.join(p, 'static') for p in tp]
622 622 return [staticfile(static, fname, req)]
623 623
624 624 def graph(web, req, tmpl):
625 625 rev = webutil.changectx(web.repo, req).rev()
626 626 bg_height = 39
627 627
628 628 revcount = 25
629 629 if 'revcount' in req.form:
630 630 revcount = int(req.form.get('revcount', [revcount])[0])
631 631 tmpl.defaults['sessionvars']['revcount'] = revcount
632 632
633 633 lessvars = copy.copy(tmpl.defaults['sessionvars'])
634 634 lessvars['revcount'] = revcount / 2
635 635 morevars = copy.copy(tmpl.defaults['sessionvars'])
636 636 morevars['revcount'] = revcount * 2
637 637
638 638 max_rev = len(web.repo) - 1
639 639 revcount = min(max_rev, revcount)
640 640 revnode = web.repo.changelog.node(rev)
641 641 revnode_hex = hex(revnode)
642 642 uprev = min(max_rev, rev + revcount)
643 643 downrev = max(0, rev - revcount)
644 644 count = len(web.repo)
645 645 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
646 646
647 647 tree = list(graphmod.graph(web.repo, rev, downrev))
648 648 canvasheight = (len(tree) + 1) * bg_height - 27;
649 649 data = []
650 650 for i, (ctx, vtx, edges) in enumerate(tree):
651 651 node = short(ctx.node())
652 652 age = templatefilters.age(ctx.date())
653 653 desc = templatefilters.firstline(ctx.description())
654 654 desc = cgi.escape(desc)
655 655 user = cgi.escape(templatefilters.person(ctx.user()))
656 656 branch = ctx.branch()
657 657 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
658 658 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
659 659
660 660 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
661 661 lessvars=lessvars, morevars=morevars, downrev=downrev,
662 662 canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
663 663 node=revnode_hex, changenav=changenav)
General Comments 0
You need to be logged in to leave comments. Login now