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