##// END OF EJS Templates
Merge with stable
Merge with stable

File last commit:

r11677:8f8a7976 stable
r11721:0b8d17bb merge stable
Show More
hgwebdir_mod.py
352 lines | 13.3 KiB | text/x-python | PythonLexer
Eric Hopper
Fixing up comment headers for split up code.
r2391 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
Eric Hopper
Final stage of the hgweb split up....
r2356 #
# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
Vadim Gelfer
update copyrights.
r2859 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
Eric Hopper
Final stage of the hgweb split up....
r2356 #
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Eric Hopper
Final stage of the hgweb split up....
r2356
Yuya Nishihara
hgweb: fix broken URLs of RSS/Atom feeds (issue1772)...
r10674 import os, re, time, urlparse
Martin Geisler
i18n: import _ instead of gettext
r7225 from mercurial.i18n import _
Dirkjan Ochtman
templater: provide the standard template filters by default
r8360 from mercurial import ui, hg, util, templater
Alexander Solovyov
hgwebdir: read --webdir-conf as actual configuration to ui (issue1586)...
r8345 from mercurial import error, encoding
Benoit Boissinot
fix coding style (reported by pylint)
r10394 from common import ErrorResponse, get_mtime, staticfile, paritygen, \
Dirkjan Ochtman
hgweb: explicit response status
r5993 get_contact, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
Matt Mackall
Replace demandload with new demandimport
r3877 from hgweb_mod import hgweb
Dirkjan Ochtman
Less indirection in the WSGI web interface. This simplifies some code, and makes it more compliant with WSGI.
r5566 from request import wsgirequest
Dirkjan Ochtman
hgweb: use new sessionvars code in hgwebdir, too
r8216 import webutil
Eric Hopper
Final stage of the hgweb split up....
r2356
Dirkjan Ochtman
hgweb: some cleanups in hgwebdir, remove double defaults...
r8215 def cleannames(items):
return [(util.pconvert(name).strip('/'), path) for name, path in items]
Eric Hopper
Final stage of the hgweb split up....
r2356
Jeremy Whitlock
hgweb: make hgwebdir handle dict/list paths the same as config paths...
r8529 def findrepos(paths):
Dirkjan Ochtman
hgweb: use a tuple-list instead of dictionary for append-only store
r9723 repos = []
Jeremy Whitlock
hgweb: make hgwebdir handle dict/list paths the same as config paths...
r8529 for prefix, root in cleannames(paths):
roothead, roottail = os.path.split(root)
# "foo = /bar/*" makes every subrepo of /bar/ to be
# mounted as foo/subrepo
# and "foo = /bar/**" also recurses into the subdirectories,
# remember to use it without working dir.
try:
recurse = {'*': False, '**': True}[roottail]
except KeyError:
Dirkjan Ochtman
hgweb: use a tuple-list instead of dictionary for append-only store
r9723 repos.append((prefix, root))
Jeremy Whitlock
hgweb: make hgwebdir handle dict/list paths the same as config paths...
r8529 continue
Mads Kiilerich
hgwebdir: allow pure relative globs in paths...
r11677 roothead = os.path.normpath(os.path.abspath(roothead))
Jeremy Whitlock
hgweb: make hgwebdir handle dict/list paths the same as config paths...
r8529 for path in util.walkrepos(roothead, followsym=True, recurse=recurse):
path = os.path.normpath(path)
name = util.pconvert(path[len(roothead):]).strip('/')
if prefix:
name = prefix + '/' + name
Dirkjan Ochtman
hgweb: use a tuple-list instead of dictionary for append-only store
r9723 repos.append((name, path))
return repos
Jeremy Whitlock
hgweb: make hgwebdir handle dict/list paths the same as config paths...
r8529
Eric Hopper
Final stage of the hgweb split up....
r2356 class hgwebdir(object):
Bryan O'Sullivan
hgwebdir: refresh configuration periodically...
r8371 refreshinterval = 20
Dirkjan Ochtman
hgweb: some cleanups in hgwebdir, remove double defaults...
r8215
Matt Mackall
hgweb: kill parentui references
r8191 def __init__(self, conf, baseui=None):
Bryan O'Sullivan
hgwebdir: refresh configuration periodically...
r8371 self.conf = conf
self.baseui = baseui
self.lastrefresh = 0
Thomas Arendsen Hein
Do not overwrite motd attribute of hgwebdir instances on refresh....
r9903 self.motd = None
Bryan O'Sullivan
hgwebdir: refresh configuration periodically...
r8371 self.refresh()
Eric Hopper
Final stage of the hgweb split up....
r2356
Bryan O'Sullivan
hgwebdir: refresh configuration periodically...
r8371 def refresh(self):
if self.lastrefresh + self.refreshinterval > time.time():
return
if self.baseui:
Matt Mackall
hgweb: fix race in refreshing repo list (issue2188)
r11239 u = self.baseui.copy()
Eric Hopper
Final stage of the hgweb split up....
r2356 else:
Matt Mackall
hgweb: fix race in refreshing repo list (issue2188)
r11239 u = ui.ui()
u.setconfig('ui', 'report_untrusted', 'off')
u.setconfig('ui', 'interactive', 'off')
Matt Mackall
ui: refactor option setting...
r8136
Jeremy Whitlock
hgweb: make hgwebdir handle dict/list paths the same as config paths...
r8529 if not isinstance(self.conf, (dict, list, tuple)):
map = {'paths': 'hgweb-paths'}
Matt Mackall
hgweb: fix race in refreshing repo list (issue2188)
r11239 u.readconfig(self.conf, remap=map, trust=True)
paths = u.configitems('hgweb-paths')
Jeremy Whitlock
hgweb: make hgwebdir handle dict/list paths the same as config paths...
r8529 elif isinstance(self.conf, (list, tuple)):
paths = self.conf
elif isinstance(self.conf, dict):
paths = self.conf.items()
Alexander Solovyov
hgwebdir: read --webdir-conf as actual configuration to ui (issue1586)...
r8345
Matt Mackall
hgweb: fix race in refreshing repo list (issue2188)
r11239 repos = findrepos(paths)
for prefix, root in u.configitems('collections'):
prefix = util.pconvert(prefix)
for path in util.walkrepos(root, followsym=True):
repo = os.path.normpath(path)
name = util.pconvert(repo)
if name.startswith(prefix):
name = name[len(prefix):]
repos.append((name.lstrip('/'), repo))
self.repos = repos
self.ui = u
Matt Mackall
hgweb: web.encoding should override encoding.encoding (issue1183)
r8859 encoding.encoding = self.ui.config('web', 'encoding',
encoding.encoding)
Dirkjan Ochtman
hgweb: extract config values after reading webdir-config
r8621 self.style = self.ui.config('web', 'style', 'paper')
Christian Fischer
hgwebdir: use template paths configured in the hgrc (issue2281)
r11649 self.templatepath = self.ui.config('web', 'templates', None)
Dirkjan Ochtman
hgweb: extract config values after reading webdir-config
r8621 self.stripecount = self.ui.config('web', 'stripes', 1)
if self.stripecount:
self.stripecount = int(self.stripecount)
self._baseurl = self.ui.config('web', 'baseurl')
Bryan O'Sullivan
hgwebdir: refresh configuration periodically...
r8371 self.lastrefresh = time.time()
Eric Hopper
Final stage of the hgweb split up....
r2356
Eric Hopper
Arrange for old copies of CGI scripts to still work.
r2535 def run(self):
Eric Hopper
Cleanup hgweb and hgwebdir's run method a bit.
r2538 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
Martin Geisler
wrap string literals in error messages
r8663 raise RuntimeError("This function is only intended to be "
"called while running as a CGI script.")
Eric Hopper
Arrange for old copies of CGI scripts to still work.
r2535 import mercurial.hgweb.wsgicgi as wsgicgi
Dirkjan Ochtman
Less indirection in the WSGI web interface. This simplifies some code, and makes it more compliant with WSGI.
r5566 wsgicgi.launch(self)
def __call__(self, env, respond):
req = wsgirequest(env, respond)
Dirkjan Ochtman
merge another backout
r6797 return self.run_wsgi(req)
Eric Hopper
Arrange for old copies of CGI scripts to still work.
r2535
Mark Edgington
hgweb: support for deny_read/allow_read options...
r7336 def read_allowed(self, ui, req):
"""Check allow_read and deny_read config options of a repo's ui object
to determine user permissions. By default, with neither option set (or
both empty), allow all users to read the repo. There are two ways a
user can be denied read access: (1) deny_read is not empty, and the
user is unauthenticated or deny_read contains user (or *), and (2)
allow_read is not empty and the user is not in allow_read. Return True
if user is allowed to read the repo, else return False."""
user = req.env.get('REMOTE_USER')
Dirkjan Ochtman
hgweb: fix long line lengths introduced in 2dc868712dcc
r7575 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
Mark Edgington
hgweb: support for deny_read/allow_read options...
r7336 if deny_read and (not user or deny_read == ['*'] or user in deny_read):
return False
Dirkjan Ochtman
hgweb: fix long line lengths introduced in 2dc868712dcc
r7575 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
Mark Edgington
hgweb: support for deny_read/allow_read options...
r7336 # by default, allow reading if no allow_read option has been set
if (not allow_read) or (allow_read == ['*']) or (user in allow_read):
return True
return False
Eric Hopper
Arrange for old copies of CGI scripts to still work.
r2535 def run_wsgi(self, req):
Dirkjan Ochtman
hgwebdir: split out makeindex function, facilitate test failure diagnosis
r5601 try:
try:
Bryan O'Sullivan
hgwebdir: refresh configuration periodically...
r8371 self.refresh()
Dirkjan Ochtman
hgwebdir: refactor inner loop
r5603
Dirkjan Ochtman
hgwebdir: split out makeindex function, facilitate test failure diagnosis
r5601 virtual = req.env.get("PATH_INFO", "").strip('/')
Dirkjan Ochtman
hgweb: explicit response status
r5993 tmpl = self.templater(req)
Matt Mackall
move encoding bits from util to encoding...
r7948 ctype = tmpl('mimetype', encoding=encoding.encoding)
Dirkjan Ochtman
Backed out changeset d2bb66a8a435 (temporary template compatibility)
r6391 ctype = templater.stringify(ctype)
Thomas Arendsen Hein
Removed tabs and trailing whitespace in python files
r5760
Dirkjan Ochtman
hgwebdir: refactor inner loop
r5603 # a static file
if virtual.startswith('static/') or 'static' in req.form:
if virtual.startswith('static/'):
fname = virtual[7:]
else:
fname = req.form['static'][0]
Brendan Cully
Allow hgweb to search for templates in more than one path....
r7107 static = templater.templatepath('static')
Patrick Mezard
hgwebdir_mod: fix a performance issue with static files
r7560 return (staticfile(static, fname, req),)
Dirkjan Ochtman
hgwebdir: refactor inner loop
r5603
# top-level index
elif not virtual:
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, ctype)
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 return self.makeindex(req, tmpl)
Dirkjan Ochtman
hgwebdir: split out makeindex function, facilitate test failure diagnosis
r5601
Dirkjan Ochtman
hgwebdir: refactor inner loop
r5603 # nested indexes and hgwebs
Thomas Arendsen Hein
Removed trailing spaces from everything except test output
r6210
Dirkjan Ochtman
hgwebdir: refactor inner loop
r5603 repos = dict(self.repos)
while virtual:
real = repos.get(virtual)
if real:
req.env['REPO_NAME'] = virtual
try:
Matt Mackall
hgweb: kill parentui references
r8191 repo = hg.repository(self.ui, real)
Dirkjan Ochtman
merge another backout
r6797 return hgweb(repo).run_wsgi(req)
Dirkjan Ochtman
hgwebdir: refactor inner loop
r5603 except IOError, inst:
Dirkjan Ochtman
hgweb: explicit response status
r5993 msg = inst.strerror
raise ErrorResponse(HTTP_SERVER_ERROR, msg)
Matt Mackall
error: move repo errors...
r7637 except error.RepoError, inst:
Dirkjan Ochtman
hgweb: explicit response status
r5993 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
Dirkjan Ochtman
hgwebdir: split out makeindex function, facilitate test failure diagnosis
r5601
Dirkjan Ochtman
hgwebdir: refactor inner loop
r5603 # browse subdirectories
subdir = virtual + '/'
if [r for r in repos if r.startswith(subdir)]:
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_OK, ctype)
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 return self.makeindex(req, tmpl, subdir)
Dirkjan Ochtman
hgwebdir: refactor inner loop
r5603
up = virtual.rfind('/')
if up < 0:
break
virtual = virtual[:up]
# prefixes not found
Dirkjan Ochtman
hgweb: explicit response status
r5993 req.respond(HTTP_NOT_FOUND, ctype)
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 return tmpl("notfound", repo=virtual)
Thomas Arendsen Hein
Removed tabs and trailing whitespace in python files
r5760
Dirkjan Ochtman
hgwebdir: split out makeindex function, facilitate test failure diagnosis
r5601 except ErrorResponse, err:
Dirkjan Ochtman
hgweb: pass ErrorResponses directly into req.respond()
r7740 req.respond(err, ctype)
Brendan Cully
templater: return data in increasing chunk sizes...
r7396 return tmpl('error', error=err.message or '')
Dirkjan Ochtman
hgwebdir: split out makeindex function, facilitate test failure diagnosis
r5601 finally:
tmpl = None
def makeindex(self, req, tmpl, subdir=""):
Eric Hopper
Final stage of the hgweb split up....
r2356 def archivelist(ui, nodeid, url):
Alexis S. L. Carvalho
use untrusted settings in hgwebdir
r3556 allowed = ui.configlist("web", "allow_archive", untrusted=True)
Brendan Cully
Teach hgwebdir about new interface
r3262 for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
Alexis S. L. Carvalho
use untrusted settings in hgwebdir
r3556 if i[0] in allowed or ui.configbool("web", "allow" + i[0],
untrusted=True):
Brendan Cully
Teach hgwebdir about new interface
r3262 yield {"type" : i[0], "extension": i[1],
"node": nodeid, "url": url}
Eric Hopper
Final stage of the hgweb split up....
r2356
Benoit Boissinot
hgweb: separate generation of entries and sorting (cleanup)
r10600 def rawentries(subdir="", **map):
Dirkjan Ochtman
hgweb: add web.descend configuration variable
r9363
descend = self.ui.configbool('web', 'descend', True)
Eric Hopper
Final stage of the hgweb split up....
r2356 for name, path in self.repos:
Dirkjan Ochtman
hgweb: add web.descend configuration variable
r9363
Brendan Cully
hgweb: let hgwebdir browse subdirectories
r4841 if not name.startswith(subdir):
continue
Brendan Cully
hgwebdir: show only trailing part of path when browsing subdirectories
r4843 name = name[len(subdir):]
Dirkjan Ochtman
hgweb: add web.descend configuration variable
r9363 if not descend and '/' in name:
continue
Brendan Cully
hgweb: let hgwebdir browse subdirectories
r4841
Matt Mackall
hgweb: kill parentui references
r8191 u = self.ui.copy()
Eric Hopper
Final stage of the hgweb split up....
r2356 try:
u.readconfig(os.path.join(path, '.hg', 'hgrc'))
Alexis S. L. Carvalho
hgwebdir: ignore hgrc parse errors while building the index page...
r5332 except Exception, e:
Martin Geisler
move % out of translatable strings...
r6913 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
Alexis S. L. Carvalho
hgwebdir: ignore hgrc parse errors while building the index page...
r5332 continue
Alexis S. L. Carvalho
use untrusted settings in hgwebdir
r3556 def get(section, name, default=None):
return u.config(section, name, default, untrusted=True)
Eric Hopper
Final stage of the hgweb split up....
r2356
Markus F.X.J. Oberhumer
Add option "hidden" to hgwebdir....
r4709 if u.configbool("web", "hidden", untrusted=True):
continue
Mark Edgington
hgweb: support for deny_read/allow_read options...
r7336 if not self.read_allowed(u, req):
continue
Dirkjan Ochtman
hgweb: make hgwebdir work in the absence of PATH_INFO...
r6459 parts = [name]
if 'PATH_INFO' in req.env:
parts.insert(0, req.env['PATH_INFO'].rstrip('/'))
Dirkjan Ochtman
Use SCRIPT_NAME and PATH_INFO instead of REQUEST_URI. This is required by WSGI (fixes issue846).
r5579 if req.env['SCRIPT_NAME']:
Thomas Arendsen Hein
Removed tabs and trailing whitespace in python files
r5760 parts.insert(0, req.env['SCRIPT_NAME'])
Yuya Nishihara
hgweb: make code to join url path simpler...
r10675 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
Eric Hopper
Final stage of the hgweb split up....
r2356
# update time with local timezone
try:
Brendan Cully
hgweb: Make get_mtime use repository to find store path....
r10078 r = hg.repository(self.ui, path)
d = (get_mtime(r.spath), util.makedate()[1])
Eric Hopper
Final stage of the hgweb split up....
r2356 except OSError:
continue
Thomas Arendsen Hein
Don't let ui.username override web.contact (issue900)...
r5779 contact = get_contact(get)
Eric Hopper
Final stage of the hgweb split up....
r2356 description = get("web", "description", "")
name = get("web", "name", name)
row = dict(contact=contact or "unknown",
contact_sort=contact.upper() or "unknown",
name=name,
name_sort=name,
url=url,
description=description or "unknown",
description_sort=description.upper() or "unknown",
lastchange=d,
lastchange_sort=d[1]-d[0],
archives=archivelist(u, "tip", url))
Benoit Boissinot
hgweb: separate generation of entries and sorting (cleanup)
r10600 yield row
sortdefault = None, False
def entries(sortcolumn="", descending=False, subdir="", **map):
rows = rawentries(subdir=subdir, **map)
if sortcolumn and sortdefault != (sortcolumn, descending):
sortkey = '%s_sort' % sortcolumn
rows = sorted(rows, key=lambda x: x[sortkey],
reverse=descending)
for row, parity in zip(rows, paritygen(self.stripecount)):
row['parity'] = parity
yield row
Eric Hopper
Final stage of the hgweb split up....
r2356
Bryan O'Sullivan
hgwebdir: refresh configuration periodically...
r8371 self.refresh()
Dirkjan Ochtman
hgwebdir: split out makeindex function, facilitate test failure diagnosis
r5601 sortable = ["name", "description", "contact", "lastchange"]
Dirkjan Ochtman
hgweb: get rid of inaccurate hgwebdir.repos_sorted, localize machinery
r8346 sortcolumn, descending = sortdefault
Christian Ebert
Prefer i in d over d.has_key(i)
r5915 if 'sort' in req.form:
Dirkjan Ochtman
hgwebdir: split out makeindex function, facilitate test failure diagnosis
r5601 sortcolumn = req.form['sort'][0]
descending = sortcolumn.startswith('-')
if descending:
sortcolumn = sortcolumn[1:]
if sortcolumn not in sortable:
sortcolumn = ""
Brendan Cully
hgweb: let hgwebdir browse subdirectories
r4841
Dirkjan Ochtman
hgwebdir: split out makeindex function, facilitate test failure diagnosis
r5601 sort = [("sort_%s" % column,
"%s%s" % ((not descending and column == sortcolumn)
and "-" or "", column))
for column in sortable]
Dirkjan Ochtman
hgweb: move HTTP content types out of header templates...
r5928
Bryan O'Sullivan
hgwebdir: refresh configuration periodically...
r8371 self.refresh()
Yuya Nishihara
hgweb: introduce helper method to update req.env
r10673 self.updatereqenv(req.env)
Brendan Cully
Support web.baseurl in hgwebdir, overriding SCRIPT_NAME
r6221
Dirkjan Ochtman
hgweb: forgot to centralize the req.write() calls in hgwebdir
r5965 return tmpl("index", entries=entries, subdir=subdir,
sortcolumn=sortcolumn, descending=descending,
**dict(sort))
Dirkjan Ochtman
hgwebdir: split out templater creation
r5602
def templater(self, req):
def header(**map):
Matt Mackall
move encoding bits from util to encoding...
r7948 yield tmpl('header', encoding=encoding.encoding, **map)
Dirkjan Ochtman
hgwebdir: split out templater creation
r5602
def footer(**map):
yield tmpl("footer", **map)
def motd(**map):
if self.motd is not None:
yield self.motd
else:
yield config('web', 'motd', '')
def config(section, name, default=None, untrusted=True):
Matt Mackall
hgweb: kill parentui references
r8191 return self.ui.config(section, name, default, untrusted)
Dirkjan Ochtman
hgwebdir: split out templater creation
r5602
Yuya Nishihara
hgweb: introduce helper method to update req.env
r10673 self.updatereqenv(req.env)
Brendan Cully
Support web.baseurl in hgwebdir, overriding SCRIPT_NAME
r6221
Dirkjan Ochtman
hgwebdir: split out templater creation
r5602 url = req.env.get('SCRIPT_NAME', '')
if not url.endswith('/'):
url += '/'
Dirkjan Ochtman
hgweb: use new sessionvars code in hgwebdir, too
r8216 vars = {}
Dirkjan Ochtman
hgweb: don't choke when an inexistent style is requested (issue1901)
r9842 styles = (
req.form.get('style', [None])[0],
config('web', 'style'),
'paper'
)
Christian Fischer
hgwebdir: use template paths configured in the hgrc (issue2281)
r11649 style, mapfile = templater.stylemap(styles, self.templatepath)
Dirkjan Ochtman
hgweb: don't choke when an inexistent style is requested (issue1901)
r9842 if style == styles[0]:
vars['style'] = style
Matt Mackall
many, many trivial check-code fixups
r10282
Dirkjan Ochtman
hgweb: use new sessionvars code in hgwebdir, too
r8216 start = url[-1] == '?' and '&' or '?'
sessionvars = webutil.sessionvars(vars, start)
Dirkjan Ochtman
hgwebdir: split out templater creation
r5602 staticurl = config('web', 'staticurl') or url + 'static/'
if not staticurl.endswith('/'):
staticurl += '/'
Dirkjan Ochtman
templater: provide the standard template filters by default
r8360 tmpl = templater.templater(mapfile,
Dirkjan Ochtman
hgwebdir: split out templater creation
r5602 defaults={"header": header,
"footer": footer,
"motd": motd,
"url": url,
Dirkjan Ochtman
hgweb: use new sessionvars code in hgwebdir, too
r8216 "staticurl": staticurl,
"sessionvars": sessionvars})
Dirkjan Ochtman
hgwebdir: split out templater creation
r5602 return tmpl
Yuya Nishihara
hgweb: introduce helper method to update req.env
r10673
def updatereqenv(self, env):
Yuya Nishihara
hgweb: fix broken URLs of RSS/Atom feeds (issue1772)...
r10674 def splitnetloc(netloc):
if ':' in netloc:
return netloc.split(':', 1)
else:
return (netloc, None)
Yuya Nishihara
hgweb: introduce helper method to update req.env
r10673 if self._baseurl is not None:
Yuya Nishihara
hgweb: fix broken URLs of RSS/Atom feeds (issue1772)...
r10674 urlcomp = urlparse.urlparse(self._baseurl)
host, port = splitnetloc(urlcomp[1])
path = urlcomp[2]
env['SERVER_NAME'] = host
if port:
env['SERVER_PORT'] = port
env['SCRIPT_NAME'] = path