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