|
1 # |
|
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> |
|
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
4 # |
|
5 # This software may be used and distributed according to the terms of the |
|
6 # GNU General Public License version 2 or any later version. |
|
7 |
|
8 import os, mimetypes, re, cgi, copy |
|
9 import webutil |
|
10 from mercurial import error, encoding, archival, templater, templatefilters |
|
11 from mercurial.node import short, hex |
|
12 from mercurial.util import binary |
|
13 from common import paritygen, staticfile, get_contact, ErrorResponse |
|
14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND |
|
15 from mercurial import graphmod |
|
16 from mercurial import help as helpmod |
|
17 from mercurial.i18n import _ |
|
18 |
|
19 # __all__ is populated with the allowed commands. Be sure to add to it if |
|
20 # you're adding a new command, or the new command won't work. |
|
21 |
|
22 __all__ = [ |
|
23 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev', |
|
24 'manifest', 'tags', 'branches', 'summary', 'filediff', 'diff', 'annotate', |
|
25 'filelog', 'archive', 'static', 'graph', 'help', |
|
26 ] |
|
27 |
|
28 def log(web, req, tmpl): |
|
29 if 'file' in req.form and req.form['file'][0]: |
|
30 return filelog(web, req, tmpl) |
|
31 else: |
|
32 return changelog(web, req, tmpl) |
|
33 |
|
34 def rawfile(web, req, tmpl): |
|
35 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) |
|
36 if not path: |
|
37 content = manifest(web, req, tmpl) |
|
38 req.respond(HTTP_OK, web.ctype) |
|
39 return content |
|
40 |
|
41 try: |
|
42 fctx = webutil.filectx(web.repo, req) |
|
43 except error.LookupError, inst: |
|
44 try: |
|
45 content = manifest(web, req, tmpl) |
|
46 req.respond(HTTP_OK, web.ctype) |
|
47 return content |
|
48 except ErrorResponse: |
|
49 raise inst |
|
50 |
|
51 path = fctx.path() |
|
52 text = fctx.data() |
|
53 mt = mimetypes.guess_type(path)[0] |
|
54 if mt is None: |
|
55 mt = binary(text) and 'application/octet-stream' or 'text/plain' |
|
56 if mt.startswith('text/'): |
|
57 mt += '; charset="%s"' % encoding.encoding |
|
58 |
|
59 req.respond(HTTP_OK, mt, path, len(text)) |
|
60 return [text] |
|
61 |
|
62 def _filerevision(web, tmpl, fctx): |
|
63 f = fctx.path() |
|
64 text = fctx.data() |
|
65 parity = paritygen(web.stripecount) |
|
66 |
|
67 if binary(text): |
|
68 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream' |
|
69 text = '(binary:%s)' % mt |
|
70 |
|
71 def lines(): |
|
72 for lineno, t in enumerate(text.splitlines(True)): |
|
73 yield {"line": t, |
|
74 "lineid": "l%d" % (lineno + 1), |
|
75 "linenumber": "% 6d" % (lineno + 1), |
|
76 "parity": parity.next()} |
|
77 |
|
78 return tmpl("filerevision", |
|
79 file=f, |
|
80 path=webutil.up(f), |
|
81 text=lines(), |
|
82 rev=fctx.rev(), |
|
83 node=hex(fctx.node()), |
|
84 author=fctx.user(), |
|
85 date=fctx.date(), |
|
86 desc=fctx.description(), |
|
87 branch=webutil.nodebranchnodefault(fctx), |
|
88 parent=webutil.parents(fctx), |
|
89 child=webutil.children(fctx), |
|
90 rename=webutil.renamelink(fctx), |
|
91 permissions=fctx.manifest().flags(f)) |
|
92 |
|
93 def file(web, req, tmpl): |
|
94 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) |
|
95 if not path: |
|
96 return manifest(web, req, tmpl) |
|
97 try: |
|
98 return _filerevision(web, tmpl, webutil.filectx(web.repo, req)) |
|
99 except error.LookupError, inst: |
|
100 try: |
|
101 return manifest(web, req, tmpl) |
|
102 except ErrorResponse: |
|
103 raise inst |
|
104 |
|
105 def _search(web, req, tmpl): |
|
106 |
|
107 query = req.form['rev'][0] |
|
108 revcount = web.maxchanges |
|
109 if 'revcount' in req.form: |
|
110 revcount = int(req.form.get('revcount', [revcount])[0]) |
|
111 tmpl.defaults['sessionvars']['revcount'] = revcount |
|
112 |
|
113 lessvars = copy.copy(tmpl.defaults['sessionvars']) |
|
114 lessvars['revcount'] = revcount / 2 |
|
115 lessvars['rev'] = query |
|
116 morevars = copy.copy(tmpl.defaults['sessionvars']) |
|
117 morevars['revcount'] = revcount * 2 |
|
118 morevars['rev'] = query |
|
119 |
|
120 def changelist(**map): |
|
121 count = 0 |
|
122 qw = query.lower().split() |
|
123 |
|
124 def revgen(): |
|
125 for i in xrange(len(web.repo) - 1, 0, -100): |
|
126 l = [] |
|
127 for j in xrange(max(0, i - 100), i + 1): |
|
128 ctx = web.repo[j] |
|
129 l.append(ctx) |
|
130 l.reverse() |
|
131 for e in l: |
|
132 yield e |
|
133 |
|
134 for ctx in revgen(): |
|
135 miss = 0 |
|
136 for q in qw: |
|
137 if not (q in ctx.user().lower() or |
|
138 q in ctx.description().lower() or |
|
139 q in " ".join(ctx.files()).lower()): |
|
140 miss = 1 |
|
141 break |
|
142 if miss: |
|
143 continue |
|
144 |
|
145 count += 1 |
|
146 n = ctx.node() |
|
147 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n) |
|
148 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles) |
|
149 |
|
150 yield tmpl('searchentry', |
|
151 parity=parity.next(), |
|
152 author=ctx.user(), |
|
153 parent=webutil.parents(ctx), |
|
154 child=webutil.children(ctx), |
|
155 changelogtag=showtags, |
|
156 desc=ctx.description(), |
|
157 date=ctx.date(), |
|
158 files=files, |
|
159 rev=ctx.rev(), |
|
160 node=hex(n), |
|
161 tags=webutil.nodetagsdict(web.repo, n), |
|
162 inbranch=webutil.nodeinbranch(web.repo, ctx), |
|
163 branches=webutil.nodebranchdict(web.repo, ctx)) |
|
164 |
|
165 if count >= revcount: |
|
166 break |
|
167 |
|
168 tip = web.repo['tip'] |
|
169 parity = paritygen(web.stripecount) |
|
170 |
|
171 return tmpl('search', query=query, node=tip.hex(), |
|
172 entries=changelist, archives=web.archivelist("tip"), |
|
173 morevars=morevars, lessvars=lessvars) |
|
174 |
|
175 def changelog(web, req, tmpl, shortlog=False): |
|
176 |
|
177 if 'node' in req.form: |
|
178 ctx = webutil.changectx(web.repo, req) |
|
179 else: |
|
180 if 'rev' in req.form: |
|
181 hi = req.form['rev'][0] |
|
182 else: |
|
183 hi = len(web.repo) - 1 |
|
184 try: |
|
185 ctx = web.repo[hi] |
|
186 except error.RepoError: |
|
187 return _search(web, req, tmpl) # XXX redirect to 404 page? |
|
188 |
|
189 def changelist(limit=0, **map): |
|
190 l = [] # build a list in forward order for efficiency |
|
191 for i in xrange(start, end): |
|
192 ctx = web.repo[i] |
|
193 n = ctx.node() |
|
194 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n) |
|
195 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles) |
|
196 |
|
197 l.insert(0, {"parity": parity.next(), |
|
198 "author": ctx.user(), |
|
199 "parent": webutil.parents(ctx, i - 1), |
|
200 "child": webutil.children(ctx, i + 1), |
|
201 "changelogtag": showtags, |
|
202 "desc": ctx.description(), |
|
203 "date": ctx.date(), |
|
204 "files": files, |
|
205 "rev": i, |
|
206 "node": hex(n), |
|
207 "tags": webutil.nodetagsdict(web.repo, n), |
|
208 "inbranch": webutil.nodeinbranch(web.repo, ctx), |
|
209 "branches": webutil.nodebranchdict(web.repo, ctx) |
|
210 }) |
|
211 |
|
212 if limit > 0: |
|
213 l = l[:limit] |
|
214 |
|
215 for e in l: |
|
216 yield e |
|
217 |
|
218 revcount = shortlog and web.maxshortchanges or web.maxchanges |
|
219 if 'revcount' in req.form: |
|
220 revcount = int(req.form.get('revcount', [revcount])[0]) |
|
221 tmpl.defaults['sessionvars']['revcount'] = revcount |
|
222 |
|
223 lessvars = copy.copy(tmpl.defaults['sessionvars']) |
|
224 lessvars['revcount'] = revcount / 2 |
|
225 morevars = copy.copy(tmpl.defaults['sessionvars']) |
|
226 morevars['revcount'] = revcount * 2 |
|
227 |
|
228 count = len(web.repo) |
|
229 pos = ctx.rev() |
|
230 start = max(0, pos - revcount + 1) |
|
231 end = min(count, start + revcount) |
|
232 pos = end - 1 |
|
233 parity = paritygen(web.stripecount, offset=start - end) |
|
234 |
|
235 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx) |
|
236 |
|
237 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav, |
|
238 node=hex(ctx.node()), rev=pos, changesets=count, |
|
239 entries=lambda **x: changelist(limit=0,**x), |
|
240 latestentry=lambda **x: changelist(limit=1,**x), |
|
241 archives=web.archivelist("tip"), revcount=revcount, |
|
242 morevars=morevars, lessvars=lessvars) |
|
243 |
|
244 def shortlog(web, req, tmpl): |
|
245 return changelog(web, req, tmpl, shortlog = True) |
|
246 |
|
247 def changeset(web, req, tmpl): |
|
248 ctx = webutil.changectx(web.repo, req) |
|
249 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node()) |
|
250 showbranch = webutil.nodebranchnodefault(ctx) |
|
251 |
|
252 files = [] |
|
253 parity = paritygen(web.stripecount) |
|
254 for f in ctx.files(): |
|
255 template = f in ctx and 'filenodelink' or 'filenolink' |
|
256 files.append(tmpl(template, |
|
257 node=ctx.hex(), file=f, |
|
258 parity=parity.next())) |
|
259 |
|
260 parity = paritygen(web.stripecount) |
|
261 style = web.config('web', 'style', 'paper') |
|
262 if 'style' in req.form: |
|
263 style = req.form['style'][0] |
|
264 |
|
265 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style) |
|
266 return tmpl('changeset', |
|
267 diff=diffs, |
|
268 rev=ctx.rev(), |
|
269 node=ctx.hex(), |
|
270 parent=webutil.parents(ctx), |
|
271 child=webutil.children(ctx), |
|
272 changesettag=showtags, |
|
273 changesetbranch=showbranch, |
|
274 author=ctx.user(), |
|
275 desc=ctx.description(), |
|
276 date=ctx.date(), |
|
277 files=files, |
|
278 archives=web.archivelist(ctx.hex()), |
|
279 tags=webutil.nodetagsdict(web.repo, ctx.node()), |
|
280 branch=webutil.nodebranchnodefault(ctx), |
|
281 inbranch=webutil.nodeinbranch(web.repo, ctx), |
|
282 branches=webutil.nodebranchdict(web.repo, ctx)) |
|
283 |
|
284 rev = changeset |
|
285 |
|
286 def manifest(web, req, tmpl): |
|
287 ctx = webutil.changectx(web.repo, req) |
|
288 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0]) |
|
289 mf = ctx.manifest() |
|
290 node = ctx.node() |
|
291 |
|
292 files = {} |
|
293 dirs = {} |
|
294 parity = paritygen(web.stripecount) |
|
295 |
|
296 if path and path[-1] != "/": |
|
297 path += "/" |
|
298 l = len(path) |
|
299 abspath = "/" + path |
|
300 |
|
301 for f, n in mf.iteritems(): |
|
302 if f[:l] != path: |
|
303 continue |
|
304 remain = f[l:] |
|
305 elements = remain.split('/') |
|
306 if len(elements) == 1: |
|
307 files[remain] = f |
|
308 else: |
|
309 h = dirs # need to retain ref to dirs (root) |
|
310 for elem in elements[0:-1]: |
|
311 if elem not in h: |
|
312 h[elem] = {} |
|
313 h = h[elem] |
|
314 if len(h) > 1: |
|
315 break |
|
316 h[None] = None # denotes files present |
|
317 |
|
318 if mf and not files and not dirs: |
|
319 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) |
|
320 |
|
321 def filelist(**map): |
|
322 for f in sorted(files): |
|
323 full = files[f] |
|
324 |
|
325 fctx = ctx.filectx(full) |
|
326 yield {"file": full, |
|
327 "parity": parity.next(), |
|
328 "basename": f, |
|
329 "date": fctx.date(), |
|
330 "size": fctx.size(), |
|
331 "permissions": mf.flags(full)} |
|
332 |
|
333 def dirlist(**map): |
|
334 for d in sorted(dirs): |
|
335 |
|
336 emptydirs = [] |
|
337 h = dirs[d] |
|
338 while isinstance(h, dict) and len(h) == 1: |
|
339 k, v = h.items()[0] |
|
340 if v: |
|
341 emptydirs.append(k) |
|
342 h = v |
|
343 |
|
344 path = "%s%s" % (abspath, d) |
|
345 yield {"parity": parity.next(), |
|
346 "path": path, |
|
347 "emptydirs": "/".join(emptydirs), |
|
348 "basename": d} |
|
349 |
|
350 return tmpl("manifest", |
|
351 rev=ctx.rev(), |
|
352 node=hex(node), |
|
353 path=abspath, |
|
354 up=webutil.up(abspath), |
|
355 upparity=parity.next(), |
|
356 fentries=filelist, |
|
357 dentries=dirlist, |
|
358 archives=web.archivelist(hex(node)), |
|
359 tags=webutil.nodetagsdict(web.repo, node), |
|
360 inbranch=webutil.nodeinbranch(web.repo, ctx), |
|
361 branches=webutil.nodebranchdict(web.repo, ctx)) |
|
362 |
|
363 def tags(web, req, tmpl): |
|
364 i = web.repo.tagslist() |
|
365 i.reverse() |
|
366 parity = paritygen(web.stripecount) |
|
367 |
|
368 def entries(notip=False, limit=0, **map): |
|
369 count = 0 |
|
370 for k, n in i: |
|
371 if notip and k == "tip": |
|
372 continue |
|
373 if limit > 0 and count >= limit: |
|
374 continue |
|
375 count = count + 1 |
|
376 yield {"parity": parity.next(), |
|
377 "tag": k, |
|
378 "date": web.repo[n].date(), |
|
379 "node": hex(n)} |
|
380 |
|
381 return tmpl("tags", |
|
382 node=hex(web.repo.changelog.tip()), |
|
383 entries=lambda **x: entries(False, 0, **x), |
|
384 entriesnotip=lambda **x: entries(True, 0, **x), |
|
385 latestentry=lambda **x: entries(True, 1, **x)) |
|
386 |
|
387 def branches(web, req, tmpl): |
|
388 tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems()) |
|
389 heads = web.repo.heads() |
|
390 parity = paritygen(web.stripecount) |
|
391 sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev()) |
|
392 |
|
393 def entries(limit, **map): |
|
394 count = 0 |
|
395 for ctx in sorted(tips, key=sortkey, reverse=True): |
|
396 if limit > 0 and count >= limit: |
|
397 return |
|
398 count += 1 |
|
399 if ctx.node() not in heads: |
|
400 status = 'inactive' |
|
401 elif not web.repo.branchheads(ctx.branch()): |
|
402 status = 'closed' |
|
403 else: |
|
404 status = 'open' |
|
405 yield {'parity': parity.next(), |
|
406 'branch': ctx.branch(), |
|
407 'status': status, |
|
408 'node': ctx.hex(), |
|
409 'date': ctx.date()} |
|
410 |
|
411 return tmpl('branches', node=hex(web.repo.changelog.tip()), |
|
412 entries=lambda **x: entries(0, **x), |
|
413 latestentry=lambda **x: entries(1, **x)) |
|
414 |
|
415 def summary(web, req, tmpl): |
|
416 i = web.repo.tagslist() |
|
417 i.reverse() |
|
418 |
|
419 def tagentries(**map): |
|
420 parity = paritygen(web.stripecount) |
|
421 count = 0 |
|
422 for k, n in i: |
|
423 if k == "tip": # skip tip |
|
424 continue |
|
425 |
|
426 count += 1 |
|
427 if count > 10: # limit to 10 tags |
|
428 break |
|
429 |
|
430 yield tmpl("tagentry", |
|
431 parity=parity.next(), |
|
432 tag=k, |
|
433 node=hex(n), |
|
434 date=web.repo[n].date()) |
|
435 |
|
436 def branches(**map): |
|
437 parity = paritygen(web.stripecount) |
|
438 |
|
439 b = web.repo.branchtags() |
|
440 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()] |
|
441 for r, n, t in sorted(l): |
|
442 yield {'parity': parity.next(), |
|
443 'branch': t, |
|
444 'node': hex(n), |
|
445 'date': web.repo[n].date()} |
|
446 |
|
447 def changelist(**map): |
|
448 parity = paritygen(web.stripecount, offset=start - end) |
|
449 l = [] # build a list in forward order for efficiency |
|
450 for i in xrange(start, end): |
|
451 ctx = web.repo[i] |
|
452 n = ctx.node() |
|
453 hn = hex(n) |
|
454 |
|
455 l.insert(0, tmpl( |
|
456 'shortlogentry', |
|
457 parity=parity.next(), |
|
458 author=ctx.user(), |
|
459 desc=ctx.description(), |
|
460 date=ctx.date(), |
|
461 rev=i, |
|
462 node=hn, |
|
463 tags=webutil.nodetagsdict(web.repo, n), |
|
464 inbranch=webutil.nodeinbranch(web.repo, ctx), |
|
465 branches=webutil.nodebranchdict(web.repo, ctx))) |
|
466 |
|
467 yield l |
|
468 |
|
469 tip = web.repo['tip'] |
|
470 count = len(web.repo) |
|
471 start = max(0, count - web.maxchanges) |
|
472 end = min(count, start + web.maxchanges) |
|
473 |
|
474 return tmpl("summary", |
|
475 desc=web.config("web", "description", "unknown"), |
|
476 owner=get_contact(web.config) or "unknown", |
|
477 lastchange=tip.date(), |
|
478 tags=tagentries, |
|
479 branches=branches, |
|
480 shortlog=changelist, |
|
481 node=tip.hex(), |
|
482 archives=web.archivelist("tip")) |
|
483 |
|
484 def filediff(web, req, tmpl): |
|
485 fctx, ctx = None, None |
|
486 try: |
|
487 fctx = webutil.filectx(web.repo, req) |
|
488 except LookupError: |
|
489 ctx = webutil.changectx(web.repo, req) |
|
490 path = webutil.cleanpath(web.repo, req.form['file'][0]) |
|
491 if path not in ctx.files(): |
|
492 raise |
|
493 |
|
494 if fctx is not None: |
|
495 n = fctx.node() |
|
496 path = fctx.path() |
|
497 else: |
|
498 n = ctx.node() |
|
499 # path already defined in except clause |
|
500 |
|
501 parity = paritygen(web.stripecount) |
|
502 style = web.config('web', 'style', 'paper') |
|
503 if 'style' in req.form: |
|
504 style = req.form['style'][0] |
|
505 |
|
506 diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity, style) |
|
507 rename = fctx and webutil.renamelink(fctx) or [] |
|
508 ctx = fctx and fctx or ctx |
|
509 return tmpl("filediff", |
|
510 file=path, |
|
511 node=hex(n), |
|
512 rev=ctx.rev(), |
|
513 date=ctx.date(), |
|
514 desc=ctx.description(), |
|
515 author=ctx.user(), |
|
516 rename=rename, |
|
517 branch=webutil.nodebranchnodefault(ctx), |
|
518 parent=webutil.parents(ctx), |
|
519 child=webutil.children(ctx), |
|
520 diff=diffs) |
|
521 |
|
522 diff = filediff |
|
523 |
|
524 def annotate(web, req, tmpl): |
|
525 fctx = webutil.filectx(web.repo, req) |
|
526 f = fctx.path() |
|
527 parity = paritygen(web.stripecount) |
|
528 |
|
529 def annotate(**map): |
|
530 last = None |
|
531 if binary(fctx.data()): |
|
532 mt = (mimetypes.guess_type(fctx.path())[0] |
|
533 or 'application/octet-stream') |
|
534 lines = enumerate([((fctx.filectx(fctx.filerev()), 1), |
|
535 '(binary:%s)' % mt)]) |
|
536 else: |
|
537 lines = enumerate(fctx.annotate(follow=True, linenumber=True)) |
|
538 for lineno, ((f, targetline), l) in lines: |
|
539 fnode = f.filenode() |
|
540 |
|
541 if last != fnode: |
|
542 last = fnode |
|
543 |
|
544 yield {"parity": parity.next(), |
|
545 "node": hex(f.node()), |
|
546 "rev": f.rev(), |
|
547 "author": f.user(), |
|
548 "desc": f.description(), |
|
549 "file": f.path(), |
|
550 "targetline": targetline, |
|
551 "line": l, |
|
552 "lineid": "l%d" % (lineno + 1), |
|
553 "linenumber": "% 6d" % (lineno + 1)} |
|
554 |
|
555 return tmpl("fileannotate", |
|
556 file=f, |
|
557 annotate=annotate, |
|
558 path=webutil.up(f), |
|
559 rev=fctx.rev(), |
|
560 node=hex(fctx.node()), |
|
561 author=fctx.user(), |
|
562 date=fctx.date(), |
|
563 desc=fctx.description(), |
|
564 rename=webutil.renamelink(fctx), |
|
565 branch=webutil.nodebranchnodefault(fctx), |
|
566 parent=webutil.parents(fctx), |
|
567 child=webutil.children(fctx), |
|
568 permissions=fctx.manifest().flags(f)) |
|
569 |
|
570 def filelog(web, req, tmpl): |
|
571 |
|
572 try: |
|
573 fctx = webutil.filectx(web.repo, req) |
|
574 f = fctx.path() |
|
575 fl = fctx.filelog() |
|
576 except error.LookupError: |
|
577 f = webutil.cleanpath(web.repo, req.form['file'][0]) |
|
578 fl = web.repo.file(f) |
|
579 numrevs = len(fl) |
|
580 if not numrevs: # file doesn't exist at all |
|
581 raise |
|
582 rev = webutil.changectx(web.repo, req).rev() |
|
583 first = fl.linkrev(0) |
|
584 if rev < first: # current rev is from before file existed |
|
585 raise |
|
586 frev = numrevs - 1 |
|
587 while fl.linkrev(frev) > rev: |
|
588 frev -= 1 |
|
589 fctx = web.repo.filectx(f, fl.linkrev(frev)) |
|
590 |
|
591 revcount = web.maxshortchanges |
|
592 if 'revcount' in req.form: |
|
593 revcount = int(req.form.get('revcount', [revcount])[0]) |
|
594 tmpl.defaults['sessionvars']['revcount'] = revcount |
|
595 |
|
596 lessvars = copy.copy(tmpl.defaults['sessionvars']) |
|
597 lessvars['revcount'] = revcount / 2 |
|
598 morevars = copy.copy(tmpl.defaults['sessionvars']) |
|
599 morevars['revcount'] = revcount * 2 |
|
600 |
|
601 count = fctx.filerev() + 1 |
|
602 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page |
|
603 end = min(count, start + revcount) # last rev on this page |
|
604 parity = paritygen(web.stripecount, offset=start - end) |
|
605 |
|
606 def entries(limit=0, **map): |
|
607 l = [] |
|
608 |
|
609 repo = web.repo |
|
610 for i in xrange(start, end): |
|
611 iterfctx = fctx.filectx(i) |
|
612 |
|
613 l.insert(0, {"parity": parity.next(), |
|
614 "filerev": i, |
|
615 "file": f, |
|
616 "node": hex(iterfctx.node()), |
|
617 "author": iterfctx.user(), |
|
618 "date": iterfctx.date(), |
|
619 "rename": webutil.renamelink(iterfctx), |
|
620 "parent": webutil.parents(iterfctx), |
|
621 "child": webutil.children(iterfctx), |
|
622 "desc": iterfctx.description(), |
|
623 "tags": webutil.nodetagsdict(repo, iterfctx.node()), |
|
624 "branch": webutil.nodebranchnodefault(iterfctx), |
|
625 "inbranch": webutil.nodeinbranch(repo, iterfctx), |
|
626 "branches": webutil.nodebranchdict(repo, iterfctx)}) |
|
627 |
|
628 if limit > 0: |
|
629 l = l[:limit] |
|
630 |
|
631 for e in l: |
|
632 yield e |
|
633 |
|
634 nodefunc = lambda x: fctx.filectx(fileid=x) |
|
635 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc) |
|
636 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav, |
|
637 entries=lambda **x: entries(limit=0, **x), |
|
638 latestentry=lambda **x: entries(limit=1, **x), |
|
639 revcount=revcount, morevars=morevars, lessvars=lessvars) |
|
640 |
|
641 def archive(web, req, tmpl): |
|
642 type_ = req.form.get('type', [None])[0] |
|
643 allowed = web.configlist("web", "allow_archive") |
|
644 key = req.form['node'][0] |
|
645 |
|
646 if type_ not in web.archives: |
|
647 msg = 'Unsupported archive type: %s' % type_ |
|
648 raise ErrorResponse(HTTP_NOT_FOUND, msg) |
|
649 |
|
650 if not ((type_ in allowed or |
|
651 web.configbool("web", "allow" + type_, False))): |
|
652 msg = 'Archive type not allowed: %s' % type_ |
|
653 raise ErrorResponse(HTTP_FORBIDDEN, msg) |
|
654 |
|
655 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame)) |
|
656 cnode = web.repo.lookup(key) |
|
657 arch_version = key |
|
658 if cnode == key or key == 'tip': |
|
659 arch_version = short(cnode) |
|
660 name = "%s-%s" % (reponame, arch_version) |
|
661 mimetype, artype, extension, encoding = web.archive_specs[type_] |
|
662 headers = [ |
|
663 ('Content-Type', mimetype), |
|
664 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension)) |
|
665 ] |
|
666 if encoding: |
|
667 headers.append(('Content-Encoding', encoding)) |
|
668 req.header(headers) |
|
669 req.respond(HTTP_OK) |
|
670 archival.archive(web.repo, req, cnode, artype, prefix=name) |
|
671 return [] |
|
672 |
|
673 |
|
674 def static(web, req, tmpl): |
|
675 fname = req.form['file'][0] |
|
676 # a repo owner may set web.static in .hg/hgrc to get any file |
|
677 # readable by the user running the CGI script |
|
678 static = web.config("web", "static", None, untrusted=False) |
|
679 if not static: |
|
680 tp = web.templatepath or templater.templatepath() |
|
681 if isinstance(tp, str): |
|
682 tp = [tp] |
|
683 static = [os.path.join(p, 'static') for p in tp] |
|
684 return [staticfile(static, fname, req)] |
|
685 |
|
686 def graph(web, req, tmpl): |
|
687 |
|
688 rev = webutil.changectx(web.repo, req).rev() |
|
689 bg_height = 39 |
|
690 revcount = web.maxshortchanges |
|
691 if 'revcount' in req.form: |
|
692 revcount = int(req.form.get('revcount', [revcount])[0]) |
|
693 tmpl.defaults['sessionvars']['revcount'] = revcount |
|
694 |
|
695 lessvars = copy.copy(tmpl.defaults['sessionvars']) |
|
696 lessvars['revcount'] = revcount / 2 |
|
697 morevars = copy.copy(tmpl.defaults['sessionvars']) |
|
698 morevars['revcount'] = revcount * 2 |
|
699 |
|
700 max_rev = len(web.repo) - 1 |
|
701 revcount = min(max_rev, revcount) |
|
702 revnode = web.repo.changelog.node(rev) |
|
703 revnode_hex = hex(revnode) |
|
704 uprev = min(max_rev, rev + revcount) |
|
705 downrev = max(0, rev - revcount) |
|
706 count = len(web.repo) |
|
707 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx) |
|
708 |
|
709 dag = graphmod.revisions(web.repo, rev, downrev) |
|
710 tree = list(graphmod.colored(dag)) |
|
711 canvasheight = (len(tree) + 1) * bg_height - 27 |
|
712 data = [] |
|
713 for (id, type, ctx, vtx, edges) in tree: |
|
714 if type != graphmod.CHANGESET: |
|
715 continue |
|
716 node = short(ctx.node()) |
|
717 age = templatefilters.age(ctx.date()) |
|
718 desc = templatefilters.firstline(ctx.description()) |
|
719 desc = cgi.escape(templatefilters.nonempty(desc)) |
|
720 user = cgi.escape(templatefilters.person(ctx.user())) |
|
721 branch = ctx.branch() |
|
722 branch = branch, web.repo.branchtags().get(branch) == ctx.node() |
|
723 data.append((node, vtx, edges, desc, user, age, branch, ctx.tags())) |
|
724 |
|
725 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev, |
|
726 lessvars=lessvars, morevars=morevars, downrev=downrev, |
|
727 canvasheight=canvasheight, jsdata=data, bg_height=bg_height, |
|
728 node=revnode_hex, changenav=changenav) |
|
729 |
|
730 def _getdoc(e): |
|
731 doc = e[0].__doc__ |
|
732 if doc: |
|
733 doc = doc.split('\n')[0] |
|
734 else: |
|
735 doc = _('(no help text available)') |
|
736 return doc |
|
737 |
|
738 def help(web, req, tmpl): |
|
739 from mercurial import commands # avoid cycle |
|
740 |
|
741 topicname = req.form.get('node', [None])[0] |
|
742 if not topicname: |
|
743 topic = [] |
|
744 |
|
745 def topics(**map): |
|
746 for entries, summary, _ in helpmod.helptable: |
|
747 entries = sorted(entries, key=len) |
|
748 yield {'topic': entries[-1], 'summary': summary} |
|
749 |
|
750 early, other = [], [] |
|
751 primary = lambda s: s.split('|')[0] |
|
752 for c, e in commands.table.iteritems(): |
|
753 doc = _getdoc(e) |
|
754 if 'DEPRECATED' in doc or c.startswith('debug'): |
|
755 continue |
|
756 cmd = primary(c) |
|
757 if cmd.startswith('^'): |
|
758 early.append((cmd[1:], doc)) |
|
759 else: |
|
760 other.append((cmd, doc)) |
|
761 |
|
762 early.sort() |
|
763 other.sort() |
|
764 |
|
765 def earlycommands(**map): |
|
766 for c, doc in early: |
|
767 yield {'topic': c, 'summary': doc} |
|
768 |
|
769 def othercommands(**map): |
|
770 for c, doc in other: |
|
771 yield {'topic': c, 'summary': doc} |
|
772 |
|
773 return tmpl('helptopics', topics=topics, earlycommands=earlycommands, |
|
774 othercommands=othercommands, title='Index') |
|
775 |
|
776 u = webutil.wsgiui() |
|
777 u.pushbuffer() |
|
778 try: |
|
779 commands.help_(u, topicname) |
|
780 except error.UnknownCommand: |
|
781 raise ErrorResponse(HTTP_NOT_FOUND) |
|
782 doc = u.popbuffer() |
|
783 return tmpl('help', topic=topicname, doc=doc) |