Mercurial > hg
comparison mercurial/hgweb/webcommands.py @ 43076:2372284d9457
formatting: blacken the codebase
This is using my patch to black
(https://github.com/psf/black/pull/826) so we don't un-wrap collection
literals.
Done with:
hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S
# skip-blame mass-reformatting only
# no-check-commit reformats foo_bar functions
Differential Revision: https://phab.mercurial-scm.org/D6971
author | Augie Fackler <augie@google.com> |
---|---|
date | Sun, 06 Oct 2019 09:45:02 -0400 |
parents | 0bd56c291359 |
children | 687b865b95ad |
comparison
equal
deleted
inserted
replaced
43075:57875cf423c9 | 43076:2372284d9457 |
---|---|
37 smartset, | 37 smartset, |
38 templater, | 38 templater, |
39 templateutil, | 39 templateutil, |
40 ) | 40 ) |
41 | 41 |
42 from ..utils import ( | 42 from ..utils import stringutil |
43 stringutil, | 43 |
44 ) | 44 from . import webutil |
45 | |
46 from . import ( | |
47 webutil, | |
48 ) | |
49 | 45 |
50 __all__ = [] | 46 __all__ = [] |
51 commands = {} | 47 commands = {} |
48 | |
52 | 49 |
53 class webcommand(object): | 50 class webcommand(object): |
54 """Decorator used to register a web command handler. | 51 """Decorator used to register a web command handler. |
55 | 52 |
56 The decorator takes as its positional arguments the name/path the | 53 The decorator takes as its positional arguments the name/path the |
79 def __call__(self, func): | 76 def __call__(self, func): |
80 __all__.append(self.name) | 77 __all__.append(self.name) |
81 commands[self.name] = func | 78 commands[self.name] = func |
82 return func | 79 return func |
83 | 80 |
81 | |
84 @webcommand('log') | 82 @webcommand('log') |
85 def log(web): | 83 def log(web): |
86 """ | 84 """ |
87 /log[/{revision}[/{path}]] | 85 /log[/{revision}[/{path}]] |
88 -------------------------- | 86 -------------------------- |
100 | 98 |
101 if web.req.qsparams.get('file'): | 99 if web.req.qsparams.get('file'): |
102 return filelog(web) | 100 return filelog(web) |
103 else: | 101 else: |
104 return changelog(web) | 102 return changelog(web) |
103 | |
105 | 104 |
106 @webcommand('rawfile') | 105 @webcommand('rawfile') |
107 def rawfile(web): | 106 def rawfile(web): |
108 guessmime = web.configbool('web', 'guessmime') | 107 guessmime = web.configbool('web', 'guessmime') |
109 | 108 |
134 | 133 |
135 if mt.startswith('text/'): | 134 if mt.startswith('text/'): |
136 mt += '; charset="%s"' % encoding.encoding | 135 mt += '; charset="%s"' % encoding.encoding |
137 | 136 |
138 web.res.headers['Content-Type'] = mt | 137 web.res.headers['Content-Type'] = mt |
139 filename = (path.rpartition('/')[-1] | 138 filename = ( |
140 .replace('\\', '\\\\').replace('"', '\\"')) | 139 path.rpartition('/')[-1].replace('\\', '\\\\').replace('"', '\\"') |
140 ) | |
141 web.res.headers['Content-Disposition'] = 'inline; filename="%s"' % filename | 141 web.res.headers['Content-Disposition'] = 'inline; filename="%s"' % filename |
142 web.res.setbodybytes(text) | 142 web.res.setbodybytes(text) |
143 return web.res.sendresponse() | 143 return web.res.sendresponse() |
144 | |
144 | 145 |
145 def _filerevision(web, fctx): | 146 def _filerevision(web, fctx): |
146 f = fctx.path() | 147 f = fctx.path() |
147 text = fctx.data() | 148 text = fctx.data() |
148 parity = paritygen(web.stripecount) | 149 parity = paritygen(web.stripecount) |
149 ishead = fctx.filenode() in fctx.filelog().heads() | 150 ishead = fctx.filenode() in fctx.filelog().heads() |
150 | 151 |
151 if stringutil.binary(text): | 152 if stringutil.binary(text): |
152 mt = pycompat.sysbytes( | 153 mt = pycompat.sysbytes( |
153 mimetypes.guess_type(pycompat.fsdecode(f))[0] | 154 mimetypes.guess_type(pycompat.fsdecode(f))[0] |
154 or r'application/octet-stream') | 155 or r'application/octet-stream' |
156 ) | |
155 text = '(binary:%s)' % mt | 157 text = '(binary:%s)' % mt |
156 | 158 |
157 def lines(context): | 159 def lines(context): |
158 for lineno, t in enumerate(text.splitlines(True)): | 160 for lineno, t in enumerate(text.splitlines(True)): |
159 yield {"line": t, | 161 yield { |
160 "lineid": "l%d" % (lineno + 1), | 162 "line": t, |
161 "linenumber": "% 6d" % (lineno + 1), | 163 "lineid": "l%d" % (lineno + 1), |
162 "parity": next(parity)} | 164 "linenumber": "% 6d" % (lineno + 1), |
165 "parity": next(parity), | |
166 } | |
163 | 167 |
164 return web.sendtemplate( | 168 return web.sendtemplate( |
165 'filerevision', | 169 'filerevision', |
166 file=f, | 170 file=f, |
167 path=webutil.up(f), | 171 path=webutil.up(f), |
168 text=templateutil.mappinggenerator(lines), | 172 text=templateutil.mappinggenerator(lines), |
169 symrev=webutil.symrevorshortnode(web.req, fctx), | 173 symrev=webutil.symrevorshortnode(web.req, fctx), |
170 rename=webutil.renamelink(fctx), | 174 rename=webutil.renamelink(fctx), |
171 permissions=fctx.manifest().flags(f), | 175 permissions=fctx.manifest().flags(f), |
172 ishead=int(ishead), | 176 ishead=int(ishead), |
173 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))) | 177 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)) |
178 ) | |
179 | |
174 | 180 |
175 @webcommand('file') | 181 @webcommand('file') |
176 def file(web): | 182 def file(web): |
177 """ | 183 """ |
178 /file/{revision}[/{path}] | 184 /file/{revision}[/{path}] |
204 try: | 210 try: |
205 return manifest(web) | 211 return manifest(web) |
206 except ErrorResponse: | 212 except ErrorResponse: |
207 raise inst | 213 raise inst |
208 | 214 |
215 | |
209 def _search(web): | 216 def _search(web): |
210 MODE_REVISION = 'rev' | 217 MODE_REVISION = 'rev' |
211 MODE_KEYWORD = 'keyword' | 218 MODE_KEYWORD = 'keyword' |
212 MODE_REVSET = 'revset' | 219 MODE_REVSET = 'revset' |
213 | 220 |
230 yield e | 237 yield e |
231 | 238 |
232 for ctx in revgen(): | 239 for ctx in revgen(): |
233 miss = 0 | 240 miss = 0 |
234 for q in qw: | 241 for q in qw: |
235 if not (q in lower(ctx.user()) or | 242 if not ( |
236 q in lower(ctx.description()) or | 243 q in lower(ctx.user()) |
237 q in lower(" ".join(ctx.files()))): | 244 or q in lower(ctx.description()) |
245 or q in lower(" ".join(ctx.files())) | |
246 ): | |
238 miss = 1 | 247 miss = 1 |
239 break | 248 break |
240 if miss: | 249 if miss: |
241 continue | 250 continue |
242 | 251 |
271 | 280 |
272 if revsetlang.depth(tree) <= 2: | 281 if revsetlang.depth(tree) <= 2: |
273 # no revset syntax used | 282 # no revset syntax used |
274 return MODE_KEYWORD, query | 283 return MODE_KEYWORD, query |
275 | 284 |
276 if any((token, (value or '')[:3]) == ('string', 're:') | 285 if any( |
277 for token, value, pos in revsetlang.tokenize(revdef)): | 286 (token, (value or '')[:3]) == ('string', 're:') |
287 for token, value, pos in revsetlang.tokenize(revdef) | |
288 ): | |
278 return MODE_KEYWORD, query | 289 return MODE_KEYWORD, query |
279 | 290 |
280 funcsused = revsetlang.funcsused(tree) | 291 funcsused = revsetlang.funcsused(tree) |
281 if not funcsused.issubset(revset.safesymbols): | 292 if not funcsused.issubset(revset.safesymbols): |
282 return MODE_KEYWORD, query | 293 return MODE_KEYWORD, query |
283 | 294 |
284 try: | 295 try: |
285 mfunc = revset.match(web.repo.ui, revdef, | 296 mfunc = revset.match( |
286 lookup=revset.lookupfn(web.repo)) | 297 web.repo.ui, revdef, lookup=revset.lookupfn(web.repo) |
298 ) | |
287 revs = mfunc(web.repo) | 299 revs = mfunc(web.repo) |
288 return MODE_REVSET, revs | 300 return MODE_REVSET, revs |
289 # ParseError: wrongly placed tokens, wrongs arguments, etc | 301 # ParseError: wrongly placed tokens, wrongs arguments, etc |
290 # RepoLookupError: no such revision, e.g. in 'revision:' | 302 # RepoLookupError: no such revision, e.g. in 'revision:' |
291 # Abort: bookmark/tag not exists | 303 # Abort: bookmark/tag not exists |
292 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo | 304 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo |
293 except (error.ParseError, error.RepoLookupError, error.Abort, | 305 except ( |
294 LookupError): | 306 error.ParseError, |
307 error.RepoLookupError, | |
308 error.Abort, | |
309 LookupError, | |
310 ): | |
295 return MODE_KEYWORD, query | 311 return MODE_KEYWORD, query |
296 | 312 |
297 def changelist(context): | 313 def changelist(context): |
298 count = 0 | 314 count = 0 |
299 | 315 |
302 n = scmutil.binnode(ctx) | 318 n = scmutil.binnode(ctx) |
303 showtags = webutil.showtag(web.repo, 'changelogtag', n) | 319 showtags = webutil.showtag(web.repo, 'changelogtag', n) |
304 files = webutil.listfilediffs(ctx.files(), n, web.maxfiles) | 320 files = webutil.listfilediffs(ctx.files(), n, web.maxfiles) |
305 | 321 |
306 lm = webutil.commonentry(web.repo, ctx) | 322 lm = webutil.commonentry(web.repo, ctx) |
307 lm.update({ | 323 lm.update( |
308 'parity': next(parity), | 324 { |
309 'changelogtag': showtags, | 325 'parity': next(parity), |
310 'files': files, | 326 'changelogtag': showtags, |
311 }) | 327 'files': files, |
328 } | |
329 ) | |
312 yield lm | 330 yield lm |
313 | 331 |
314 if count >= revcount: | 332 if count >= revcount: |
315 break | 333 break |
316 | 334 |
359 archives=web.archivelist('tip'), | 377 archives=web.archivelist('tip'), |
360 morevars=morevars, | 378 morevars=morevars, |
361 lessvars=lessvars, | 379 lessvars=lessvars, |
362 modedesc=searchfunc[1], | 380 modedesc=searchfunc[1], |
363 showforcekw=showforcekw, | 381 showforcekw=showforcekw, |
364 showunforcekw=showunforcekw) | 382 showunforcekw=showunforcekw, |
383 ) | |
384 | |
365 | 385 |
366 @webcommand('changelog') | 386 @webcommand('changelog') |
367 def changelog(web, shortlog=False): | 387 def changelog(web, shortlog=False): |
368 """ | 388 """ |
369 /changelog[/{revision}] | 389 /changelog[/{revision}] |
451 nextentry=templateutil.mappinglist(nextentry), | 471 nextentry=templateutil.mappinglist(nextentry), |
452 archives=web.archivelist('tip'), | 472 archives=web.archivelist('tip'), |
453 revcount=revcount, | 473 revcount=revcount, |
454 morevars=morevars, | 474 morevars=morevars, |
455 lessvars=lessvars, | 475 lessvars=lessvars, |
456 query=query) | 476 query=query, |
477 ) | |
478 | |
457 | 479 |
458 @webcommand('shortlog') | 480 @webcommand('shortlog') |
459 def shortlog(web): | 481 def shortlog(web): |
460 """ | 482 """ |
461 /shortlog | 483 /shortlog |
467 difference is the ``shortlog`` template will be rendered instead of the | 489 difference is the ``shortlog`` template will be rendered instead of the |
468 ``changelog`` template. | 490 ``changelog`` template. |
469 """ | 491 """ |
470 return changelog(web, shortlog=True) | 492 return changelog(web, shortlog=True) |
471 | 493 |
494 | |
472 @webcommand('changeset') | 495 @webcommand('changeset') |
473 def changeset(web): | 496 def changeset(web): |
474 """ | 497 """ |
475 /changeset[/{revision}] | 498 /changeset[/{revision}] |
476 ----------------------- | 499 ----------------------- |
485 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many | 508 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many |
486 templates related to diffs may all be used to produce the output. | 509 templates related to diffs may all be used to produce the output. |
487 """ | 510 """ |
488 ctx = webutil.changectx(web.repo, web.req) | 511 ctx = webutil.changectx(web.repo, web.req) |
489 | 512 |
490 return web.sendtemplate( | 513 return web.sendtemplate('changeset', **webutil.changesetentry(web, ctx)) |
491 'changeset', | 514 |
492 **webutil.changesetentry(web, ctx)) | |
493 | 515 |
494 rev = webcommand('rev')(changeset) | 516 rev = webcommand('rev')(changeset) |
517 | |
495 | 518 |
496 def decodepath(path): | 519 def decodepath(path): |
497 """Hook for mapping a path in the repository to a path in the | 520 """Hook for mapping a path in the repository to a path in the |
498 working copy. | 521 working copy. |
499 | 522 |
500 Extensions (e.g., largefiles) can override this to remap files in | 523 Extensions (e.g., largefiles) can override this to remap files in |
501 the virtual file system presented by the manifest command below.""" | 524 the virtual file system presented by the manifest command below.""" |
502 return path | 525 return path |
526 | |
503 | 527 |
504 @webcommand('manifest') | 528 @webcommand('manifest') |
505 def manifest(web): | 529 def manifest(web): |
506 """ | 530 """ |
507 /manifest[/{revision}[/{path}]] | 531 /manifest[/{revision}[/{path}]] |
547 remain = f[l:] | 571 remain = f[l:] |
548 elements = remain.split('/') | 572 elements = remain.split('/') |
549 if len(elements) == 1: | 573 if len(elements) == 1: |
550 files[remain] = full | 574 files[remain] = full |
551 else: | 575 else: |
552 h = dirs # need to retain ref to dirs (root) | 576 h = dirs # need to retain ref to dirs (root) |
553 for elem in elements[0:-1]: | 577 for elem in elements[0:-1]: |
554 if elem not in h: | 578 if elem not in h: |
555 h[elem] = {} | 579 h[elem] = {} |
556 h = h[elem] | 580 h = h[elem] |
557 if len(h) > 1: | 581 if len(h) > 1: |
558 break | 582 break |
559 h[None] = None # denotes files present | 583 h[None] = None # denotes files present |
560 | 584 |
561 if mf and not files and not dirs: | 585 if mf and not files and not dirs: |
562 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) | 586 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) |
563 | 587 |
564 def filelist(context): | 588 def filelist(context): |
565 for f in sorted(files): | 589 for f in sorted(files): |
566 full = files[f] | 590 full = files[f] |
567 | 591 |
568 fctx = ctx.filectx(full) | 592 fctx = ctx.filectx(full) |
569 yield {"file": full, | 593 yield { |
570 "parity": next(parity), | 594 "file": full, |
571 "basename": f, | 595 "parity": next(parity), |
572 "date": fctx.date(), | 596 "basename": f, |
573 "size": fctx.size(), | 597 "date": fctx.date(), |
574 "permissions": mf.flags(full)} | 598 "size": fctx.size(), |
599 "permissions": mf.flags(full), | |
600 } | |
575 | 601 |
576 def dirlist(context): | 602 def dirlist(context): |
577 for d in sorted(dirs): | 603 for d in sorted(dirs): |
578 | 604 |
579 emptydirs = [] | 605 emptydirs = [] |
583 if v: | 609 if v: |
584 emptydirs.append(k) | 610 emptydirs.append(k) |
585 h = v | 611 h = v |
586 | 612 |
587 path = "%s%s" % (abspath, d) | 613 path = "%s%s" % (abspath, d) |
588 yield {"parity": next(parity), | 614 yield { |
589 "path": path, | 615 "parity": next(parity), |
590 "emptydirs": "/".join(emptydirs), | 616 "path": path, |
591 "basename": d} | 617 "emptydirs": "/".join(emptydirs), |
618 "basename": d, | |
619 } | |
592 | 620 |
593 return web.sendtemplate( | 621 return web.sendtemplate( |
594 'manifest', | 622 'manifest', |
595 symrev=symrev, | 623 symrev=symrev, |
596 path=abspath, | 624 path=abspath, |
597 up=webutil.up(abspath), | 625 up=webutil.up(abspath), |
598 upparity=next(parity), | 626 upparity=next(parity), |
599 fentries=templateutil.mappinggenerator(filelist), | 627 fentries=templateutil.mappinggenerator(filelist), |
600 dentries=templateutil.mappinggenerator(dirlist), | 628 dentries=templateutil.mappinggenerator(dirlist), |
601 archives=web.archivelist(hex(node)), | 629 archives=web.archivelist(hex(node)), |
602 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))) | 630 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)) |
631 ) | |
632 | |
603 | 633 |
604 @webcommand('tags') | 634 @webcommand('tags') |
605 def tags(web): | 635 def tags(web): |
606 """ | 636 """ |
607 /tags | 637 /tags |
621 if notip: | 651 if notip: |
622 t = [(k, n) for k, n in i if k != "tip"] | 652 t = [(k, n) for k, n in i if k != "tip"] |
623 if latestonly: | 653 if latestonly: |
624 t = t[:1] | 654 t = t[:1] |
625 for k, n in t: | 655 for k, n in t: |
626 yield {"parity": next(parity), | 656 yield { |
627 "tag": k, | 657 "parity": next(parity), |
628 "date": web.repo[n].date(), | 658 "tag": k, |
629 "node": hex(n)} | 659 "date": web.repo[n].date(), |
660 "node": hex(n), | |
661 } | |
630 | 662 |
631 return web.sendtemplate( | 663 return web.sendtemplate( |
632 'tags', | 664 'tags', |
633 node=hex(web.repo.changelog.tip()), | 665 node=hex(web.repo.changelog.tip()), |
634 entries=templateutil.mappinggenerator(entries, args=(False, False)), | 666 entries=templateutil.mappinggenerator(entries, args=(False, False)), |
635 entriesnotip=templateutil.mappinggenerator(entries, | 667 entriesnotip=templateutil.mappinggenerator(entries, args=(True, False)), |
636 args=(True, False)), | 668 latestentry=templateutil.mappinggenerator(entries, args=(True, True)), |
637 latestentry=templateutil.mappinggenerator(entries, args=(True, True))) | 669 ) |
670 | |
638 | 671 |
639 @webcommand('bookmarks') | 672 @webcommand('bookmarks') |
640 def bookmarks(web): | 673 def bookmarks(web): |
641 """ | 674 """ |
642 /bookmarks | 675 /bookmarks |
656 def entries(context, latestonly): | 689 def entries(context, latestonly): |
657 t = i | 690 t = i |
658 if latestonly: | 691 if latestonly: |
659 t = i[:1] | 692 t = i[:1] |
660 for k, n in t: | 693 for k, n in t: |
661 yield {"parity": next(parity), | 694 yield { |
662 "bookmark": k, | 695 "parity": next(parity), |
663 "date": web.repo[n].date(), | 696 "bookmark": k, |
664 "node": hex(n)} | 697 "date": web.repo[n].date(), |
698 "node": hex(n), | |
699 } | |
665 | 700 |
666 if i: | 701 if i: |
667 latestrev = i[0][1] | 702 latestrev = i[0][1] |
668 else: | 703 else: |
669 latestrev = -1 | 704 latestrev = -1 |
672 return web.sendtemplate( | 707 return web.sendtemplate( |
673 'bookmarks', | 708 'bookmarks', |
674 node=hex(web.repo.changelog.tip()), | 709 node=hex(web.repo.changelog.tip()), |
675 lastchange=templateutil.mappinglist([{'date': lastdate}]), | 710 lastchange=templateutil.mappinglist([{'date': lastdate}]), |
676 entries=templateutil.mappinggenerator(entries, args=(False,)), | 711 entries=templateutil.mappinggenerator(entries, args=(False,)), |
677 latestentry=templateutil.mappinggenerator(entries, args=(True,))) | 712 latestentry=templateutil.mappinggenerator(entries, args=(True,)), |
713 ) | |
714 | |
678 | 715 |
679 @webcommand('branches') | 716 @webcommand('branches') |
680 def branches(web): | 717 def branches(web): |
681 """ | 718 """ |
682 /branches | 719 /branches |
695 | 732 |
696 return web.sendtemplate( | 733 return web.sendtemplate( |
697 'branches', | 734 'branches', |
698 node=hex(web.repo.changelog.tip()), | 735 node=hex(web.repo.changelog.tip()), |
699 entries=entries, | 736 entries=entries, |
700 latestentry=latestentry) | 737 latestentry=latestentry, |
738 ) | |
739 | |
701 | 740 |
702 @webcommand('summary') | 741 @webcommand('summary') |
703 def summary(web): | 742 def summary(web): |
704 """ | 743 """ |
705 /summary | 744 /summary |
716 | 755 |
717 def tagentries(context): | 756 def tagentries(context): |
718 parity = paritygen(web.stripecount) | 757 parity = paritygen(web.stripecount) |
719 count = 0 | 758 count = 0 |
720 for k, n in i: | 759 for k, n in i: |
721 if k == "tip": # skip tip | 760 if k == "tip": # skip tip |
722 continue | 761 continue |
723 | 762 |
724 count += 1 | 763 count += 1 |
725 if count > 10: # limit to 10 tags | 764 if count > 10: # limit to 10 tags |
726 break | 765 break |
727 | 766 |
728 yield { | 767 yield { |
729 'parity': next(parity), | 768 'parity': next(parity), |
730 'tag': k, | 769 'tag': k, |
736 parity = paritygen(web.stripecount) | 775 parity = paritygen(web.stripecount) |
737 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo] | 776 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo] |
738 sortkey = lambda b: (web.repo[b[1]].rev(), b[0]) | 777 sortkey = lambda b: (web.repo[b[1]].rev(), b[0]) |
739 marks = sorted(marks, key=sortkey, reverse=True) | 778 marks = sorted(marks, key=sortkey, reverse=True) |
740 for k, n in marks[:10]: # limit to 10 bookmarks | 779 for k, n in marks[:10]: # limit to 10 bookmarks |
741 yield {'parity': next(parity), | 780 yield { |
742 'bookmark': k, | 781 'parity': next(parity), |
743 'date': web.repo[n].date(), | 782 'bookmark': k, |
744 'node': hex(n)} | 783 'date': web.repo[n].date(), |
784 'node': hex(n), | |
785 } | |
745 | 786 |
746 def changelist(context): | 787 def changelist(context): |
747 parity = paritygen(web.stripecount, offset=start - end) | 788 parity = paritygen(web.stripecount, offset=start - end) |
748 l = [] # build a list in forward order for efficiency | 789 l = [] # build a list in forward order for efficiency |
749 revs = [] | 790 revs = [] |
750 if start < end: | 791 if start < end: |
751 revs = web.repo.changelog.revs(start, end - 1) | 792 revs = web.repo.changelog.revs(start, end - 1) |
752 for i in revs: | 793 for i in revs: |
753 ctx = web.repo[i] | 794 ctx = web.repo[i] |
774 owner=get_contact(web.config) or 'unknown', | 815 owner=get_contact(web.config) or 'unknown', |
775 lastchange=tip.date(), | 816 lastchange=tip.date(), |
776 tags=templateutil.mappinggenerator(tagentries, name='tagentry'), | 817 tags=templateutil.mappinggenerator(tagentries, name='tagentry'), |
777 bookmarks=templateutil.mappinggenerator(bookmarks), | 818 bookmarks=templateutil.mappinggenerator(bookmarks), |
778 branches=webutil.branchentries(web.repo, web.stripecount, 10), | 819 branches=webutil.branchentries(web.repo, web.stripecount, 10), |
779 shortlog=templateutil.mappinggenerator(changelist, | 820 shortlog=templateutil.mappinggenerator( |
780 name='shortlogentry'), | 821 changelist, name='shortlogentry' |
822 ), | |
781 node=tip.hex(), | 823 node=tip.hex(), |
782 symrev='tip', | 824 symrev='tip', |
783 archives=web.archivelist('tip'), | 825 archives=web.archivelist('tip'), |
784 labels=templateutil.hybridlist(labels, name='label')) | 826 labels=templateutil.hybridlist(labels, name='label'), |
827 ) | |
828 | |
785 | 829 |
786 @webcommand('filediff') | 830 @webcommand('filediff') |
787 def filediff(web): | 831 def filediff(web): |
788 """ | 832 """ |
789 /diff/{revision}/{path} | 833 /diff/{revision}/{path} |
826 'filediff', | 870 'filediff', |
827 file=path, | 871 file=path, |
828 symrev=webutil.symrevorshortnode(web.req, ctx), | 872 symrev=webutil.symrevorshortnode(web.req, ctx), |
829 rename=rename, | 873 rename=rename, |
830 diff=diffs, | 874 diff=diffs, |
831 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))) | 875 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)) |
876 ) | |
877 | |
832 | 878 |
833 diff = webcommand('diff')(filediff) | 879 diff = webcommand('diff')(filediff) |
880 | |
834 | 881 |
835 @webcommand('comparison') | 882 @webcommand('comparison') |
836 def comparison(web): | 883 def comparison(web): |
837 """ | 884 """ |
838 /comparison/{revision}/{path} | 885 /comparison/{revision}/{path} |
862 | 909 |
863 def filelines(f): | 910 def filelines(f): |
864 if f.isbinary(): | 911 if f.isbinary(): |
865 mt = pycompat.sysbytes( | 912 mt = pycompat.sysbytes( |
866 mimetypes.guess_type(pycompat.fsdecode(f.path()))[0] | 913 mimetypes.guess_type(pycompat.fsdecode(f.path()))[0] |
867 or r'application/octet-stream') | 914 or r'application/octet-stream' |
915 ) | |
868 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))] | 916 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))] |
869 return f.data().splitlines() | 917 return f.data().splitlines() |
870 | 918 |
871 fctx = None | 919 fctx = None |
872 parent = ctx.p1() | 920 parent = ctx.p1() |
903 leftrev=leftrev, | 951 leftrev=leftrev, |
904 leftnode=hex(leftnode), | 952 leftnode=hex(leftnode), |
905 rightrev=rightrev, | 953 rightrev=rightrev, |
906 rightnode=hex(rightnode), | 954 rightnode=hex(rightnode), |
907 comparison=comparison, | 955 comparison=comparison, |
908 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx))) | 956 **pycompat.strkwargs(webutil.commonentry(web.repo, ctx)) |
957 ) | |
958 | |
909 | 959 |
910 @webcommand('annotate') | 960 @webcommand('annotate') |
911 def annotate(web): | 961 def annotate(web): |
912 """ | 962 """ |
913 /annotate/{revision}/{path} | 963 /annotate/{revision}/{path} |
932 # parents() is called once per line and several lines likely belong to | 982 # parents() is called once per line and several lines likely belong to |
933 # same revision. So it is worth caching. | 983 # same revision. So it is worth caching. |
934 # TODO there are still redundant operations within basefilectx.parents() | 984 # TODO there are still redundant operations within basefilectx.parents() |
935 # and from the fctx.annotate() call itself that could be cached. | 985 # and from the fctx.annotate() call itself that could be cached. |
936 parentscache = {} | 986 parentscache = {} |
987 | |
937 def parents(context, f): | 988 def parents(context, f): |
938 rev = f.rev() | 989 rev = f.rev() |
939 if rev not in parentscache: | 990 if rev not in parentscache: |
940 parentscache[rev] = [] | 991 parentscache[rev] = [] |
941 for p in f.parents(): | 992 for p in f.parents(): |
950 | 1001 |
951 def annotate(context): | 1002 def annotate(context): |
952 if fctx.isbinary(): | 1003 if fctx.isbinary(): |
953 mt = pycompat.sysbytes( | 1004 mt = pycompat.sysbytes( |
954 mimetypes.guess_type(pycompat.fsdecode(fctx.path()))[0] | 1005 mimetypes.guess_type(pycompat.fsdecode(fctx.path()))[0] |
955 or r'application/octet-stream') | 1006 or r'application/octet-stream' |
956 lines = [dagop.annotateline(fctx=fctx.filectx(fctx.filerev()), | 1007 ) |
957 lineno=1, text='(binary:%s)' % mt)] | 1008 lines = [ |
1009 dagop.annotateline( | |
1010 fctx=fctx.filectx(fctx.filerev()), | |
1011 lineno=1, | |
1012 text='(binary:%s)' % mt, | |
1013 ) | |
1014 ] | |
958 else: | 1015 else: |
959 lines = webutil.annotate(web.req, fctx, web.repo.ui) | 1016 lines = webutil.annotate(web.req, fctx, web.repo.ui) |
960 | 1017 |
961 previousrev = None | 1018 previousrev = None |
962 blockparitygen = paritygen(1) | 1019 blockparitygen = paritygen(1) |
967 blockhead = True | 1024 blockhead = True |
968 blockparity = next(blockparitygen) | 1025 blockparity = next(blockparitygen) |
969 else: | 1026 else: |
970 blockhead = None | 1027 blockhead = None |
971 previousrev = rev | 1028 previousrev = rev |
972 yield {"parity": next(parity), | 1029 yield { |
973 "node": f.hex(), | 1030 "parity": next(parity), |
974 "rev": rev, | 1031 "node": f.hex(), |
975 "author": f.user(), | 1032 "rev": rev, |
976 "parents": templateutil.mappinggenerator(parents, args=(f,)), | 1033 "author": f.user(), |
977 "desc": f.description(), | 1034 "parents": templateutil.mappinggenerator(parents, args=(f,)), |
978 "extra": f.extra(), | 1035 "desc": f.description(), |
979 "file": f.path(), | 1036 "extra": f.extra(), |
980 "blockhead": blockhead, | 1037 "file": f.path(), |
981 "blockparity": blockparity, | 1038 "blockhead": blockhead, |
982 "targetline": aline.lineno, | 1039 "blockparity": blockparity, |
983 "line": aline.text, | 1040 "targetline": aline.lineno, |
984 "lineno": lineno + 1, | 1041 "line": aline.text, |
985 "lineid": "l%d" % (lineno + 1), | 1042 "lineno": lineno + 1, |
986 "linenumber": "% 6d" % (lineno + 1), | 1043 "lineid": "l%d" % (lineno + 1), |
987 "revdate": f.date()} | 1044 "linenumber": "% 6d" % (lineno + 1), |
1045 "revdate": f.date(), | |
1046 } | |
988 | 1047 |
989 diffopts = webutil.difffeatureopts(web.req, web.repo.ui, 'annotate') | 1048 diffopts = webutil.difffeatureopts(web.req, web.repo.ui, 'annotate') |
990 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults} | 1049 diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults} |
991 | 1050 |
992 return web.sendtemplate( | 1051 return web.sendtemplate( |
997 symrev=webutil.symrevorshortnode(web.req, fctx), | 1056 symrev=webutil.symrevorshortnode(web.req, fctx), |
998 rename=webutil.renamelink(fctx), | 1057 rename=webutil.renamelink(fctx), |
999 permissions=fctx.manifest().flags(f), | 1058 permissions=fctx.manifest().flags(f), |
1000 ishead=int(ishead), | 1059 ishead=int(ishead), |
1001 diffopts=templateutil.hybriddict(diffopts), | 1060 diffopts=templateutil.hybriddict(diffopts), |
1002 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))) | 1061 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)) |
1062 ) | |
1063 | |
1003 | 1064 |
1004 @webcommand('filelog') | 1065 @webcommand('filelog') |
1005 def filelog(web): | 1066 def filelog(web): |
1006 """ | 1067 """ |
1007 /filelog/{revision}/{path} | 1068 /filelog/{revision}/{path} |
1021 fl = fctx.filelog() | 1082 fl = fctx.filelog() |
1022 except error.LookupError: | 1083 except error.LookupError: |
1023 f = webutil.cleanpath(web.repo, web.req.qsparams['file']) | 1084 f = webutil.cleanpath(web.repo, web.req.qsparams['file']) |
1024 fl = web.repo.file(f) | 1085 fl = web.repo.file(f) |
1025 numrevs = len(fl) | 1086 numrevs = len(fl) |
1026 if not numrevs: # file doesn't exist at all | 1087 if not numrevs: # file doesn't exist at all |
1027 raise | 1088 raise |
1028 rev = webutil.changectx(web.repo, web.req).rev() | 1089 rev = webutil.changectx(web.repo, web.req).rev() |
1029 first = fl.linkrev(0) | 1090 first = fl.linkrev(0) |
1030 if rev < first: # current rev is from before file existed | 1091 if rev < first: # current rev is from before file existed |
1031 raise | 1092 raise |
1032 frev = numrevs - 1 | 1093 frev = numrevs - 1 |
1033 while fl.linkrev(frev) > rev: | 1094 while fl.linkrev(frev) > rev: |
1034 frev -= 1 | 1095 frev -= 1 |
1035 fctx = web.repo.filectx(f, fl.linkrev(frev)) | 1096 fctx = web.repo.filectx(f, fl.linkrev(frev)) |
1056 descend = 'descend' in web.req.qsparams | 1117 descend = 'descend' in web.req.qsparams |
1057 if descend: | 1118 if descend: |
1058 lessvars['descend'] = morevars['descend'] = web.req.qsparams['descend'] | 1119 lessvars['descend'] = morevars['descend'] = web.req.qsparams['descend'] |
1059 | 1120 |
1060 count = fctx.filerev() + 1 | 1121 count = fctx.filerev() + 1 |
1061 start = max(0, count - revcount) # first rev on this page | 1122 start = max(0, count - revcount) # first rev on this page |
1062 end = min(count, start + revcount) # last rev on this page | 1123 end = min(count, start + revcount) # last rev on this page |
1063 parity = paritygen(web.stripecount, offset=start - end) | 1124 parity = paritygen(web.stripecount, offset=start - end) |
1064 | 1125 |
1065 repo = web.repo | 1126 repo = web.repo |
1066 filelog = fctx.filelog() | 1127 filelog = fctx.filelog() |
1067 revs = [filerev for filerev in filelog.revs(start, end - 1) | 1128 revs = [ |
1068 if filelog.linkrev(filerev) in repo] | 1129 filerev |
1130 for filerev in filelog.revs(start, end - 1) | |
1131 if filelog.linkrev(filerev) in repo | |
1132 ] | |
1069 entries = [] | 1133 entries = [] |
1070 | 1134 |
1071 diffstyle = web.config('web', 'style') | 1135 diffstyle = web.config('web', 'style') |
1072 if 'style' in web.req.qsparams: | 1136 if 'style' in web.req.qsparams: |
1073 diffstyle = web.req.qsparams['style'] | 1137 diffstyle = web.req.qsparams['style'] |
1074 | 1138 |
1075 def diff(fctx, linerange=None): | 1139 def diff(fctx, linerange=None): |
1076 ctx = fctx.changectx() | 1140 ctx = fctx.changectx() |
1077 basectx = ctx.p1() | 1141 basectx = ctx.p1() |
1078 path = fctx.path() | 1142 path = fctx.path() |
1079 return webutil.diffs(web, ctx, basectx, [path], diffstyle, | 1143 return webutil.diffs( |
1080 linerange=linerange, | 1144 web, |
1081 lineidprefix='%s-' % ctx.hex()[:12]) | 1145 ctx, |
1146 basectx, | |
1147 [path], | |
1148 diffstyle, | |
1149 linerange=linerange, | |
1150 lineidprefix='%s-' % ctx.hex()[:12], | |
1151 ) | |
1082 | 1152 |
1083 linerange = None | 1153 linerange = None |
1084 if lrange is not None: | 1154 if lrange is not None: |
1085 linerange = webutil.formatlinerange(*lrange) | 1155 linerange = webutil.formatlinerange(*lrange) |
1086 # deactivate numeric nav links when linerange is specified as this | 1156 # deactivate numeric nav links when linerange is specified as this |
1095 if patch: | 1165 if patch: |
1096 diffs = diff(c, linerange=lr) | 1166 diffs = diff(c, linerange=lr) |
1097 # follow renames accross filtered (not in range) revisions | 1167 # follow renames accross filtered (not in range) revisions |
1098 path = c.path() | 1168 path = c.path() |
1099 lm = webutil.commonentry(repo, c) | 1169 lm = webutil.commonentry(repo, c) |
1100 lm.update({ | 1170 lm.update( |
1101 'parity': next(parity), | 1171 { |
1102 'filerev': c.rev(), | 1172 'parity': next(parity), |
1103 'file': path, | 1173 'filerev': c.rev(), |
1104 'diff': diffs, | 1174 'file': path, |
1105 'linerange': webutil.formatlinerange(*lr), | 1175 'diff': diffs, |
1106 'rename': templateutil.mappinglist([]), | 1176 'linerange': webutil.formatlinerange(*lr), |
1107 }) | 1177 'rename': templateutil.mappinglist([]), |
1178 } | |
1179 ) | |
1108 entries.append(lm) | 1180 entries.append(lm) |
1109 if i == revcount: | 1181 if i == revcount: |
1110 break | 1182 break |
1111 lessvars['linerange'] = webutil.formatlinerange(*lrange) | 1183 lessvars['linerange'] = webutil.formatlinerange(*lrange) |
1112 morevars['linerange'] = lessvars['linerange'] | 1184 morevars['linerange'] = lessvars['linerange'] |
1115 iterfctx = fctx.filectx(i) | 1187 iterfctx = fctx.filectx(i) |
1116 diffs = None | 1188 diffs = None |
1117 if patch: | 1189 if patch: |
1118 diffs = diff(iterfctx) | 1190 diffs = diff(iterfctx) |
1119 lm = webutil.commonentry(repo, iterfctx) | 1191 lm = webutil.commonentry(repo, iterfctx) |
1120 lm.update({ | 1192 lm.update( |
1121 'parity': next(parity), | 1193 { |
1122 'filerev': i, | 1194 'parity': next(parity), |
1123 'file': f, | 1195 'filerev': i, |
1124 'diff': diffs, | 1196 'file': f, |
1125 'rename': webutil.renamelink(iterfctx), | 1197 'diff': diffs, |
1126 }) | 1198 'rename': webutil.renamelink(iterfctx), |
1199 } | |
1200 ) | |
1127 entries.append(lm) | 1201 entries.append(lm) |
1128 entries.reverse() | 1202 entries.reverse() |
1129 revnav = webutil.filerevnav(web.repo, fctx.path()) | 1203 revnav = webutil.filerevnav(web.repo, fctx.path()) |
1130 nav = revnav.gen(end - 1, revcount, count) | 1204 nav = revnav.gen(end - 1, revcount, count) |
1131 | 1205 |
1142 latestentry=templateutil.mappinglist(latestentry), | 1216 latestentry=templateutil.mappinglist(latestentry), |
1143 linerange=linerange, | 1217 linerange=linerange, |
1144 revcount=revcount, | 1218 revcount=revcount, |
1145 morevars=morevars, | 1219 morevars=morevars, |
1146 lessvars=lessvars, | 1220 lessvars=lessvars, |
1147 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx))) | 1221 **pycompat.strkwargs(webutil.commonentry(web.repo, fctx)) |
1222 ) | |
1223 | |
1148 | 1224 |
1149 @webcommand('archive') | 1225 @webcommand('archive') |
1150 def archive(web): | 1226 def archive(web): |
1151 """ | 1227 """ |
1152 /archive/{revision}.{format}[/{path}] | 1228 /archive/{revision}.{format}[/{path}] |
1173 | 1249 |
1174 if type_ not in webutil.archivespecs: | 1250 if type_ not in webutil.archivespecs: |
1175 msg = 'Unsupported archive type: %s' % stringutil.pprint(type_) | 1251 msg = 'Unsupported archive type: %s' % stringutil.pprint(type_) |
1176 raise ErrorResponse(HTTP_NOT_FOUND, msg) | 1252 raise ErrorResponse(HTTP_NOT_FOUND, msg) |
1177 | 1253 |
1178 if not ((type_ in allowed or | 1254 if not ((type_ in allowed or web.configbool("web", "allow" + type_))): |
1179 web.configbool("web", "allow" + type_))): | |
1180 msg = 'Archive type not allowed: %s' % type_ | 1255 msg = 'Archive type not allowed: %s' % type_ |
1181 raise ErrorResponse(HTTP_FORBIDDEN, msg) | 1256 raise ErrorResponse(HTTP_FORBIDDEN, msg) |
1182 | 1257 |
1183 reponame = re.sub(br"\W+", "-", os.path.basename(web.reponame)) | 1258 reponame = re.sub(br"\W+", "-", os.path.basename(web.reponame)) |
1184 cnode = web.repo.lookup(key) | 1259 cnode = web.repo.lookup(key) |
1195 pats = ['path:' + file] | 1270 pats = ['path:' + file] |
1196 match = scmutil.match(ctx, pats, default='path') | 1271 match = scmutil.match(ctx, pats, default='path') |
1197 if pats: | 1272 if pats: |
1198 files = [f for f in ctx.manifest().keys() if match(f)] | 1273 files = [f for f in ctx.manifest().keys() if match(f)] |
1199 if not files: | 1274 if not files: |
1200 raise ErrorResponse(HTTP_NOT_FOUND, | 1275 raise ErrorResponse( |
1201 'file(s) not found: %s' % file) | 1276 HTTP_NOT_FOUND, 'file(s) not found: %s' % file |
1277 ) | |
1202 | 1278 |
1203 mimetype, artype, extension, encoding = webutil.archivespecs[type_] | 1279 mimetype, artype, extension, encoding = webutil.archivespecs[type_] |
1204 | 1280 |
1205 web.res.headers['Content-Type'] = mimetype | 1281 web.res.headers['Content-Type'] = mimetype |
1206 web.res.headers['Content-Disposition'] = 'attachment; filename=%s%s' % ( | 1282 web.res.headers['Content-Disposition'] = 'attachment; filename=%s%s' % ( |
1207 name, extension) | 1283 name, |
1284 extension, | |
1285 ) | |
1208 | 1286 |
1209 if encoding: | 1287 if encoding: |
1210 web.res.headers['Content-Encoding'] = encoding | 1288 web.res.headers['Content-Encoding'] = encoding |
1211 | 1289 |
1212 web.res.setbodywillwrite() | 1290 web.res.setbodywillwrite() |
1213 if list(web.res.sendresponse()): | 1291 if list(web.res.sendresponse()): |
1214 raise error.ProgrammingError('sendresponse() should not emit data ' | 1292 raise error.ProgrammingError( |
1215 'if writing later') | 1293 'sendresponse() should not emit data ' 'if writing later' |
1294 ) | |
1216 | 1295 |
1217 bodyfh = web.res.getbodyfile() | 1296 bodyfh = web.res.getbodyfile() |
1218 | 1297 |
1219 archival.archive(web.repo, bodyfh, cnode, artype, prefix=name, match=match, | 1298 archival.archive( |
1220 subrepos=web.configbool("web", "archivesubrepos")) | 1299 web.repo, |
1300 bodyfh, | |
1301 cnode, | |
1302 artype, | |
1303 prefix=name, | |
1304 match=match, | |
1305 subrepos=web.configbool("web", "archivesubrepos"), | |
1306 ) | |
1221 | 1307 |
1222 return [] | 1308 return [] |
1309 | |
1223 | 1310 |
1224 @webcommand('static') | 1311 @webcommand('static') |
1225 def static(web): | 1312 def static(web): |
1226 fname = web.req.qsparams['file'] | 1313 fname = web.req.qsparams['file'] |
1227 # a repo owner may set web.static in .hg/hgrc to get any file | 1314 # a repo owner may set web.static in .hg/hgrc to get any file |
1234 static = [os.path.join(p, 'static') for p in tp] | 1321 static = [os.path.join(p, 'static') for p in tp] |
1235 | 1322 |
1236 staticfile(static, fname, web.res) | 1323 staticfile(static, fname, web.res) |
1237 return web.res.sendresponse() | 1324 return web.res.sendresponse() |
1238 | 1325 |
1326 | |
1239 @webcommand('graph') | 1327 @webcommand('graph') |
1240 def graph(web): | 1328 def graph(web): |
1241 """ | 1329 """ |
1242 /graph[/{revision}] | 1330 /graph[/{revision}] |
1243 ------------------- | 1331 ------------------- |
1314 # We have to feed a baseset to dagwalker as it is expecting smartset | 1402 # We have to feed a baseset to dagwalker as it is expecting smartset |
1315 # object. This does not have a big impact on hgweb performance itself | 1403 # object. This does not have a big impact on hgweb performance itself |
1316 # since hgweb graphing code is not itself lazy yet. | 1404 # since hgweb graphing code is not itself lazy yet. |
1317 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs)) | 1405 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs)) |
1318 # As we said one line above... not lazy. | 1406 # As we said one line above... not lazy. |
1319 tree = list(item for item in graphmod.colored(dag, web.repo) | 1407 tree = list( |
1320 if item[1] == graphmod.CHANGESET) | 1408 item |
1409 for item in graphmod.colored(dag, web.repo) | |
1410 if item[1] == graphmod.CHANGESET | |
1411 ) | |
1321 | 1412 |
1322 def fulltree(): | 1413 def fulltree(): |
1323 pos = web.repo[graphtop].rev() | 1414 pos = web.repo[graphtop].rev() |
1324 tree = [] | 1415 tree = [] |
1325 if pos != -1: | 1416 if pos != -1: |
1326 revs = web.repo.changelog.revs(pos, lastrev) | 1417 revs = web.repo.changelog.revs(pos, lastrev) |
1327 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs)) | 1418 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs)) |
1328 tree = list(item for item in graphmod.colored(dag, web.repo) | 1419 tree = list( |
1329 if item[1] == graphmod.CHANGESET) | 1420 item |
1421 for item in graphmod.colored(dag, web.repo) | |
1422 if item[1] == graphmod.CHANGESET | |
1423 ) | |
1330 return tree | 1424 return tree |
1331 | 1425 |
1332 def jsdata(context): | 1426 def jsdata(context): |
1333 for (id, type, ctx, vtx, edges) in fulltree(): | 1427 for (id, type, ctx, vtx, edges) in fulltree(): |
1334 yield {'node': pycompat.bytestr(ctx), | 1428 yield { |
1335 'graphnode': webutil.getgraphnode(web.repo, ctx), | 1429 'node': pycompat.bytestr(ctx), |
1336 'vertex': vtx, | 1430 'graphnode': webutil.getgraphnode(web.repo, ctx), |
1337 'edges': edges} | 1431 'vertex': vtx, |
1432 'edges': edges, | |
1433 } | |
1338 | 1434 |
1339 def nodes(context): | 1435 def nodes(context): |
1340 parity = paritygen(web.stripecount) | 1436 parity = paritygen(web.stripecount) |
1341 for row, (id, type, ctx, vtx, edges) in enumerate(tree): | 1437 for row, (id, type, ctx, vtx, edges) in enumerate(tree): |
1342 entry = webutil.commonentry(web.repo, ctx) | 1438 entry = webutil.commonentry(web.repo, ctx) |
1343 edgedata = [{'col': edge[0], | 1439 edgedata = [ |
1344 'nextcol': edge[1], | 1440 { |
1345 'color': (edge[2] - 1) % 6 + 1, | 1441 'col': edge[0], |
1346 'width': edge[3], | 1442 'nextcol': edge[1], |
1347 'bcolor': edge[4]} | 1443 'color': (edge[2] - 1) % 6 + 1, |
1348 for edge in edges] | 1444 'width': edge[3], |
1349 | 1445 'bcolor': edge[4], |
1350 entry.update({'col': vtx[0], | 1446 } |
1351 'color': (vtx[1] - 1) % 6 + 1, | 1447 for edge in edges |
1352 'parity': next(parity), | 1448 ] |
1353 'edges': templateutil.mappinglist(edgedata), | 1449 |
1354 'row': row, | 1450 entry.update( |
1355 'nextrow': row + 1}) | 1451 { |
1452 'col': vtx[0], | |
1453 'color': (vtx[1] - 1) % 6 + 1, | |
1454 'parity': next(parity), | |
1455 'edges': templateutil.mappinglist(edgedata), | |
1456 'row': row, | |
1457 'nextrow': row + 1, | |
1458 } | |
1459 ) | |
1356 | 1460 |
1357 yield entry | 1461 yield entry |
1358 | 1462 |
1359 rows = len(tree) | 1463 rows = len(tree) |
1360 | 1464 |
1374 nextentry=templateutil.mappinglist(nextentry), | 1478 nextentry=templateutil.mappinglist(nextentry), |
1375 jsdata=templateutil.mappinggenerator(jsdata), | 1479 jsdata=templateutil.mappinggenerator(jsdata), |
1376 nodes=templateutil.mappinggenerator(nodes), | 1480 nodes=templateutil.mappinggenerator(nodes), |
1377 node=ctx.hex(), | 1481 node=ctx.hex(), |
1378 archives=web.archivelist('tip'), | 1482 archives=web.archivelist('tip'), |
1379 changenav=changenav) | 1483 changenav=changenav, |
1484 ) | |
1485 | |
1380 | 1486 |
1381 def _getdoc(e): | 1487 def _getdoc(e): |
1382 doc = e[0].__doc__ | 1488 doc = e[0].__doc__ |
1383 if doc: | 1489 if doc: |
1384 doc = _(doc).partition('\n')[0] | 1490 doc = _(doc).partition('\n')[0] |
1385 else: | 1491 else: |
1386 doc = _('(no help text available)') | 1492 doc = _('(no help text available)') |
1387 return doc | 1493 return doc |
1388 | 1494 |
1495 | |
1389 @webcommand('help') | 1496 @webcommand('help') |
1390 def help(web): | 1497 def help(web): |
1391 """ | 1498 """ |
1392 /help[/{topic}] | 1499 /help[/{topic}] |
1393 --------------- | 1500 --------------- |
1403 """ | 1510 """ |
1404 from .. import commands, help as helpmod # avoid cycle | 1511 from .. import commands, help as helpmod # avoid cycle |
1405 | 1512 |
1406 topicname = web.req.qsparams.get('node') | 1513 topicname = web.req.qsparams.get('node') |
1407 if not topicname: | 1514 if not topicname: |
1515 | |
1408 def topics(context): | 1516 def topics(context): |
1409 for h in helpmod.helptable: | 1517 for h in helpmod.helptable: |
1410 entries, summary, _doc = h[0:3] | 1518 entries, summary, _doc = h[0:3] |
1411 yield {'topic': entries[0], 'summary': summary} | 1519 yield {'topic': entries[0], 'summary': summary} |
1412 | 1520 |
1436 return web.sendtemplate( | 1544 return web.sendtemplate( |
1437 'helptopics', | 1545 'helptopics', |
1438 topics=templateutil.mappinggenerator(topics), | 1546 topics=templateutil.mappinggenerator(topics), |
1439 earlycommands=templateutil.mappinggenerator(earlycommands), | 1547 earlycommands=templateutil.mappinggenerator(earlycommands), |
1440 othercommands=templateutil.mappinggenerator(othercommands), | 1548 othercommands=templateutil.mappinggenerator(othercommands), |
1441 title='Index') | 1549 title='Index', |
1550 ) | |
1442 | 1551 |
1443 # Render an index of sub-topics. | 1552 # Render an index of sub-topics. |
1444 if topicname in helpmod.subtopics: | 1553 if topicname in helpmod.subtopics: |
1445 topics = [] | 1554 topics = [] |
1446 for entries, summary, _doc in helpmod.subtopics[topicname]: | 1555 for entries, summary, _doc in helpmod.subtopics[topicname]: |
1447 topics.append({ | 1556 topics.append( |
1448 'topic': '%s.%s' % (topicname, entries[0]), | 1557 { |
1449 'basename': entries[0], | 1558 'topic': '%s.%s' % (topicname, entries[0]), |
1450 'summary': summary, | 1559 'basename': entries[0], |
1451 }) | 1560 'summary': summary, |
1561 } | |
1562 ) | |
1452 | 1563 |
1453 return web.sendtemplate( | 1564 return web.sendtemplate( |
1454 'helptopics', | 1565 'helptopics', |
1455 topics=templateutil.mappinglist(topics), | 1566 topics=templateutil.mappinglist(topics), |
1456 title=topicname, | 1567 title=topicname, |
1457 subindex=True) | 1568 subindex=True, |
1569 ) | |
1458 | 1570 |
1459 u = webutil.wsgiui.load() | 1571 u = webutil.wsgiui.load() |
1460 u.verbose = True | 1572 u.verbose = True |
1461 | 1573 |
1462 # Render a page from a sub-topic. | 1574 # Render a page from a sub-topic. |
1473 try: | 1585 try: |
1474 doc = helpmod.help_(u, commands, topic, subtopic=subtopic) | 1586 doc = helpmod.help_(u, commands, topic, subtopic=subtopic) |
1475 except error.Abort: | 1587 except error.Abort: |
1476 raise ErrorResponse(HTTP_NOT_FOUND) | 1588 raise ErrorResponse(HTTP_NOT_FOUND) |
1477 | 1589 |
1478 return web.sendtemplate( | 1590 return web.sendtemplate('help', topic=topicname, doc=doc) |
1479 'help', | 1591 |
1480 topic=topicname, | |
1481 doc=doc) | |
1482 | 1592 |
1483 # tell hggettext to extract docstrings from these functions: | 1593 # tell hggettext to extract docstrings from these functions: |
1484 i18nfunctions = commands.values() | 1594 i18nfunctions = commands.values() |