##// END OF EJS Templates
hgweb: limit branches shown on summary page to 10...
av6 -
r26131:0a9009d5 default
parent child Browse files
Show More
@@ -1,1315 +1,1315 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 import util
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 scmutil
17 17 from mercurial.i18n import _
18 18 from mercurial.error import ParseError, RepoLookupError, Abort
19 19 from mercurial import revset
20 20
21 21 __all__ = []
22 22 commands = {}
23 23
24 24 class webcommand(object):
25 25 """Decorator used to register a web command handler.
26 26
27 27 The decorator takes as its positional arguments the name/path the
28 28 command should be accessible under.
29 29
30 30 Usage:
31 31
32 32 @webcommand('mycommand')
33 33 def mycommand(web, req, tmpl):
34 34 pass
35 35 """
36 36
37 37 def __init__(self, name):
38 38 self.name = name
39 39
40 40 def __call__(self, func):
41 41 __all__.append(self.name)
42 42 commands[self.name] = func
43 43 return func
44 44
45 45 @webcommand('log')
46 46 def log(web, req, tmpl):
47 47 """
48 48 /log[/{revision}[/{path}]]
49 49 --------------------------
50 50
51 51 Show repository or file history.
52 52
53 53 For URLs of the form ``/log/{revision}``, a list of changesets starting at
54 54 the specified changeset identifier is shown. If ``{revision}`` is not
55 55 defined, the default is ``tip``. This form is equivalent to the
56 56 ``changelog`` handler.
57 57
58 58 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
59 59 file will be shown. This form is equivalent to the ``filelog`` handler.
60 60 """
61 61
62 62 if 'file' in req.form and req.form['file'][0]:
63 63 return filelog(web, req, tmpl)
64 64 else:
65 65 return changelog(web, req, tmpl)
66 66
67 67 @webcommand('rawfile')
68 68 def rawfile(web, req, tmpl):
69 69 guessmime = web.configbool('web', 'guessmime', False)
70 70
71 71 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
72 72 if not path:
73 73 content = manifest(web, req, tmpl)
74 74 req.respond(HTTP_OK, web.ctype)
75 75 return content
76 76
77 77 try:
78 78 fctx = webutil.filectx(web.repo, req)
79 79 except error.LookupError as inst:
80 80 try:
81 81 content = manifest(web, req, tmpl)
82 82 req.respond(HTTP_OK, web.ctype)
83 83 return content
84 84 except ErrorResponse:
85 85 raise inst
86 86
87 87 path = fctx.path()
88 88 text = fctx.data()
89 89 mt = 'application/binary'
90 90 if guessmime:
91 91 mt = mimetypes.guess_type(path)[0]
92 92 if mt is None:
93 93 if util.binary(text):
94 94 mt = 'application/binary'
95 95 else:
96 96 mt = 'text/plain'
97 97 if mt.startswith('text/'):
98 98 mt += '; charset="%s"' % encoding.encoding
99 99
100 100 req.respond(HTTP_OK, mt, path, body=text)
101 101 return []
102 102
103 103 def _filerevision(web, req, tmpl, fctx):
104 104 f = fctx.path()
105 105 text = fctx.data()
106 106 parity = paritygen(web.stripecount)
107 107
108 108 if util.binary(text):
109 109 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
110 110 text = '(binary:%s)' % mt
111 111
112 112 def lines():
113 113 for lineno, t in enumerate(text.splitlines(True)):
114 114 yield {"line": t,
115 115 "lineid": "l%d" % (lineno + 1),
116 116 "linenumber": "% 6d" % (lineno + 1),
117 117 "parity": parity.next()}
118 118
119 119 return tmpl("filerevision",
120 120 file=f,
121 121 path=webutil.up(f),
122 122 text=lines(),
123 123 rev=fctx.rev(),
124 124 symrev=webutil.symrevorshortnode(req, fctx),
125 125 node=fctx.hex(),
126 126 author=fctx.user(),
127 127 date=fctx.date(),
128 128 desc=fctx.description(),
129 129 extra=fctx.extra(),
130 130 branch=webutil.nodebranchnodefault(fctx),
131 131 parent=webutil.parents(fctx),
132 132 child=webutil.children(fctx),
133 133 rename=webutil.renamelink(fctx),
134 134 tags=webutil.nodetagsdict(web.repo, fctx.node()),
135 135 bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()),
136 136 permissions=fctx.manifest().flags(f))
137 137
138 138 @webcommand('file')
139 139 def file(web, req, tmpl):
140 140 """
141 141 /file/{revision}[/{path}]
142 142 -------------------------
143 143
144 144 Show information about a directory or file in the repository.
145 145
146 146 Info about the ``path`` given as a URL parameter will be rendered.
147 147
148 148 If ``path`` is a directory, information about the entries in that
149 149 directory will be rendered. This form is equivalent to the ``manifest``
150 150 handler.
151 151
152 152 If ``path`` is a file, information about that file will be shown via
153 153 the ``filerevision`` template.
154 154
155 155 If ``path`` is not defined, information about the root directory will
156 156 be rendered.
157 157 """
158 158 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
159 159 if not path:
160 160 return manifest(web, req, tmpl)
161 161 try:
162 162 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
163 163 except error.LookupError as inst:
164 164 try:
165 165 return manifest(web, req, tmpl)
166 166 except ErrorResponse:
167 167 raise inst
168 168
169 169 def _search(web, req, tmpl):
170 170 MODE_REVISION = 'rev'
171 171 MODE_KEYWORD = 'keyword'
172 172 MODE_REVSET = 'revset'
173 173
174 174 def revsearch(ctx):
175 175 yield ctx
176 176
177 177 def keywordsearch(query):
178 178 lower = encoding.lower
179 179 qw = lower(query).split()
180 180
181 181 def revgen():
182 182 cl = web.repo.changelog
183 183 for i in xrange(len(web.repo) - 1, 0, -100):
184 184 l = []
185 185 for j in cl.revs(max(0, i - 99), i):
186 186 ctx = web.repo[j]
187 187 l.append(ctx)
188 188 l.reverse()
189 189 for e in l:
190 190 yield e
191 191
192 192 for ctx in revgen():
193 193 miss = 0
194 194 for q in qw:
195 195 if not (q in lower(ctx.user()) or
196 196 q in lower(ctx.description()) or
197 197 q in lower(" ".join(ctx.files()))):
198 198 miss = 1
199 199 break
200 200 if miss:
201 201 continue
202 202
203 203 yield ctx
204 204
205 205 def revsetsearch(revs):
206 206 for r in revs:
207 207 yield web.repo[r]
208 208
209 209 searchfuncs = {
210 210 MODE_REVISION: (revsearch, 'exact revision search'),
211 211 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
212 212 MODE_REVSET: (revsetsearch, 'revset expression search'),
213 213 }
214 214
215 215 def getsearchmode(query):
216 216 try:
217 217 ctx = web.repo[query]
218 218 except (error.RepoError, error.LookupError):
219 219 # query is not an exact revision pointer, need to
220 220 # decide if it's a revset expression or keywords
221 221 pass
222 222 else:
223 223 return MODE_REVISION, ctx
224 224
225 225 revdef = 'reverse(%s)' % query
226 226 try:
227 227 tree = revset.parse(revdef)
228 228 except ParseError:
229 229 # can't parse to a revset tree
230 230 return MODE_KEYWORD, query
231 231
232 232 if revset.depth(tree) <= 2:
233 233 # no revset syntax used
234 234 return MODE_KEYWORD, query
235 235
236 236 if any((token, (value or '')[:3]) == ('string', 're:')
237 237 for token, value, pos in revset.tokenize(revdef)):
238 238 return MODE_KEYWORD, query
239 239
240 240 funcsused = revset.funcsused(tree)
241 241 if not funcsused.issubset(revset.safesymbols):
242 242 return MODE_KEYWORD, query
243 243
244 244 mfunc = revset.match(web.repo.ui, revdef)
245 245 try:
246 246 revs = mfunc(web.repo)
247 247 return MODE_REVSET, revs
248 248 # ParseError: wrongly placed tokens, wrongs arguments, etc
249 249 # RepoLookupError: no such revision, e.g. in 'revision:'
250 250 # Abort: bookmark/tag not exists
251 251 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
252 252 except (ParseError, RepoLookupError, Abort, LookupError):
253 253 return MODE_KEYWORD, query
254 254
255 255 def changelist(**map):
256 256 count = 0
257 257
258 258 for ctx in searchfunc[0](funcarg):
259 259 count += 1
260 260 n = ctx.node()
261 261 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
262 262 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
263 263
264 264 yield tmpl('searchentry',
265 265 parity=parity.next(),
266 266 author=ctx.user(),
267 267 parent=webutil.parents(ctx),
268 268 child=webutil.children(ctx),
269 269 changelogtag=showtags,
270 270 desc=ctx.description(),
271 271 extra=ctx.extra(),
272 272 date=ctx.date(),
273 273 files=files,
274 274 rev=ctx.rev(),
275 275 node=hex(n),
276 276 tags=webutil.nodetagsdict(web.repo, n),
277 277 bookmarks=webutil.nodebookmarksdict(web.repo, n),
278 278 inbranch=webutil.nodeinbranch(web.repo, ctx),
279 279 branches=webutil.nodebranchdict(web.repo, ctx))
280 280
281 281 if count >= revcount:
282 282 break
283 283
284 284 query = req.form['rev'][0]
285 285 revcount = web.maxchanges
286 286 if 'revcount' in req.form:
287 287 try:
288 288 revcount = int(req.form.get('revcount', [revcount])[0])
289 289 revcount = max(revcount, 1)
290 290 tmpl.defaults['sessionvars']['revcount'] = revcount
291 291 except ValueError:
292 292 pass
293 293
294 294 lessvars = copy.copy(tmpl.defaults['sessionvars'])
295 295 lessvars['revcount'] = max(revcount / 2, 1)
296 296 lessvars['rev'] = query
297 297 morevars = copy.copy(tmpl.defaults['sessionvars'])
298 298 morevars['revcount'] = revcount * 2
299 299 morevars['rev'] = query
300 300
301 301 mode, funcarg = getsearchmode(query)
302 302
303 303 if 'forcekw' in req.form:
304 304 showforcekw = ''
305 305 showunforcekw = searchfuncs[mode][1]
306 306 mode = MODE_KEYWORD
307 307 funcarg = query
308 308 else:
309 309 if mode != MODE_KEYWORD:
310 310 showforcekw = searchfuncs[MODE_KEYWORD][1]
311 311 else:
312 312 showforcekw = ''
313 313 showunforcekw = ''
314 314
315 315 searchfunc = searchfuncs[mode]
316 316
317 317 tip = web.repo['tip']
318 318 parity = paritygen(web.stripecount)
319 319
320 320 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
321 321 entries=changelist, archives=web.archivelist("tip"),
322 322 morevars=morevars, lessvars=lessvars,
323 323 modedesc=searchfunc[1],
324 324 showforcekw=showforcekw, showunforcekw=showunforcekw)
325 325
326 326 @webcommand('changelog')
327 327 def changelog(web, req, tmpl, shortlog=False):
328 328 """
329 329 /changelog[/{revision}]
330 330 -----------------------
331 331
332 332 Show information about multiple changesets.
333 333
334 334 If the optional ``revision`` URL argument is absent, information about
335 335 all changesets starting at ``tip`` will be rendered. If the ``revision``
336 336 argument is present, changesets will be shown starting from the specified
337 337 revision.
338 338
339 339 If ``revision`` is absent, the ``rev`` query string argument may be
340 340 defined. This will perform a search for changesets.
341 341
342 342 The argument for ``rev`` can be a single revision, a revision set,
343 343 or a literal keyword to search for in changeset data (equivalent to
344 344 :hg:`log -k`).
345 345
346 346 The ``revcount`` query string argument defines the maximum numbers of
347 347 changesets to render.
348 348
349 349 For non-searches, the ``changelog`` template will be rendered.
350 350 """
351 351
352 352 query = ''
353 353 if 'node' in req.form:
354 354 ctx = webutil.changectx(web.repo, req)
355 355 symrev = webutil.symrevorshortnode(req, ctx)
356 356 elif 'rev' in req.form:
357 357 return _search(web, req, tmpl)
358 358 else:
359 359 ctx = web.repo['tip']
360 360 symrev = 'tip'
361 361
362 362 def changelist():
363 363 revs = []
364 364 if pos != -1:
365 365 revs = web.repo.changelog.revs(pos, 0)
366 366 curcount = 0
367 367 for rev in revs:
368 368 curcount += 1
369 369 if curcount > revcount + 1:
370 370 break
371 371
372 372 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
373 373 entry['parity'] = parity.next()
374 374 yield entry
375 375
376 376 if shortlog:
377 377 revcount = web.maxshortchanges
378 378 else:
379 379 revcount = web.maxchanges
380 380
381 381 if 'revcount' in req.form:
382 382 try:
383 383 revcount = int(req.form.get('revcount', [revcount])[0])
384 384 revcount = max(revcount, 1)
385 385 tmpl.defaults['sessionvars']['revcount'] = revcount
386 386 except ValueError:
387 387 pass
388 388
389 389 lessvars = copy.copy(tmpl.defaults['sessionvars'])
390 390 lessvars['revcount'] = max(revcount / 2, 1)
391 391 morevars = copy.copy(tmpl.defaults['sessionvars'])
392 392 morevars['revcount'] = revcount * 2
393 393
394 394 count = len(web.repo)
395 395 pos = ctx.rev()
396 396 parity = paritygen(web.stripecount)
397 397
398 398 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
399 399
400 400 entries = list(changelist())
401 401 latestentry = entries[:1]
402 402 if len(entries) > revcount:
403 403 nextentry = entries[-1:]
404 404 entries = entries[:-1]
405 405 else:
406 406 nextentry = []
407 407
408 408 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
409 409 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
410 410 entries=entries,
411 411 latestentry=latestentry, nextentry=nextentry,
412 412 archives=web.archivelist("tip"), revcount=revcount,
413 413 morevars=morevars, lessvars=lessvars, query=query)
414 414
415 415 @webcommand('shortlog')
416 416 def shortlog(web, req, tmpl):
417 417 """
418 418 /shortlog
419 419 ---------
420 420
421 421 Show basic information about a set of changesets.
422 422
423 423 This accepts the same parameters as the ``changelog`` handler. The only
424 424 difference is the ``shortlog`` template will be rendered instead of the
425 425 ``changelog`` template.
426 426 """
427 427 return changelog(web, req, tmpl, shortlog=True)
428 428
429 429 @webcommand('changeset')
430 430 def changeset(web, req, tmpl):
431 431 """
432 432 /changeset[/{revision}]
433 433 -----------------------
434 434
435 435 Show information about a single changeset.
436 436
437 437 A URL path argument is the changeset identifier to show. See ``hg help
438 438 revisions`` for possible values. If not defined, the ``tip`` changeset
439 439 will be shown.
440 440
441 441 The ``changeset`` template is rendered. Contents of the ``changesettag``,
442 442 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
443 443 templates related to diffs may all be used to produce the output.
444 444 """
445 445 ctx = webutil.changectx(web.repo, req)
446 446
447 447 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
448 448
449 449 rev = webcommand('rev')(changeset)
450 450
451 451 def decodepath(path):
452 452 """Hook for mapping a path in the repository to a path in the
453 453 working copy.
454 454
455 455 Extensions (e.g., largefiles) can override this to remap files in
456 456 the virtual file system presented by the manifest command below."""
457 457 return path
458 458
459 459 @webcommand('manifest')
460 460 def manifest(web, req, tmpl):
461 461 """
462 462 /manifest[/{revision}[/{path}]]
463 463 -------------------------------
464 464
465 465 Show information about a directory.
466 466
467 467 If the URL path arguments are omitted, information about the root
468 468 directory for the ``tip`` changeset will be shown.
469 469
470 470 Because this handler can only show information for directories, it
471 471 is recommended to use the ``file`` handler instead, as it can handle both
472 472 directories and files.
473 473
474 474 The ``manifest`` template will be rendered for this handler.
475 475 """
476 476 if 'node' in req.form:
477 477 ctx = webutil.changectx(web.repo, req)
478 478 symrev = webutil.symrevorshortnode(req, ctx)
479 479 else:
480 480 ctx = web.repo['tip']
481 481 symrev = 'tip'
482 482 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
483 483 mf = ctx.manifest()
484 484 node = ctx.node()
485 485
486 486 files = {}
487 487 dirs = {}
488 488 parity = paritygen(web.stripecount)
489 489
490 490 if path and path[-1] != "/":
491 491 path += "/"
492 492 l = len(path)
493 493 abspath = "/" + path
494 494
495 495 for full, n in mf.iteritems():
496 496 # the virtual path (working copy path) used for the full
497 497 # (repository) path
498 498 f = decodepath(full)
499 499
500 500 if f[:l] != path:
501 501 continue
502 502 remain = f[l:]
503 503 elements = remain.split('/')
504 504 if len(elements) == 1:
505 505 files[remain] = full
506 506 else:
507 507 h = dirs # need to retain ref to dirs (root)
508 508 for elem in elements[0:-1]:
509 509 if elem not in h:
510 510 h[elem] = {}
511 511 h = h[elem]
512 512 if len(h) > 1:
513 513 break
514 514 h[None] = None # denotes files present
515 515
516 516 if mf and not files and not dirs:
517 517 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
518 518
519 519 def filelist(**map):
520 520 for f in sorted(files):
521 521 full = files[f]
522 522
523 523 fctx = ctx.filectx(full)
524 524 yield {"file": full,
525 525 "parity": parity.next(),
526 526 "basename": f,
527 527 "date": fctx.date(),
528 528 "size": fctx.size(),
529 529 "permissions": mf.flags(full)}
530 530
531 531 def dirlist(**map):
532 532 for d in sorted(dirs):
533 533
534 534 emptydirs = []
535 535 h = dirs[d]
536 536 while isinstance(h, dict) and len(h) == 1:
537 537 k, v = h.items()[0]
538 538 if v:
539 539 emptydirs.append(k)
540 540 h = v
541 541
542 542 path = "%s%s" % (abspath, d)
543 543 yield {"parity": parity.next(),
544 544 "path": path,
545 545 "emptydirs": "/".join(emptydirs),
546 546 "basename": d}
547 547
548 548 return tmpl("manifest",
549 549 rev=ctx.rev(),
550 550 symrev=symrev,
551 551 node=hex(node),
552 552 path=abspath,
553 553 up=webutil.up(abspath),
554 554 upparity=parity.next(),
555 555 fentries=filelist,
556 556 dentries=dirlist,
557 557 archives=web.archivelist(hex(node)),
558 558 tags=webutil.nodetagsdict(web.repo, node),
559 559 bookmarks=webutil.nodebookmarksdict(web.repo, node),
560 560 branch=webutil.nodebranchnodefault(ctx),
561 561 inbranch=webutil.nodeinbranch(web.repo, ctx),
562 562 branches=webutil.nodebranchdict(web.repo, ctx))
563 563
564 564 @webcommand('tags')
565 565 def tags(web, req, tmpl):
566 566 """
567 567 /tags
568 568 -----
569 569
570 570 Show information about tags.
571 571
572 572 No arguments are accepted.
573 573
574 574 The ``tags`` template is rendered.
575 575 """
576 576 i = list(reversed(web.repo.tagslist()))
577 577 parity = paritygen(web.stripecount)
578 578
579 579 def entries(notip, latestonly, **map):
580 580 t = i
581 581 if notip:
582 582 t = [(k, n) for k, n in i if k != "tip"]
583 583 if latestonly:
584 584 t = t[:1]
585 585 for k, n in t:
586 586 yield {"parity": parity.next(),
587 587 "tag": k,
588 588 "date": web.repo[n].date(),
589 589 "node": hex(n)}
590 590
591 591 return tmpl("tags",
592 592 node=hex(web.repo.changelog.tip()),
593 593 entries=lambda **x: entries(False, False, **x),
594 594 entriesnotip=lambda **x: entries(True, False, **x),
595 595 latestentry=lambda **x: entries(True, True, **x))
596 596
597 597 @webcommand('bookmarks')
598 598 def bookmarks(web, req, tmpl):
599 599 """
600 600 /bookmarks
601 601 ----------
602 602
603 603 Show information about bookmarks.
604 604
605 605 No arguments are accepted.
606 606
607 607 The ``bookmarks`` template is rendered.
608 608 """
609 609 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
610 610 parity = paritygen(web.stripecount)
611 611
612 612 def entries(latestonly, **map):
613 613 if latestonly:
614 614 t = [min(i)]
615 615 else:
616 616 t = sorted(i)
617 617 for k, n in t:
618 618 yield {"parity": parity.next(),
619 619 "bookmark": k,
620 620 "date": web.repo[n].date(),
621 621 "node": hex(n)}
622 622
623 623 return tmpl("bookmarks",
624 624 node=hex(web.repo.changelog.tip()),
625 625 entries=lambda **x: entries(latestonly=False, **x),
626 626 latestentry=lambda **x: entries(latestonly=True, **x))
627 627
628 628 @webcommand('branches')
629 629 def branches(web, req, tmpl):
630 630 """
631 631 /branches
632 632 ---------
633 633
634 634 Show information about branches.
635 635
636 636 All known branches are contained in the output, even closed branches.
637 637
638 638 No arguments are accepted.
639 639
640 640 The ``branches`` template is rendered.
641 641 """
642 642 entries = webutil.branchentries(web.repo, web.stripecount)
643 643 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
644 644 return tmpl('branches', node=hex(web.repo.changelog.tip()),
645 645 entries=entries, latestentry=latestentry)
646 646
647 647 @webcommand('summary')
648 648 def summary(web, req, tmpl):
649 649 """
650 650 /summary
651 651 --------
652 652
653 653 Show a summary of repository state.
654 654
655 655 Information about the latest changesets, bookmarks, tags, and branches
656 656 is captured by this handler.
657 657
658 658 The ``summary`` template is rendered.
659 659 """
660 660 i = reversed(web.repo.tagslist())
661 661
662 662 def tagentries(**map):
663 663 parity = paritygen(web.stripecount)
664 664 count = 0
665 665 for k, n in i:
666 666 if k == "tip": # skip tip
667 667 continue
668 668
669 669 count += 1
670 670 if count > 10: # limit to 10 tags
671 671 break
672 672
673 673 yield tmpl("tagentry",
674 674 parity=parity.next(),
675 675 tag=k,
676 676 node=hex(n),
677 677 date=web.repo[n].date())
678 678
679 679 def bookmarks(**map):
680 680 parity = paritygen(web.stripecount)
681 681 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
682 682 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
683 683 yield {'parity': parity.next(),
684 684 'bookmark': k,
685 685 'date': web.repo[n].date(),
686 686 'node': hex(n)}
687 687
688 688 def changelist(**map):
689 689 parity = paritygen(web.stripecount, offset=start - end)
690 690 l = [] # build a list in forward order for efficiency
691 691 revs = []
692 692 if start < end:
693 693 revs = web.repo.changelog.revs(start, end - 1)
694 694 for i in revs:
695 695 ctx = web.repo[i]
696 696 n = ctx.node()
697 697 hn = hex(n)
698 698
699 699 l.append(tmpl(
700 700 'shortlogentry',
701 701 parity=parity.next(),
702 702 author=ctx.user(),
703 703 desc=ctx.description(),
704 704 extra=ctx.extra(),
705 705 date=ctx.date(),
706 706 rev=i,
707 707 node=hn,
708 708 tags=webutil.nodetagsdict(web.repo, n),
709 709 bookmarks=webutil.nodebookmarksdict(web.repo, n),
710 710 inbranch=webutil.nodeinbranch(web.repo, ctx),
711 711 branches=webutil.nodebranchdict(web.repo, ctx)))
712 712
713 713 l.reverse()
714 714 yield l
715 715
716 716 tip = web.repo['tip']
717 717 count = len(web.repo)
718 718 start = max(0, count - web.maxchanges)
719 719 end = min(count, start + web.maxchanges)
720 720
721 721 return tmpl("summary",
722 722 desc=web.config("web", "description", "unknown"),
723 723 owner=get_contact(web.config) or "unknown",
724 724 lastchange=tip.date(),
725 725 tags=tagentries,
726 726 bookmarks=bookmarks,
727 branches=webutil.branchentries(web.repo, web.stripecount),
727 branches=webutil.branchentries(web.repo, web.stripecount, 10),
728 728 shortlog=changelist,
729 729 node=tip.hex(),
730 730 symrev='tip',
731 731 archives=web.archivelist("tip"))
732 732
733 733 @webcommand('filediff')
734 734 def filediff(web, req, tmpl):
735 735 """
736 736 /diff/{revision}/{path}
737 737 -----------------------
738 738
739 739 Show how a file changed in a particular commit.
740 740
741 741 The ``filediff`` template is rendered.
742 742
743 743 This hander is registered under both the ``/diff`` and ``/filediff``
744 744 paths. ``/diff`` is used in modern code.
745 745 """
746 746 fctx, ctx = None, None
747 747 try:
748 748 fctx = webutil.filectx(web.repo, req)
749 749 except LookupError:
750 750 ctx = webutil.changectx(web.repo, req)
751 751 path = webutil.cleanpath(web.repo, req.form['file'][0])
752 752 if path not in ctx.files():
753 753 raise
754 754
755 755 if fctx is not None:
756 756 n = fctx.node()
757 757 path = fctx.path()
758 758 ctx = fctx.changectx()
759 759 else:
760 760 n = ctx.node()
761 761 # path already defined in except clause
762 762
763 763 parity = paritygen(web.stripecount)
764 764 style = web.config('web', 'style', 'paper')
765 765 if 'style' in req.form:
766 766 style = req.form['style'][0]
767 767
768 768 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
769 769 if fctx:
770 770 rename = webutil.renamelink(fctx)
771 771 ctx = fctx
772 772 else:
773 773 rename = []
774 774 ctx = ctx
775 775 return tmpl("filediff",
776 776 file=path,
777 777 node=hex(n),
778 778 rev=ctx.rev(),
779 779 symrev=webutil.symrevorshortnode(req, ctx),
780 780 date=ctx.date(),
781 781 desc=ctx.description(),
782 782 extra=ctx.extra(),
783 783 author=ctx.user(),
784 784 rename=rename,
785 785 branch=webutil.nodebranchnodefault(ctx),
786 786 parent=webutil.parents(ctx),
787 787 child=webutil.children(ctx),
788 788 tags=webutil.nodetagsdict(web.repo, n),
789 789 bookmarks=webutil.nodebookmarksdict(web.repo, n),
790 790 diff=diffs)
791 791
792 792 diff = webcommand('diff')(filediff)
793 793
794 794 @webcommand('comparison')
795 795 def comparison(web, req, tmpl):
796 796 """
797 797 /comparison/{revision}/{path}
798 798 -----------------------------
799 799
800 800 Show a comparison between the old and new versions of a file from changes
801 801 made on a particular revision.
802 802
803 803 This is similar to the ``diff`` handler. However, this form features
804 804 a split or side-by-side diff rather than a unified diff.
805 805
806 806 The ``context`` query string argument can be used to control the lines of
807 807 context in the diff.
808 808
809 809 The ``filecomparison`` template is rendered.
810 810 """
811 811 ctx = webutil.changectx(web.repo, req)
812 812 if 'file' not in req.form:
813 813 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
814 814 path = webutil.cleanpath(web.repo, req.form['file'][0])
815 815 rename = path in ctx and webutil.renamelink(ctx[path]) or []
816 816
817 817 parsecontext = lambda v: v == 'full' and -1 or int(v)
818 818 if 'context' in req.form:
819 819 context = parsecontext(req.form['context'][0])
820 820 else:
821 821 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
822 822
823 823 def filelines(f):
824 824 if util.binary(f.data()):
825 825 mt = mimetypes.guess_type(f.path())[0]
826 826 if not mt:
827 827 mt = 'application/octet-stream'
828 828 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
829 829 return f.data().splitlines()
830 830
831 831 parent = ctx.p1()
832 832 leftrev = parent.rev()
833 833 leftnode = parent.node()
834 834 rightrev = ctx.rev()
835 835 rightnode = ctx.node()
836 836 if path in ctx:
837 837 fctx = ctx[path]
838 838 rightlines = filelines(fctx)
839 839 if path not in parent:
840 840 leftlines = ()
841 841 else:
842 842 pfctx = parent[path]
843 843 leftlines = filelines(pfctx)
844 844 else:
845 845 rightlines = ()
846 846 fctx = ctx.parents()[0][path]
847 847 leftlines = filelines(fctx)
848 848
849 849 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
850 850 return tmpl('filecomparison',
851 851 file=path,
852 852 node=hex(ctx.node()),
853 853 rev=ctx.rev(),
854 854 symrev=webutil.symrevorshortnode(req, ctx),
855 855 date=ctx.date(),
856 856 desc=ctx.description(),
857 857 extra=ctx.extra(),
858 858 author=ctx.user(),
859 859 rename=rename,
860 860 branch=webutil.nodebranchnodefault(ctx),
861 861 parent=webutil.parents(fctx),
862 862 child=webutil.children(fctx),
863 863 tags=webutil.nodetagsdict(web.repo, ctx.node()),
864 864 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
865 865 leftrev=leftrev,
866 866 leftnode=hex(leftnode),
867 867 rightrev=rightrev,
868 868 rightnode=hex(rightnode),
869 869 comparison=comparison)
870 870
871 871 @webcommand('annotate')
872 872 def annotate(web, req, tmpl):
873 873 """
874 874 /annotate/{revision}/{path}
875 875 ---------------------------
876 876
877 877 Show changeset information for each line in a file.
878 878
879 879 The ``fileannotate`` template is rendered.
880 880 """
881 881 fctx = webutil.filectx(web.repo, req)
882 882 f = fctx.path()
883 883 parity = paritygen(web.stripecount)
884 884 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
885 885 section='annotate', whitespace=True)
886 886
887 887 def annotate(**map):
888 888 last = None
889 889 if util.binary(fctx.data()):
890 890 mt = (mimetypes.guess_type(fctx.path())[0]
891 891 or 'application/octet-stream')
892 892 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
893 893 '(binary:%s)' % mt)])
894 894 else:
895 895 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
896 896 diffopts=diffopts))
897 897 for lineno, ((f, targetline), l) in lines:
898 898 fnode = f.filenode()
899 899
900 900 if last != fnode:
901 901 last = fnode
902 902
903 903 yield {"parity": parity.next(),
904 904 "node": f.hex(),
905 905 "rev": f.rev(),
906 906 "author": f.user(),
907 907 "desc": f.description(),
908 908 "extra": f.extra(),
909 909 "file": f.path(),
910 910 "targetline": targetline,
911 911 "line": l,
912 912 "lineno": lineno + 1,
913 913 "lineid": "l%d" % (lineno + 1),
914 914 "linenumber": "% 6d" % (lineno + 1),
915 915 "revdate": f.date()}
916 916
917 917 return tmpl("fileannotate",
918 918 file=f,
919 919 annotate=annotate,
920 920 path=webutil.up(f),
921 921 rev=fctx.rev(),
922 922 symrev=webutil.symrevorshortnode(req, fctx),
923 923 node=fctx.hex(),
924 924 author=fctx.user(),
925 925 date=fctx.date(),
926 926 desc=fctx.description(),
927 927 extra=fctx.extra(),
928 928 rename=webutil.renamelink(fctx),
929 929 branch=webutil.nodebranchnodefault(fctx),
930 930 parent=webutil.parents(fctx),
931 931 child=webutil.children(fctx),
932 932 tags=webutil.nodetagsdict(web.repo, fctx.node()),
933 933 bookmarks=webutil.nodebookmarksdict(web.repo, fctx.node()),
934 934 permissions=fctx.manifest().flags(f))
935 935
936 936 @webcommand('filelog')
937 937 def filelog(web, req, tmpl):
938 938 """
939 939 /filelog/{revision}/{path}
940 940 --------------------------
941 941
942 942 Show information about the history of a file in the repository.
943 943
944 944 The ``revcount`` query string argument can be defined to control the
945 945 maximum number of entries to show.
946 946
947 947 The ``filelog`` template will be rendered.
948 948 """
949 949
950 950 try:
951 951 fctx = webutil.filectx(web.repo, req)
952 952 f = fctx.path()
953 953 fl = fctx.filelog()
954 954 except error.LookupError:
955 955 f = webutil.cleanpath(web.repo, req.form['file'][0])
956 956 fl = web.repo.file(f)
957 957 numrevs = len(fl)
958 958 if not numrevs: # file doesn't exist at all
959 959 raise
960 960 rev = webutil.changectx(web.repo, req).rev()
961 961 first = fl.linkrev(0)
962 962 if rev < first: # current rev is from before file existed
963 963 raise
964 964 frev = numrevs - 1
965 965 while fl.linkrev(frev) > rev:
966 966 frev -= 1
967 967 fctx = web.repo.filectx(f, fl.linkrev(frev))
968 968
969 969 revcount = web.maxshortchanges
970 970 if 'revcount' in req.form:
971 971 try:
972 972 revcount = int(req.form.get('revcount', [revcount])[0])
973 973 revcount = max(revcount, 1)
974 974 tmpl.defaults['sessionvars']['revcount'] = revcount
975 975 except ValueError:
976 976 pass
977 977
978 978 lessvars = copy.copy(tmpl.defaults['sessionvars'])
979 979 lessvars['revcount'] = max(revcount / 2, 1)
980 980 morevars = copy.copy(tmpl.defaults['sessionvars'])
981 981 morevars['revcount'] = revcount * 2
982 982
983 983 count = fctx.filerev() + 1
984 984 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
985 985 end = min(count, start + revcount) # last rev on this page
986 986 parity = paritygen(web.stripecount, offset=start - end)
987 987
988 988 def entries():
989 989 l = []
990 990
991 991 repo = web.repo
992 992 revs = fctx.filelog().revs(start, end - 1)
993 993 for i in revs:
994 994 iterfctx = fctx.filectx(i)
995 995
996 996 l.append({"parity": parity.next(),
997 997 "filerev": i,
998 998 "file": f,
999 999 "node": iterfctx.hex(),
1000 1000 "author": iterfctx.user(),
1001 1001 "date": iterfctx.date(),
1002 1002 "rename": webutil.renamelink(iterfctx),
1003 1003 "parent": webutil.parents(iterfctx),
1004 1004 "child": webutil.children(iterfctx),
1005 1005 "desc": iterfctx.description(),
1006 1006 "extra": iterfctx.extra(),
1007 1007 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
1008 1008 "bookmarks": webutil.nodebookmarksdict(
1009 1009 repo, iterfctx.node()),
1010 1010 "branch": webutil.nodebranchnodefault(iterfctx),
1011 1011 "inbranch": webutil.nodeinbranch(repo, iterfctx),
1012 1012 "branches": webutil.nodebranchdict(repo, iterfctx)})
1013 1013 for e in reversed(l):
1014 1014 yield e
1015 1015
1016 1016 entries = list(entries())
1017 1017 latestentry = entries[:1]
1018 1018
1019 1019 revnav = webutil.filerevnav(web.repo, fctx.path())
1020 1020 nav = revnav.gen(end - 1, revcount, count)
1021 1021 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
1022 1022 symrev=webutil.symrevorshortnode(req, fctx),
1023 1023 entries=entries,
1024 1024 latestentry=latestentry,
1025 1025 revcount=revcount, morevars=morevars, lessvars=lessvars)
1026 1026
1027 1027 @webcommand('archive')
1028 1028 def archive(web, req, tmpl):
1029 1029 """
1030 1030 /archive/{revision}.{format}[/{path}]
1031 1031 -------------------------------------
1032 1032
1033 1033 Obtain an archive of repository content.
1034 1034
1035 1035 The content and type of the archive is defined by a URL path parameter.
1036 1036 ``format`` is the file extension of the archive type to be generated. e.g.
1037 1037 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1038 1038 server configuration.
1039 1039
1040 1040 The optional ``path`` URL parameter controls content to include in the
1041 1041 archive. If omitted, every file in the specified revision is present in the
1042 1042 archive. If included, only the specified file or contents of the specified
1043 1043 directory will be included in the archive.
1044 1044
1045 1045 No template is used for this handler. Raw, binary content is generated.
1046 1046 """
1047 1047
1048 1048 type_ = req.form.get('type', [None])[0]
1049 1049 allowed = web.configlist("web", "allow_archive")
1050 1050 key = req.form['node'][0]
1051 1051
1052 1052 if type_ not in web.archives:
1053 1053 msg = 'Unsupported archive type: %s' % type_
1054 1054 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1055 1055
1056 1056 if not ((type_ in allowed or
1057 1057 web.configbool("web", "allow" + type_, False))):
1058 1058 msg = 'Archive type not allowed: %s' % type_
1059 1059 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1060 1060
1061 1061 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1062 1062 cnode = web.repo.lookup(key)
1063 1063 arch_version = key
1064 1064 if cnode == key or key == 'tip':
1065 1065 arch_version = short(cnode)
1066 1066 name = "%s-%s" % (reponame, arch_version)
1067 1067
1068 1068 ctx = webutil.changectx(web.repo, req)
1069 1069 pats = []
1070 1070 matchfn = scmutil.match(ctx, [])
1071 1071 file = req.form.get('file', None)
1072 1072 if file:
1073 1073 pats = ['path:' + file[0]]
1074 1074 matchfn = scmutil.match(ctx, pats, default='path')
1075 1075 if pats:
1076 1076 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1077 1077 if not files:
1078 1078 raise ErrorResponse(HTTP_NOT_FOUND,
1079 1079 'file(s) not found: %s' % file[0])
1080 1080
1081 1081 mimetype, artype, extension, encoding = web.archive_specs[type_]
1082 1082 headers = [
1083 1083 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1084 1084 ]
1085 1085 if encoding:
1086 1086 headers.append(('Content-Encoding', encoding))
1087 1087 req.headers.extend(headers)
1088 1088 req.respond(HTTP_OK, mimetype)
1089 1089
1090 1090 archival.archive(web.repo, req, cnode, artype, prefix=name,
1091 1091 matchfn=matchfn,
1092 1092 subrepos=web.configbool("web", "archivesubrepos"))
1093 1093 return []
1094 1094
1095 1095
1096 1096 @webcommand('static')
1097 1097 def static(web, req, tmpl):
1098 1098 fname = req.form['file'][0]
1099 1099 # a repo owner may set web.static in .hg/hgrc to get any file
1100 1100 # readable by the user running the CGI script
1101 1101 static = web.config("web", "static", None, untrusted=False)
1102 1102 if not static:
1103 1103 tp = web.templatepath or templater.templatepaths()
1104 1104 if isinstance(tp, str):
1105 1105 tp = [tp]
1106 1106 static = [os.path.join(p, 'static') for p in tp]
1107 1107 staticfile(static, fname, req)
1108 1108 return []
1109 1109
1110 1110 @webcommand('graph')
1111 1111 def graph(web, req, tmpl):
1112 1112 """
1113 1113 /graph[/{revision}]
1114 1114 -------------------
1115 1115
1116 1116 Show information about the graphical topology of the repository.
1117 1117
1118 1118 Information rendered by this handler can be used to create visual
1119 1119 representations of repository topology.
1120 1120
1121 1121 The ``revision`` URL parameter controls the starting changeset.
1122 1122
1123 1123 The ``revcount`` query string argument can define the number of changesets
1124 1124 to show information for.
1125 1125
1126 1126 This handler will render the ``graph`` template.
1127 1127 """
1128 1128
1129 1129 if 'node' in req.form:
1130 1130 ctx = webutil.changectx(web.repo, req)
1131 1131 symrev = webutil.symrevorshortnode(req, ctx)
1132 1132 else:
1133 1133 ctx = web.repo['tip']
1134 1134 symrev = 'tip'
1135 1135 rev = ctx.rev()
1136 1136
1137 1137 bg_height = 39
1138 1138 revcount = web.maxshortchanges
1139 1139 if 'revcount' in req.form:
1140 1140 try:
1141 1141 revcount = int(req.form.get('revcount', [revcount])[0])
1142 1142 revcount = max(revcount, 1)
1143 1143 tmpl.defaults['sessionvars']['revcount'] = revcount
1144 1144 except ValueError:
1145 1145 pass
1146 1146
1147 1147 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1148 1148 lessvars['revcount'] = max(revcount / 2, 1)
1149 1149 morevars = copy.copy(tmpl.defaults['sessionvars'])
1150 1150 morevars['revcount'] = revcount * 2
1151 1151
1152 1152 count = len(web.repo)
1153 1153 pos = rev
1154 1154
1155 1155 uprev = min(max(0, count - 1), rev + revcount)
1156 1156 downrev = max(0, rev - revcount)
1157 1157 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1158 1158
1159 1159 tree = []
1160 1160 if pos != -1:
1161 1161 allrevs = web.repo.changelog.revs(pos, 0)
1162 1162 revs = []
1163 1163 for i in allrevs:
1164 1164 revs.append(i)
1165 1165 if len(revs) >= revcount:
1166 1166 break
1167 1167
1168 1168 # We have to feed a baseset to dagwalker as it is expecting smartset
1169 1169 # object. This does not have a big impact on hgweb performance itself
1170 1170 # since hgweb graphing code is not itself lazy yet.
1171 1171 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
1172 1172 # As we said one line above... not lazy.
1173 1173 tree = list(graphmod.colored(dag, web.repo))
1174 1174
1175 1175 def getcolumns(tree):
1176 1176 cols = 0
1177 1177 for (id, type, ctx, vtx, edges) in tree:
1178 1178 if type != graphmod.CHANGESET:
1179 1179 continue
1180 1180 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1181 1181 max([edge[1] for edge in edges] or [0]))
1182 1182 return cols
1183 1183
1184 1184 def graphdata(usetuples, **map):
1185 1185 data = []
1186 1186
1187 1187 row = 0
1188 1188 for (id, type, ctx, vtx, edges) in tree:
1189 1189 if type != graphmod.CHANGESET:
1190 1190 continue
1191 1191 node = str(ctx)
1192 1192 age = templatefilters.age(ctx.date())
1193 1193 desc = templatefilters.firstline(ctx.description())
1194 1194 desc = cgi.escape(templatefilters.nonempty(desc))
1195 1195 user = cgi.escape(templatefilters.person(ctx.user()))
1196 1196 branch = cgi.escape(ctx.branch())
1197 1197 try:
1198 1198 branchnode = web.repo.branchtip(branch)
1199 1199 except error.RepoLookupError:
1200 1200 branchnode = None
1201 1201 branch = branch, branchnode == ctx.node()
1202 1202
1203 1203 if usetuples:
1204 1204 data.append((node, vtx, edges, desc, user, age, branch,
1205 1205 [cgi.escape(x) for x in ctx.tags()],
1206 1206 [cgi.escape(x) for x in ctx.bookmarks()]))
1207 1207 else:
1208 1208 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1209 1209 'color': (edge[2] - 1) % 6 + 1,
1210 1210 'width': edge[3], 'bcolor': edge[4]}
1211 1211 for edge in edges]
1212 1212
1213 1213 data.append(
1214 1214 {'node': node,
1215 1215 'col': vtx[0],
1216 1216 'color': (vtx[1] - 1) % 6 + 1,
1217 1217 'edges': edgedata,
1218 1218 'row': row,
1219 1219 'nextrow': row + 1,
1220 1220 'desc': desc,
1221 1221 'user': user,
1222 1222 'age': age,
1223 1223 'bookmarks': webutil.nodebookmarksdict(
1224 1224 web.repo, ctx.node()),
1225 1225 'branches': webutil.nodebranchdict(web.repo, ctx),
1226 1226 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1227 1227 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1228 1228
1229 1229 row += 1
1230 1230
1231 1231 return data
1232 1232
1233 1233 cols = getcolumns(tree)
1234 1234 rows = len(tree)
1235 1235 canvasheight = (rows + 1) * bg_height - 27
1236 1236
1237 1237 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1238 1238 uprev=uprev,
1239 1239 lessvars=lessvars, morevars=morevars, downrev=downrev,
1240 1240 cols=cols, rows=rows,
1241 1241 canvaswidth=(cols + 1) * bg_height,
1242 1242 truecanvasheight=rows * bg_height,
1243 1243 canvasheight=canvasheight, bg_height=bg_height,
1244 1244 jsdata=lambda **x: graphdata(True, **x),
1245 1245 nodes=lambda **x: graphdata(False, **x),
1246 1246 node=ctx.hex(), changenav=changenav)
1247 1247
1248 1248 def _getdoc(e):
1249 1249 doc = e[0].__doc__
1250 1250 if doc:
1251 1251 doc = _(doc).split('\n')[0]
1252 1252 else:
1253 1253 doc = _('(no help text available)')
1254 1254 return doc
1255 1255
1256 1256 @webcommand('help')
1257 1257 def help(web, req, tmpl):
1258 1258 """
1259 1259 /help[/{topic}]
1260 1260 ---------------
1261 1261
1262 1262 Render help documentation.
1263 1263
1264 1264 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1265 1265 is defined, that help topic will be rendered. If not, an index of
1266 1266 available help topics will be rendered.
1267 1267
1268 1268 The ``help`` template will be rendered when requesting help for a topic.
1269 1269 ``helptopics`` will be rendered for the index of help topics.
1270 1270 """
1271 1271 from mercurial import commands # avoid cycle
1272 1272 from mercurial import help as helpmod # avoid cycle
1273 1273
1274 1274 topicname = req.form.get('node', [None])[0]
1275 1275 if not topicname:
1276 1276 def topics(**map):
1277 1277 for entries, summary, _doc in helpmod.helptable:
1278 1278 yield {'topic': entries[0], 'summary': summary}
1279 1279
1280 1280 early, other = [], []
1281 1281 primary = lambda s: s.split('|')[0]
1282 1282 for c, e in commands.table.iteritems():
1283 1283 doc = _getdoc(e)
1284 1284 if 'DEPRECATED' in doc or c.startswith('debug'):
1285 1285 continue
1286 1286 cmd = primary(c)
1287 1287 if cmd.startswith('^'):
1288 1288 early.append((cmd[1:], doc))
1289 1289 else:
1290 1290 other.append((cmd, doc))
1291 1291
1292 1292 early.sort()
1293 1293 other.sort()
1294 1294
1295 1295 def earlycommands(**map):
1296 1296 for c, doc in early:
1297 1297 yield {'topic': c, 'summary': doc}
1298 1298
1299 1299 def othercommands(**map):
1300 1300 for c, doc in other:
1301 1301 yield {'topic': c, 'summary': doc}
1302 1302
1303 1303 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1304 1304 othercommands=othercommands, title='Index')
1305 1305
1306 1306 u = webutil.wsgiui()
1307 1307 u.verbose = True
1308 1308 try:
1309 1309 doc = helpmod.help_(u, topicname)
1310 1310 except error.UnknownCommand:
1311 1311 raise ErrorResponse(HTTP_NOT_FOUND)
1312 1312 return tmpl('help', topic=topicname, doc=doc)
1313 1313
1314 1314 # tell hggettext to extract docstrings from these functions:
1315 1315 i18nfunctions = commands.values()
General Comments 0
You need to be logged in to leave comments. Login now