--- a/mercurial/hgweb/hgweb_mod.py Fri Dec 07 02:29:55 2007 -0600
+++ b/mercurial/hgweb/hgweb_mod.py Fri Dec 07 14:59:33 2007 -0600
@@ -6,14 +6,28 @@
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
-import errno, os, mimetypes, re, zlib, mimetools, cStringIO, sys
-import tempfile, urllib, bz2
+import os, mimetypes, re, mimetools, cStringIO
from mercurial.node import *
-from mercurial.i18n import gettext as _
-from mercurial import mdiff, ui, hg, util, archival, streamclone, patch
+from mercurial import mdiff, ui, hg, util, archival, patch
from mercurial import revlog, templater
-from common import ErrorResponse, get_mtime, staticfile, style_map, paritygen
+from common import ErrorResponse, get_mtime, style_map, paritygen
from request import wsgirequest
+import webcommands, protocol
+
+shortcuts = {
+ 'cl': [('cmd', ['changelog']), ('rev', None)],
+ 'sl': [('cmd', ['shortlog']), ('rev', None)],
+ 'cs': [('cmd', ['changeset']), ('node', None)],
+ 'f': [('cmd', ['file']), ('filenode', None)],
+ 'fl': [('cmd', ['filelog']), ('filenode', None)],
+ 'fd': [('cmd', ['filediff']), ('node', None)],
+ 'fa': [('cmd', ['annotate']), ('filenode', None)],
+ 'mf': [('cmd', ['manifest']), ('manifest', None)],
+ 'ca': [('cmd', ['archive']), ('node', None)],
+ 'tags': [('cmd', ['tags'])],
+ 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
+ 'static': [('cmd', ['static']), ('file', None)]
+}
def _up(p):
if p[0] != "/":
@@ -107,17 +121,200 @@
self.allowpull = self.configbool("web", "allowpull", True)
self.encoding = self.config("web", "encoding", util._encoding)
+ def run(self):
+ if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
+ raise RuntimeError("This function is only intended to be called while running as a CGI script.")
+ import mercurial.hgweb.wsgicgi as wsgicgi
+ wsgicgi.launch(self)
+
+ def __call__(self, env, respond):
+ req = wsgirequest(env, respond)
+ self.run_wsgi(req)
+ return req
+
+ def run_wsgi(self, req):
+
+ self.refresh()
+
+ # expand form shortcuts
+
+ for k in shortcuts.iterkeys():
+ if k in req.form:
+ for name, value in shortcuts[k]:
+ if value is None:
+ value = req.form[k]
+ req.form[name] = value
+ del req.form[k]
+
+ # work with CGI variables to create coherent structure
+ # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
+
+ req.url = req.env['SCRIPT_NAME']
+ if not req.url.endswith('/'):
+ req.url += '/'
+ if req.env.has_key('REPO_NAME'):
+ req.url += req.env['REPO_NAME'] + '/'
+
+ if req.env.get('PATH_INFO'):
+ parts = req.env.get('PATH_INFO').strip('/').split('/')
+ repo_parts = req.env.get('REPO_NAME', '').split('/')
+ if parts[:len(repo_parts)] == repo_parts:
+ parts = parts[len(repo_parts):]
+ query = '/'.join(parts)
+ else:
+ query = req.env['QUERY_STRING'].split('&', 1)[0]
+ query = query.split(';', 1)[0]
+
+ # translate user-visible url structure to internal structure
+
+ args = query.split('/', 2)
+ if 'cmd' not in req.form and args and args[0]:
+
+ cmd = args.pop(0)
+ style = cmd.rfind('-')
+ if style != -1:
+ req.form['style'] = [cmd[:style]]
+ cmd = cmd[style+1:]
+
+ # avoid accepting e.g. style parameter as command
+ if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
+ req.form['cmd'] = [cmd]
+
+ if args and args[0]:
+ node = args.pop(0)
+ req.form['node'] = [node]
+ if args:
+ req.form['file'] = args
+
+ if cmd == 'static':
+ req.form['file'] = req.form['node']
+ elif cmd == 'archive':
+ fn = req.form['node'][0]
+ for type_, spec in self.archive_specs.iteritems():
+ ext = spec[2]
+ if fn.endswith(ext):
+ req.form['node'] = [fn[:-len(ext)]]
+ req.form['type'] = [type_]
+
+ # actually process the request
+
+ try:
+
+ cmd = req.form.get('cmd', [''])[0]
+ if hasattr(protocol, cmd):
+ method = getattr(protocol, cmd)
+ method(self, req)
+ else:
+ tmpl = self.templater(req)
+ if cmd == '':
+ req.form['cmd'] = [tmpl.cache['default']]
+ cmd = req.form['cmd'][0]
+ method = getattr(webcommands, cmd)
+ method(self, req, tmpl)
+ del tmpl
+
+ except revlog.LookupError, err:
+ req.respond(404, tmpl(
+ 'error', error='revision not found: %s' % err.name))
+ except (hg.RepoError, revlog.RevlogError), inst:
+ req.respond('500 Internal Server Error',
+ tmpl('error', error=str(inst)))
+ except ErrorResponse, inst:
+ req.respond(inst.code, tmpl('error', error=inst.message))
+ except AttributeError:
+ req.respond(400, tmpl('error', error='No such method: ' + cmd))
+
+ def templater(self, req):
+
+ # determine scheme, port and server name
+ # this is needed to create absolute urls
+
+ proto = req.env.get('wsgi.url_scheme')
+ if proto == 'https':
+ proto = 'https'
+ default_port = "443"
+ else:
+ proto = 'http'
+ default_port = "80"
+
+ port = req.env["SERVER_PORT"]
+ port = port != default_port and (":" + port) or ""
+ urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
+ staticurl = self.config("web", "staticurl") or req.url + 'static/'
+ if not staticurl.endswith('/'):
+ staticurl += '/'
+
+ # some functions for the templater
+
+ def header(**map):
+ header_file = cStringIO.StringIO(
+ ''.join(tmpl("header", encoding=self.encoding, **map)))
+ msg = mimetools.Message(header_file, 0)
+ req.header(msg.items())
+ yield header_file.read()
+
+ def rawfileheader(**map):
+ req.header([('Content-type', map['mimetype']),
+ ('Content-disposition', 'filename=%s' % map['file']),
+ ('Content-length', str(len(map['raw'])))])
+ yield ''
+
+ def footer(**map):
+ yield tmpl("footer", **map)
+
+ def motd(**map):
+ yield self.config("web", "motd", "")
+
+ def sessionvars(**map):
+ fields = []
+ if req.form.has_key('style'):
+ style = req.form['style'][0]
+ if style != self.config('web', 'style', ''):
+ fields.append(('style', style))
+
+ separator = req.url[-1] == '?' and ';' or '?'
+ for name, value in fields:
+ yield dict(name=name, value=value, separator=separator)
+ separator = ';'
+
+ # figure out which style to use
+
+ style = self.config("web", "style", "")
+ if req.form.has_key('style'):
+ style = req.form['style'][0]
+ mapfile = style_map(self.templatepath, style)
+
+ if not self.reponame:
+ self.reponame = (self.config("web", "name")
+ or req.env.get('REPO_NAME')
+ or req.url.strip('/') or self.repo.root)
+
+ # create the templater
+
+ tmpl = templater.templater(mapfile, templater.common_filters,
+ defaults={"url": req.url,
+ "staticurl": staticurl,
+ "urlbase": urlbase,
+ "repo": self.reponame,
+ "header": header,
+ "footer": footer,
+ "motd": motd,
+ "rawfileheader": rawfileheader,
+ "sessionvars": sessionvars
+ })
+ return tmpl
+
def archivelist(self, nodeid):
allowed = self.configlist("web", "allow_archive")
for i, spec in self.archive_specs.iteritems():
if i in allowed or self.configbool("web", "allow" + i):
yield {"type" : i, "extension" : spec[2], "node" : nodeid}
- def listfilediffs(self, files, changeset):
+ def listfilediffs(self, tmpl, files, changeset):
for f in files[:self.maxfiles]:
- yield self.t("filedifflink", node=hex(changeset), file=f)
+ yield tmpl("filedifflink", node=hex(changeset), file=f)
if len(files) > self.maxfiles:
- yield self.t("fileellipses")
+ yield tmpl("fileellipses")
def siblings(self, siblings=[], hiderev=None, **args):
siblings = [s for s in siblings if s.node() != nullid]
@@ -149,11 +346,11 @@
branches.append({"name": branch})
return branches
- def showtag(self, t1, node=nullid, **args):
+ def showtag(self, tmpl, t1, node=nullid, **args):
for t in self.repo.nodetags(node):
- yield self.t(t1, tag=t, **args)
+ yield tmpl(t1, tag=t, **args)
- def diff(self, node1, node2, files):
+ def diff(self, tmpl, node1, node2, files):
def filterfiles(filters, files):
l = [x for x in files if x in filters]
@@ -165,22 +362,22 @@
parity = paritygen(self.stripecount)
def diffblock(diff, f, fn):
- yield self.t("diffblock",
- lines=prettyprintlines(diff),
- parity=parity.next(),
- file=f,
- filenode=hex(fn or nullid))
+ yield tmpl("diffblock",
+ lines=prettyprintlines(diff),
+ parity=parity.next(),
+ file=f,
+ filenode=hex(fn or nullid))
def prettyprintlines(diff):
for l in diff.splitlines(1):
if l.startswith('+'):
- yield self.t("difflineplus", line=l)
+ yield tmpl("difflineplus", line=l)
elif l.startswith('-'):
- yield self.t("difflineminus", line=l)
+ yield tmpl("difflineminus", line=l)
elif l.startswith('@'):
- yield self.t("difflineat", line=l)
+ yield tmpl("difflineat", line=l)
else:
- yield self.t("diffline", line=l)
+ yield tmpl("diffline", line=l)
r = self.repo
c1 = r.changectx(node1)
@@ -210,7 +407,7 @@
yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
opts=diffopts), f, tn)
- def changelog(self, ctx, shortlog=False):
+ def changelog(self, tmpl, ctx, shortlog=False):
def changelist(limit=0,**map):
cl = self.repo.changelog
l = [] # build a list in forward order for efficiency
@@ -225,7 +422,7 @@
"changelogtag": self.showtag("changelogtag",n),
"desc": ctx.description(),
"date": ctx.date(),
- "files": self.listfilediffs(ctx.files(), n),
+ "files": self.listfilediffs(tmpl, ctx.files(), n),
"rev": i,
"node": hex(n),
"tags": self.nodetagsdict(n),
@@ -248,15 +445,15 @@
changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
- yield self.t(shortlog and 'shortlog' or 'changelog',
- changenav=changenav,
- node=hex(cl.tip()),
- rev=pos, changesets=count,
- entries=lambda **x: changelist(limit=0,**x),
- latestentry=lambda **x: changelist(limit=1,**x),
- archives=self.archivelist("tip"))
+ yield tmpl(shortlog and 'shortlog' or 'changelog',
+ changenav=changenav,
+ node=hex(cl.tip()),
+ rev=pos, changesets=count,
+ entries=lambda **x: changelist(limit=0,**x),
+ latestentry=lambda **x: changelist(limit=1,**x),
+ archives=self.archivelist("tip"))
- def search(self, query):
+ def search(self, tmpl, query):
def changelist(**map):
cl = self.repo.changelog
@@ -287,19 +484,19 @@
count += 1
n = ctx.node()
- yield self.t('searchentry',
- parity=parity.next(),
- author=ctx.user(),
- parent=self.siblings(ctx.parents()),
- child=self.siblings(ctx.children()),
- changelogtag=self.showtag("changelogtag",n),
- desc=ctx.description(),
- date=ctx.date(),
- files=self.listfilediffs(ctx.files(), n),
- rev=ctx.rev(),
- node=hex(n),
- tags=self.nodetagsdict(n),
- branches=self.nodebranchdict(ctx))
+ yield tmpl('searchentry',
+ parity=parity.next(),
+ author=ctx.user(),
+ parent=self.siblings(ctx.parents()),
+ child=self.siblings(ctx.children()),
+ changelogtag=self.showtag("changelogtag",n),
+ desc=ctx.description(),
+ date=ctx.date(),
+ files=self.listfilediffs(tmpl, ctx.files(), n),
+ rev=ctx.rev(),
+ node=hex(n),
+ tags=self.nodetagsdict(n),
+ branches=self.nodebranchdict(ctx))
if count >= self.maxchanges:
break
@@ -307,13 +504,13 @@
cl = self.repo.changelog
parity = paritygen(self.stripecount)
- yield self.t('search',
- query=query,
- node=hex(cl.tip()),
- entries=changelist,
- archives=self.archivelist("tip"))
+ yield tmpl('search',
+ query=query,
+ node=hex(cl.tip()),
+ entries=changelist,
+ archives=self.archivelist("tip"))
- def changeset(self, ctx):
+ def changeset(self, tmpl, ctx):
n = ctx.node()
parents = ctx.parents()
p1 = parents[0].node()
@@ -321,29 +518,29 @@
files = []
parity = paritygen(self.stripecount)
for f in ctx.files():
- files.append(self.t("filenodelink",
- node=hex(n), file=f,
- parity=parity.next()))
+ files.append(tmpl("filenodelink",
+ node=hex(n), file=f,
+ parity=parity.next()))
def diff(**map):
- yield self.diff(p1, n, None)
+ yield self.diff(tmpl, p1, n, None)
- yield self.t('changeset',
- diff=diff,
- rev=ctx.rev(),
- node=hex(n),
- parent=self.siblings(parents),
- child=self.siblings(ctx.children()),
- changesettag=self.showtag("changesettag",n),
- author=ctx.user(),
- desc=ctx.description(),
- date=ctx.date(),
- files=files,
- archives=self.archivelist(hex(n)),
- tags=self.nodetagsdict(n),
- branches=self.nodebranchdict(ctx))
+ yield tmpl('changeset',
+ diff=diff,
+ rev=ctx.rev(),
+ node=hex(n),
+ parent=self.siblings(parents),
+ child=self.siblings(ctx.children()),
+ changesettag=self.showtag("changesettag",n),
+ author=ctx.user(),
+ desc=ctx.description(),
+ date=ctx.date(),
+ files=files,
+ archives=self.archivelist(hex(n)),
+ tags=self.nodetagsdict(n),
+ branches=self.nodebranchdict(ctx))
- def filelog(self, fctx):
+ def filelog(self, tmpl, fctx):
f = fctx.path()
fl = fctx.filelog()
count = fl.count()
@@ -380,11 +577,11 @@
nodefunc = lambda x: fctx.filectx(fileid=x)
nav = revnavgen(pos, pagelen, count, nodefunc)
- yield self.t("filelog", file=f, node=hex(fctx.node()), nav=nav,
- entries=lambda **x: entries(limit=0, **x),
- latestentry=lambda **x: entries(limit=1, **x))
+ yield tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
+ entries=lambda **x: entries(limit=0, **x),
+ latestentry=lambda **x: entries(limit=1, **x))
- def filerevision(self, fctx):
+ def filerevision(self, tmpl, fctx):
f = fctx.path()
text = fctx.data()
fl = fctx.filelog()
@@ -404,23 +601,23 @@
"linenumber": "% 6d" % (l + 1),
"parity": parity.next()}
- yield self.t("filerevision",
- file=f,
- path=_up(f),
- text=lines(),
- raw=rawtext,
- mimetype=mt,
- rev=fctx.rev(),
- node=hex(fctx.node()),
- author=fctx.user(),
- date=fctx.date(),
- desc=fctx.description(),
- parent=self.siblings(fctx.parents()),
- child=self.siblings(fctx.children()),
- rename=self.renamelink(fl, n),
- permissions=fctx.manifest().flags(f))
+ yield tmpl("filerevision",
+ file=f,
+ path=_up(f),
+ text=lines(),
+ raw=rawtext,
+ mimetype=mt,
+ rev=fctx.rev(),
+ node=hex(fctx.node()),
+ author=fctx.user(),
+ date=fctx.date(),
+ desc=fctx.description(),
+ parent=self.siblings(fctx.parents()),
+ child=self.siblings(fctx.children()),
+ rename=self.renamelink(fl, n),
+ permissions=fctx.manifest().flags(f))
- def fileannotate(self, fctx):
+ def fileannotate(self, tmpl, fctx):
f = fctx.path()
n = fctx.filenode()
fl = fctx.filelog()
@@ -442,21 +639,21 @@
"file": f.path(),
"line": l}
- yield self.t("fileannotate",
- file=f,
- annotate=annotate,
- path=_up(f),
- rev=fctx.rev(),
- node=hex(fctx.node()),
- author=fctx.user(),
- date=fctx.date(),
- desc=fctx.description(),
- rename=self.renamelink(fl, n),
- parent=self.siblings(fctx.parents()),
- child=self.siblings(fctx.children()),
- permissions=fctx.manifest().flags(f))
+ yield tmpl("fileannotate",
+ file=f,
+ annotate=annotate,
+ path=_up(f),
+ rev=fctx.rev(),
+ node=hex(fctx.node()),
+ author=fctx.user(),
+ date=fctx.date(),
+ desc=fctx.description(),
+ rename=self.renamelink(fl, n),
+ parent=self.siblings(fctx.parents()),
+ child=self.siblings(fctx.children()),
+ permissions=fctx.manifest().flags(f))
- def manifest(self, ctx, path):
+ def manifest(self, tmpl, ctx, path):
mf = ctx.manifest()
node = ctx.node()
@@ -510,19 +707,19 @@
"path": "%s%s" % (abspath, f),
"basename": f[:-1]}
- yield self.t("manifest",
- rev=ctx.rev(),
- node=hex(node),
- path=abspath,
- up=_up(abspath),
- upparity=parity.next(),
- fentries=filelist,
- dentries=dirlist,
- archives=self.archivelist(hex(node)),
- tags=self.nodetagsdict(node),
- branches=self.nodebranchdict(ctx))
+ yield tmpl("manifest",
+ rev=ctx.rev(),
+ node=hex(node),
+ path=abspath,
+ up=_up(abspath),
+ upparity=parity.next(),
+ fentries=filelist,
+ dentries=dirlist,
+ archives=self.archivelist(hex(node)),
+ tags=self.nodetagsdict(node),
+ branches=self.nodebranchdict(ctx))
- def tags(self):
+ def tags(self, tmpl):
i = self.repo.tagslist()
i.reverse()
parity = paritygen(self.stripecount)
@@ -540,13 +737,13 @@
"date": self.repo.changectx(n).date(),
"node": hex(n)}
- yield self.t("tags",
- node=hex(self.repo.changelog.tip()),
- entries=lambda **x: entries(False,0, **x),
- entriesnotip=lambda **x: entries(True,0, **x),
- latestentry=lambda **x: entries(True,1, **x))
+ yield tmpl("tags",
+ node=hex(self.repo.changelog.tip()),
+ entries=lambda **x: entries(False,0, **x),
+ entriesnotip=lambda **x: entries(True,0, **x),
+ latestentry=lambda **x: entries(True,1, **x))
- def summary(self):
+ def summary(self, tmpl):
i = self.repo.tagslist()
i.reverse()
@@ -561,11 +758,11 @@
if count > 10: # limit to 10 tags
break;
- yield self.t("tagentry",
- parity=parity.next(),
- tag=k,
- node=hex(n),
- date=self.repo.changectx(n).date())
+ yield tmpl("tagentry",
+ parity=parity.next(),
+ tag=k,
+ node=hex(n),
+ date=self.repo.changectx(n).date())
def branches(**map):
@@ -591,8 +788,8 @@
n = ctx.node()
hn = hex(n)
- l.insert(0, self.t(
- 'shortlogentry',
+ l.insert(0, tmpl(
+ 'shortlogentry',
parity=parity.next(),
author=ctx.user(),
desc=ctx.description(),
@@ -609,34 +806,34 @@
start = max(0, count - self.maxchanges)
end = min(count, start + self.maxchanges)
- yield self.t("summary",
- desc=self.config("web", "description", "unknown"),
- owner=(self.config("ui", "username") or # preferred
- self.config("web", "contact") or # deprecated
- self.config("web", "author", "unknown")), # also
- lastchange=cl.read(cl.tip())[2],
- tags=tagentries,
- branches=branches,
- shortlog=changelist,
- node=hex(cl.tip()),
- archives=self.archivelist("tip"))
+ yield tmpl("summary",
+ desc=self.config("web", "description", "unknown"),
+ owner=(self.config("ui", "username") or # preferred
+ self.config("web", "contact") or # deprecated
+ self.config("web", "author", "unknown")), # also
+ lastchange=cl.read(cl.tip())[2],
+ tags=tagentries,
+ branches=branches,
+ shortlog=changelist,
+ node=hex(cl.tip()),
+ archives=self.archivelist("tip"))
- def filediff(self, fctx):
+ def filediff(self, tmpl, fctx):
n = fctx.node()
path = fctx.path()
parents = fctx.parents()
p1 = parents and parents[0].node() or nullid
def diff(**map):
- yield self.diff(p1, n, [path])
+ yield self.diff(tmpl, p1, n, [path])
- yield self.t("filediff",
- file=path,
- node=hex(n),
- rev=fctx.rev(),
- parent=self.siblings(parents),
- child=self.siblings(fctx.children()),
- diff=diff)
+ yield tmpl("filediff",
+ file=path,
+ node=hex(n),
+ rev=fctx.rev(),
+ parent=self.siblings(parents),
+ child=self.siblings(fctx.children()),
+ diff=diff)
archive_specs = {
'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
@@ -644,7 +841,7 @@
'zip': ('application/zip', 'zip', '.zip', None),
}
- def archive(self, req, key, type_):
+ def archive(self, tmpl, req, key, type_):
reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
cnode = self.repo.lookup(key)
arch_version = key
@@ -668,191 +865,6 @@
path = path.lstrip('/')
return util.canonpath(self.repo.root, '', path)
- def run(self):
- if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
- raise RuntimeError("This function is only intended to be called while running as a CGI script.")
- import mercurial.hgweb.wsgicgi as wsgicgi
- wsgicgi.launch(self)
-
- def __call__(self, env, respond):
- req = wsgirequest(env, respond)
- self.run_wsgi(req)
- return req
-
- def run_wsgi(self, req):
- def header(**map):
- header_file = cStringIO.StringIO(
- ''.join(self.t("header", encoding=self.encoding, **map)))
- msg = mimetools.Message(header_file, 0)
- req.header(msg.items())
- yield header_file.read()
-
- def rawfileheader(**map):
- req.header([('Content-type', map['mimetype']),
- ('Content-disposition', 'filename=%s' % map['file']),
- ('Content-length', str(len(map['raw'])))])
- yield ''
-
- def footer(**map):
- yield self.t("footer", **map)
-
- def motd(**map):
- yield self.config("web", "motd", "")
-
- def expand_form(form):
- shortcuts = {
- 'cl': [('cmd', ['changelog']), ('rev', None)],
- 'sl': [('cmd', ['shortlog']), ('rev', None)],
- 'cs': [('cmd', ['changeset']), ('node', None)],
- 'f': [('cmd', ['file']), ('filenode', None)],
- 'fl': [('cmd', ['filelog']), ('filenode', None)],
- 'fd': [('cmd', ['filediff']), ('node', None)],
- 'fa': [('cmd', ['annotate']), ('filenode', None)],
- 'mf': [('cmd', ['manifest']), ('manifest', None)],
- 'ca': [('cmd', ['archive']), ('node', None)],
- 'tags': [('cmd', ['tags'])],
- 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
- 'static': [('cmd', ['static']), ('file', None)]
- }
-
- for k in shortcuts.iterkeys():
- if form.has_key(k):
- for name, value in shortcuts[k]:
- if value is None:
- value = form[k]
- form[name] = value
- del form[k]
-
- def rewrite_request(req):
- '''translate new web interface to traditional format'''
-
- req.url = req.env['SCRIPT_NAME']
- if not req.url.endswith('/'):
- req.url += '/'
- if req.env.has_key('REPO_NAME'):
- req.url += req.env['REPO_NAME'] + '/'
-
- if req.env.get('PATH_INFO'):
- parts = req.env.get('PATH_INFO').strip('/').split('/')
- repo_parts = req.env.get('REPO_NAME', '').split('/')
- if parts[:len(repo_parts)] == repo_parts:
- parts = parts[len(repo_parts):]
- query = '/'.join(parts)
- else:
- query = req.env['QUERY_STRING'].split('&', 1)[0]
- query = query.split(';', 1)[0]
-
- if req.form.has_key('cmd'):
- # old style
- return
-
- args = query.split('/', 2)
- if not args or not args[0]:
- return
-
- cmd = args.pop(0)
- style = cmd.rfind('-')
- if style != -1:
- req.form['style'] = [cmd[:style]]
- cmd = cmd[style+1:]
- # avoid accepting e.g. style parameter as command
- if hasattr(self, 'do_' + cmd):
- req.form['cmd'] = [cmd]
-
- if args and args[0]:
- node = args.pop(0)
- req.form['node'] = [node]
- if args:
- req.form['file'] = args
-
- if cmd == 'static':
- req.form['file'] = req.form['node']
- elif cmd == 'archive':
- fn = req.form['node'][0]
- for type_, spec in self.archive_specs.iteritems():
- ext = spec[2]
- if fn.endswith(ext):
- req.form['node'] = [fn[:-len(ext)]]
- req.form['type'] = [type_]
-
- def sessionvars(**map):
- fields = []
- if req.form.has_key('style'):
- style = req.form['style'][0]
- if style != self.config('web', 'style', ''):
- fields.append(('style', style))
-
- separator = req.url[-1] == '?' and ';' or '?'
- for name, value in fields:
- yield dict(name=name, value=value, separator=separator)
- separator = ';'
-
- self.refresh()
-
- expand_form(req.form)
- rewrite_request(req)
-
- style = self.config("web", "style", "")
- if req.form.has_key('style'):
- style = req.form['style'][0]
- mapfile = style_map(self.templatepath, style)
-
- proto = req.env.get('wsgi.url_scheme')
- if proto == 'https':
- proto = 'https'
- default_port = "443"
- else:
- proto = 'http'
- default_port = "80"
-
- port = req.env["SERVER_PORT"]
- port = port != default_port and (":" + port) or ""
- urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
- staticurl = self.config("web", "staticurl") or req.url + 'static/'
- if not staticurl.endswith('/'):
- staticurl += '/'
-
- if not self.reponame:
- self.reponame = (self.config("web", "name")
- or req.env.get('REPO_NAME')
- or req.url.strip('/')
- or os.path.basename(self.repo.root))
-
- self.t = templater.templater(mapfile, templater.common_filters,
- defaults={"url": req.url,
- "staticurl": staticurl,
- "urlbase": urlbase,
- "repo": self.reponame,
- "header": header,
- "footer": footer,
- "motd": motd,
- "rawfileheader": rawfileheader,
- "sessionvars": sessionvars
- })
-
- try:
- if not req.form.has_key('cmd'):
- req.form['cmd'] = [self.t.cache['default']]
-
- cmd = req.form['cmd'][0]
-
- try:
- method = getattr(self, 'do_' + cmd)
- method(req)
- except revlog.LookupError, err:
- req.respond(404, self.t(
- 'error', error='revision not found: %s' % err.name))
- except (hg.RepoError, revlog.RevlogError), inst:
- req.respond('500 Internal Server Error',
- self.t('error', error=str(inst)))
- except ErrorResponse, inst:
- req.respond(inst.code, self.t('error', error=inst.message))
- except AttributeError:
- req.respond(400,
- self.t('error', error='No such method: ' + cmd))
- finally:
- self.t = None
-
def changectx(self, req):
if req.form.has_key('node'):
changeid = req.form['node'][0]
@@ -884,181 +896,6 @@
return fctx
- def do_log(self, req):
- if req.form.has_key('file') and req.form['file'][0]:
- self.do_filelog(req)
- else:
- self.do_changelog(req)
-
- def do_rev(self, req):
- self.do_changeset(req)
-
- def do_file(self, req):
- path = self.cleanpath(req.form.get('file', [''])[0])
- if path:
- try:
- req.write(self.filerevision(self.filectx(req)))
- return
- except revlog.LookupError:
- pass
-
- req.write(self.manifest(self.changectx(req), path))
-
- def do_diff(self, req):
- self.do_filediff(req)
-
- def do_changelog(self, req, shortlog = False):
- if req.form.has_key('node'):
- ctx = self.changectx(req)
- else:
- if req.form.has_key('rev'):
- hi = req.form['rev'][0]
- else:
- hi = self.repo.changelog.count() - 1
- try:
- ctx = self.repo.changectx(hi)
- except hg.RepoError:
- req.write(self.search(hi)) # XXX redirect to 404 page?
- return
-
- req.write(self.changelog(ctx, shortlog = shortlog))
-
- def do_shortlog(self, req):
- self.do_changelog(req, shortlog = True)
-
- def do_changeset(self, req):
- req.write(self.changeset(self.changectx(req)))
-
- def do_manifest(self, req):
- req.write(self.manifest(self.changectx(req),
- self.cleanpath(req.form['path'][0])))
-
- def do_tags(self, req):
- req.write(self.tags())
-
- def do_summary(self, req):
- req.write(self.summary())
-
- def do_filediff(self, req):
- req.write(self.filediff(self.filectx(req)))
-
- def do_annotate(self, req):
- req.write(self.fileannotate(self.filectx(req)))
-
- def do_filelog(self, req):
- req.write(self.filelog(self.filectx(req)))
-
- def do_lookup(self, req):
- try:
- r = hex(self.repo.lookup(req.form['key'][0]))
- success = 1
- except Exception,inst:
- r = str(inst)
- success = 0
- resp = "%s %s\n" % (success, r)
- req.httphdr("application/mercurial-0.1", length=len(resp))
- req.write(resp)
-
- def do_heads(self, req):
- resp = " ".join(map(hex, self.repo.heads())) + "\n"
- req.httphdr("application/mercurial-0.1", length=len(resp))
- req.write(resp)
-
- def do_branches(self, req):
- nodes = []
- if req.form.has_key('nodes'):
- nodes = map(bin, req.form['nodes'][0].split(" "))
- resp = cStringIO.StringIO()
- for b in self.repo.branches(nodes):
- resp.write(" ".join(map(hex, b)) + "\n")
- resp = resp.getvalue()
- req.httphdr("application/mercurial-0.1", length=len(resp))
- req.write(resp)
-
- def do_between(self, req):
- if req.form.has_key('pairs'):
- pairs = [map(bin, p.split("-"))
- for p in req.form['pairs'][0].split(" ")]
- resp = cStringIO.StringIO()
- for b in self.repo.between(pairs):
- resp.write(" ".join(map(hex, b)) + "\n")
- resp = resp.getvalue()
- req.httphdr("application/mercurial-0.1", length=len(resp))
- req.write(resp)
-
- def do_changegroup(self, req):
- req.httphdr("application/mercurial-0.1")
- nodes = []
- if not self.allowpull:
- return
-
- if req.form.has_key('roots'):
- nodes = map(bin, req.form['roots'][0].split(" "))
-
- z = zlib.compressobj()
- f = self.repo.changegroup(nodes, 'serve')
- while 1:
- chunk = f.read(4096)
- if not chunk:
- break
- req.write(z.compress(chunk))
-
- req.write(z.flush())
-
- def do_changegroupsubset(self, req):
- req.httphdr("application/mercurial-0.1")
- bases = []
- heads = []
- if not self.allowpull:
- return
-
- if req.form.has_key('bases'):
- bases = [bin(x) for x in req.form['bases'][0].split(' ')]
- if req.form.has_key('heads'):
- heads = [bin(x) for x in req.form['heads'][0].split(' ')]
-
- z = zlib.compressobj()
- f = self.repo.changegroupsubset(bases, heads, 'serve')
- while 1:
- chunk = f.read(4096)
- if not chunk:
- break
- req.write(z.compress(chunk))
-
- req.write(z.flush())
-
- def do_archive(self, req):
- type_ = req.form['type'][0]
- allowed = self.configlist("web", "allow_archive")
- if (type_ in self.archives and (type_ in allowed or
- self.configbool("web", "allow" + type_, False))):
- self.archive(req, req.form['node'][0], type_)
- return
-
- req.respond(400, self.t('error',
- error='Unsupported archive type: %s' % type_))
-
- def do_static(self, req):
- fname = req.form['file'][0]
- # a repo owner may set web.static in .hg/hgrc to get any file
- # readable by the user running the CGI script
- static = self.config("web", "static",
- os.path.join(self.templatepath, "static"),
- untrusted=False)
- req.write(staticfile(static, fname, req))
-
- def do_capabilities(self, req):
- caps = ['lookup', 'changegroupsubset']
- if self.configbool('server', 'uncompressed'):
- caps.append('stream=%d' % self.repo.changelog.version)
- # XXX: make configurable and/or share code with do_unbundle:
- unbundleversions = ['HG10GZ', 'HG10BZ', 'HG10UN']
- if unbundleversions:
- caps.append('unbundle=%s' % ','.join(unbundleversions))
- resp = ' '.join(caps)
- req.httphdr("application/mercurial-0.1", length=len(resp))
- req.write(resp)
-
def check_perm(self, req, op, default):
'''check permission for operation based on user auth.
return true if op allowed, else false.
@@ -1072,138 +909,3 @@
allow = self.configlist('web', 'allow_' + op)
return (allow and (allow == ['*'] or user in allow)) or default
-
- def do_unbundle(self, req):
- def bail(response, headers={}):
- length = int(req.env['CONTENT_LENGTH'])
- for s in util.filechunkiter(req, limit=length):
- # drain incoming bundle, else client will not see
- # response when run outside cgi script
- pass
- req.httphdr("application/mercurial-0.1", headers=headers)
- req.write('0\n')
- req.write(response)
-
- # require ssl by default, auth info cannot be sniffed and
- # replayed
- ssl_req = self.configbool('web', 'push_ssl', True)
- if ssl_req:
- if req.env.get('wsgi.url_scheme') != 'https':
- bail(_('ssl required\n'))
- return
- proto = 'https'
- else:
- proto = 'http'
-
- # do not allow push unless explicitly allowed
- if not self.check_perm(req, 'push', False):
- bail(_('push not authorized\n'),
- headers={'status': '401 Unauthorized'})
- return
-
- their_heads = req.form['heads'][0].split(' ')
-
- def check_heads():
- heads = map(hex, self.repo.heads())
- return their_heads == [hex('force')] or their_heads == heads
-
- # fail early if possible
- if not check_heads():
- bail(_('unsynced changes\n'))
- return
-
- req.httphdr("application/mercurial-0.1")
-
- # do not lock repo until all changegroup data is
- # streamed. save to temporary file.
-
- fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
- fp = os.fdopen(fd, 'wb+')
- try:
- length = int(req.env['CONTENT_LENGTH'])
- for s in util.filechunkiter(req, limit=length):
- fp.write(s)
-
- try:
- lock = self.repo.lock()
- try:
- if not check_heads():
- req.write('0\n')
- req.write(_('unsynced changes\n'))
- return
-
- fp.seek(0)
- header = fp.read(6)
- if not header.startswith("HG"):
- # old client with uncompressed bundle
- def generator(f):
- yield header
- for chunk in f:
- yield chunk
- elif not header.startswith("HG10"):
- req.write("0\n")
- req.write(_("unknown bundle version\n"))
- return
- elif header == "HG10GZ":
- def generator(f):
- zd = zlib.decompressobj()
- for chunk in f:
- yield zd.decompress(chunk)
- elif header == "HG10BZ":
- def generator(f):
- zd = bz2.BZ2Decompressor()
- zd.decompress("BZ")
- for chunk in f:
- yield zd.decompress(chunk)
- elif header == "HG10UN":
- def generator(f):
- for chunk in f:
- yield chunk
- else:
- req.write("0\n")
- req.write(_("unknown bundle compression type\n"))
- return
- gen = generator(util.filechunkiter(fp, 4096))
-
- # send addchangegroup output to client
-
- old_stdout = sys.stdout
- sys.stdout = cStringIO.StringIO()
-
- try:
- url = 'remote:%s:%s' % (proto,
- req.env.get('REMOTE_HOST', ''))
- try:
- ret = self.repo.addchangegroup(
- util.chunkbuffer(gen), 'serve', url)
- except util.Abort, inst:
- sys.stdout.write("abort: %s\n" % inst)
- ret = 0
- finally:
- val = sys.stdout.getvalue()
- sys.stdout = old_stdout
- req.write('%d\n' % ret)
- req.write(val)
- finally:
- del lock
- except (OSError, IOError), inst:
- req.write('0\n')
- filename = getattr(inst, 'filename', '')
- # Don't send our filesystem layout to the client
- if filename.startswith(self.repo.root):
- filename = filename[len(self.repo.root)+1:]
- else:
- filename = ''
- error = getattr(inst, 'strerror', 'Unknown error')
- if inst.errno == errno.ENOENT:
- code = 404
- else:
- code = 500
- req.respond(code, '%s: %s\n' % (error, filename))
- finally:
- fp.close()
- os.unlink(tempname)
-
- def do_stream_out(self, req):
- req.httphdr("application/mercurial-0.1")
- streamclone.stream_out(self.repo, req, untrusted=True)