mercurial/hgweb/hgweb_mod.py
changeset 5620 652f57de3ccf
parent 5588 083b6e3142a2
parent 5600 9d900f7282e6
child 5779 e9f68860d5ed
equal deleted inserted replaced
5610:2493a478f395 5620:652f57de3ccf
     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 errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
     9 import os, mimetypes, re, mimetools, cStringIO
    10 import tempfile, urllib, bz2
       
    11 from mercurial.node import *
    10 from mercurial.node import *
    12 from mercurial.i18n import gettext as _
    11 from mercurial import mdiff, ui, hg, util, archival, patch
    13 from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
       
    14 from mercurial import revlog, templater
    12 from mercurial import revlog, templater
    15 from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
    13 from common import ErrorResponse, get_mtime, style_map, paritygen
    16 from request import wsgirequest
    14 from request import wsgirequest
       
    15 import webcommands, protocol
       
    16 
       
    17 shortcuts = {
       
    18     'cl': [('cmd', ['changelog']), ('rev', None)],
       
    19     'sl': [('cmd', ['shortlog']), ('rev', None)],
       
    20     'cs': [('cmd', ['changeset']), ('node', None)],
       
    21     'f': [('cmd', ['file']), ('filenode', None)],
       
    22     'fl': [('cmd', ['filelog']), ('filenode', None)],
       
    23     'fd': [('cmd', ['filediff']), ('node', None)],
       
    24     'fa': [('cmd', ['annotate']), ('filenode', None)],
       
    25     'mf': [('cmd', ['manifest']), ('manifest', None)],
       
    26     'ca': [('cmd', ['archive']), ('node', None)],
       
    27     'tags': [('cmd', ['tags'])],
       
    28     'tip': [('cmd', ['changeset']), ('node', ['tip'])],
       
    29     'static': [('cmd', ['static']), ('file', None)]
       
    30 }
    17 
    31 
    18 def _up(p):
    32 def _up(p):
    19     if p[0] != "/":
    33     if p[0] != "/":
    20         p = "/" + p
    34         p = "/" + p
    21     if p[-1] == "/":
    35     if p[-1] == "/":
   105             self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
   119             self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
   106             self.maxfiles = int(self.config("web", "maxfiles", 10))
   120             self.maxfiles = int(self.config("web", "maxfiles", 10))
   107             self.allowpull = self.configbool("web", "allowpull", True)
   121             self.allowpull = self.configbool("web", "allowpull", True)
   108             self.encoding = self.config("web", "encoding", util._encoding)
   122             self.encoding = self.config("web", "encoding", util._encoding)
   109 
   123 
       
   124     def run(self):
       
   125         if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
       
   126             raise RuntimeError("This function is only intended to be called while running as a CGI script.")
       
   127         import mercurial.hgweb.wsgicgi as wsgicgi
       
   128         wsgicgi.launch(self)
       
   129 
       
   130     def __call__(self, env, respond):
       
   131         req = wsgirequest(env, respond)
       
   132         self.run_wsgi(req)
       
   133         return req
       
   134 
       
   135     def run_wsgi(self, req):
       
   136 
       
   137         self.refresh()
       
   138 
       
   139         # expand form shortcuts
       
   140 
       
   141         for k in shortcuts.iterkeys():
       
   142             if k in req.form:
       
   143                 for name, value in shortcuts[k]:
       
   144                     if value is None:
       
   145                         value = req.form[k]
       
   146                     req.form[name] = value
       
   147                 del req.form[k]
       
   148 
       
   149         # work with CGI variables to create coherent structure
       
   150         # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
       
   151 
       
   152         req.url = req.env['SCRIPT_NAME']
       
   153         if not req.url.endswith('/'):
       
   154             req.url += '/'
       
   155         if req.env.has_key('REPO_NAME'):
       
   156             req.url += req.env['REPO_NAME'] + '/'
       
   157 
       
   158         if req.env.get('PATH_INFO'):
       
   159             parts = req.env.get('PATH_INFO').strip('/').split('/')
       
   160             repo_parts = req.env.get('REPO_NAME', '').split('/')
       
   161             if parts[:len(repo_parts)] == repo_parts:
       
   162                 parts = parts[len(repo_parts):]
       
   163             query = '/'.join(parts)
       
   164         else:
       
   165             query = req.env['QUERY_STRING'].split('&', 1)[0]
       
   166             query = query.split(';', 1)[0]
       
   167 
       
   168         # translate user-visible url structure to internal structure
       
   169 
       
   170         args = query.split('/', 2)
       
   171         if 'cmd' not in req.form and args and args[0]:
       
   172 
       
   173             cmd = args.pop(0)
       
   174             style = cmd.rfind('-')
       
   175             if style != -1:
       
   176                 req.form['style'] = [cmd[:style]]
       
   177                 cmd = cmd[style+1:]
       
   178 
       
   179             # avoid accepting e.g. style parameter as command
       
   180             if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
       
   181                 req.form['cmd'] = [cmd]
       
   182 
       
   183             if args and args[0]:
       
   184                 node = args.pop(0)
       
   185                 req.form['node'] = [node]
       
   186             if args:
       
   187                 req.form['file'] = args
       
   188 
       
   189             if cmd == 'static':
       
   190                 req.form['file'] = req.form['node']
       
   191             elif cmd == 'archive':
       
   192                 fn = req.form['node'][0]
       
   193                 for type_, spec in self.archive_specs.iteritems():
       
   194                     ext = spec[2]
       
   195                     if fn.endswith(ext):
       
   196                         req.form['node'] = [fn[:-len(ext)]]
       
   197                         req.form['type'] = [type_]
       
   198 
       
   199         # actually process the request
       
   200 
       
   201         try:
       
   202 
       
   203             cmd = req.form.get('cmd', [''])[0]
       
   204             if hasattr(protocol, cmd):
       
   205                 method = getattr(protocol, cmd)
       
   206                 method(self, req)
       
   207             else:
       
   208                 tmpl = self.templater(req)
       
   209                 if cmd == '':
       
   210                     req.form['cmd'] = [tmpl.cache['default']]
       
   211                     cmd = req.form['cmd'][0]
       
   212                 method = getattr(webcommands, cmd)
       
   213                 method(self, req, tmpl)
       
   214                 del tmpl
       
   215 
       
   216         except revlog.LookupError, err:
       
   217             req.respond(404, tmpl(
       
   218                         'error', error='revision not found: %s' % err.name))
       
   219         except (hg.RepoError, revlog.RevlogError), inst:
       
   220             req.respond('500 Internal Server Error',
       
   221                         tmpl('error', error=str(inst)))
       
   222         except ErrorResponse, inst:
       
   223             req.respond(inst.code, tmpl('error', error=inst.message))
       
   224         except AttributeError:
       
   225             req.respond(400, tmpl('error', error='No such method: ' + cmd))
       
   226 
       
   227     def templater(self, req):
       
   228 
       
   229         # determine scheme, port and server name
       
   230         # this is needed to create absolute urls
       
   231 
       
   232         proto = req.env.get('wsgi.url_scheme')
       
   233         if proto == 'https':
       
   234             proto = 'https'
       
   235             default_port = "443"
       
   236         else:
       
   237             proto = 'http'
       
   238             default_port = "80"
       
   239 
       
   240         port = req.env["SERVER_PORT"]
       
   241         port = port != default_port and (":" + port) or ""
       
   242         urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
       
   243         staticurl = self.config("web", "staticurl") or req.url + 'static/'
       
   244         if not staticurl.endswith('/'):
       
   245             staticurl += '/'
       
   246 
       
   247         # some functions for the templater
       
   248 
       
   249         def header(**map):
       
   250             header_file = cStringIO.StringIO(
       
   251                 ''.join(tmpl("header", encoding=self.encoding, **map)))
       
   252             msg = mimetools.Message(header_file, 0)
       
   253             req.header(msg.items())
       
   254             yield header_file.read()
       
   255 
       
   256         def rawfileheader(**map):
       
   257             req.header([('Content-type', map['mimetype']),
       
   258                         ('Content-disposition', 'filename=%s' % map['file']),
       
   259                         ('Content-length', str(len(map['raw'])))])
       
   260             yield ''
       
   261 
       
   262         def footer(**map):
       
   263             yield tmpl("footer", **map)
       
   264 
       
   265         def motd(**map):
       
   266             yield self.config("web", "motd", "")
       
   267 
       
   268         def sessionvars(**map):
       
   269             fields = []
       
   270             if req.form.has_key('style'):
       
   271                 style = req.form['style'][0]
       
   272                 if style != self.config('web', 'style', ''):
       
   273                     fields.append(('style', style))
       
   274 
       
   275             separator = req.url[-1] == '?' and ';' or '?'
       
   276             for name, value in fields:
       
   277                 yield dict(name=name, value=value, separator=separator)
       
   278                 separator = ';'
       
   279 
       
   280         # figure out which style to use
       
   281 
       
   282         style = self.config("web", "style", "")
       
   283         if req.form.has_key('style'):
       
   284             style = req.form['style'][0]
       
   285         mapfile = style_map(self.templatepath, style)
       
   286 
       
   287         if not self.reponame:
       
   288             self.reponame = (self.config("web", "name")
       
   289                              or req.env.get('REPO_NAME')
       
   290                              or req.url.strip('/') or self.repo.root)
       
   291 
       
   292         # create the templater
       
   293 
       
   294         tmpl = templater.templater(mapfile, templater.common_filters,
       
   295                                    defaults={"url": req.url,
       
   296                                              "staticurl": staticurl,
       
   297                                              "urlbase": urlbase,
       
   298                                              "repo": self.reponame,
       
   299                                              "header": header,
       
   300                                              "footer": footer,
       
   301                                              "motd": motd,
       
   302                                              "rawfileheader": rawfileheader,
       
   303                                              "sessionvars": sessionvars
       
   304                                             })
       
   305         return tmpl
       
   306 
   110     def archivelist(self, nodeid):
   307     def archivelist(self, nodeid):
   111         allowed = self.configlist("web", "allow_archive")
   308         allowed = self.configlist("web", "allow_archive")
   112         for i, spec in self.archive_specs.iteritems():
   309         for i, spec in self.archive_specs.iteritems():
   113             if i in allowed or self.configbool("web", "allow" + i):
   310             if i in allowed or self.configbool("web", "allow" + i):
   114                 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
   311                 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
   115 
   312 
   116     def listfilediffs(self, files, changeset):
   313     def listfilediffs(self, tmpl, files, changeset):
   117         for f in files[:self.maxfiles]:
   314         for f in files[:self.maxfiles]:
   118             yield self.t("filedifflink", node=hex(changeset), file=f)
   315             yield tmpl("filedifflink", node=hex(changeset), file=f)
   119         if len(files) > self.maxfiles:
   316         if len(files) > self.maxfiles:
   120             yield self.t("fileellipses")
   317             yield tmpl("fileellipses")
   121 
   318 
   122     def siblings(self, siblings=[], hiderev=None, **args):
   319     def siblings(self, siblings=[], hiderev=None, **args):
   123         siblings = [s for s in siblings if s.node() != nullid]
   320         siblings = [s for s in siblings if s.node() != nullid]
   124         if len(siblings) == 1 and siblings[0].rev() == hiderev:
   321         if len(siblings) == 1 and siblings[0].rev() == hiderev:
   125             return
   322             return
   147         # an empty dict. Using dict.get avoids a traceback.
   344         # an empty dict. Using dict.get avoids a traceback.
   148         if self.repo.branchtags().get(branch) == ctx.node():
   345         if self.repo.branchtags().get(branch) == ctx.node():
   149             branches.append({"name": branch})
   346             branches.append({"name": branch})
   150         return branches
   347         return branches
   151 
   348 
   152     def showtag(self, t1, node=nullid, **args):
   349     def showtag(self, tmpl, t1, node=nullid, **args):
   153         for t in self.repo.nodetags(node):
   350         for t in self.repo.nodetags(node):
   154             yield self.t(t1, tag=t, **args)
   351             yield tmpl(t1, tag=t, **args)
   155 
   352 
   156     def diff(self, node1, node2, files):
   353     def diff(self, tmpl, node1, node2, files):
   157         def filterfiles(filters, files):
   354         def filterfiles(filters, files):
   158             l = [x for x in files if x in filters]
   355             l = [x for x in files if x in filters]
   159 
   356 
   160             for t in filters:
   357             for t in filters:
   161                 if t and t[-1] != os.sep:
   358                 if t and t[-1] != os.sep:
   163                 l += [x for x in files if x.startswith(t)]
   360                 l += [x for x in files if x.startswith(t)]
   164             return l
   361             return l
   165 
   362 
   166         parity = paritygen(self.stripecount)
   363         parity = paritygen(self.stripecount)
   167         def diffblock(diff, f, fn):
   364         def diffblock(diff, f, fn):
   168             yield self.t("diffblock",
   365             yield tmpl("diffblock",
   169                          lines=prettyprintlines(diff),
   366                        lines=prettyprintlines(diff),
   170                          parity=parity.next(),
   367                        parity=parity.next(),
   171                          file=f,
   368                        file=f,
   172                          filenode=hex(fn or nullid))
   369                        filenode=hex(fn or nullid))
   173 
   370 
   174         def prettyprintlines(diff):
   371         def prettyprintlines(diff):
   175             for l in diff.splitlines(1):
   372             for l in diff.splitlines(1):
   176                 if l.startswith('+'):
   373                 if l.startswith('+'):
   177                     yield self.t("difflineplus", line=l)
   374                     yield tmpl("difflineplus", line=l)
   178                 elif l.startswith('-'):
   375                 elif l.startswith('-'):
   179                     yield self.t("difflineminus", line=l)
   376                     yield tmpl("difflineminus", line=l)
   180                 elif l.startswith('@'):
   377                 elif l.startswith('@'):
   181                     yield self.t("difflineat", line=l)
   378                     yield tmpl("difflineat", line=l)
   182                 else:
   379                 else:
   183                     yield self.t("diffline", line=l)
   380                     yield tmpl("diffline", line=l)
   184 
   381 
   185         r = self.repo
   382         r = self.repo
   186         c1 = r.changectx(node1)
   383         c1 = r.changectx(node1)
   187         c2 = r.changectx(node2)
   384         c2 = r.changectx(node2)
   188         date1 = util.datestr(c1.date())
   385         date1 = util.datestr(c1.date())
   208             to = c1.filectx(f).data()
   405             to = c1.filectx(f).data()
   209             tn = None
   406             tn = None
   210             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
   407             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
   211                                           opts=diffopts), f, tn)
   408                                           opts=diffopts), f, tn)
   212 
   409 
   213     def changelog(self, ctx, shortlog=False):
   410     def changelog(self, tmpl, ctx, shortlog=False):
   214         def changelist(limit=0,**map):
   411         def changelist(limit=0,**map):
   215             cl = self.repo.changelog
   412             cl = self.repo.changelog
   216             l = [] # build a list in forward order for efficiency
   413             l = [] # build a list in forward order for efficiency
   217             for i in xrange(start, end):
   414             for i in xrange(start, end):
   218                 ctx = self.repo.changectx(i)
   415                 ctx = self.repo.changectx(i)
   223                              "parent": self.siblings(ctx.parents(), i - 1),
   420                              "parent": self.siblings(ctx.parents(), i - 1),
   224                              "child": self.siblings(ctx.children(), i + 1),
   421                              "child": self.siblings(ctx.children(), i + 1),
   225                              "changelogtag": self.showtag("changelogtag",n),
   422                              "changelogtag": self.showtag("changelogtag",n),
   226                              "desc": ctx.description(),
   423                              "desc": ctx.description(),
   227                              "date": ctx.date(),
   424                              "date": ctx.date(),
   228                              "files": self.listfilediffs(ctx.files(), n),
   425                              "files": self.listfilediffs(tmpl, ctx.files(), n),
   229                              "rev": i,
   426                              "rev": i,
   230                              "node": hex(n),
   427                              "node": hex(n),
   231                              "tags": self.nodetagsdict(n),
   428                              "tags": self.nodetagsdict(n),
   232                              "branches": self.nodebranchdict(ctx)})
   429                              "branches": self.nodebranchdict(ctx)})
   233 
   430 
   246         pos = end - 1
   443         pos = end - 1
   247         parity = paritygen(self.stripecount, offset=start-end)
   444         parity = paritygen(self.stripecount, offset=start-end)
   248 
   445 
   249         changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
   446         changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
   250 
   447 
   251         yield self.t(shortlog and 'shortlog' or 'changelog',
   448         yield tmpl(shortlog and 'shortlog' or 'changelog',
   252                      changenav=changenav,
   449                    changenav=changenav,
   253                      node=hex(cl.tip()),
   450                    node=hex(cl.tip()),
   254                      rev=pos, changesets=count,
   451                    rev=pos, changesets=count,
   255                      entries=lambda **x: changelist(limit=0,**x),
   452                    entries=lambda **x: changelist(limit=0,**x),
   256                      latestentry=lambda **x: changelist(limit=1,**x),
   453                    latestentry=lambda **x: changelist(limit=1,**x),
   257                      archives=self.archivelist("tip"))
   454                    archives=self.archivelist("tip"))
   258 
   455 
   259     def search(self, query):
   456     def search(self, tmpl, query):
   260 
   457 
   261         def changelist(**map):
   458         def changelist(**map):
   262             cl = self.repo.changelog
   459             cl = self.repo.changelog
   263             count = 0
   460             count = 0
   264             qw = query.lower().split()
   461             qw = query.lower().split()
   285                     continue
   482                     continue
   286 
   483 
   287                 count += 1
   484                 count += 1
   288                 n = ctx.node()
   485                 n = ctx.node()
   289 
   486 
   290                 yield self.t('searchentry',
   487                 yield tmpl('searchentry',
   291                              parity=parity.next(),
   488                            parity=parity.next(),
   292                              author=ctx.user(),
   489                            author=ctx.user(),
   293                              parent=self.siblings(ctx.parents()),
   490                            parent=self.siblings(ctx.parents()),
   294                              child=self.siblings(ctx.children()),
   491                            child=self.siblings(ctx.children()),
   295                              changelogtag=self.showtag("changelogtag",n),
   492                            changelogtag=self.showtag("changelogtag",n),
   296                              desc=ctx.description(),
   493                            desc=ctx.description(),
   297                              date=ctx.date(),
   494                            date=ctx.date(),
   298                              files=self.listfilediffs(ctx.files(), n),
   495                            files=self.listfilediffs(tmpl, ctx.files(), n),
   299                              rev=ctx.rev(),
   496                            rev=ctx.rev(),
   300                              node=hex(n),
   497                            node=hex(n),
   301                              tags=self.nodetagsdict(n),
   498                            tags=self.nodetagsdict(n),
   302                              branches=self.nodebranchdict(ctx))
   499                            branches=self.nodebranchdict(ctx))
   303 
   500 
   304                 if count >= self.maxchanges:
   501                 if count >= self.maxchanges:
   305                     break
   502                     break
   306 
   503 
   307         cl = self.repo.changelog
   504         cl = self.repo.changelog
   308         parity = paritygen(self.stripecount)
   505         parity = paritygen(self.stripecount)
   309 
   506 
   310         yield self.t('search',
   507         yield tmpl('search',
   311                      query=query,
   508                    query=query,
   312                      node=hex(cl.tip()),
   509                    node=hex(cl.tip()),
   313                      entries=changelist,
   510                    entries=changelist,
   314                      archives=self.archivelist("tip"))
   511                    archives=self.archivelist("tip"))
   315 
   512 
   316     def changeset(self, ctx):
   513     def changeset(self, tmpl, ctx):
   317         n = ctx.node()
   514         n = ctx.node()
   318         parents = ctx.parents()
   515         parents = ctx.parents()
   319         p1 = parents[0].node()
   516         p1 = parents[0].node()
   320 
   517 
   321         files = []
   518         files = []
   322         parity = paritygen(self.stripecount)
   519         parity = paritygen(self.stripecount)
   323         for f in ctx.files():
   520         for f in ctx.files():
   324             files.append(self.t("filenodelink",
   521             files.append(tmpl("filenodelink",
   325                                 node=hex(n), file=f,
   522                               node=hex(n), file=f,
   326                                 parity=parity.next()))
   523                               parity=parity.next()))
   327 
   524 
   328         def diff(**map):
   525         def diff(**map):
   329             yield self.diff(p1, n, None)
   526             yield self.diff(tmpl, p1, n, None)
   330 
   527 
   331         yield self.t('changeset',
   528         yield tmpl('changeset',
   332                      diff=diff,
   529                    diff=diff,
   333                      rev=ctx.rev(),
   530                    rev=ctx.rev(),
   334                      node=hex(n),
   531                    node=hex(n),
   335                      parent=self.siblings(parents),
   532                    parent=self.siblings(parents),
   336                      child=self.siblings(ctx.children()),
   533                    child=self.siblings(ctx.children()),
   337                      changesettag=self.showtag("changesettag",n),
   534                    changesettag=self.showtag("changesettag",n),
   338                      author=ctx.user(),
   535                    author=ctx.user(),
   339                      desc=ctx.description(),
   536                    desc=ctx.description(),
   340                      date=ctx.date(),
   537                    date=ctx.date(),
   341                      files=files,
   538                    files=files,
   342                      archives=self.archivelist(hex(n)),
   539                    archives=self.archivelist(hex(n)),
   343                      tags=self.nodetagsdict(n),
   540                    tags=self.nodetagsdict(n),
   344                      branches=self.nodebranchdict(ctx))
   541                    branches=self.nodebranchdict(ctx))
   345 
   542 
   346     def filelog(self, fctx):
   543     def filelog(self, tmpl, fctx):
   347         f = fctx.path()
   544         f = fctx.path()
   348         fl = fctx.filelog()
   545         fl = fctx.filelog()
   349         count = fl.count()
   546         count = fl.count()
   350         pagelen = self.maxshortchanges
   547         pagelen = self.maxshortchanges
   351         pos = fctx.filerev()
   548         pos = fctx.filerev()
   378             for e in l:
   575             for e in l:
   379                 yield e
   576                 yield e
   380 
   577 
   381         nodefunc = lambda x: fctx.filectx(fileid=x)
   578         nodefunc = lambda x: fctx.filectx(fileid=x)
   382         nav = revnavgen(pos, pagelen, count, nodefunc)
   579         nav = revnavgen(pos, pagelen, count, nodefunc)
   383         yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
   580         yield tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
   384                      entries=lambda **x: entries(limit=0, **x),
   581                    entries=lambda **x: entries(limit=0, **x),
   385                      latestentry=lambda **x: entries(limit=1, **x))
   582                    latestentry=lambda **x: entries(limit=1, **x))
   386 
   583 
   387     def filerevision(self, fctx):
   584     def filerevision(self, tmpl, fctx):
   388         f = fctx.path()
   585         f = fctx.path()
   389         text = fctx.data()
   586         text = fctx.data()
   390         fl = fctx.filelog()
   587         fl = fctx.filelog()
   391         n = fctx.filenode()
   588         n = fctx.filenode()
   392         parity = paritygen(self.stripecount)
   589         parity = paritygen(self.stripecount)
   402             for l, t in enumerate(text.splitlines(1)):
   599             for l, t in enumerate(text.splitlines(1)):
   403                 yield {"line": t,
   600                 yield {"line": t,
   404                        "linenumber": "% 6d" % (l + 1),
   601                        "linenumber": "% 6d" % (l + 1),
   405                        "parity": parity.next()}
   602                        "parity": parity.next()}
   406 
   603 
   407         yield self.t("filerevision",
   604         yield tmpl("filerevision",
   408                      file=f,
   605                    file=f,
   409                      path=_up(f),
   606                    path=_up(f),
   410                      text=lines(),
   607                    text=lines(),
   411                      raw=rawtext,
   608                    raw=rawtext,
   412                      mimetype=mt,
   609                    mimetype=mt,
   413                      rev=fctx.rev(),
   610                    rev=fctx.rev(),
   414                      node=hex(fctx.node()),
   611                    node=hex(fctx.node()),
   415                      author=fctx.user(),
   612                    author=fctx.user(),
   416                      date=fctx.date(),
   613                    date=fctx.date(),
   417                      desc=fctx.description(),
   614                    desc=fctx.description(),
   418                      parent=self.siblings(fctx.parents()),
   615                    parent=self.siblings(fctx.parents()),
   419                      child=self.siblings(fctx.children()),
   616                    child=self.siblings(fctx.children()),
   420                      rename=self.renamelink(fl, n),
   617                    rename=self.renamelink(fl, n),
   421                      permissions=fctx.manifest().flags(f))
   618                    permissions=fctx.manifest().flags(f))
   422 
   619 
   423     def fileannotate(self, fctx):
   620     def fileannotate(self, tmpl, fctx):
   424         f = fctx.path()
   621         f = fctx.path()
   425         n = fctx.filenode()
   622         n = fctx.filenode()
   426         fl = fctx.filelog()
   623         fl = fctx.filelog()
   427         parity = paritygen(self.stripecount)
   624         parity = paritygen(self.stripecount)
   428 
   625 
   440                        "rev": f.rev(),
   637                        "rev": f.rev(),
   441                        "author": name,
   638                        "author": name,
   442                        "file": f.path(),
   639                        "file": f.path(),
   443                        "line": l}
   640                        "line": l}
   444 
   641 
   445         yield self.t("fileannotate",
   642         yield tmpl("fileannotate",
   446                      file=f,
   643                    file=f,
   447                      annotate=annotate,
   644                    annotate=annotate,
   448                      path=_up(f),
   645                    path=_up(f),
   449                      rev=fctx.rev(),
   646                    rev=fctx.rev(),
   450                      node=hex(fctx.node()),
   647                    node=hex(fctx.node()),
   451                      author=fctx.user(),
   648                    author=fctx.user(),
   452                      date=fctx.date(),
   649                    date=fctx.date(),
   453                      desc=fctx.description(),
   650                    desc=fctx.description(),
   454                      rename=self.renamelink(fl, n),
   651                    rename=self.renamelink(fl, n),
   455                      parent=self.siblings(fctx.parents()),
   652                    parent=self.siblings(fctx.parents()),
   456                      child=self.siblings(fctx.children()),
   653                    child=self.siblings(fctx.children()),
   457                      permissions=fctx.manifest().flags(f))
   654                    permissions=fctx.manifest().flags(f))
   458 
   655 
   459     def manifest(self, ctx, path):
   656     def manifest(self, tmpl, ctx, path):
   460         mf = ctx.manifest()
   657         mf = ctx.manifest()
   461         node = ctx.node()
   658         node = ctx.node()
   462 
   659 
   463         files = {}
   660         files = {}
   464         parity = paritygen(self.stripecount)
   661         parity = paritygen(self.stripecount)
   508 
   705 
   509                 yield {"parity": parity.next(),
   706                 yield {"parity": parity.next(),
   510                        "path": "%s%s" % (abspath, f),
   707                        "path": "%s%s" % (abspath, f),
   511                        "basename": f[:-1]}
   708                        "basename": f[:-1]}
   512 
   709 
   513         yield self.t("manifest",
   710         yield tmpl("manifest",
   514                      rev=ctx.rev(),
   711                    rev=ctx.rev(),
   515                      node=hex(node),
   712                    node=hex(node),
   516                      path=abspath,
   713                    path=abspath,
   517                      up=_up(abspath),
   714                    up=_up(abspath),
   518                      upparity=parity.next(),
   715                    upparity=parity.next(),
   519                      fentries=filelist,
   716                    fentries=filelist,
   520                      dentries=dirlist,
   717                    dentries=dirlist,
   521                      archives=self.archivelist(hex(node)),
   718                    archives=self.archivelist(hex(node)),
   522                      tags=self.nodetagsdict(node),
   719                    tags=self.nodetagsdict(node),
   523                      branches=self.nodebranchdict(ctx))
   720                    branches=self.nodebranchdict(ctx))
   524 
   721 
   525     def tags(self):
   722     def tags(self, tmpl):
   526         i = self.repo.tagslist()
   723         i = self.repo.tagslist()
   527         i.reverse()
   724         i.reverse()
   528         parity = paritygen(self.stripecount)
   725         parity = paritygen(self.stripecount)
   529 
   726 
   530         def entries(notip=False,limit=0, **map):
   727         def entries(notip=False,limit=0, **map):
   538                 yield {"parity": parity.next(),
   735                 yield {"parity": parity.next(),
   539                        "tag": k,
   736                        "tag": k,
   540                        "date": self.repo.changectx(n).date(),
   737                        "date": self.repo.changectx(n).date(),
   541                        "node": hex(n)}
   738                        "node": hex(n)}
   542 
   739 
   543         yield self.t("tags",
   740         yield tmpl("tags",
   544                      node=hex(self.repo.changelog.tip()),
   741                    node=hex(self.repo.changelog.tip()),
   545                      entries=lambda **x: entries(False,0, **x),
   742                    entries=lambda **x: entries(False,0, **x),
   546                      entriesnotip=lambda **x: entries(True,0, **x),
   743                    entriesnotip=lambda **x: entries(True,0, **x),
   547                      latestentry=lambda **x: entries(True,1, **x))
   744                    latestentry=lambda **x: entries(True,1, **x))
   548 
   745 
   549     def summary(self):
   746     def summary(self, tmpl):
   550         i = self.repo.tagslist()
   747         i = self.repo.tagslist()
   551         i.reverse()
   748         i.reverse()
   552 
   749 
   553         def tagentries(**map):
   750         def tagentries(**map):
   554             parity = paritygen(self.stripecount)
   751             parity = paritygen(self.stripecount)
   559 
   756 
   560                 count += 1
   757                 count += 1
   561                 if count > 10: # limit to 10 tags
   758                 if count > 10: # limit to 10 tags
   562                     break;
   759                     break;
   563 
   760 
   564                 yield self.t("tagentry",
   761                 yield tmpl("tagentry",
   565                              parity=parity.next(),
   762                            parity=parity.next(),
   566                              tag=k,
   763                            tag=k,
   567                              node=hex(n),
   764                            node=hex(n),
   568                              date=self.repo.changectx(n).date())
   765                            date=self.repo.changectx(n).date())
   569 
   766 
   570 
   767 
   571         def branches(**map):
   768         def branches(**map):
   572             parity = paritygen(self.stripecount)
   769             parity = paritygen(self.stripecount)
   573 
   770 
   589             for i in xrange(start, end):
   786             for i in xrange(start, end):
   590                 ctx = self.repo.changectx(i)
   787                 ctx = self.repo.changectx(i)
   591                 n = ctx.node()
   788                 n = ctx.node()
   592                 hn = hex(n)
   789                 hn = hex(n)
   593 
   790 
   594                 l.insert(0, self.t(
   791                 l.insert(0, tmpl(
   595                     'shortlogentry',
   792                    'shortlogentry',
   596                     parity=parity.next(),
   793                     parity=parity.next(),
   597                     author=ctx.user(),
   794                     author=ctx.user(),
   598                     desc=ctx.description(),
   795                     desc=ctx.description(),
   599                     date=ctx.date(),
   796                     date=ctx.date(),
   600                     rev=i,
   797                     rev=i,
   607         cl = self.repo.changelog
   804         cl = self.repo.changelog
   608         count = cl.count()
   805         count = cl.count()
   609         start = max(0, count - self.maxchanges)
   806         start = max(0, count - self.maxchanges)
   610         end = min(count, start + self.maxchanges)
   807         end = min(count, start + self.maxchanges)
   611 
   808 
   612         yield self.t("summary",
   809         yield tmpl("summary",
   613                  desc=self.config("web", "description", "unknown"),
   810                    desc=self.config("web", "description", "unknown"),
   614                  owner=(self.config("ui", "username") or # preferred
   811                    owner=(self.config("ui", "username") or # preferred
   615                         self.config("web", "contact") or # deprecated
   812                           self.config("web", "contact") or # deprecated
   616                         self.config("web", "author", "unknown")), # also
   813                           self.config("web", "author", "unknown")), # also
   617                  lastchange=cl.read(cl.tip())[2],
   814                    lastchange=cl.read(cl.tip())[2],
   618                  tags=tagentries,
   815                    tags=tagentries,
   619                  branches=branches,
   816                    branches=branches,
   620                  shortlog=changelist,
   817                    shortlog=changelist,
   621                  node=hex(cl.tip()),
   818                    node=hex(cl.tip()),
   622                  archives=self.archivelist("tip"))
   819                    archives=self.archivelist("tip"))
   623 
   820 
   624     def filediff(self, fctx):
   821     def filediff(self, tmpl, fctx):
   625         n = fctx.node()
   822         n = fctx.node()
   626         path = fctx.path()
   823         path = fctx.path()
   627         parents = fctx.parents()
   824         parents = fctx.parents()
   628         p1 = parents and parents[0].node() or nullid
   825         p1 = parents and parents[0].node() or nullid
   629 
   826 
   630         def diff(**map):
   827         def diff(**map):
   631             yield self.diff(p1, n, [path])
   828             yield self.diff(tmpl, p1, n, [path])
   632 
   829 
   633         yield self.t("filediff",
   830         yield tmpl("filediff",
   634                      file=path,
   831                    file=path,
   635                      node=hex(n),
   832                    node=hex(n),
   636                      rev=fctx.rev(),
   833                    rev=fctx.rev(),
   637                      parent=self.siblings(parents),
   834                    parent=self.siblings(parents),
   638                      child=self.siblings(fctx.children()),
   835                    child=self.siblings(fctx.children()),
   639                      diff=diff)
   836                    diff=diff)
   640 
   837 
   641     archive_specs = {
   838     archive_specs = {
   642         'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
   839         'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
   643         'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
   840         'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
   644         'zip': ('application/zip', 'zip', '.zip', None),
   841         'zip': ('application/zip', 'zip', '.zip', None),
   645         }
   842         }
   646 
   843 
   647     def archive(self, req, key, type_):
   844     def archive(self, tmpl, req, key, type_):
   648         reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
   845         reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
   649         cnode = self.repo.lookup(key)
   846         cnode = self.repo.lookup(key)
   650         arch_version = key
   847         arch_version = key
   651         if cnode == key or key == 'tip':
   848         if cnode == key or key == 'tip':
   652             arch_version = short(cnode)
   849             arch_version = short(cnode)
   666 
   863 
   667     def cleanpath(self, path):
   864     def cleanpath(self, path):
   668         path = path.lstrip('/')
   865         path = path.lstrip('/')
   669         return util.canonpath(self.repo.root, '', path)
   866         return util.canonpath(self.repo.root, '', path)
   670 
   867 
   671     def run(self):
       
   672         if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
       
   673             raise RuntimeError("This function is only intended to be called while running as a CGI script.")
       
   674         import mercurial.hgweb.wsgicgi as wsgicgi
       
   675         wsgicgi.launch(self)
       
   676 
       
   677     def __call__(self, env, respond):
       
   678         req = wsgirequest(env, respond)
       
   679         self.run_wsgi(req)
       
   680         return req
       
   681 
       
   682     def run_wsgi(self, req):
       
   683         def header(**map):
       
   684             header_file = cStringIO.StringIO(
       
   685                 ''.join(self.t("header", encoding=self.encoding, **map)))
       
   686             msg = mimetools.Message(header_file, 0)
       
   687             req.header(msg.items())
       
   688             yield header_file.read()
       
   689 
       
   690         def rawfileheader(**map):
       
   691             req.header([('Content-type', map['mimetype']),
       
   692                         ('Content-disposition', 'filename=%s' % map['file']),
       
   693                         ('Content-length', str(len(map['raw'])))])
       
   694             yield ''
       
   695 
       
   696         def footer(**map):
       
   697             yield self.t("footer", **map)
       
   698 
       
   699         def motd(**map):
       
   700             yield self.config("web", "motd", "")
       
   701 
       
   702         def expand_form(form):
       
   703             shortcuts = {
       
   704                 'cl': [('cmd', ['changelog']), ('rev', None)],
       
   705                 'sl': [('cmd', ['shortlog']), ('rev', None)],
       
   706                 'cs': [('cmd', ['changeset']), ('node', None)],
       
   707                 'f': [('cmd', ['file']), ('filenode', None)],
       
   708                 'fl': [('cmd', ['filelog']), ('filenode', None)],
       
   709                 'fd': [('cmd', ['filediff']), ('node', None)],
       
   710                 'fa': [('cmd', ['annotate']), ('filenode', None)],
       
   711                 'mf': [('cmd', ['manifest']), ('manifest', None)],
       
   712                 'ca': [('cmd', ['archive']), ('node', None)],
       
   713                 'tags': [('cmd', ['tags'])],
       
   714                 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
       
   715                 'static': [('cmd', ['static']), ('file', None)]
       
   716             }
       
   717 
       
   718             for k in shortcuts.iterkeys():
       
   719                 if form.has_key(k):
       
   720                     for name, value in shortcuts[k]:
       
   721                         if value is None:
       
   722                             value = form[k]
       
   723                         form[name] = value
       
   724                     del form[k]
       
   725 
       
   726         def rewrite_request(req):
       
   727             '''translate new web interface to traditional format'''
       
   728 
       
   729             req.url = req.env['SCRIPT_NAME']
       
   730             if not req.url.endswith('/'):
       
   731                 req.url += '/'
       
   732             if req.env.has_key('REPO_NAME'):
       
   733                 req.url += req.env['REPO_NAME'] + '/'
       
   734             
       
   735             if req.env.get('PATH_INFO'):
       
   736                 parts = req.env.get('PATH_INFO').strip('/').split('/')
       
   737                 repo_parts = req.env.get('REPO_NAME', '').split('/')
       
   738                 if parts[:len(repo_parts)] == repo_parts:
       
   739                     parts = parts[len(repo_parts):]
       
   740                 query = '/'.join(parts)
       
   741             else:
       
   742                 query = req.env['QUERY_STRING'].split('&', 1)[0]
       
   743                 query = query.split(';', 1)[0]
       
   744 
       
   745             if req.form.has_key('cmd'):
       
   746                 # old style
       
   747                 return
       
   748 
       
   749             args = query.split('/', 2)
       
   750             if not args or not args[0]:
       
   751                 return
       
   752 
       
   753             cmd = args.pop(0)
       
   754             style = cmd.rfind('-')
       
   755             if style != -1:
       
   756                 req.form['style'] = [cmd[:style]]
       
   757                 cmd = cmd[style+1:]
       
   758             # avoid accepting e.g. style parameter as command
       
   759             if hasattr(self, 'do_' + cmd):
       
   760                 req.form['cmd'] = [cmd]
       
   761 
       
   762             if args and args[0]:
       
   763                 node = args.pop(0)
       
   764                 req.form['node'] = [node]
       
   765             if args:
       
   766                 req.form['file'] = args
       
   767 
       
   768             if cmd == 'static':
       
   769                 req.form['file'] = req.form['node']
       
   770             elif cmd == 'archive':
       
   771                 fn = req.form['node'][0]
       
   772                 for type_, spec in self.archive_specs.iteritems():
       
   773                     ext = spec[2]
       
   774                     if fn.endswith(ext):
       
   775                         req.form['node'] = [fn[:-len(ext)]]
       
   776                         req.form['type'] = [type_]
       
   777 
       
   778         def sessionvars(**map):
       
   779             fields = []
       
   780             if req.form.has_key('style'):
       
   781                 style = req.form['style'][0]
       
   782                 if style != self.config('web', 'style', ''):
       
   783                     fields.append(('style', style))
       
   784 
       
   785             separator = req.url[-1] == '?' and ';' or '?'
       
   786             for name, value in fields:
       
   787                 yield dict(name=name, value=value, separator=separator)
       
   788                 separator = ';'
       
   789 
       
   790         self.refresh()
       
   791 
       
   792         expand_form(req.form)
       
   793         rewrite_request(req)
       
   794 
       
   795         style = self.config("web", "style", "")
       
   796         if req.form.has_key('style'):
       
   797             style = req.form['style'][0]
       
   798         mapfile = style_map(self.templatepath, style)
       
   799 
       
   800         proto = req.env.get('wsgi.url_scheme')
       
   801         if proto == 'https':
       
   802             proto = 'https'
       
   803             default_port = "443"
       
   804         else:
       
   805             proto = 'http'
       
   806             default_port = "80"
       
   807 
       
   808         port = req.env["SERVER_PORT"]
       
   809         port = port != default_port and (":" + port) or ""
       
   810         urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
       
   811         staticurl = self.config("web", "staticurl") or req.url + 'static/'
       
   812         if not staticurl.endswith('/'):
       
   813             staticurl += '/'
       
   814 
       
   815         if not self.reponame:
       
   816             self.reponame = (self.config("web", "name")
       
   817                              or req.env.get('REPO_NAME')
       
   818                              or req.url.strip('/')
       
   819                              or os.path.basename(self.repo.root))
       
   820 
       
   821         self.t = templater.templater(mapfile, templater.common_filters,
       
   822                                      defaults={"url": req.url,
       
   823                                                "staticurl": staticurl,
       
   824                                                "urlbase": urlbase,
       
   825                                                "repo": self.reponame,
       
   826                                                "header": header,
       
   827                                                "footer": footer,
       
   828                                                "motd": motd,
       
   829                                                "rawfileheader": rawfileheader,
       
   830                                                "sessionvars": sessionvars
       
   831                                                })
       
   832 
       
   833         try:
       
   834             if not req.form.has_key('cmd'):
       
   835                 req.form['cmd'] = [self.t.cache['default']]
       
   836 
       
   837             cmd = req.form['cmd'][0]
       
   838 
       
   839             try:
       
   840                 method = getattr(self, 'do_' + cmd)
       
   841                 method(req)
       
   842             except revlog.LookupError, err:
       
   843                 req.respond(404, self.t(
       
   844                     'error', error='revision not found: %s' % err.name))
       
   845             except (hg.RepoError, revlog.RevlogError), inst:
       
   846                 req.respond('500 Internal Server Error',
       
   847                             self.t('error', error=str(inst)))
       
   848             except ErrorResponse, inst:
       
   849                 req.respond(inst.code, self.t('error', error=inst.message))
       
   850             except AttributeError:
       
   851                 req.respond(400,
       
   852                             self.t('error', error='No such method: ' + cmd))
       
   853         finally:
       
   854             self.t = None
       
   855 
       
   856     def changectx(self, req):
   868     def changectx(self, req):
   857         if req.form.has_key('node'):
   869         if req.form.has_key('node'):
   858             changeid = req.form['node'][0]
   870             changeid = req.form['node'][0]
   859         elif req.form.has_key('manifest'):
   871         elif req.form.has_key('manifest'):
   860             changeid = req.form['manifest'][0]
   872             changeid = req.form['manifest'][0]
   882         except hg.RepoError:
   894         except hg.RepoError:
   883             fctx = self.repo.filectx(path, fileid=changeid)
   895             fctx = self.repo.filectx(path, fileid=changeid)
   884 
   896 
   885         return fctx
   897         return fctx
   886 
   898 
   887     def do_log(self, req):
       
   888         if req.form.has_key('file') and req.form['file'][0]:
       
   889             self.do_filelog(req)
       
   890         else:
       
   891             self.do_changelog(req)
       
   892 
       
   893     def do_rev(self, req):
       
   894         self.do_changeset(req)
       
   895 
       
   896     def do_file(self, req):
       
   897         path = self.cleanpath(req.form.get('file', [''])[0])
       
   898         if path:
       
   899             try:
       
   900                 req.write(self.filerevision(self.filectx(req)))
       
   901                 return
       
   902             except revlog.LookupError:
       
   903                 pass
       
   904 
       
   905         req.write(self.manifest(self.changectx(req), path))
       
   906 
       
   907     def do_diff(self, req):
       
   908         self.do_filediff(req)
       
   909 
       
   910     def do_changelog(self, req, shortlog = False):
       
   911         if req.form.has_key('node'):
       
   912             ctx = self.changectx(req)
       
   913         else:
       
   914             if req.form.has_key('rev'):
       
   915                 hi = req.form['rev'][0]
       
   916             else:
       
   917                 hi = self.repo.changelog.count() - 1
       
   918             try:
       
   919                 ctx = self.repo.changectx(hi)
       
   920             except hg.RepoError:
       
   921                 req.write(self.search(hi)) # XXX redirect to 404 page?
       
   922                 return
       
   923 
       
   924         req.write(self.changelog(ctx, shortlog = shortlog))
       
   925 
       
   926     def do_shortlog(self, req):
       
   927         self.do_changelog(req, shortlog = True)
       
   928 
       
   929     def do_changeset(self, req):
       
   930         req.write(self.changeset(self.changectx(req)))
       
   931 
       
   932     def do_manifest(self, req):
       
   933         req.write(self.manifest(self.changectx(req),
       
   934                                 self.cleanpath(req.form['path'][0])))
       
   935 
       
   936     def do_tags(self, req):
       
   937         req.write(self.tags())
       
   938 
       
   939     def do_summary(self, req):
       
   940         req.write(self.summary())
       
   941 
       
   942     def do_filediff(self, req):
       
   943         req.write(self.filediff(self.filectx(req)))
       
   944 
       
   945     def do_annotate(self, req):
       
   946         req.write(self.fileannotate(self.filectx(req)))
       
   947 
       
   948     def do_filelog(self, req):
       
   949         req.write(self.filelog(self.filectx(req)))
       
   950 
       
   951     def do_lookup(self, req):
       
   952         try:
       
   953             r = hex(self.repo.lookup(req.form['key'][0]))
       
   954             success = 1
       
   955         except Exception,inst:
       
   956             r = str(inst)
       
   957             success = 0
       
   958         resp = "%s %s\n" % (success, r)
       
   959         req.httphdr("application/mercurial-0.1", length=len(resp))
       
   960         req.write(resp)
       
   961 
       
   962     def do_heads(self, req):
       
   963         resp = " ".join(map(hex, self.repo.heads())) + "\n"
       
   964         req.httphdr("application/mercurial-0.1", length=len(resp))
       
   965         req.write(resp)
       
   966 
       
   967     def do_branches(self, req):
       
   968         nodes = []
       
   969         if req.form.has_key('nodes'):
       
   970             nodes = map(bin, req.form['nodes'][0].split(" "))
       
   971         resp = cStringIO.StringIO()
       
   972         for b in self.repo.branches(nodes):
       
   973             resp.write(" ".join(map(hex, b)) + "\n")
       
   974         resp = resp.getvalue()
       
   975         req.httphdr("application/mercurial-0.1", length=len(resp))
       
   976         req.write(resp)
       
   977 
       
   978     def do_between(self, req):
       
   979         if req.form.has_key('pairs'):
       
   980             pairs = [map(bin, p.split("-"))
       
   981                      for p in req.form['pairs'][0].split(" ")]
       
   982         resp = cStringIO.StringIO()
       
   983         for b in self.repo.between(pairs):
       
   984             resp.write(" ".join(map(hex, b)) + "\n")
       
   985         resp = resp.getvalue()
       
   986         req.httphdr("application/mercurial-0.1", length=len(resp))
       
   987         req.write(resp)
       
   988 
       
   989     def do_changegroup(self, req):
       
   990         req.httphdr("application/mercurial-0.1")
       
   991         nodes = []
       
   992         if not self.allowpull:
       
   993             return
       
   994 
       
   995         if req.form.has_key('roots'):
       
   996             nodes = map(bin, req.form['roots'][0].split(" "))
       
   997 
       
   998         z = zlib.compressobj()
       
   999         f = self.repo.changegroup(nodes, 'serve')
       
  1000         while 1:
       
  1001             chunk = f.read(4096)
       
  1002             if not chunk:
       
  1003                 break
       
  1004             req.write(z.compress(chunk))
       
  1005 
       
  1006         req.write(z.flush())
       
  1007 
       
  1008     def do_changegroupsubset(self, req):
       
  1009         req.httphdr("application/mercurial-0.1")
       
  1010         bases = []
       
  1011         heads = []
       
  1012         if not self.allowpull:
       
  1013             return
       
  1014 
       
  1015         if req.form.has_key('bases'):
       
  1016             bases = [bin(x) for x in req.form['bases'][0].split(' ')]
       
  1017         if req.form.has_key('heads'):
       
  1018             heads = [bin(x) for x in req.form['heads'][0].split(' ')]
       
  1019 
       
  1020         z = zlib.compressobj()
       
  1021         f = self.repo.changegroupsubset(bases, heads, 'serve')
       
  1022         while 1:
       
  1023             chunk = f.read(4096)
       
  1024             if not chunk:
       
  1025                 break
       
  1026             req.write(z.compress(chunk))
       
  1027 
       
  1028         req.write(z.flush())
       
  1029 
       
  1030     def do_archive(self, req):
       
  1031         type_ = req.form['type'][0]
       
  1032         allowed = self.configlist("web", "allow_archive")
       
  1033         if (type_ in self.archives and (type_ in allowed or
       
  1034             self.configbool("web", "allow" + type_, False))):
       
  1035             self.archive(req, req.form['node'][0], type_)
       
  1036             return
       
  1037 
       
  1038         req.respond(400, self.t('error',
       
  1039                                 error='Unsupported archive type: %s' % type_))
       
  1040 
       
  1041     def do_static(self, req):
       
  1042         fname = req.form['file'][0]
       
  1043         # a repo owner may set web.static in .hg/hgrc to get any file
       
  1044         # readable by the user running the CGI script
       
  1045         static = self.config("web", "static",
       
  1046                              os.path.join(self.templatepath, "static"),
       
  1047                              untrusted=False)
       
  1048         req.write(staticfile(static, fname, req))
       
  1049 
       
  1050     def do_capabilities(self, req):
       
  1051         caps = ['lookup', 'changegroupsubset']
       
  1052         if self.configbool('server', 'uncompressed'):
       
  1053             caps.append('stream=%d' % self.repo.changelog.version)
       
  1054         # XXX: make configurable and/or share code with do_unbundle:
       
  1055         unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
       
  1056         if unbundleversions:
       
  1057             caps.append('unbundle=%s' % ','.join(unbundleversions))
       
  1058         resp = ' '.join(caps)
       
  1059         req.httphdr("application/mercurial-0.1", length=len(resp))
       
  1060         req.write(resp)
       
  1061 
       
  1062     def check_perm(self, req, op, default):
   899     def check_perm(self, req, op, default):
  1063         '''check permission for operation based on user auth.
   900         '''check permission for operation based on user auth.
  1064         return true if op allowed, else false.
   901         return true if op allowed, else false.
  1065         default is policy to use if no config given.'''
   902         default is policy to use if no config given.'''
  1066 
   903 
  1070         if deny and (not user or deny == ['*'] or user in deny):
   907         if deny and (not user or deny == ['*'] or user in deny):
  1071             return False
   908             return False
  1072 
   909 
  1073         allow = self.configlist('web', 'allow_' + op)
   910         allow = self.configlist('web', 'allow_' + op)
  1074         return (allow and (allow == ['*'] or user in allow)) or default
   911         return (allow and (allow == ['*'] or user in allow)) or default
  1075 
       
  1076     def do_unbundle(self, req):
       
  1077         def bail(response, headers={}):
       
  1078             length = int(req.env['CONTENT_LENGTH'])
       
  1079             for s in util.filechunkiter(req, limit=length):
       
  1080                 # drain incoming bundle, else client will not see
       
  1081                 # response when run outside cgi script
       
  1082                 pass
       
  1083             req.httphdr("application/mercurial-0.1", headers=headers)
       
  1084             req.write('0\n')
       
  1085             req.write(response)
       
  1086 
       
  1087         # require ssl by default, auth info cannot be sniffed and
       
  1088         # replayed
       
  1089         ssl_req = self.configbool('web', 'push_ssl', True)
       
  1090         if ssl_req:
       
  1091             if req.env.get('wsgi.url_scheme') != 'https':
       
  1092                 bail(_('ssl required\n'))
       
  1093                 return
       
  1094             proto = 'https'
       
  1095         else:
       
  1096             proto = 'http'
       
  1097 
       
  1098         # do not allow push unless explicitly allowed
       
  1099         if not self.check_perm(req, 'push', False):
       
  1100             bail(_('push not authorized\n'),
       
  1101                  headers={'status': '401 Unauthorized'})
       
  1102             return
       
  1103 
       
  1104         their_heads = req.form['heads'][0].split(' ')
       
  1105 
       
  1106         def check_heads():
       
  1107             heads = map(hex, self.repo.heads())
       
  1108             return their_heads == [hex('force')] or their_heads == heads
       
  1109 
       
  1110         # fail early if possible
       
  1111         if not check_heads():
       
  1112             bail(_('unsynced changes\n'))
       
  1113             return
       
  1114 
       
  1115         req.httphdr("application/mercurial-0.1")
       
  1116 
       
  1117         # do not lock repo until all changegroup data is
       
  1118         # streamed. save to temporary file.
       
  1119 
       
  1120         fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
       
  1121         fp = os.fdopen(fd, 'wb+')
       
  1122         try:
       
  1123             length = int(req.env['CONTENT_LENGTH'])
       
  1124             for s in util.filechunkiter(req, limit=length):
       
  1125                 fp.write(s)
       
  1126 
       
  1127             try:
       
  1128                 lock = self.repo.lock()
       
  1129                 try:
       
  1130                     if not check_heads():
       
  1131                         req.write('0\n')
       
  1132                         req.write(_('unsynced changes\n'))
       
  1133                         return
       
  1134 
       
  1135                     fp.seek(0)
       
  1136                     header = fp.read(6)
       
  1137                     if not header.startswith("HG"):
       
  1138                         # old client with uncompressed bundle
       
  1139                         def generator(f):
       
  1140                             yield header
       
  1141                             for chunk in f:
       
  1142                                 yield chunk
       
  1143                     elif not header.startswith("HG10"):
       
  1144                         req.write("0\n")
       
  1145                         req.write(_("unknown bundle version\n"))
       
  1146                         return
       
  1147                     elif header == "HG10GZ":
       
  1148                         def generator(f):
       
  1149                             zd = zlib.decompressobj()
       
  1150                             for chunk in f:
       
  1151                                 yield zd.decompress(chunk)
       
  1152                     elif header == "HG10BZ":
       
  1153                         def generator(f):
       
  1154                             zd = bz2.BZ2Decompressor()
       
  1155                             zd.decompress("BZ")
       
  1156                             for chunk in f:
       
  1157                                 yield zd.decompress(chunk)
       
  1158                     elif header == "HG10UN":
       
  1159                         def generator(f):
       
  1160                             for chunk in f:
       
  1161                                 yield chunk
       
  1162                     else:
       
  1163                         req.write("0\n")
       
  1164                         req.write(_("unknown bundle compression type\n"))
       
  1165                         return
       
  1166                     gen = generator(util.filechunkiter(fp, 4096))
       
  1167 
       
  1168                     # send addchangegroup output to client
       
  1169 
       
  1170                     old_stdout = sys.stdout
       
  1171                     sys.stdout = cStringIO.StringIO()
       
  1172 
       
  1173                     try:
       
  1174                         url = 'remote:%s:%s' % (proto,
       
  1175                                                 req.env.get('REMOTE_HOST', ''))
       
  1176                         try:
       
  1177                             ret = self.repo.addchangegroup(
       
  1178                                         util.chunkbuffer(gen), 'serve', url)
       
  1179                         except util.Abort, inst:
       
  1180                             sys.stdout.write("abort: %s\n" % inst)
       
  1181                             ret = 0
       
  1182                     finally:
       
  1183                         val = sys.stdout.getvalue()
       
  1184                         sys.stdout = old_stdout
       
  1185                     req.write('%d\n' % ret)
       
  1186                     req.write(val)
       
  1187                 finally:
       
  1188                     del lock
       
  1189             except (OSError, IOError), inst:
       
  1190                 req.write('0\n')
       
  1191                 filename = getattr(inst, 'filename', '')
       
  1192                 # Don't send our filesystem layout to the client
       
  1193                 if filename.startswith(self.repo.root):
       
  1194                     filename = filename[len(self.repo.root)+1:]
       
  1195                 else:
       
  1196                     filename = ''
       
  1197                 error = getattr(inst, 'strerror', 'Unknown error')
       
  1198                 if inst.errno == errno.ENOENT:
       
  1199                     code = 404
       
  1200                 else:
       
  1201                     code = 500
       
  1202                 req.respond(code, '%s: %s\n' % (error, filename))
       
  1203         finally:
       
  1204             fp.close()
       
  1205             os.unlink(tempname)
       
  1206 
       
  1207     def do_stream_out(self, req):
       
  1208         req.httphdr("application/mercurial-0.1")
       
  1209         streamclone.stream_out(self.repo, req, untrusted=True)