Mercurial > public > mercurial-scm > hg
comparison mercurial/hgweb/hgweb_mod.py @ 6393:894875eae49b
hgweb: refactor hgweb code
author | Dirkjan Ochtman <dirkjan@ochtman.nl> |
---|---|
date | Fri, 28 Mar 2008 19:40:44 +0100 |
parents | 2540521dc7c1 |
children | a63aed912e54 |
comparison
equal
deleted
inserted
replaced
6392:2540521dc7c1 | 6393:894875eae49b |
---|---|
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
5 # | 5 # |
6 # This software may be used and distributed according to the terms | 6 # This software may be used and distributed according to the terms |
7 # of the GNU General Public License, incorporated herein by reference. | 7 # of the GNU General Public License, incorporated herein by reference. |
8 | 8 |
9 import os, mimetypes, re | 9 import os, mimetypes |
10 from mercurial.node import hex, nullid, short | 10 from mercurial.node import hex, nullid |
11 from mercurial.repo import RepoError | 11 from mercurial.repo import RepoError |
12 from mercurial import mdiff, ui, hg, util, archival, patch, hook | 12 from mercurial import mdiff, ui, hg, util, patch, hook |
13 from mercurial import revlog, templater, templatefilters, changegroup | 13 from mercurial import revlog, templater, templatefilters, changegroup |
14 from common import get_mtime, style_map, paritygen, countgen, get_contact | 14 from common import get_mtime, style_map, paritygen, countgen, ErrorResponse |
15 from common import ErrorResponse | |
16 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR | 15 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR |
17 from request import wsgirequest | 16 from request import wsgirequest |
18 import webcommands, protocol, webutil | 17 import webcommands, protocol, webutil |
19 | 18 |
20 shortcuts = { | 19 shortcuts = { |
29 'ca': [('cmd', ['archive']), ('node', None)], | 28 'ca': [('cmd', ['archive']), ('node', None)], |
30 'tags': [('cmd', ['tags'])], | 29 'tags': [('cmd', ['tags'])], |
31 'tip': [('cmd', ['changeset']), ('node', ['tip'])], | 30 'tip': [('cmd', ['changeset']), ('node', ['tip'])], |
32 'static': [('cmd', ['static']), ('file', None)] | 31 'static': [('cmd', ['static']), ('file', None)] |
33 } | 32 } |
34 | |
35 def _up(p): | |
36 if p[0] != "/": | |
37 p = "/" + p | |
38 if p[-1] == "/": | |
39 p = p[:-1] | |
40 up = os.path.dirname(p) | |
41 if up == "/": | |
42 return "/" | |
43 return up + "/" | |
44 | |
45 def revnavgen(pos, pagelen, limit, nodefunc): | |
46 def seq(factor, limit=None): | |
47 if limit: | |
48 yield limit | |
49 if limit >= 20 and limit <= 40: | |
50 yield 50 | |
51 else: | |
52 yield 1 * factor | |
53 yield 3 * factor | |
54 for f in seq(factor * 10): | |
55 yield f | |
56 | |
57 def nav(**map): | |
58 l = [] | |
59 last = 0 | |
60 for f in seq(1, pagelen): | |
61 if f < pagelen or f <= last: | |
62 continue | |
63 if f > limit: | |
64 break | |
65 last = f | |
66 if pos + f < limit: | |
67 l.append(("+%d" % f, hex(nodefunc(pos + f).node()))) | |
68 if pos - f >= 0: | |
69 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node()))) | |
70 | |
71 try: | |
72 yield {"label": "(0)", "node": hex(nodefunc('0').node())} | |
73 | |
74 for label, node in l: | |
75 yield {"label": label, "node": node} | |
76 | |
77 yield {"label": "tip", "node": "tip"} | |
78 except RepoError: | |
79 pass | |
80 | |
81 return nav | |
82 | 33 |
83 class hgweb(object): | 34 class hgweb(object): |
84 def __init__(self, repo, name=None): | 35 def __init__(self, repo, name=None): |
85 if isinstance(repo, str): | 36 if isinstance(repo, str): |
86 parentui = ui.ui(report_untrusted=False, interactive=False) | 37 parentui = ui.ui(report_untrusted=False, interactive=False) |
405 to = c1.filectx(f).data() | 356 to = c1.filectx(f).data() |
406 tn = None | 357 tn = None |
407 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f, | 358 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f, |
408 opts=diffopts), f, tn) | 359 opts=diffopts), f, tn) |
409 | 360 |
410 def changelog(self, tmpl, ctx, shortlog=False): | |
411 def changelist(limit=0,**map): | |
412 cl = self.repo.changelog | |
413 l = [] # build a list in forward order for efficiency | |
414 for i in xrange(start, end): | |
415 ctx = self.repo.changectx(i) | |
416 n = ctx.node() | |
417 showtags = webutil.showtag(self.repo, tmpl, 'changelogtag', n) | |
418 | |
419 l.insert(0, {"parity": parity.next(), | |
420 "author": ctx.user(), | |
421 "parent": webutil.siblings(ctx.parents(), i - 1), | |
422 "child": webutil.siblings(ctx.children(), i + 1), | |
423 "changelogtag": showtags, | |
424 "desc": ctx.description(), | |
425 "date": ctx.date(), | |
426 "files": self.listfilediffs(tmpl, ctx.files(), n), | |
427 "rev": i, | |
428 "node": hex(n), | |
429 "tags": webutil.nodetagsdict(self.repo, n), | |
430 "inbranch": webutil.nodeinbranch(self.repo, ctx), | |
431 "branches": webutil.nodebranchdict(self.repo, ctx) | |
432 }) | |
433 | |
434 if limit > 0: | |
435 l = l[:limit] | |
436 | |
437 for e in l: | |
438 yield e | |
439 | |
440 maxchanges = shortlog and self.maxshortchanges or self.maxchanges | |
441 cl = self.repo.changelog | |
442 count = cl.count() | |
443 pos = ctx.rev() | |
444 start = max(0, pos - maxchanges + 1) | |
445 end = min(count, start + maxchanges) | |
446 pos = end - 1 | |
447 parity = paritygen(self.stripecount, offset=start-end) | |
448 | |
449 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx) | |
450 | |
451 return tmpl(shortlog and 'shortlog' or 'changelog', | |
452 changenav=changenav, | |
453 node=hex(cl.tip()), | |
454 rev=pos, changesets=count, | |
455 entries=lambda **x: changelist(limit=0,**x), | |
456 latestentry=lambda **x: changelist(limit=1,**x), | |
457 archives=self.archivelist("tip")) | |
458 | |
459 def search(self, tmpl, query): | |
460 | |
461 def changelist(**map): | |
462 cl = self.repo.changelog | |
463 count = 0 | |
464 qw = query.lower().split() | |
465 | |
466 def revgen(): | |
467 for i in xrange(cl.count() - 1, 0, -100): | |
468 l = [] | |
469 for j in xrange(max(0, i - 100), i + 1): | |
470 ctx = self.repo.changectx(j) | |
471 l.append(ctx) | |
472 l.reverse() | |
473 for e in l: | |
474 yield e | |
475 | |
476 for ctx in revgen(): | |
477 miss = 0 | |
478 for q in qw: | |
479 if not (q in ctx.user().lower() or | |
480 q in ctx.description().lower() or | |
481 q in " ".join(ctx.files()).lower()): | |
482 miss = 1 | |
483 break | |
484 if miss: | |
485 continue | |
486 | |
487 count += 1 | |
488 n = ctx.node() | |
489 showtags = webutil.showtag(self.repo, tmpl, 'changelogtag', n) | |
490 | |
491 yield tmpl('searchentry', | |
492 parity=parity.next(), | |
493 author=ctx.user(), | |
494 parent=webutil.siblings(ctx.parents()), | |
495 child=webutil.siblings(ctx.children()), | |
496 changelogtag=showtags, | |
497 desc=ctx.description(), | |
498 date=ctx.date(), | |
499 files=self.listfilediffs(tmpl, ctx.files(), n), | |
500 rev=ctx.rev(), | |
501 node=hex(n), | |
502 tags=webutil.nodetagsdict(self.repo, n), | |
503 inbranch=webutil.nodeinbranch(self.repo, ctx), | |
504 branches=webutil.nodebranchdict(self.repo, ctx)) | |
505 | |
506 if count >= self.maxchanges: | |
507 break | |
508 | |
509 cl = self.repo.changelog | |
510 parity = paritygen(self.stripecount) | |
511 | |
512 return tmpl('search', | |
513 query=query, | |
514 node=hex(cl.tip()), | |
515 entries=changelist, | |
516 archives=self.archivelist("tip")) | |
517 | |
518 def changeset(self, tmpl, ctx): | |
519 n = ctx.node() | |
520 showtags = webutil.showtag(self.repo, tmpl, 'changesettag', n) | |
521 parents = ctx.parents() | |
522 p1 = parents[0].node() | |
523 | |
524 files = [] | |
525 parity = paritygen(self.stripecount) | |
526 for f in ctx.files(): | |
527 files.append(tmpl("filenodelink", | |
528 node=hex(n), file=f, | |
529 parity=parity.next())) | |
530 | |
531 def diff(**map): | |
532 yield self.diff(tmpl, p1, n, None) | |
533 | |
534 return tmpl('changeset', | |
535 diff=diff, | |
536 rev=ctx.rev(), | |
537 node=hex(n), | |
538 parent=webutil.siblings(parents), | |
539 child=webutil.siblings(ctx.children()), | |
540 changesettag=showtags, | |
541 author=ctx.user(), | |
542 desc=ctx.description(), | |
543 date=ctx.date(), | |
544 files=files, | |
545 archives=self.archivelist(hex(n)), | |
546 tags=webutil.nodetagsdict(self.repo, n), | |
547 branch=webutil.nodebranchnodefault(ctx), | |
548 inbranch=webutil.nodeinbranch(self.repo, ctx), | |
549 branches=webutil.nodebranchdict(self.repo, ctx)) | |
550 | |
551 def filelog(self, tmpl, fctx): | |
552 f = fctx.path() | |
553 fl = fctx.filelog() | |
554 count = fl.count() | |
555 pagelen = self.maxshortchanges | |
556 pos = fctx.filerev() | |
557 start = max(0, pos - pagelen + 1) | |
558 end = min(count, start + pagelen) | |
559 pos = end - 1 | |
560 parity = paritygen(self.stripecount, offset=start-end) | |
561 | |
562 def entries(limit=0, **map): | |
563 l = [] | |
564 | |
565 for i in xrange(start, end): | |
566 ctx = fctx.filectx(i) | |
567 n = fl.node(i) | |
568 | |
569 l.insert(0, {"parity": parity.next(), | |
570 "filerev": i, | |
571 "file": f, | |
572 "node": hex(ctx.node()), | |
573 "author": ctx.user(), | |
574 "date": ctx.date(), | |
575 "rename": webutil.renamelink(fl, n), | |
576 "parent": webutil.siblings(fctx.parents()), | |
577 "child": webutil.siblings(fctx.children()), | |
578 "desc": ctx.description()}) | |
579 | |
580 if limit > 0: | |
581 l = l[:limit] | |
582 | |
583 for e in l: | |
584 yield e | |
585 | |
586 nodefunc = lambda x: fctx.filectx(fileid=x) | |
587 nav = revnavgen(pos, pagelen, count, nodefunc) | |
588 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav, | |
589 entries=lambda **x: entries(limit=0, **x), | |
590 latestentry=lambda **x: entries(limit=1, **x)) | |
591 | |
592 def filerevision(self, tmpl, fctx): | |
593 f = fctx.path() | |
594 text = fctx.data() | |
595 fl = fctx.filelog() | |
596 n = fctx.filenode() | |
597 parity = paritygen(self.stripecount) | |
598 | |
599 if util.binary(text): | |
600 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream' | |
601 text = '(binary:%s)' % mt | |
602 | |
603 def lines(): | |
604 for lineno, t in enumerate(text.splitlines(1)): | |
605 yield {"line": t, | |
606 "lineid": "l%d" % (lineno + 1), | |
607 "linenumber": "% 6d" % (lineno + 1), | |
608 "parity": parity.next()} | |
609 | |
610 return tmpl("filerevision", | |
611 file=f, | |
612 path=_up(f), | |
613 text=lines(), | |
614 rev=fctx.rev(), | |
615 node=hex(fctx.node()), | |
616 author=fctx.user(), | |
617 date=fctx.date(), | |
618 desc=fctx.description(), | |
619 branch=webutil.nodebranchnodefault(fctx), | |
620 parent=webutil.siblings(fctx.parents()), | |
621 child=webutil.siblings(fctx.children()), | |
622 rename=webutil.renamelink(fl, n), | |
623 permissions=fctx.manifest().flags(f)) | |
624 | |
625 def fileannotate(self, tmpl, fctx): | |
626 f = fctx.path() | |
627 n = fctx.filenode() | |
628 fl = fctx.filelog() | |
629 parity = paritygen(self.stripecount) | |
630 | |
631 def annotate(**map): | |
632 last = None | |
633 if util.binary(fctx.data()): | |
634 mt = (mimetypes.guess_type(fctx.path())[0] | |
635 or 'application/octet-stream') | |
636 lines = enumerate([((fctx.filectx(fctx.filerev()), 1), | |
637 '(binary:%s)' % mt)]) | |
638 else: | |
639 lines = enumerate(fctx.annotate(follow=True, linenumber=True)) | |
640 for lineno, ((f, targetline), l) in lines: | |
641 fnode = f.filenode() | |
642 name = self.repo.ui.shortuser(f.user()) | |
643 | |
644 if last != fnode: | |
645 last = fnode | |
646 | |
647 yield {"parity": parity.next(), | |
648 "node": hex(f.node()), | |
649 "rev": f.rev(), | |
650 "author": name, | |
651 "file": f.path(), | |
652 "targetline": targetline, | |
653 "line": l, | |
654 "lineid": "l%d" % (lineno + 1), | |
655 "linenumber": "% 6d" % (lineno + 1)} | |
656 | |
657 return tmpl("fileannotate", | |
658 file=f, | |
659 annotate=annotate, | |
660 path=_up(f), | |
661 rev=fctx.rev(), | |
662 node=hex(fctx.node()), | |
663 author=fctx.user(), | |
664 date=fctx.date(), | |
665 desc=fctx.description(), | |
666 rename=webutil.renamelink(fl, n), | |
667 branch=webutil.nodebranchnodefault(fctx), | |
668 parent=webutil.siblings(fctx.parents()), | |
669 child=webutil.siblings(fctx.children()), | |
670 permissions=fctx.manifest().flags(f)) | |
671 | |
672 def manifest(self, tmpl, ctx, path): | |
673 mf = ctx.manifest() | |
674 node = ctx.node() | |
675 | |
676 files = {} | |
677 parity = paritygen(self.stripecount) | |
678 | |
679 if path and path[-1] != "/": | |
680 path += "/" | |
681 l = len(path) | |
682 abspath = "/" + path | |
683 | |
684 for f, n in mf.items(): | |
685 if f[:l] != path: | |
686 continue | |
687 remain = f[l:] | |
688 if "/" in remain: | |
689 short = remain[:remain.index("/") + 1] # bleah | |
690 files[short] = (f, None) | |
691 else: | |
692 short = os.path.basename(remain) | |
693 files[short] = (f, n) | |
694 | |
695 if not files: | |
696 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path) | |
697 | |
698 def filelist(**map): | |
699 fl = files.keys() | |
700 fl.sort() | |
701 for f in fl: | |
702 full, fnode = files[f] | |
703 if not fnode: | |
704 continue | |
705 | |
706 fctx = ctx.filectx(full) | |
707 yield {"file": full, | |
708 "parity": parity.next(), | |
709 "basename": f, | |
710 "date": fctx.changectx().date(), | |
711 "size": fctx.size(), | |
712 "permissions": mf.flags(full)} | |
713 | |
714 def dirlist(**map): | |
715 fl = files.keys() | |
716 fl.sort() | |
717 for f in fl: | |
718 full, fnode = files[f] | |
719 if fnode: | |
720 continue | |
721 | |
722 yield {"parity": parity.next(), | |
723 "path": "%s%s" % (abspath, f), | |
724 "basename": f[:-1]} | |
725 | |
726 return tmpl("manifest", | |
727 rev=ctx.rev(), | |
728 node=hex(node), | |
729 path=abspath, | |
730 up=_up(abspath), | |
731 upparity=parity.next(), | |
732 fentries=filelist, | |
733 dentries=dirlist, | |
734 archives=self.archivelist(hex(node)), | |
735 tags=webutil.nodetagsdict(self.repo, node), | |
736 inbranch=webutil.nodeinbranch(self.repo, ctx), | |
737 branches=webutil.nodebranchdict(self.repo, ctx)) | |
738 | |
739 def tags(self, tmpl): | |
740 i = self.repo.tagslist() | |
741 i.reverse() | |
742 parity = paritygen(self.stripecount) | |
743 | |
744 def entries(notip=False,limit=0, **map): | |
745 count = 0 | |
746 for k, n in i: | |
747 if notip and k == "tip": | |
748 continue | |
749 if limit > 0 and count >= limit: | |
750 continue | |
751 count = count + 1 | |
752 yield {"parity": parity.next(), | |
753 "tag": k, | |
754 "date": self.repo.changectx(n).date(), | |
755 "node": hex(n)} | |
756 | |
757 return tmpl("tags", | |
758 node=hex(self.repo.changelog.tip()), | |
759 entries=lambda **x: entries(False,0, **x), | |
760 entriesnotip=lambda **x: entries(True,0, **x), | |
761 latestentry=lambda **x: entries(True,1, **x)) | |
762 | |
763 def summary(self, tmpl): | |
764 i = self.repo.tagslist() | |
765 i.reverse() | |
766 | |
767 def tagentries(**map): | |
768 parity = paritygen(self.stripecount) | |
769 count = 0 | |
770 for k, n in i: | |
771 if k == "tip": # skip tip | |
772 continue; | |
773 | |
774 count += 1 | |
775 if count > 10: # limit to 10 tags | |
776 break; | |
777 | |
778 yield tmpl("tagentry", | |
779 parity=parity.next(), | |
780 tag=k, | |
781 node=hex(n), | |
782 date=self.repo.changectx(n).date()) | |
783 | |
784 | |
785 def branches(**map): | |
786 parity = paritygen(self.stripecount) | |
787 | |
788 b = self.repo.branchtags() | |
789 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()] | |
790 l.sort() | |
791 | |
792 for r,n,t in l: | |
793 ctx = self.repo.changectx(n) | |
794 | |
795 yield {'parity': parity.next(), | |
796 'branch': t, | |
797 'node': hex(n), | |
798 'date': ctx.date()} | |
799 | |
800 def changelist(**map): | |
801 parity = paritygen(self.stripecount, offset=start-end) | |
802 l = [] # build a list in forward order for efficiency | |
803 for i in xrange(start, end): | |
804 ctx = self.repo.changectx(i) | |
805 n = ctx.node() | |
806 hn = hex(n) | |
807 | |
808 l.insert(0, tmpl( | |
809 'shortlogentry', | |
810 parity=parity.next(), | |
811 author=ctx.user(), | |
812 desc=ctx.description(), | |
813 date=ctx.date(), | |
814 rev=i, | |
815 node=hn, | |
816 tags=webutil.nodetagsdict(self.repo, n), | |
817 inbranch=webutil.nodeinbranch(self.repo, ctx), | |
818 branches=webutil.nodebranchdict(self.repo, ctx))) | |
819 | |
820 yield l | |
821 | |
822 cl = self.repo.changelog | |
823 count = cl.count() | |
824 start = max(0, count - self.maxchanges) | |
825 end = min(count, start + self.maxchanges) | |
826 | |
827 return tmpl("summary", | |
828 desc=self.config("web", "description", "unknown"), | |
829 owner=get_contact(self.config) or "unknown", | |
830 lastchange=cl.read(cl.tip())[2], | |
831 tags=tagentries, | |
832 branches=branches, | |
833 shortlog=changelist, | |
834 node=hex(cl.tip()), | |
835 archives=self.archivelist("tip")) | |
836 | |
837 def filediff(self, tmpl, fctx): | |
838 n = fctx.node() | |
839 path = fctx.path() | |
840 parents = fctx.parents() | |
841 p1 = parents and parents[0].node() or nullid | |
842 | |
843 def diff(**map): | |
844 yield self.diff(tmpl, p1, n, [path]) | |
845 | |
846 return tmpl("filediff", | |
847 file=path, | |
848 node=hex(n), | |
849 rev=fctx.rev(), | |
850 branch=webutil.nodebranchnodefault(fctx), | |
851 parent=webutil.siblings(parents), | |
852 child=webutil.siblings(fctx.children()), | |
853 diff=diff) | |
854 | |
855 archive_specs = { | 361 archive_specs = { |
856 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None), | 362 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None), |
857 'gz': ('application/x-tar', 'tgz', '.tar.gz', None), | 363 'gz': ('application/x-tar', 'tgz', '.tar.gz', None), |
858 'zip': ('application/zip', 'zip', '.zip', None), | 364 'zip': ('application/zip', 'zip', '.zip', None), |
859 } | 365 } |
860 | 366 |
861 def archive(self, tmpl, req, key, type_): | |
862 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame)) | |
863 cnode = self.repo.lookup(key) | |
864 arch_version = key | |
865 if cnode == key or key == 'tip': | |
866 arch_version = short(cnode) | |
867 name = "%s-%s" % (reponame, arch_version) | |
868 mimetype, artype, extension, encoding = self.archive_specs[type_] | |
869 headers = [ | |
870 ('Content-Type', mimetype), | |
871 ('Content-Disposition', 'attachment; filename=%s%s' % | |
872 (name, extension)) | |
873 ] | |
874 if encoding: | |
875 headers.append(('Content-Encoding', encoding)) | |
876 req.header(headers) | |
877 req.respond(HTTP_OK) | |
878 archival.archive(self.repo, req, cnode, artype, prefix=name) | |
879 | |
880 def check_perm(self, req, op, default): | 367 def check_perm(self, req, op, default): |
881 '''check permission for operation based on user auth. | 368 '''check permission for operation based on user auth. |
882 return true if op allowed, else false. | 369 return true if op allowed, else false. |
883 default is policy to use if no config given.''' | 370 default is policy to use if no config given.''' |
884 | 371 |