##// END OF EJS Templates
hgweb: Respond with HTTP 403 for disabled archive types instead of 404...
Rocco Rutte -
r7029:b84d2738 default
parent child Browse files
Show More
@@ -1,120 +1,121 b''
1 1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 import errno, mimetypes, os
10 10
11 11 HTTP_OK = 200
12 12 HTTP_BAD_REQUEST = 400
13 13 HTTP_UNAUTHORIZED = 401
14 HTTP_FORBIDDEN = 403
14 15 HTTP_NOT_FOUND = 404
15 16 HTTP_METHOD_NOT_ALLOWED = 405
16 17 HTTP_SERVER_ERROR = 500
17 18
18 19 class ErrorResponse(Exception):
19 20 def __init__(self, code, message=None):
20 21 Exception.__init__(self)
21 22 self.code = code
22 23 if message is not None:
23 24 self.message = message
24 25 else:
25 26 self.message = _statusmessage(code)
26 27
27 28 def _statusmessage(code):
28 29 from BaseHTTPServer import BaseHTTPRequestHandler
29 30 responses = BaseHTTPRequestHandler.responses
30 31 return responses.get(code, ('Error', 'Unknown error'))[0]
31 32
32 33 def statusmessage(code):
33 34 return '%d %s' % (code, _statusmessage(code))
34 35
35 36 def get_mtime(repo_path):
36 37 store_path = os.path.join(repo_path, ".hg")
37 38 if not os.path.isdir(os.path.join(store_path, "data")):
38 39 store_path = os.path.join(store_path, "store")
39 40 cl_path = os.path.join(store_path, "00changelog.i")
40 41 if os.path.exists(cl_path):
41 42 return os.stat(cl_path).st_mtime
42 43 else:
43 44 return os.stat(store_path).st_mtime
44 45
45 46 def staticfile(directory, fname, req):
46 47 """return a file inside directory with guessed Content-Type header
47 48
48 49 fname always uses '/' as directory separator and isn't allowed to
49 50 contain unusual path components.
50 51 Content-Type is guessed using the mimetypes module.
51 52 Return an empty string if fname is illegal or file not found.
52 53
53 54 """
54 55 parts = fname.split('/')
55 56 path = directory
56 57 for part in parts:
57 58 if (part in ('', os.curdir, os.pardir) or
58 59 os.sep in part or os.altsep is not None and os.altsep in part):
59 60 return ""
60 61 path = os.path.join(path, part)
61 62 try:
62 63 os.stat(path)
63 64 ct = mimetypes.guess_type(path)[0] or "text/plain"
64 65 req.respond(HTTP_OK, ct, length = os.path.getsize(path))
65 66 return file(path, 'rb').read()
66 67 except TypeError:
67 68 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal file name')
68 69 except OSError, err:
69 70 if err.errno == errno.ENOENT:
70 71 raise ErrorResponse(HTTP_NOT_FOUND)
71 72 else:
72 73 raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
73 74
74 75 def style_map(templatepath, style):
75 76 """Return path to mapfile for a given style.
76 77
77 78 Searches mapfile in the following locations:
78 79 1. templatepath/style/map
79 80 2. templatepath/map-style
80 81 3. templatepath/map
81 82 """
82 83 locations = style and [os.path.join(style, "map"), "map-"+style] or []
83 84 locations.append("map")
84 85 for location in locations:
85 86 mapfile = os.path.join(templatepath, location)
86 87 if os.path.isfile(mapfile):
87 88 return mapfile
88 89 raise RuntimeError("No hgweb templates found in %r" % templatepath)
89 90
90 91 def paritygen(stripecount, offset=0):
91 92 """count parity of horizontal stripes for easier reading"""
92 93 if stripecount and offset:
93 94 # account for offset, e.g. due to building the list in reverse
94 95 count = (stripecount + offset) % stripecount
95 96 parity = (stripecount + offset) / stripecount & 1
96 97 else:
97 98 count = 0
98 99 parity = 0
99 100 while True:
100 101 yield parity
101 102 count += 1
102 103 if stripecount and count >= stripecount:
103 104 parity = 1 - parity
104 105 count = 0
105 106
106 107 def countgen(start=0, step=1):
107 108 """count forever -- useful for line numbers"""
108 109 while True:
109 110 yield start
110 111 start += step
111 112
112 113 def get_contact(config):
113 114 """Return repo contact information or empty string.
114 115
115 116 web.contact is the primary source, but if that is not set, try
116 117 ui.username or $EMAIL as a fallback to display something useful.
117 118 """
118 119 return (config("web", "contact") or
119 120 config("ui", "username") or
120 121 os.environ.get("EMAIL") or "")
@@ -1,605 +1,609 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
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 from common import HTTP_OK, HTTP_NOT_FOUND
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 fl = fctx.filelog()
63 63 n = fctx.filenode()
64 64 parity = paritygen(web.stripecount)
65 65
66 66 if binary(text):
67 67 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
68 68 text = '(binary:%s)' % mt
69 69
70 70 def lines():
71 71 for lineno, t in enumerate(text.splitlines(1)):
72 72 yield {"line": t,
73 73 "lineid": "l%d" % (lineno + 1),
74 74 "linenumber": "% 6d" % (lineno + 1),
75 75 "parity": parity.next()}
76 76
77 77 return tmpl("filerevision",
78 78 file=f,
79 79 path=webutil.up(f),
80 80 text=lines(),
81 81 rev=fctx.rev(),
82 82 node=hex(fctx.node()),
83 83 author=fctx.user(),
84 84 date=fctx.date(),
85 85 desc=fctx.description(),
86 86 branch=webutil.nodebranchnodefault(fctx),
87 87 parent=webutil.siblings(fctx.parents()),
88 88 child=webutil.siblings(fctx.children()),
89 89 rename=webutil.renamelink(fctx),
90 90 permissions=fctx.manifest().flags(f))
91 91
92 92 def file(web, req, tmpl):
93 93 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
94 94 if not path:
95 95 return manifest(web, req, tmpl)
96 96 try:
97 97 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
98 98 except revlog.LookupError, inst:
99 99 try:
100 100 return manifest(web, req, tmpl)
101 101 except ErrorResponse:
102 102 raise inst
103 103
104 104 def _search(web, tmpl, query):
105 105
106 106 def changelist(**map):
107 107 cl = web.repo.changelog
108 108 count = 0
109 109 qw = query.lower().split()
110 110
111 111 def revgen():
112 112 for i in xrange(len(cl) - 1, 0, -100):
113 113 l = []
114 114 for j in xrange(max(0, i - 100), i + 1):
115 115 ctx = web.repo[j]
116 116 l.append(ctx)
117 117 l.reverse()
118 118 for e in l:
119 119 yield e
120 120
121 121 for ctx in revgen():
122 122 miss = 0
123 123 for q in qw:
124 124 if not (q in ctx.user().lower() or
125 125 q in ctx.description().lower() or
126 126 q in " ".join(ctx.files()).lower()):
127 127 miss = 1
128 128 break
129 129 if miss:
130 130 continue
131 131
132 132 count += 1
133 133 n = ctx.node()
134 134 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
135 135
136 136 yield tmpl('searchentry',
137 137 parity=parity.next(),
138 138 author=ctx.user(),
139 139 parent=webutil.siblings(ctx.parents()),
140 140 child=webutil.siblings(ctx.children()),
141 141 changelogtag=showtags,
142 142 desc=ctx.description(),
143 143 date=ctx.date(),
144 144 files=web.listfilediffs(tmpl, ctx.files(), n),
145 145 rev=ctx.rev(),
146 146 node=hex(n),
147 147 tags=webutil.nodetagsdict(web.repo, n),
148 148 inbranch=webutil.nodeinbranch(web.repo, ctx),
149 149 branches=webutil.nodebranchdict(web.repo, ctx))
150 150
151 151 if count >= web.maxchanges:
152 152 break
153 153
154 154 cl = web.repo.changelog
155 155 parity = paritygen(web.stripecount)
156 156
157 157 return tmpl('search',
158 158 query=query,
159 159 node=hex(cl.tip()),
160 160 entries=changelist,
161 161 archives=web.archivelist("tip"))
162 162
163 163 def changelog(web, req, tmpl, shortlog = False):
164 164 if 'node' in req.form:
165 165 ctx = webutil.changectx(web.repo, req)
166 166 else:
167 167 if 'rev' in req.form:
168 168 hi = req.form['rev'][0]
169 169 else:
170 170 hi = len(web.repo) - 1
171 171 try:
172 172 ctx = web.repo[hi]
173 173 except RepoError:
174 174 return _search(web, tmpl, hi) # XXX redirect to 404 page?
175 175
176 176 def changelist(limit=0, **map):
177 177 cl = web.repo.changelog
178 178 l = [] # build a list in forward order for efficiency
179 179 for i in xrange(start, end):
180 180 ctx = web.repo[i]
181 181 n = ctx.node()
182 182 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
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": web.listfilediffs(tmpl, ctx.files(), n),
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 n = ctx.node()
230 230 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', n)
231 231 parents = ctx.parents()
232 232 p1 = parents[0].node()
233 233
234 234 files = []
235 235 parity = paritygen(web.stripecount)
236 236 for f in ctx.files():
237 237 files.append(tmpl("filenodelink",
238 238 node=hex(n), file=f,
239 239 parity=parity.next()))
240 240
241 241 diffs = web.diff(tmpl, p1, n, None)
242 242 return tmpl('changeset',
243 243 diff=diffs,
244 244 rev=ctx.rev(),
245 245 node=hex(n),
246 246 parent=webutil.siblings(parents),
247 247 child=webutil.siblings(ctx.children()),
248 248 changesettag=showtags,
249 249 author=ctx.user(),
250 250 desc=ctx.description(),
251 251 date=ctx.date(),
252 252 files=files,
253 253 archives=web.archivelist(hex(n)),
254 254 tags=webutil.nodetagsdict(web.repo, n),
255 255 branch=webutil.nodebranchnodefault(ctx),
256 256 inbranch=webutil.nodeinbranch(web.repo, ctx),
257 257 branches=webutil.nodebranchdict(web.repo, ctx))
258 258
259 259 rev = changeset
260 260
261 261 def manifest(web, req, tmpl):
262 262 ctx = webutil.changectx(web.repo, req)
263 263 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
264 264 mf = ctx.manifest()
265 265 node = ctx.node()
266 266
267 267 files = {}
268 268 parity = paritygen(web.stripecount)
269 269
270 270 if path and path[-1] != "/":
271 271 path += "/"
272 272 l = len(path)
273 273 abspath = "/" + path
274 274
275 275 for f, n in mf.items():
276 276 if f[:l] != path:
277 277 continue
278 278 remain = f[l:]
279 279 if "/" in remain:
280 280 short = remain[:remain.index("/") + 1] # bleah
281 281 files[short] = (f, None)
282 282 else:
283 283 short = os.path.basename(remain)
284 284 files[short] = (f, n)
285 285
286 286 if not files:
287 287 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
288 288
289 289 def filelist(**map):
290 290 for f in util.sort(files):
291 291 full, fnode = files[f]
292 292 if not fnode:
293 293 continue
294 294
295 295 fctx = ctx.filectx(full)
296 296 yield {"file": full,
297 297 "parity": parity.next(),
298 298 "basename": f,
299 299 "date": fctx.date(),
300 300 "size": fctx.size(),
301 301 "permissions": mf.flags(full)}
302 302
303 303 def dirlist(**map):
304 304 for f in util.sort(files):
305 305 full, fnode = files[f]
306 306 if fnode:
307 307 continue
308 308
309 309 yield {"parity": parity.next(),
310 310 "path": "%s%s" % (abspath, f),
311 311 "basename": f[:-1]}
312 312
313 313 return tmpl("manifest",
314 314 rev=ctx.rev(),
315 315 node=hex(node),
316 316 path=abspath,
317 317 up=webutil.up(abspath),
318 318 upparity=parity.next(),
319 319 fentries=filelist,
320 320 dentries=dirlist,
321 321 archives=web.archivelist(hex(node)),
322 322 tags=webutil.nodetagsdict(web.repo, node),
323 323 inbranch=webutil.nodeinbranch(web.repo, ctx),
324 324 branches=webutil.nodebranchdict(web.repo, ctx))
325 325
326 326 def tags(web, req, tmpl):
327 327 i = web.repo.tagslist()
328 328 i.reverse()
329 329 parity = paritygen(web.stripecount)
330 330
331 331 def entries(notip=False,limit=0, **map):
332 332 count = 0
333 333 for k, n in i:
334 334 if notip and k == "tip":
335 335 continue
336 336 if limit > 0 and count >= limit:
337 337 continue
338 338 count = count + 1
339 339 yield {"parity": parity.next(),
340 340 "tag": k,
341 341 "date": web.repo[n].date(),
342 342 "node": hex(n)}
343 343
344 344 return tmpl("tags",
345 345 node=hex(web.repo.changelog.tip()),
346 346 entries=lambda **x: entries(False,0, **x),
347 347 entriesnotip=lambda **x: entries(True,0, **x),
348 348 latestentry=lambda **x: entries(True,1, **x))
349 349
350 350 def summary(web, req, tmpl):
351 351 i = web.repo.tagslist()
352 352 i.reverse()
353 353
354 354 def tagentries(**map):
355 355 parity = paritygen(web.stripecount)
356 356 count = 0
357 357 for k, n in i:
358 358 if k == "tip": # skip tip
359 359 continue
360 360
361 361 count += 1
362 362 if count > 10: # limit to 10 tags
363 363 break
364 364
365 365 yield tmpl("tagentry",
366 366 parity=parity.next(),
367 367 tag=k,
368 368 node=hex(n),
369 369 date=web.repo[n].date())
370 370
371 371 def branches(**map):
372 372 parity = paritygen(web.stripecount)
373 373
374 374 b = web.repo.branchtags()
375 375 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()]
376 376 for r,n,t in util.sort(l):
377 377 yield {'parity': parity.next(),
378 378 'branch': t,
379 379 'node': hex(n),
380 380 'date': web.repo[n].date()}
381 381
382 382 def changelist(**map):
383 383 parity = paritygen(web.stripecount, offset=start-end)
384 384 l = [] # build a list in forward order for efficiency
385 385 for i in xrange(start, end):
386 386 ctx = web.repo[i]
387 387 n = ctx.node()
388 388 hn = hex(n)
389 389
390 390 l.insert(0, tmpl(
391 391 'shortlogentry',
392 392 parity=parity.next(),
393 393 author=ctx.user(),
394 394 desc=ctx.description(),
395 395 date=ctx.date(),
396 396 rev=i,
397 397 node=hn,
398 398 tags=webutil.nodetagsdict(web.repo, n),
399 399 inbranch=webutil.nodeinbranch(web.repo, ctx),
400 400 branches=webutil.nodebranchdict(web.repo, ctx)))
401 401
402 402 yield l
403 403
404 404 cl = web.repo.changelog
405 405 count = len(cl)
406 406 start = max(0, count - web.maxchanges)
407 407 end = min(count, start + web.maxchanges)
408 408
409 409 return tmpl("summary",
410 410 desc=web.config("web", "description", "unknown"),
411 411 owner=get_contact(web.config) or "unknown",
412 412 lastchange=cl.read(cl.tip())[2],
413 413 tags=tagentries,
414 414 branches=branches,
415 415 shortlog=changelist,
416 416 node=hex(cl.tip()),
417 417 archives=web.archivelist("tip"))
418 418
419 419 def filediff(web, req, tmpl):
420 420 fctx = webutil.filectx(web.repo, req)
421 421 n = fctx.node()
422 422 path = fctx.path()
423 423 parents = fctx.parents()
424 424 p1 = parents and parents[0].node() or nullid
425 425
426 426 diffs = web.diff(tmpl, p1, n, [path])
427 427 return tmpl("filediff",
428 428 file=path,
429 429 node=hex(n),
430 430 rev=fctx.rev(),
431 431 date=fctx.date(),
432 432 desc=fctx.description(),
433 433 author=fctx.user(),
434 434 rename=webutil.renamelink(fctx),
435 435 branch=webutil.nodebranchnodefault(fctx),
436 436 parent=webutil.siblings(parents),
437 437 child=webutil.siblings(fctx.children()),
438 438 diff=diffs)
439 439
440 440 diff = filediff
441 441
442 442 def annotate(web, req, tmpl):
443 443 fctx = webutil.filectx(web.repo, req)
444 444 f = fctx.path()
445 445 n = fctx.filenode()
446 446 fl = fctx.filelog()
447 447 parity = paritygen(web.stripecount)
448 448
449 449 def annotate(**map):
450 450 last = None
451 451 if binary(fctx.data()):
452 452 mt = (mimetypes.guess_type(fctx.path())[0]
453 453 or 'application/octet-stream')
454 454 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
455 455 '(binary:%s)' % mt)])
456 456 else:
457 457 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
458 458 for lineno, ((f, targetline), l) in lines:
459 459 fnode = f.filenode()
460 460
461 461 if last != fnode:
462 462 last = fnode
463 463
464 464 yield {"parity": parity.next(),
465 465 "node": hex(f.node()),
466 466 "rev": f.rev(),
467 467 "author": f.user(),
468 468 "desc": f.description(),
469 469 "file": f.path(),
470 470 "targetline": targetline,
471 471 "line": l,
472 472 "lineid": "l%d" % (lineno + 1),
473 473 "linenumber": "% 6d" % (lineno + 1)}
474 474
475 475 return tmpl("fileannotate",
476 476 file=f,
477 477 annotate=annotate,
478 478 path=webutil.up(f),
479 479 rev=fctx.rev(),
480 480 node=hex(fctx.node()),
481 481 author=fctx.user(),
482 482 date=fctx.date(),
483 483 desc=fctx.description(),
484 484 rename=webutil.renamelink(fctx),
485 485 branch=webutil.nodebranchnodefault(fctx),
486 486 parent=webutil.siblings(fctx.parents()),
487 487 child=webutil.siblings(fctx.children()),
488 488 permissions=fctx.manifest().flags(f))
489 489
490 490 def filelog(web, req, tmpl):
491 491 fctx = webutil.filectx(web.repo, req)
492 492 f = fctx.path()
493 493 fl = fctx.filelog()
494 494 count = len(fl)
495 495 pagelen = web.maxshortchanges
496 496 pos = fctx.filerev()
497 497 start = max(0, pos - pagelen + 1)
498 498 end = min(count, start + pagelen)
499 499 pos = end - 1
500 500 parity = paritygen(web.stripecount, offset=start-end)
501 501
502 502 def entries(limit=0, **map):
503 503 l = []
504 504
505 505 for i in xrange(start, end):
506 506 ctx = fctx.filectx(i)
507 507 n = fl.node(i)
508 508
509 509 l.insert(0, {"parity": parity.next(),
510 510 "filerev": i,
511 511 "file": f,
512 512 "node": hex(ctx.node()),
513 513 "author": ctx.user(),
514 514 "date": ctx.date(),
515 515 "rename": webutil.renamelink(fctx),
516 516 "parent": webutil.siblings(fctx.parents()),
517 517 "child": webutil.siblings(fctx.children()),
518 518 "desc": ctx.description()})
519 519
520 520 if limit > 0:
521 521 l = l[:limit]
522 522
523 523 for e in l:
524 524 yield e
525 525
526 526 nodefunc = lambda x: fctx.filectx(fileid=x)
527 527 nav = webutil.revnavgen(pos, pagelen, count, nodefunc)
528 528 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
529 529 entries=lambda **x: entries(limit=0, **x),
530 530 latestentry=lambda **x: entries(limit=1, **x))
531 531
532 532
533 533 def archive(web, req, tmpl):
534 534 type_ = req.form.get('type', [None])[0]
535 535 allowed = web.configlist("web", "allow_archive")
536 536 key = req.form['node'][0]
537 537
538 if not (type_ in web.archives and (type_ in allowed or
539 web.configbool("web", "allow" + type_, False))):
538 if type_ not in web.archives:
540 539 msg = 'Unsupported archive type: %s' % type_
541 540 raise ErrorResponse(HTTP_NOT_FOUND, msg)
542 541
542 if not ((type_ in allowed or
543 web.configbool("web", "allow" + type_, False))):
544 msg = 'Archive type not allowed: %s' % type_
545 raise ErrorResponse(HTTP_FORBIDDEN, msg)
546
543 547 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
544 548 cnode = web.repo.lookup(key)
545 549 arch_version = key
546 550 if cnode == key or key == 'tip':
547 551 arch_version = short(cnode)
548 552 name = "%s-%s" % (reponame, arch_version)
549 553 mimetype, artype, extension, encoding = web.archive_specs[type_]
550 554 headers = [
551 555 ('Content-Type', mimetype),
552 556 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
553 557 ]
554 558 if encoding:
555 559 headers.append(('Content-Encoding', encoding))
556 560 req.header(headers)
557 561 req.respond(HTTP_OK)
558 562 archival.archive(web.repo, req, cnode, artype, prefix=name)
559 563 return []
560 564
561 565
562 566 def static(web, req, tmpl):
563 567 fname = req.form['file'][0]
564 568 # a repo owner may set web.static in .hg/hgrc to get any file
565 569 # readable by the user running the CGI script
566 570 static = web.config("web", "static",
567 571 os.path.join(web.templatepath, "static"),
568 572 untrusted=False)
569 573 return [staticfile(static, fname, req)]
570 574
571 575 def graph(web, req, tmpl):
572 576 rev = webutil.changectx(web.repo, req).rev()
573 577 bg_height = 39
574 578
575 579 max_rev = len(web.repo) - 1
576 580 revcount = min(max_rev, int(req.form.get('revcount', [25])[0]))
577 581 revnode = web.repo.changelog.node(rev)
578 582 revnode_hex = hex(revnode)
579 583 uprev = min(max_rev, rev + revcount)
580 584 downrev = max(0, rev - revcount)
581 585 lessrev = max(0, rev - revcount / 2)
582 586
583 587 maxchanges = web.maxshortchanges or web.maxchanges
584 588 count = len(web.repo)
585 589 changenav = webutil.revnavgen(rev, maxchanges, count, web.repo.changectx)
586 590
587 591 tree = list(graphmod.graph(web.repo, rev, rev - revcount))
588 592 canvasheight = (len(tree) + 1) * bg_height - 27;
589 593
590 594 data = []
591 595 for i, (ctx, vtx, edges) in enumerate(tree):
592 596 node = short(ctx.node())
593 597 age = templatefilters.age(ctx.date())
594 598 desc = templatefilters.firstline(ctx.description())
595 599 desc = cgi.escape(desc)
596 600 user = cgi.escape(templatefilters.person(ctx.user()))
597 601 branch = ctx.branch()
598 602 branch = branch, web.repo.branchtags().get(branch) == ctx.node()
599 603 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
600 604
601 605 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
602 606 lessrev=lessrev, revcountmore=revcount and 2 * revcount or 1,
603 607 revcountless=revcount / 2, downrev=downrev,
604 608 canvasheight=canvasheight, bg_height=bg_height,
605 609 jsdata=data, node=revnode_hex, changenav=changenav)
@@ -1,83 +1,109 b''
1 1 #!/bin/sh
2 2
3 3 mkdir test
4 4 cd test
5 5 hg init
6 6 echo foo>foo
7 7 hg commit -Am 1 -d '1 0'
8 8 echo bar>bar
9 9 hg commit -Am 2 -d '2 0'
10 10 mkdir baz
11 11 echo bletch>baz/bletch
12 12 hg commit -Am 3 -d '1000000000 0'
13 13 echo "[web]" >> .hg/hgrc
14 14 echo "name = test-archive" >> .hg/hgrc
15 echo "allow_archive = gz bz2, zip" >> .hg/hgrc
15 cp .hg/hgrc .hg/hgrc-base
16
17 # check http return codes
18 test_archtype() {
19 echo "allow_archive = $1" >> .hg/hgrc
20 hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
21 cat hg.pid >> $DAEMON_PIDS
22 echo % $1 allowed should give 200
23 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$2" | head -n 1
24 echo % $3 and $4 disallowed should both give 403
25 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$3" | head -n 1
26 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.$4" | head -n 1
27 kill `cat hg.pid`
28 cat errors.log
29 cp .hg/hgrc-base .hg/hgrc
30 }
31
32 echo
33 test_archtype gz tar.gz tar.bz2 zip
34 test_archtype bz2 tar.bz2 zip tar.gz
35 test_archtype zip zip tar.gz tar.bz2
36
37 echo "allow_archive = gz bz2 zip" >> .hg/hgrc
16 38 hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
17 39 cat hg.pid >> $DAEMON_PIDS
18 40
41 echo % invalid arch type should give 404
42 "$TESTDIR/get-with-headers.py" localhost:$HGPORT "/archive/tip.invalid" | head -n 1
43 echo
44
19 45 TIP=`hg id -v | cut -f1 -d' '`
20 46 QTIP=`hg id -q`
21 47 cat > getarchive.py <<EOF
22 48 import os, sys, urllib2
23 49 node, archive = sys.argv[1:]
24 50 f = urllib2.urlopen('http://127.0.0.1:%s/?cmd=archive;node=%s;type=%s'
25 51 % (os.environ['HGPORT'], node, archive))
26 52 sys.stdout.write(f.read())
27 53 EOF
28 54 http_proxy= python getarchive.py "$TIP" gz | gunzip | tar tf - | sed "s/$QTIP/TIP/"
29 55 http_proxy= python getarchive.py "$TIP" bz2 | bunzip2 | tar tf - | sed "s/$QTIP/TIP/"
30 56 http_proxy= python getarchive.py "$TIP" zip > archive.zip
31 57 unzip -t archive.zip | sed "s/$QTIP/TIP/"
32 58
33 59 hg archive -t tar test.tar
34 60 tar tf test.tar
35 61
36 62 hg archive -t tbz2 -X baz test.tar.bz2
37 63 bunzip2 -dc test.tar.bz2 | tar tf -
38 64
39 65 hg archive -t tgz -p %b-%h test-%h.tar.gz
40 66 gzip -dc test-$QTIP.tar.gz | tar tf - | sed "s/$QTIP/TIP/"
41 67
42 68 cat > md5comp.py <<EOF
43 69 from mercurial.util import md5
44 70 import sys
45 71 f1, f2 = sys.argv[1:3]
46 72 h1 = md5(file(f1, 'rb').read()).hexdigest()
47 73 h2 = md5(file(f2, 'rb').read()).hexdigest()
48 74 print h1 == h2 or "md5 differ: " + repr((h1, h2))
49 75 EOF
50 76
51 77 # archive name is stored in the archive, so create similar
52 78 # archives and rename them afterwards.
53 79 hg archive -t tgz tip.tar.gz
54 80 mv tip.tar.gz tip1.tar.gz
55 81 sleep 1
56 82 hg archive -t tgz tip.tar.gz
57 83 mv tip.tar.gz tip2.tar.gz
58 84 python md5comp.py tip1.tar.gz tip2.tar.gz
59 85
60 86 hg archive -t zip -p /illegal test.zip
61 87 hg archive -t zip -p very/../bad test.zip
62 88
63 89 hg archive --config ui.archivemeta=false -t zip -r 2 test.zip
64 90 unzip -t test.zip
65 91
66 92 hg archive -t tar - | tar tf - | sed "s/$QTIP/TIP/"
67 93
68 94 hg archive -r 0 -t tar rev-%r.tar
69 95 if [ -f rev-0.tar ]; then
70 96 echo 'rev-0.tar created'
71 97 fi
72 98
73 99 hg archive -t bogus test.bogus
74 100
75 101 echo % server errors
76 102 cat errors.log
77 103
78 104 echo '% empty repo'
79 105 hg init ../empty
80 106 cd ../empty
81 107 hg archive ../test-empty
82 108
83 109 exit 0
@@ -1,44 +1,63 b''
1 1 adding foo
2 2 adding bar
3 3 adding baz/bletch
4
5 % gz allowed should give 200
6 200 Script output follows
7 % tar.bz2 and zip disallowed should both give 403
8 403 Forbidden
9 403 Forbidden
10 % bz2 allowed should give 200
11 200 Script output follows
12 % zip and tar.gz disallowed should both give 403
13 403 Forbidden
14 403 Forbidden
15 % zip allowed should give 200
16 200 Script output follows
17 % tar.gz and tar.bz2 disallowed should both give 403
18 403 Forbidden
19 403 Forbidden
20 % invalid arch type should give 404
21 404 Not Found
22
4 23 test-archive-TIP/.hg_archival.txt
5 24 test-archive-TIP/bar
6 25 test-archive-TIP/baz/bletch
7 26 test-archive-TIP/foo
8 27 test-archive-TIP/.hg_archival.txt
9 28 test-archive-TIP/bar
10 29 test-archive-TIP/baz/bletch
11 30 test-archive-TIP/foo
12 31 Archive: archive.zip
13 32 testing: test-archive-TIP/.hg_archival.txt OK
14 33 testing: test-archive-TIP/bar OK
15 34 testing: test-archive-TIP/baz/bletch OK
16 35 testing: test-archive-TIP/foo OK
17 36 No errors detected in compressed data of archive.zip.
18 37 test/.hg_archival.txt
19 38 test/bar
20 39 test/baz/bletch
21 40 test/foo
22 41 test/.hg_archival.txt
23 42 test/bar
24 43 test/foo
25 44 test-TIP/.hg_archival.txt
26 45 test-TIP/bar
27 46 test-TIP/baz/bletch
28 47 test-TIP/foo
29 48 True
30 49 abort: archive prefix contains illegal components
31 50 Archive: test.zip
32 51 testing: test/bar OK
33 52 testing: test/baz/bletch OK
34 53 testing: test/foo OK
35 54 No errors detected in compressed data of test.zip.
36 55 test-TIP/.hg_archival.txt
37 56 test-TIP/bar
38 57 test-TIP/baz/bletch
39 58 test-TIP/foo
40 59 rev-0.tar created
41 60 abort: unknown archive type 'bogus'
42 61 % server errors
43 62 % empty repo
44 63 abort: repository has no revisions
General Comments 0
You need to be logged in to leave comments. Login now