mercurial/hgweb/hgweb_mod.py
changeset 6393 894875eae49b
parent 6392 2540521dc7c1
child 6460 a63aed912e54
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