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