mercurial/hgweb/hgweb_mod.py
changeset 2356 2db831b33e8f
parent 2355 eb08fb4d41e1
child 2358 8819fc1dcf4b
equal deleted inserted replaced
2355:eb08fb4d41e1 2356:2db831b33e8f
       
     1 # hgweb.py - web interface to a mercurial repository
       
     2 #
       
     3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
       
     4 # Copyright 2005 Matt Mackall <mpm@selenic.com>
       
     5 #
       
     6 # This software may be used and distributed according to the terms
       
     7 # of the GNU General Public License, incorporated herein by reference.
       
     8 
       
     9 import os
       
    10 import os.path
       
    11 import mimetypes
       
    12 from mercurial.demandload import demandload
       
    13 demandload(globals(), "re zlib ConfigParser")
       
    14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,templater")
       
    15 demandload(globals(), "mercurial.hgweb.request:hgrequest")
       
    16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
       
    17 from mercurial.node import *
       
    18 from mercurial.i18n import gettext as _
       
    19 
       
    20 def _up(p):
       
    21     if p[0] != "/":
       
    22         p = "/" + p
       
    23     if p[-1] == "/":
       
    24         p = p[:-1]
       
    25     up = os.path.dirname(p)
       
    26     if up == "/":
       
    27         return "/"
       
    28     return up + "/"
       
    29 
       
    30 class hgweb(object):
       
    31     def __init__(self, repo, name=None):
       
    32         if type(repo) == type(""):
       
    33             self.repo = hg.repository(ui.ui(), repo)
       
    34         else:
       
    35             self.repo = repo
       
    36 
       
    37         self.mtime = -1
       
    38         self.reponame = name
       
    39         self.archives = 'zip', 'gz', 'bz2'
       
    40 
       
    41     def refresh(self):
       
    42         mtime = get_mtime(self.repo.root)
       
    43         if mtime != self.mtime:
       
    44             self.mtime = mtime
       
    45             self.repo = hg.repository(self.repo.ui, self.repo.root)
       
    46             self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10))
       
    47             self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10))
       
    48             self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
       
    49 
       
    50     def archivelist(self, nodeid):
       
    51         for i in self.archives:
       
    52             if self.repo.ui.configbool("web", "allow" + i, False):
       
    53                 yield {"type" : i, "node" : nodeid, "url": ""}
       
    54 
       
    55     def listfiles(self, files, mf):
       
    56         for f in files[:self.maxfiles]:
       
    57             yield self.t("filenodelink", node=hex(mf[f]), file=f)
       
    58         if len(files) > self.maxfiles:
       
    59             yield self.t("fileellipses")
       
    60 
       
    61     def listfilediffs(self, files, changeset):
       
    62         for f in files[:self.maxfiles]:
       
    63             yield self.t("filedifflink", node=hex(changeset), file=f)
       
    64         if len(files) > self.maxfiles:
       
    65             yield self.t("fileellipses")
       
    66 
       
    67     def siblings(self, siblings=[], rev=None, hiderev=None, **args):
       
    68         if not rev:
       
    69             rev = lambda x: ""
       
    70         siblings = [s for s in siblings if s != nullid]
       
    71         if len(siblings) == 1 and rev(siblings[0]) == hiderev:
       
    72             return
       
    73         for s in siblings:
       
    74             yield dict(node=hex(s), rev=rev(s), **args)
       
    75 
       
    76     def renamelink(self, fl, node):
       
    77         r = fl.renamed(node)
       
    78         if r:
       
    79             return [dict(file=r[0], node=hex(r[1]))]
       
    80         return []
       
    81 
       
    82     def showtag(self, t1, node=nullid, **args):
       
    83         for t in self.repo.nodetags(node):
       
    84              yield self.t(t1, tag=t, **args)
       
    85 
       
    86     def diff(self, node1, node2, files):
       
    87         def filterfiles(filters, files):
       
    88             l = [x for x in files if x in filters]
       
    89 
       
    90             for t in filters:
       
    91                 if t and t[-1] != os.sep:
       
    92                     t += os.sep
       
    93                 l += [x for x in files if x.startswith(t)]
       
    94             return l
       
    95 
       
    96         parity = [0]
       
    97         def diffblock(diff, f, fn):
       
    98             yield self.t("diffblock",
       
    99                          lines=prettyprintlines(diff),
       
   100                          parity=parity[0],
       
   101                          file=f,
       
   102                          filenode=hex(fn or nullid))
       
   103             parity[0] = 1 - parity[0]
       
   104 
       
   105         def prettyprintlines(diff):
       
   106             for l in diff.splitlines(1):
       
   107                 if l.startswith('+'):
       
   108                     yield self.t("difflineplus", line=l)
       
   109                 elif l.startswith('-'):
       
   110                     yield self.t("difflineminus", line=l)
       
   111                 elif l.startswith('@'):
       
   112                     yield self.t("difflineat", line=l)
       
   113                 else:
       
   114                     yield self.t("diffline", line=l)
       
   115 
       
   116         r = self.repo
       
   117         cl = r.changelog
       
   118         mf = r.manifest
       
   119         change1 = cl.read(node1)
       
   120         change2 = cl.read(node2)
       
   121         mmap1 = mf.read(change1[0])
       
   122         mmap2 = mf.read(change2[0])
       
   123         date1 = util.datestr(change1[2])
       
   124         date2 = util.datestr(change2[2])
       
   125 
       
   126         modified, added, removed, deleted, unknown = r.changes(node1, node2)
       
   127         if files:
       
   128             modified, added, removed = map(lambda x: filterfiles(files, x),
       
   129                                            (modified, added, removed))
       
   130 
       
   131         diffopts = self.repo.ui.diffopts()
       
   132         showfunc = diffopts['showfunc']
       
   133         ignorews = diffopts['ignorews']
       
   134         for f in modified:
       
   135             to = r.file(f).read(mmap1[f])
       
   136             tn = r.file(f).read(mmap2[f])
       
   137             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
       
   138                             showfunc=showfunc, ignorews=ignorews), f, tn)
       
   139         for f in added:
       
   140             to = None
       
   141             tn = r.file(f).read(mmap2[f])
       
   142             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
       
   143                             showfunc=showfunc, ignorews=ignorews), f, tn)
       
   144         for f in removed:
       
   145             to = r.file(f).read(mmap1[f])
       
   146             tn = None
       
   147             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f,
       
   148                             showfunc=showfunc, ignorews=ignorews), f, tn)
       
   149 
       
   150     def changelog(self, pos):
       
   151         def changenav(**map):
       
   152             def seq(factor, maxchanges=None):
       
   153                 if maxchanges:
       
   154                     yield maxchanges
       
   155                     if maxchanges >= 20 and maxchanges <= 40:
       
   156                         yield 50
       
   157                 else:
       
   158                     yield 1 * factor
       
   159                     yield 3 * factor
       
   160                 for f in seq(factor * 10):
       
   161                     yield f
       
   162 
       
   163             l = []
       
   164             last = 0
       
   165             for f in seq(1, self.maxchanges):
       
   166                 if f < self.maxchanges or f <= last:
       
   167                     continue
       
   168                 if f > count:
       
   169                     break
       
   170                 last = f
       
   171                 r = "%d" % f
       
   172                 if pos + f < count:
       
   173                     l.append(("+" + r, pos + f))
       
   174                 if pos - f >= 0:
       
   175                     l.insert(0, ("-" + r, pos - f))
       
   176 
       
   177             yield {"rev": 0, "label": "(0)"}
       
   178 
       
   179             for label, rev in l:
       
   180                 yield {"label": label, "rev": rev}
       
   181 
       
   182             yield {"label": "tip", "rev": "tip"}
       
   183 
       
   184         def changelist(**map):
       
   185             parity = (start - end) & 1
       
   186             cl = self.repo.changelog
       
   187             l = [] # build a list in forward order for efficiency
       
   188             for i in range(start, end):
       
   189                 n = cl.node(i)
       
   190                 changes = cl.read(n)
       
   191                 hn = hex(n)
       
   192 
       
   193                 l.insert(0, {"parity": parity,
       
   194                              "author": changes[1],
       
   195                              "parent": self.siblings(cl.parents(n), cl.rev,
       
   196                                                      cl.rev(n) - 1),
       
   197                              "child": self.siblings(cl.children(n), cl.rev,
       
   198                                                     cl.rev(n) + 1),
       
   199                              "changelogtag": self.showtag("changelogtag",n),
       
   200                              "manifest": hex(changes[0]),
       
   201                              "desc": changes[4],
       
   202                              "date": changes[2],
       
   203                              "files": self.listfilediffs(changes[3], n),
       
   204                              "rev": i,
       
   205                              "node": hn})
       
   206                 parity = 1 - parity
       
   207 
       
   208             for e in l:
       
   209                 yield e
       
   210 
       
   211         cl = self.repo.changelog
       
   212         mf = cl.read(cl.tip())[0]
       
   213         count = cl.count()
       
   214         start = max(0, pos - self.maxchanges + 1)
       
   215         end = min(count, start + self.maxchanges)
       
   216         pos = end - 1
       
   217 
       
   218         yield self.t('changelog',
       
   219                      changenav=changenav,
       
   220                      manifest=hex(mf),
       
   221                      rev=pos, changesets=count, entries=changelist,
       
   222                      archives=self.archivelist("tip"))
       
   223 
       
   224     def search(self, query):
       
   225 
       
   226         def changelist(**map):
       
   227             cl = self.repo.changelog
       
   228             count = 0
       
   229             qw = query.lower().split()
       
   230 
       
   231             def revgen():
       
   232                 for i in range(cl.count() - 1, 0, -100):
       
   233                     l = []
       
   234                     for j in range(max(0, i - 100), i):
       
   235                         n = cl.node(j)
       
   236                         changes = cl.read(n)
       
   237                         l.append((n, j, changes))
       
   238                     l.reverse()
       
   239                     for e in l:
       
   240                         yield e
       
   241 
       
   242             for n, i, changes in revgen():
       
   243                 miss = 0
       
   244                 for q in qw:
       
   245                     if not (q in changes[1].lower() or
       
   246                             q in changes[4].lower() or
       
   247                             q in " ".join(changes[3][:20]).lower()):
       
   248                         miss = 1
       
   249                         break
       
   250                 if miss:
       
   251                     continue
       
   252 
       
   253                 count += 1
       
   254                 hn = hex(n)
       
   255 
       
   256                 yield self.t('searchentry',
       
   257                              parity=count & 1,
       
   258                              author=changes[1],
       
   259                              parent=self.siblings(cl.parents(n), cl.rev),
       
   260                              child=self.siblings(cl.children(n), cl.rev),
       
   261                              changelogtag=self.showtag("changelogtag",n),
       
   262                              manifest=hex(changes[0]),
       
   263                              desc=changes[4],
       
   264                              date=changes[2],
       
   265                              files=self.listfilediffs(changes[3], n),
       
   266                              rev=i,
       
   267                              node=hn)
       
   268 
       
   269                 if count >= self.maxchanges:
       
   270                     break
       
   271 
       
   272         cl = self.repo.changelog
       
   273         mf = cl.read(cl.tip())[0]
       
   274 
       
   275         yield self.t('search',
       
   276                      query=query,
       
   277                      manifest=hex(mf),
       
   278                      entries=changelist)
       
   279 
       
   280     def changeset(self, nodeid):
       
   281         cl = self.repo.changelog
       
   282         n = self.repo.lookup(nodeid)
       
   283         nodeid = hex(n)
       
   284         changes = cl.read(n)
       
   285         p1 = cl.parents(n)[0]
       
   286 
       
   287         files = []
       
   288         mf = self.repo.manifest.read(changes[0])
       
   289         for f in changes[3]:
       
   290             files.append(self.t("filenodelink",
       
   291                                 filenode=hex(mf.get(f, nullid)), file=f))
       
   292 
       
   293         def diff(**map):
       
   294             yield self.diff(p1, n, None)
       
   295 
       
   296         yield self.t('changeset',
       
   297                      diff=diff,
       
   298                      rev=cl.rev(n),
       
   299                      node=nodeid,
       
   300                      parent=self.siblings(cl.parents(n), cl.rev),
       
   301                      child=self.siblings(cl.children(n), cl.rev),
       
   302                      changesettag=self.showtag("changesettag",n),
       
   303                      manifest=hex(changes[0]),
       
   304                      author=changes[1],
       
   305                      desc=changes[4],
       
   306                      date=changes[2],
       
   307                      files=files,
       
   308                      archives=self.archivelist(nodeid))
       
   309 
       
   310     def filelog(self, f, filenode):
       
   311         cl = self.repo.changelog
       
   312         fl = self.repo.file(f)
       
   313         filenode = hex(fl.lookup(filenode))
       
   314         count = fl.count()
       
   315 
       
   316         def entries(**map):
       
   317             l = []
       
   318             parity = (count - 1) & 1
       
   319 
       
   320             for i in range(count):
       
   321                 n = fl.node(i)
       
   322                 lr = fl.linkrev(n)
       
   323                 cn = cl.node(lr)
       
   324                 cs = cl.read(cl.node(lr))
       
   325 
       
   326                 l.insert(0, {"parity": parity,
       
   327                              "filenode": hex(n),
       
   328                              "filerev": i,
       
   329                              "file": f,
       
   330                              "node": hex(cn),
       
   331                              "author": cs[1],
       
   332                              "date": cs[2],
       
   333                              "rename": self.renamelink(fl, n),
       
   334                              "parent": self.siblings(fl.parents(n),
       
   335                                                      fl.rev, file=f),
       
   336                              "child": self.siblings(fl.children(n),
       
   337                                                     fl.rev, file=f),
       
   338                              "desc": cs[4]})
       
   339                 parity = 1 - parity
       
   340 
       
   341             for e in l:
       
   342                 yield e
       
   343 
       
   344         yield self.t("filelog", file=f, filenode=filenode, entries=entries)
       
   345 
       
   346     def filerevision(self, f, node):
       
   347         fl = self.repo.file(f)
       
   348         n = fl.lookup(node)
       
   349         node = hex(n)
       
   350         text = fl.read(n)
       
   351         changerev = fl.linkrev(n)
       
   352         cl = self.repo.changelog
       
   353         cn = cl.node(changerev)
       
   354         cs = cl.read(cn)
       
   355         mfn = cs[0]
       
   356 
       
   357         mt = mimetypes.guess_type(f)[0]
       
   358         rawtext = text
       
   359         if util.binary(text):
       
   360             mt = mt or 'application/octet-stream'
       
   361             text = "(binary:%s)" % mt
       
   362         mt = mt or 'text/plain'
       
   363 
       
   364         def lines():
       
   365             for l, t in enumerate(text.splitlines(1)):
       
   366                 yield {"line": t,
       
   367                        "linenumber": "% 6d" % (l + 1),
       
   368                        "parity": l & 1}
       
   369 
       
   370         yield self.t("filerevision",
       
   371                      file=f,
       
   372                      filenode=node,
       
   373                      path=_up(f),
       
   374                      text=lines(),
       
   375                      raw=rawtext,
       
   376                      mimetype=mt,
       
   377                      rev=changerev,
       
   378                      node=hex(cn),
       
   379                      manifest=hex(mfn),
       
   380                      author=cs[1],
       
   381                      date=cs[2],
       
   382                      parent=self.siblings(fl.parents(n), fl.rev, file=f),
       
   383                      child=self.siblings(fl.children(n), fl.rev, file=f),
       
   384                      rename=self.renamelink(fl, n),
       
   385                      permissions=self.repo.manifest.readflags(mfn)[f])
       
   386 
       
   387     def fileannotate(self, f, node):
       
   388         bcache = {}
       
   389         ncache = {}
       
   390         fl = self.repo.file(f)
       
   391         n = fl.lookup(node)
       
   392         node = hex(n)
       
   393         changerev = fl.linkrev(n)
       
   394 
       
   395         cl = self.repo.changelog
       
   396         cn = cl.node(changerev)
       
   397         cs = cl.read(cn)
       
   398         mfn = cs[0]
       
   399 
       
   400         def annotate(**map):
       
   401             parity = 1
       
   402             last = None
       
   403             for r, l in fl.annotate(n):
       
   404                 try:
       
   405                     cnode = ncache[r]
       
   406                 except KeyError:
       
   407                     cnode = ncache[r] = self.repo.changelog.node(r)
       
   408 
       
   409                 try:
       
   410                     name = bcache[r]
       
   411                 except KeyError:
       
   412                     cl = self.repo.changelog.read(cnode)
       
   413                     bcache[r] = name = self.repo.ui.shortuser(cl[1])
       
   414 
       
   415                 if last != cnode:
       
   416                     parity = 1 - parity
       
   417                     last = cnode
       
   418 
       
   419                 yield {"parity": parity,
       
   420                        "node": hex(cnode),
       
   421                        "rev": r,
       
   422                        "author": name,
       
   423                        "file": f,
       
   424                        "line": l}
       
   425 
       
   426         yield self.t("fileannotate",
       
   427                      file=f,
       
   428                      filenode=node,
       
   429                      annotate=annotate,
       
   430                      path=_up(f),
       
   431                      rev=changerev,
       
   432                      node=hex(cn),
       
   433                      manifest=hex(mfn),
       
   434                      author=cs[1],
       
   435                      date=cs[2],
       
   436                      rename=self.renamelink(fl, n),
       
   437                      parent=self.siblings(fl.parents(n), fl.rev, file=f),
       
   438                      child=self.siblings(fl.children(n), fl.rev, file=f),
       
   439                      permissions=self.repo.manifest.readflags(mfn)[f])
       
   440 
       
   441     def manifest(self, mnode, path):
       
   442         man = self.repo.manifest
       
   443         mn = man.lookup(mnode)
       
   444         mnode = hex(mn)
       
   445         mf = man.read(mn)
       
   446         rev = man.rev(mn)
       
   447         changerev = man.linkrev(mn)
       
   448         node = self.repo.changelog.node(changerev)
       
   449         mff = man.readflags(mn)
       
   450 
       
   451         files = {}
       
   452 
       
   453         p = path[1:]
       
   454         if p and p[-1] != "/":
       
   455             p += "/"
       
   456         l = len(p)
       
   457 
       
   458         for f,n in mf.items():
       
   459             if f[:l] != p:
       
   460                 continue
       
   461             remain = f[l:]
       
   462             if "/" in remain:
       
   463                 short = remain[:remain.find("/") + 1] # bleah
       
   464                 files[short] = (f, None)
       
   465             else:
       
   466                 short = os.path.basename(remain)
       
   467                 files[short] = (f, n)
       
   468 
       
   469         def filelist(**map):
       
   470             parity = 0
       
   471             fl = files.keys()
       
   472             fl.sort()
       
   473             for f in fl:
       
   474                 full, fnode = files[f]
       
   475                 if not fnode:
       
   476                     continue
       
   477 
       
   478                 yield {"file": full,
       
   479                        "manifest": mnode,
       
   480                        "filenode": hex(fnode),
       
   481                        "parity": parity,
       
   482                        "basename": f,
       
   483                        "permissions": mff[full]}
       
   484                 parity = 1 - parity
       
   485 
       
   486         def dirlist(**map):
       
   487             parity = 0
       
   488             fl = files.keys()
       
   489             fl.sort()
       
   490             for f in fl:
       
   491                 full, fnode = files[f]
       
   492                 if fnode:
       
   493                     continue
       
   494 
       
   495                 yield {"parity": parity,
       
   496                        "path": os.path.join(path, f),
       
   497                        "manifest": mnode,
       
   498                        "basename": f[:-1]}
       
   499                 parity = 1 - parity
       
   500 
       
   501         yield self.t("manifest",
       
   502                      manifest=mnode,
       
   503                      rev=rev,
       
   504                      node=hex(node),
       
   505                      path=path,
       
   506                      up=_up(path),
       
   507                      fentries=filelist,
       
   508                      dentries=dirlist,
       
   509                      archives=self.archivelist(hex(node)))
       
   510 
       
   511     def tags(self):
       
   512         cl = self.repo.changelog
       
   513         mf = cl.read(cl.tip())[0]
       
   514 
       
   515         i = self.repo.tagslist()
       
   516         i.reverse()
       
   517 
       
   518         def entries(notip=False, **map):
       
   519             parity = 0
       
   520             for k,n in i:
       
   521                 if notip and k == "tip": continue
       
   522                 yield {"parity": parity,
       
   523                        "tag": k,
       
   524                        "tagmanifest": hex(cl.read(n)[0]),
       
   525                        "date": cl.read(n)[2],
       
   526                        "node": hex(n)}
       
   527                 parity = 1 - parity
       
   528 
       
   529         yield self.t("tags",
       
   530                      manifest=hex(mf),
       
   531                      entries=lambda **x: entries(False, **x),
       
   532                      entriesnotip=lambda **x: entries(True, **x))
       
   533 
       
   534     def summary(self):
       
   535         cl = self.repo.changelog
       
   536         mf = cl.read(cl.tip())[0]
       
   537 
       
   538         i = self.repo.tagslist()
       
   539         i.reverse()
       
   540 
       
   541         def tagentries(**map):
       
   542             parity = 0
       
   543             count = 0
       
   544             for k,n in i:
       
   545                 if k == "tip": # skip tip
       
   546                     continue;
       
   547 
       
   548                 count += 1
       
   549                 if count > 10: # limit to 10 tags
       
   550                     break;
       
   551 
       
   552                 c = cl.read(n)
       
   553                 m = c[0]
       
   554                 t = c[2]
       
   555 
       
   556                 yield self.t("tagentry",
       
   557                              parity = parity,
       
   558                              tag = k,
       
   559                              node = hex(n),
       
   560                              date = t,
       
   561                              tagmanifest = hex(m))
       
   562                 parity = 1 - parity
       
   563 
       
   564         def changelist(**map):
       
   565             parity = 0
       
   566             cl = self.repo.changelog
       
   567             l = [] # build a list in forward order for efficiency
       
   568             for i in range(start, end):
       
   569                 n = cl.node(i)
       
   570                 changes = cl.read(n)
       
   571                 hn = hex(n)
       
   572                 t = changes[2]
       
   573 
       
   574                 l.insert(0, self.t(
       
   575                     'shortlogentry',
       
   576                     parity = parity,
       
   577                     author = changes[1],
       
   578                     manifest = hex(changes[0]),
       
   579                     desc = changes[4],
       
   580                     date = t,
       
   581                     rev = i,
       
   582                     node = hn))
       
   583                 parity = 1 - parity
       
   584 
       
   585             yield l
       
   586 
       
   587         cl = self.repo.changelog
       
   588         mf = cl.read(cl.tip())[0]
       
   589         count = cl.count()
       
   590         start = max(0, count - self.maxchanges)
       
   591         end = min(count, start + self.maxchanges)
       
   592         pos = end - 1
       
   593 
       
   594         yield self.t("summary",
       
   595                  desc = self.repo.ui.config("web", "description", "unknown"),
       
   596                  owner = (self.repo.ui.config("ui", "username") or # preferred
       
   597                           self.repo.ui.config("web", "contact") or # deprecated
       
   598                           self.repo.ui.config("web", "author", "unknown")), # also
       
   599                  lastchange = (0, 0), # FIXME
       
   600                  manifest = hex(mf),
       
   601                  tags = tagentries,
       
   602                  shortlog = changelist)
       
   603 
       
   604     def filediff(self, file, changeset):
       
   605         cl = self.repo.changelog
       
   606         n = self.repo.lookup(changeset)
       
   607         changeset = hex(n)
       
   608         p1 = cl.parents(n)[0]
       
   609         cs = cl.read(n)
       
   610         mf = self.repo.manifest.read(cs[0])
       
   611 
       
   612         def diff(**map):
       
   613             yield self.diff(p1, n, [file])
       
   614 
       
   615         yield self.t("filediff",
       
   616                      file=file,
       
   617                      filenode=hex(mf.get(file, nullid)),
       
   618                      node=changeset,
       
   619                      rev=self.repo.changelog.rev(n),
       
   620                      parent=self.siblings(cl.parents(n), cl.rev),
       
   621                      child=self.siblings(cl.children(n), cl.rev),
       
   622                      diff=diff)
       
   623 
       
   624     archive_specs = {
       
   625         'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', 'x-bzip2'),
       
   626         'gz': ('application/x-tar', 'tgz', '.tar.gz', 'x-gzip'),
       
   627         'zip': ('application/zip', 'zip', '.zip', None),
       
   628         }
       
   629 
       
   630     def archive(self, req, cnode, type):
       
   631         reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
       
   632         name = "%s-%s" % (reponame, short(cnode))
       
   633         mimetype, artype, extension, encoding = self.archive_specs[type]
       
   634         headers = [('Content-type', mimetype),
       
   635                    ('Content-disposition', 'attachment; filename=%s%s' %
       
   636                     (name, extension))]
       
   637         if encoding:
       
   638             headers.append(('Content-encoding', encoding))
       
   639         req.header(headers)
       
   640         archival.archive(self.repo, req.out, cnode, artype, prefix=name)
       
   641 
       
   642     # add tags to things
       
   643     # tags -> list of changesets corresponding to tags
       
   644     # find tag, changeset, file
       
   645 
       
   646     def run(self, req=hgrequest()):
       
   647         def clean(path):
       
   648             p = util.normpath(path)
       
   649             if p[:2] == "..":
       
   650                 raise "suspicious path"
       
   651             return p
       
   652 
       
   653         def header(**map):
       
   654             yield self.t("header", **map)
       
   655 
       
   656         def footer(**map):
       
   657             yield self.t("footer",
       
   658                          motd=self.repo.ui.config("web", "motd", ""),
       
   659                          **map)
       
   660 
       
   661         def expand_form(form):
       
   662             shortcuts = {
       
   663                 'cl': [('cmd', ['changelog']), ('rev', None)],
       
   664                 'cs': [('cmd', ['changeset']), ('node', None)],
       
   665                 'f': [('cmd', ['file']), ('filenode', None)],
       
   666                 'fl': [('cmd', ['filelog']), ('filenode', None)],
       
   667                 'fd': [('cmd', ['filediff']), ('node', None)],
       
   668                 'fa': [('cmd', ['annotate']), ('filenode', None)],
       
   669                 'mf': [('cmd', ['manifest']), ('manifest', None)],
       
   670                 'ca': [('cmd', ['archive']), ('node', None)],
       
   671                 'tags': [('cmd', ['tags'])],
       
   672                 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
       
   673                 'static': [('cmd', ['static']), ('file', None)]
       
   674             }
       
   675 
       
   676             for k in shortcuts.iterkeys():
       
   677                 if form.has_key(k):
       
   678                     for name, value in shortcuts[k]:
       
   679                         if value is None:
       
   680                             value = form[k]
       
   681                         form[name] = value
       
   682                     del form[k]
       
   683 
       
   684         self.refresh()
       
   685 
       
   686         expand_form(req.form)
       
   687 
       
   688         t = self.repo.ui.config("web", "templates", templater.templatepath())
       
   689         static = self.repo.ui.config("web", "static", os.path.join(t,"static"))
       
   690         m = os.path.join(t, "map")
       
   691         style = self.repo.ui.config("web", "style", "")
       
   692         if req.form.has_key('style'):
       
   693             style = req.form['style'][0]
       
   694         if style:
       
   695             b = os.path.basename("map-" + style)
       
   696             p = os.path.join(t, b)
       
   697             if os.path.isfile(p):
       
   698                 m = p
       
   699 
       
   700         port = req.env["SERVER_PORT"]
       
   701         port = port != "80" and (":" + port) or ""
       
   702         uri = req.env["REQUEST_URI"]
       
   703         if "?" in uri:
       
   704             uri = uri.split("?")[0]
       
   705         url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri)
       
   706         if not self.reponame:
       
   707             self.reponame = (self.repo.ui.config("web", "name")
       
   708                              or uri.strip('/') or self.repo.root)
       
   709 
       
   710         self.t = templater.templater(m, templater.common_filters,
       
   711                                      defaults={"url": url,
       
   712                                                "repo": self.reponame,
       
   713                                                "header": header,
       
   714                                                "footer": footer,
       
   715                                                })
       
   716 
       
   717         if not req.form.has_key('cmd'):
       
   718             req.form['cmd'] = [self.t.cache['default'],]
       
   719 
       
   720         cmd = req.form['cmd'][0]
       
   721         if cmd == 'changelog':
       
   722             hi = self.repo.changelog.count() - 1
       
   723             if req.form.has_key('rev'):
       
   724                 hi = req.form['rev'][0]
       
   725                 try:
       
   726                     hi = self.repo.changelog.rev(self.repo.lookup(hi))
       
   727                 except hg.RepoError:
       
   728                     req.write(self.search(hi)) # XXX redirect to 404 page?
       
   729                     return
       
   730 
       
   731             req.write(self.changelog(hi))
       
   732 
       
   733         elif cmd == 'changeset':
       
   734             req.write(self.changeset(req.form['node'][0]))
       
   735 
       
   736         elif cmd == 'manifest':
       
   737             req.write(self.manifest(req.form['manifest'][0],
       
   738                                     clean(req.form['path'][0])))
       
   739 
       
   740         elif cmd == 'tags':
       
   741             req.write(self.tags())
       
   742 
       
   743         elif cmd == 'summary':
       
   744             req.write(self.summary())
       
   745 
       
   746         elif cmd == 'filediff':
       
   747             req.write(self.filediff(clean(req.form['file'][0]),
       
   748                                     req.form['node'][0]))
       
   749 
       
   750         elif cmd == 'file':
       
   751             req.write(self.filerevision(clean(req.form['file'][0]),
       
   752                                         req.form['filenode'][0]))
       
   753 
       
   754         elif cmd == 'annotate':
       
   755             req.write(self.fileannotate(clean(req.form['file'][0]),
       
   756                                         req.form['filenode'][0]))
       
   757 
       
   758         elif cmd == 'filelog':
       
   759             req.write(self.filelog(clean(req.form['file'][0]),
       
   760                                    req.form['filenode'][0]))
       
   761 
       
   762         elif cmd == 'heads':
       
   763             req.httphdr("application/mercurial-0.1")
       
   764             h = self.repo.heads()
       
   765             req.write(" ".join(map(hex, h)) + "\n")
       
   766 
       
   767         elif cmd == 'branches':
       
   768             req.httphdr("application/mercurial-0.1")
       
   769             nodes = []
       
   770             if req.form.has_key('nodes'):
       
   771                 nodes = map(bin, req.form['nodes'][0].split(" "))
       
   772             for b in self.repo.branches(nodes):
       
   773                 req.write(" ".join(map(hex, b)) + "\n")
       
   774 
       
   775         elif cmd == 'between':
       
   776             req.httphdr("application/mercurial-0.1")
       
   777             nodes = []
       
   778             if req.form.has_key('pairs'):
       
   779                 pairs = [map(bin, p.split("-"))
       
   780                          for p in req.form['pairs'][0].split(" ")]
       
   781             for b in self.repo.between(pairs):
       
   782                 req.write(" ".join(map(hex, b)) + "\n")
       
   783 
       
   784         elif cmd == 'changegroup':
       
   785             req.httphdr("application/mercurial-0.1")
       
   786             nodes = []
       
   787             if not self.allowpull:
       
   788                 return
       
   789 
       
   790             if req.form.has_key('roots'):
       
   791                 nodes = map(bin, req.form['roots'][0].split(" "))
       
   792 
       
   793             z = zlib.compressobj()
       
   794             f = self.repo.changegroup(nodes, 'serve')
       
   795             while 1:
       
   796                 chunk = f.read(4096)
       
   797                 if not chunk:
       
   798                     break
       
   799                 req.write(z.compress(chunk))
       
   800 
       
   801             req.write(z.flush())
       
   802 
       
   803         elif cmd == 'archive':
       
   804             changeset = self.repo.lookup(req.form['node'][0])
       
   805             type = req.form['type'][0]
       
   806             if (type in self.archives and
       
   807                 self.repo.ui.configbool("web", "allow" + type, False)):
       
   808                 self.archive(req, changeset, type)
       
   809                 return
       
   810 
       
   811             req.write(self.t("error"))
       
   812 
       
   813         elif cmd == 'static':
       
   814             fname = req.form['file'][0]
       
   815             req.write(staticfile(static, fname)
       
   816                       or self.t("error", error="%r not found" % fname))
       
   817 
       
   818         else:
       
   819             req.write(self.t("error"))