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