hgweb.py
1188 lines
| 40.0 KiB
| text/x-python
|
PythonLexer
/ mercurial / hgweb.py
|
r238 | # hgweb.py - web interface to a mercurial repository | ||
|
r131 | # | ||
|
r238 | # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> | ||
|
r575 | # Copyright 2005 Matt Mackall <mpm@selenic.com> | ||
|
r131 | # | ||
# This software may be used and distributed according to the terms | ||||
# of the GNU General Public License, incorporated herein by reference. | ||||
|
r1650 | import os, cgi, sys, urllib | ||
|
r1777 | import mimetypes | ||
|
r1213 | from demandload import demandload | ||
|
r1217 | demandload(globals(), "mdiff time re socket zlib errno ui hg ConfigParser") | ||
|
r1320 | demandload(globals(), "zipfile tempfile StringIO tarfile BaseHTTPServer util") | ||
|
r1411 | demandload(globals(), "mimetypes") | ||
|
r1213 | from node import * | ||
|
r1400 | from i18n import gettext as _ | ||
|
r138 | |||
|
r157 | def templatepath(): | ||
|
r201 | for f in "templates", "../templates": | ||
|
r157 | p = os.path.join(os.path.dirname(__file__), f) | ||
|
r1063 | if os.path.isdir(p): | ||
|
r1795 | return os.path.normpath(p) | ||
|
r157 | |||
|
r1320 | def age(x): | ||
|
r138 | def plural(t, c): | ||
|
r1063 | if c == 1: | ||
return t | ||||
|
r138 | return t + "s" | ||
def fmt(t, c): | ||||
return "%d %s" % (c, plural(t, c)) | ||||
now = time.time() | ||||
|
r1324 | then = x[0] | ||
|
r1320 | delta = max(1, int(now - then)) | ||
|
r138 | |||
scales = [["second", 1], | ||||
["minute", 60], | ||||
["hour", 3600], | ||||
["day", 3600 * 24], | ||||
["week", 3600 * 24 * 7], | ||||
["month", 3600 * 24 * 30], | ||||
["year", 3600 * 24 * 365]] | ||||
scales.reverse() | ||||
for t, s in scales: | ||||
n = delta / s | ||||
|
r1063 | if n >= 2 or s == 1: | ||
return fmt(t, n) | ||||
|
r131 | |||
def nl2br(text): | ||||
|
r201 | return text.replace('\n', '<br/>\n') | ||
|
r131 | |||
def obfuscate(text): | ||||
|
r1063 | return ''.join(['&#%d;' % ord(c) for c in text]) | ||
|
r138 | |||
def up(p): | ||||
|
r1063 | if p[0] != "/": | ||
p = "/" + p | ||||
if p[-1] == "/": | ||||
p = p[:-1] | ||||
|
r138 | up = os.path.dirname(p) | ||
if up == "/": | ||||
return "/" | ||||
return up + "/" | ||||
|
r131 | |||
|
r1418 | def get_mtime(repo_path): | ||
hg_path = os.path.join(repo_path, ".hg") | ||||
cl_path = os.path.join(hg_path, "00changelog.i") | ||||
if os.path.exists(os.path.join(cl_path)): | ||||
return os.stat(cl_path).st_mtime | ||||
else: | ||||
return os.stat(hg_path).st_mtime | ||||
|
r1793 | def staticfile(directory, fname): | ||
fname = os.path.realpath(os.path.join(directory, fname)) | ||||
try: | ||||
# the static dir should be a substring in the real | ||||
# file path, if it is not, we have something strange | ||||
# going on => security breach attempt? | ||||
# | ||||
# This will either: | ||||
# 1) find the `static' path at index 0 = success | ||||
# 2) find the `static' path at other index = error | ||||
# 3) not find the `static' path = ValueError generated | ||||
if fname.index(directory) != 0: | ||||
# generate ValueError manually | ||||
raise ValueError() | ||||
os.stat(fname) | ||||
ct = mimetypes.guess_type(fname)[0] or "text/plain" | ||||
return "Content-type: %s\n\n%s" % (ct, file(fname).read()) | ||||
except ValueError: | ||||
# security breach attempt | ||||
return "" | ||||
except OSError, e: | ||||
if e.errno == errno.ENOENT: | ||||
return "" | ||||
|
r1559 | class hgrequest(object): | ||
|
r1159 | def __init__(self, inp=None, out=None, env=None): | ||
self.inp = inp or sys.stdin | ||||
self.out = out or sys.stdout | ||||
self.env = env or os.environ | ||||
|
r1407 | self.form = cgi.parse(self.inp, self.env, keep_blank_values=1) | ||
|
r131 | |||
|
r1159 | def write(self, *things): | ||
for thing in things: | ||||
if hasattr(thing, "__iter__"): | ||||
for part in thing: | ||||
self.write(part) | ||||
else: | ||||
try: | ||||
|
r1160 | self.out.write(str(thing)) | ||
|
r1174 | except socket.error, inst: | ||
if inst[0] != errno.ECONNRESET: | ||||
|
r1159 | raise | ||
def header(self, headers=[('Content-type','text/html')]): | ||||
for header in headers: | ||||
self.out.write("%s: %s\r\n" % header) | ||||
self.out.write("\r\n") | ||||
def httphdr(self, type, file="", size=0): | ||||
headers = [('Content-type', type)] | ||||
if file: | ||||
headers.append(('Content-disposition', 'attachment; filename=%s' % file)) | ||||
if size > 0: | ||||
headers.append(('Content-length', str(size))) | ||||
self.header(headers) | ||||
|
r135 | |||
|
r1559 | class templater(object): | ||
|
r1062 | def __init__(self, mapfile, filters={}, defaults={}): | ||
|
r138 | self.cache = {} | ||
self.map = {} | ||||
self.base = os.path.dirname(mapfile) | ||||
|
r201 | self.filters = filters | ||
|
r601 | self.defaults = defaults | ||
|
r515 | |||
|
r138 | for l in file(mapfile): | ||
m = re.match(r'(\S+)\s*=\s*"(.*)"$', l) | ||||
|
r133 | if m: | ||
|
r138 | self.cache[m.group(1)] = m.group(2) | ||
else: | ||||
m = re.match(r'(\S+)\s*=\s*(\S+)', l) | ||||
if m: | ||||
self.map[m.group(1)] = os.path.join(self.base, m.group(2)) | ||||
|
r133 | else: | ||
|
r1402 | raise LookupError(_("unknown map entry '%s'") % l) | ||
|
r133 | |||
|
r138 | def __call__(self, t, **map): | ||
|
r601 | m = self.defaults.copy() | ||
m.update(map) | ||||
|
r138 | try: | ||
tmpl = self.cache[t] | ||||
except KeyError: | ||||
tmpl = self.cache[t] = file(self.map[t]).read() | ||||
|
r974 | return self.template(tmpl, self.filters, **m) | ||
|
r1062 | def template(self, tmpl, filters={}, **map): | ||
|
r974 | while tmpl: | ||
m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl) | ||||
if m: | ||||
yield tmpl[:m.start(0)] | ||||
v = map.get(m.group(1), "") | ||||
v = callable(v) and v(**map) or v | ||||
format = m.group(2) | ||||
fl = m.group(4) | ||||
if format: | ||||
q = v.__iter__ | ||||
for i in q(): | ||||
lm = map.copy() | ||||
lm.update(i) | ||||
yield self(format[1:], **lm) | ||||
v = "" | ||||
elif fl: | ||||
for f in fl.split("|")[1:]: | ||||
v = filters[f](v) | ||||
yield v | ||||
tmpl = tmpl[m.end(0):] | ||||
else: | ||||
yield tmpl | ||||
return | ||||
|
r515 | |||
|
r941 | common_filters = { | ||
|
r1650 | "escape": lambda x: cgi.escape(x, True), | ||
"urlescape": urllib.quote, | ||||
|
r1445 | "strip": lambda x: x.strip(), | ||
|
r941 | "age": age, | ||
|
r1324 | "date": lambda x: util.datestr(x), | ||
|
r941 | "addbreaks": nl2br, | ||
"obfuscate": obfuscate, | ||||
"short": (lambda x: x[:12]), | ||||
"firstline": (lambda x: x.splitlines(1)[0]), | ||||
"permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"), | ||||
|
r1324 | "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"), | ||
|
r941 | } | ||
|
r1559 | class hgweb(object): | ||
|
r987 | def __init__(self, repo, name=None): | ||
if type(repo) == type(""): | ||||
|
r1213 | self.repo = hg.repository(ui.ui(), repo) | ||
|
r987 | else: | ||
self.repo = repo | ||||
|
r133 | |||
|
r258 | self.mtime = -1 | ||
|
r1172 | self.reponame = name | ||
|
r1078 | self.archives = 'zip', 'gz', 'bz2' | ||
|
r131 | |||
|
r258 | def refresh(self): | ||
|
r1418 | mtime = get_mtime(self.repo.root) | ||
if mtime != self.mtime: | ||||
self.mtime = mtime | ||||
|
r1213 | self.repo = hg.repository(self.repo.ui, self.repo.root) | ||
|
r1275 | self.maxchanges = int(self.repo.ui.config("web", "maxchanges", 10)) | ||
self.maxfiles = int(self.repo.ui.config("web", "maxfiles", 10)) | ||||
|
r964 | self.allowpull = self.repo.ui.configbool("web", "allowpull", True) | ||
|
r258 | |||
|
r1498 | def archivelist(self, nodeid): | ||
for i in self.archives: | ||||
if self.repo.ui.configbool("web", "allow" + i, False): | ||||
yield {"type" : i, "node" : nodeid} | ||||
|
r138 | def listfiles(self, files, mf): | ||
for f in files[:self.maxfiles]: | ||||
|
r1062 | yield self.t("filenodelink", node=hex(mf[f]), file=f) | ||
|
r138 | if len(files) > self.maxfiles: | ||
yield self.t("fileellipses") | ||||
def listfilediffs(self, files, changeset): | ||||
for f in files[:self.maxfiles]: | ||||
|
r1062 | yield self.t("filedifflink", node=hex(changeset), file=f) | ||
|
r138 | if len(files) > self.maxfiles: | ||
yield self.t("fileellipses") | ||||
|
r1606 | def siblings(self, siblings=[], rev=None, hiderev=None, **args): | ||
|
r1063 | if not rev: | ||
rev = lambda x: "" | ||||
|
r1606 | siblings = [s for s in siblings if s != nullid] | ||
if len(siblings) == 1 and rev(siblings[0]) == hiderev: | ||||
|
r1416 | return | ||
|
r1606 | for s in siblings: | ||
yield dict(node=hex(s), rev=rev(s), **args) | ||||
|
r569 | |||
|
r1653 | def renamelink(self, fl, node): | ||
r = fl.renamed(node) | ||||
if r: | ||||
return [dict(file=r[0], node=hex(r[1]))] | ||||
return [] | ||||
|
r568 | def showtag(self, t1, node=nullid, **args): | ||
for t in self.repo.nodetags(node): | ||||
|
r1062 | yield self.t(t1, tag=t, **args) | ||
|
r568 | |||
|
r138 | def diff(self, node1, node2, files): | ||
|
r1626 | def filterfiles(filters, files): | ||
|
r1627 | l = [x for x in files if x in filters] | ||
|
r515 | |||
|
r1626 | for t in filters: | ||
|
r1627 | if t and t[-1] != os.sep: | ||
|
r1626 | t += os.sep | ||
l += [x for x in files if x.startswith(t)] | ||||
|
r138 | return l | ||
|
r131 | |||
|
r172 | parity = [0] | ||
def diffblock(diff, f, fn): | ||||
yield self.t("diffblock", | ||||
|
r1063 | lines=prettyprintlines(diff), | ||
parity=parity[0], | ||||
file=f, | ||||
filenode=hex(fn or nullid)) | ||||
|
r172 | parity[0] = 1 - parity[0] | ||
|
r515 | |||
|
r172 | def prettyprintlines(diff): | ||
|
r138 | for l in diff.splitlines(1): | ||
|
r201 | if l.startswith('+'): | ||
|
r1062 | yield self.t("difflineplus", line=l) | ||
|
r201 | elif l.startswith('-'): | ||
|
r1062 | yield self.t("difflineminus", line=l) | ||
|
r201 | elif l.startswith('@'): | ||
|
r1062 | yield self.t("difflineat", line=l) | ||
|
r138 | else: | ||
|
r1062 | yield self.t("diffline", line=l) | ||
|
r131 | |||
|
r138 | r = self.repo | ||
cl = r.changelog | ||||
mf = r.manifest | ||||
change1 = cl.read(node1) | ||||
change2 = cl.read(node2) | ||||
mmap1 = mf.read(change1[0]) | ||||
mmap2 = mf.read(change2[0]) | ||||
|
r1333 | date1 = util.datestr(change1[2]) | ||
date2 = util.datestr(change2[2]) | ||||
|
r131 | |||
|
r1619 | modified, added, removed, deleted, unknown = r.changes(node1, node2) | ||
|
r645 | if files: | ||
|
r1626 | modified, added, removed = map(lambda x: filterfiles(files, x), | ||
|
r1618 | (modified, added, removed)) | ||
|
r131 | |||
|
r1637 | diffopts = self.repo.ui.diffopts() | ||
showfunc = diffopts['showfunc'] | ||||
ignorews = diffopts['ignorews'] | ||||
|
r1618 | for f in modified: | ||
|
r138 | to = r.file(f).read(mmap1[f]) | ||
tn = r.file(f).read(mmap2[f]) | ||||
|
r1637 | yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, | ||
showfunc=showfunc, ignorews=ignorews), f, tn) | ||||
|
r1618 | for f in added: | ||
|
r265 | to = None | ||
|
r138 | tn = r.file(f).read(mmap2[f]) | ||
|
r1637 | yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, | ||
showfunc=showfunc, ignorews=ignorews), f, tn) | ||||
|
r1618 | for f in removed: | ||
|
r138 | to = r.file(f).read(mmap1[f]) | ||
|
r265 | tn = None | ||
|
r1637 | yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, | ||
showfunc=showfunc, ignorews=ignorews), f, tn) | ||||
|
r131 | |||
|
r180 | def changelog(self, pos): | ||
|
r857 | def changenav(**map): | ||
|
r1703 | def seq(factor, maxchanges=None): | ||
if maxchanges: | ||||
yield maxchanges | ||||
if maxchanges >= 20 and maxchanges <= 40: | ||||
yield 50 | ||||
else: | ||||
yield 1 * factor | ||||
yield 3 * factor | ||||
|
r138 | for f in seq(factor * 10): | ||
yield f | ||||
|
r131 | |||
|
r173 | l = [] | ||
|
r1703 | last = 0 | ||
for f in seq(1, self.maxchanges): | ||||
if f < self.maxchanges or f <= last: | ||||
|
r1063 | continue | ||
if f > count: | ||||
break | ||||
|
r1703 | last = f | ||
|
r173 | r = "%d" % f | ||
|
r1063 | if pos + f < count: | ||
l.append(("+" + r, pos + f)) | ||||
if pos - f >= 0: | ||||
l.insert(0, ("-" + r, pos - f)) | ||||
|
r173 | |||
|
r975 | yield {"rev": 0, "label": "(0)"} | ||
|
r515 | |||
|
r173 | for label, rev in l: | ||
|
r975 | yield {"label": label, "rev": rev} | ||
|
r173 | |||
|
r1409 | yield {"label": "tip", "rev": "tip"} | ||
|
r131 | |||
|
r857 | def changelist(**map): | ||
|
r142 | parity = (start - end) & 1 | ||
|
r138 | cl = self.repo.changelog | ||
l = [] # build a list in forward order for efficiency | ||||
|
r351 | for i in range(start, end): | ||
|
r138 | n = cl.node(i) | ||
changes = cl.read(n) | ||||
hn = hex(n) | ||||
|
r131 | |||
|
r1063 | l.insert(0, {"parity": parity, | ||
"author": changes[1], | ||||
|
r1606 | "parent": self.siblings(cl.parents(n), cl.rev, | ||
cl.rev(n) - 1), | ||||
"child": self.siblings(cl.children(n), cl.rev, | ||||
cl.rev(n) + 1), | ||||
|
r1063 | "changelogtag": self.showtag("changelogtag",n), | ||
"manifest": hex(changes[0]), | ||||
"desc": changes[4], | ||||
|
r1324 | "date": changes[2], | ||
|
r1063 | "files": self.listfilediffs(changes[3], n), | ||
"rev": i, | ||||
"node": hn}) | ||||
|
r142 | parity = 1 - parity | ||
|
r138 | |||
|
r1063 | for e in l: | ||
yield e | ||||
|
r131 | |||
|
r168 | cl = self.repo.changelog | ||
mf = cl.read(cl.tip())[0] | ||||
count = cl.count() | ||||
|
r351 | start = max(0, pos - self.maxchanges + 1) | ||
end = min(count, start + self.maxchanges) | ||||
pos = end - 1 | ||||
|
r138 | |||
|
r142 | yield self.t('changelog', | ||
|
r1062 | changenav=changenav, | ||
manifest=hex(mf), | ||||
rev=pos, changesets=count, entries=changelist) | ||||
|
r131 | |||
|
r538 | def search(self, query): | ||
|
r857 | def changelist(**map): | ||
|
r538 | cl = self.repo.changelog | ||
count = 0 | ||||
qw = query.lower().split() | ||||
def revgen(): | ||||
for i in range(cl.count() - 1, 0, -100): | ||||
l = [] | ||||
for j in range(max(0, i - 100), i): | ||||
n = cl.node(j) | ||||
changes = cl.read(n) | ||||
|
r1023 | l.append((n, j, changes)) | ||
l.reverse() | ||||
|
r538 | for e in l: | ||
yield e | ||||
for n, i, changes in revgen(): | ||||
miss = 0 | ||||
for q in qw: | ||||
if not (q in changes[1].lower() or | ||||
q in changes[4].lower() or | ||||
q in " ".join(changes[3][:20]).lower()): | ||||
miss = 1 | ||||
break | ||||
|
r1063 | if miss: | ||
continue | ||||
|
r538 | |||
count += 1 | ||||
hn = hex(n) | ||||
|
r1063 | yield self.t('searchentry', | ||
parity=count & 1, | ||||
author=changes[1], | ||||
|
r1606 | parent=self.siblings(cl.parents(n), cl.rev), | ||
child=self.siblings(cl.children(n), cl.rev), | ||||
|
r1063 | changelogtag=self.showtag("changelogtag",n), | ||
manifest=hex(changes[0]), | ||||
desc=changes[4], | ||||
|
r1324 | date=changes[2], | ||
|
r1063 | files=self.listfilediffs(changes[3], n), | ||
rev=i, | ||||
node=hn) | ||||
|
r538 | |||
|
r1063 | if count >= self.maxchanges: | ||
break | ||||
|
r538 | |||
cl = self.repo.changelog | ||||
mf = cl.read(cl.tip())[0] | ||||
yield self.t('search', | ||||
|
r1062 | query=query, | ||
manifest=hex(mf), | ||||
entries=changelist) | ||||
|
r538 | |||
|
r138 | def changeset(self, nodeid): | ||
cl = self.repo.changelog | ||||
|
r1369 | n = self.repo.lookup(nodeid) | ||
nodeid = hex(n) | ||||
|
r138 | changes = cl.read(n) | ||
|
r598 | p1 = cl.parents(n)[0] | ||
|
r515 | |||
|
r133 | files = [] | ||
|
r138 | mf = self.repo.manifest.read(changes[0]) | ||
|
r131 | for f in changes[3]: | ||
|
r138 | files.append(self.t("filenodelink", | ||
|
r1063 | filenode=hex(mf.get(f, nullid)), file=f)) | ||
|
r138 | |||
|
r857 | def diff(**map): | ||
|
r645 | yield self.diff(p1, n, None) | ||
|
r131 | |||
|
r138 | yield self.t('changeset', | ||
|
r1062 | diff=diff, | ||
rev=cl.rev(n), | ||||
node=nodeid, | ||||
|
r1606 | parent=self.siblings(cl.parents(n), cl.rev), | ||
child=self.siblings(cl.children(n), cl.rev), | ||||
|
r1062 | changesettag=self.showtag("changesettag",n), | ||
manifest=hex(changes[0]), | ||||
author=changes[1], | ||||
desc=changes[4], | ||||
|
r1324 | date=changes[2], | ||
|
r1076 | files=files, | ||
|
r1498 | archives=self.archivelist(nodeid)) | ||
|
r131 | |||
|
r138 | def filelog(self, f, filenode): | ||
cl = self.repo.changelog | ||||
fl = self.repo.file(f) | ||||
|
r1369 | filenode = hex(fl.lookup(filenode)) | ||
|
r138 | count = fl.count() | ||
|
r857 | def entries(**map): | ||
|
r138 | l = [] | ||
|
r142 | parity = (count - 1) & 1 | ||
|
r515 | |||
|
r138 | for i in range(count): | ||
n = fl.node(i) | ||||
lr = fl.linkrev(n) | ||||
cn = cl.node(lr) | ||||
cs = cl.read(cl.node(lr)) | ||||
|
r133 | |||
|
r978 | l.insert(0, {"parity": parity, | ||
"filenode": hex(n), | ||||
"filerev": i, | ||||
"file": f, | ||||
"node": hex(cn), | ||||
"author": cs[1], | ||||
|
r1324 | "date": cs[2], | ||
|
r1653 | "rename": self.renamelink(fl, n), | ||
|
r1606 | "parent": self.siblings(fl.parents(n), | ||
fl.rev, file=f), | ||||
"child": self.siblings(fl.children(n), | ||||
|
r1063 | fl.rev, file=f), | ||
|
r978 | "desc": cs[4]}) | ||
|
r142 | parity = 1 - parity | ||
|
r138 | |||
|
r1063 | for e in l: | ||
yield e | ||||
|
r138 | |||
|
r1063 | yield self.t("filelog", file=f, filenode=filenode, entries=entries) | ||
|
r131 | |||
|
r138 | def filerevision(self, f, node): | ||
fl = self.repo.file(f) | ||||
|
r1369 | n = fl.lookup(node) | ||
node = hex(n) | ||||
|
r201 | text = fl.read(n) | ||
|
r138 | changerev = fl.linkrev(n) | ||
cl = self.repo.changelog | ||||
cn = cl.node(changerev) | ||||
cs = cl.read(cn) | ||||
mfn = cs[0] | ||||
|
r142 | |||
|
r1411 | mt = mimetypes.guess_type(f)[0] | ||
rawtext = text | ||||
if util.binary(text): | ||||
text = "(binary:%s)" % mt | ||||
|
r142 | def lines(): | ||
for l, t in enumerate(text.splitlines(1)): | ||||
|
r976 | yield {"line": t, | ||
"linenumber": "% 6d" % (l + 1), | ||||
"parity": l & 1} | ||||
|
r359 | |||
|
r1063 | yield self.t("filerevision", | ||
file=f, | ||||
|
r1062 | filenode=node, | ||
path=up(f), | ||||
text=lines(), | ||||
|
r1411 | raw=rawtext, | ||
mimetype=mt, | ||||
|
r1062 | rev=changerev, | ||
node=hex(cn), | ||||
manifest=hex(mfn), | ||||
author=cs[1], | ||||
|
r1324 | date=cs[2], | ||
|
r1606 | parent=self.siblings(fl.parents(n), fl.rev, file=f), | ||
child=self.siblings(fl.children(n), fl.rev, file=f), | ||||
|
r1653 | rename=self.renamelink(fl, n), | ||
|
r1062 | permissions=self.repo.manifest.readflags(mfn)[f]) | ||
|
r138 | |||
def fileannotate(self, f, node): | ||||
bcache = {} | ||||
ncache = {} | ||||
fl = self.repo.file(f) | ||||
|
r1369 | n = fl.lookup(node) | ||
node = hex(n) | ||||
|
r138 | changerev = fl.linkrev(n) | ||
cl = self.repo.changelog | ||||
cn = cl.node(changerev) | ||||
cs = cl.read(cn) | ||||
mfn = cs[0] | ||||
|
r131 | |||
|
r857 | def annotate(**map): | ||
|
r142 | parity = 1 | ||
last = None | ||||
|
r138 | for r, l in fl.annotate(n): | ||
try: | ||||
cnode = ncache[r] | ||||
except KeyError: | ||||
cnode = ncache[r] = self.repo.changelog.node(r) | ||||
|
r515 | |||
|
r138 | try: | ||
name = bcache[r] | ||||
except KeyError: | ||||
cl = self.repo.changelog.read(cnode) | ||||
|
r1129 | bcache[r] = name = self.repo.ui.shortuser(cl[1]) | ||
|
r131 | |||
|
r142 | if last != cnode: | ||
parity = 1 - parity | ||||
last = cnode | ||||
|
r977 | yield {"parity": parity, | ||
"node": hex(cnode), | ||||
"rev": r, | ||||
"author": name, | ||||
"file": f, | ||||
"line": l} | ||||
|
r138 | |||
yield self.t("fileannotate", | ||||
|
r1063 | file=f, | ||
filenode=node, | ||||
annotate=annotate, | ||||
path=up(f), | ||||
rev=changerev, | ||||
node=hex(cn), | ||||
manifest=hex(mfn), | ||||
author=cs[1], | ||||
|
r1324 | date=cs[2], | ||
|
r1653 | rename=self.renamelink(fl, n), | ||
|
r1606 | parent=self.siblings(fl.parents(n), fl.rev, file=f), | ||
child=self.siblings(fl.children(n), fl.rev, file=f), | ||||
|
r1063 | permissions=self.repo.manifest.readflags(mfn)[f]) | ||
|
r136 | |||
|
r138 | def manifest(self, mnode, path): | ||
|
r1369 | man = self.repo.manifest | ||
mn = man.lookup(mnode) | ||||
mnode = hex(mn) | ||||
mf = man.read(mn) | ||||
rev = man.rev(mn) | ||||
|
r138 | node = self.repo.changelog.node(rev) | ||
|
r1369 | mff = man.readflags(mn) | ||
|
r138 | |||
files = {} | ||||
|
r515 | |||
|
r138 | p = path[1:] | ||
|
r1649 | if p and p[-1] != "/": | ||
p += "/" | ||||
|
r138 | l = len(p) | ||
|
r131 | |||
|
r138 | for f,n in mf.items(): | ||
if f[:l] != p: | ||||
continue | ||||
remain = f[l:] | ||||
if "/" in remain: | ||||
short = remain[:remain.find("/") + 1] # bleah | ||||
|
r142 | files[short] = (f, None) | ||
|
r138 | else: | ||
short = os.path.basename(remain) | ||||
files[short] = (f, n) | ||||
|
r131 | |||
|
r857 | def filelist(**map): | ||
|
r142 | parity = 0 | ||
|
r138 | fl = files.keys() | ||
fl.sort() | ||||
for f in fl: | ||||
full, fnode = files[f] | ||||
|
r979 | if not fnode: | ||
continue | ||||
yield {"file": full, | ||||
"manifest": mnode, | ||||
"filenode": hex(fnode), | ||||
"parity": parity, | ||||
"basename": f, | ||||
"permissions": mff[full]} | ||||
|
r142 | parity = 1 - parity | ||
|
r138 | |||
|
r979 | def dirlist(**map): | ||
parity = 0 | ||||
fl = files.keys() | ||||
fl.sort() | ||||
for f in fl: | ||||
full, fnode = files[f] | ||||
if fnode: | ||||
continue | ||||
yield {"parity": parity, | ||||
"path": os.path.join(path, f), | ||||
"manifest": mnode, | ||||
|
r980 | "basename": f[:-1]} | ||
|
r979 | parity = 1 - parity | ||
|
r982 | |||
|
r138 | yield self.t("manifest", | ||
|
r1062 | manifest=mnode, | ||
rev=rev, | ||||
node=hex(node), | ||||
path=path, | ||||
up=up(path), | ||||
fentries=filelist, | ||||
|
r1498 | dentries=dirlist, | ||
archives=self.archivelist(hex(node))) | ||||
|
r131 | |||
|
r168 | def tags(self): | ||
cl = self.repo.changelog | ||||
mf = cl.read(cl.tip())[0] | ||||
|
r343 | i = self.repo.tagslist() | ||
i.reverse() | ||||
|
r168 | |||
|
r1767 | def entries(notip=False, **map): | ||
|
r168 | parity = 0 | ||
for k,n in i: | ||||
|
r1767 | if notip and k == "tip": continue | ||
|
r974 | yield {"parity": parity, | ||
"tag": k, | ||||
|
r1579 | "tagmanifest": hex(cl.read(n)[0]), | ||
"date": cl.read(n)[2], | ||||
|
r974 | "node": hex(n)} | ||
|
r168 | parity = 1 - parity | ||
yield self.t("tags", | ||||
|
r1062 | manifest=hex(mf), | ||
|
r1767 | entries=lambda **x: entries(False, **x), | ||
entriesnotip=lambda **x: entries(True, **x)) | ||||
|
r168 | |||
|
r1572 | def summary(self): | ||
cl = self.repo.changelog | ||||
mf = cl.read(cl.tip())[0] | ||||
i = self.repo.tagslist() | ||||
i.reverse() | ||||
def tagentries(**map): | ||||
parity = 0 | ||||
|
r1579 | count = 0 | ||
|
r1572 | for k,n in i: | ||
|
r1579 | if k == "tip": # skip tip | ||
|
r1572 | continue; | ||
count += 1 | ||||
|
r1579 | if count > 10: # limit to 10 tags | ||
|
r1572 | break; | ||
c = cl.read(n) | ||||
m = c[0] | ||||
t = c[2] | ||||
|
r1579 | |||
|
r1572 | yield self.t("tagentry", | ||
parity = parity, | ||||
tag = k, | ||||
node = hex(n), | ||||
date = t, | ||||
|
r1575 | tagmanifest = hex(m)) | ||
|
r1572 | parity = 1 - parity | ||
|
r1579 | |||
|
r1572 | def changelist(**map): | ||
parity = 0 | ||||
cl = self.repo.changelog | ||||
l = [] # build a list in forward order for efficiency | ||||
for i in range(start, end): | ||||
n = cl.node(i) | ||||
changes = cl.read(n) | ||||
hn = hex(n) | ||||
t = changes[2] | ||||
l.insert(0, self.t( | ||||
'shortlogentry', | ||||
parity = parity, | ||||
author = changes[1], | ||||
manifest = hex(changes[0]), | ||||
desc = changes[4], | ||||
date = t, | ||||
rev = i, | ||||
node = hn)) | ||||
parity = 1 - parity | ||||
yield l | ||||
cl = self.repo.changelog | ||||
mf = cl.read(cl.tip())[0] | ||||
count = cl.count() | ||||
start = max(0, count - self.maxchanges) | ||||
end = min(count, start + self.maxchanges) | ||||
pos = end - 1 | ||||
yield self.t("summary", | ||||
|
r1579 | desc = self.repo.ui.config("web", "description", "unknown"), | ||
owner = (self.repo.ui.config("ui", "username") or # preferred | ||||
|
r1572 | self.repo.ui.config("web", "contact") or # deprecated | ||
self.repo.ui.config("web", "author", "unknown")), # also | ||||
|
r1579 | lastchange = (0, 0), # FIXME | ||
|
r1572 | manifest = hex(mf), | ||
tags = tagentries, | ||||
shortlog = changelist) | ||||
|
r1579 | |||
|
r138 | def filediff(self, file, changeset): | ||
cl = self.repo.changelog | ||||
|
r1369 | n = self.repo.lookup(changeset) | ||
changeset = hex(n) | ||||
|
r138 | p1 = cl.parents(n)[0] | ||
cs = cl.read(n) | ||||
mf = self.repo.manifest.read(cs[0]) | ||||
|
r515 | |||
|
r857 | def diff(**map): | ||
|
r138 | yield self.diff(p1, n, file) | ||
|
r131 | |||
|
r138 | yield self.t("filediff", | ||
|
r1062 | file=file, | ||
filenode=hex(mf.get(file, nullid)), | ||||
node=changeset, | ||||
rev=self.repo.changelog.rev(n), | ||||
|
r1606 | parent=self.siblings(cl.parents(n), cl.rev), | ||
child=self.siblings(cl.children(n), cl.rev), | ||||
|
r1062 | diff=diff) | ||
|
r515 | |||
|
r1159 | def archive(self, req, cnode, type): | ||
|
r1078 | cs = self.repo.changelog.read(cnode) | ||
mnode = cs[0] | ||||
mf = self.repo.manifest.read(mnode) | ||||
rev = self.repo.manifest.rev(mnode) | ||||
reponame = re.sub(r"\W+", "-", self.reponame) | ||||
name = "%s-%s/" % (reponame, short(cnode)) | ||||
|
r1112 | files = mf.keys() | ||
files.sort() | ||||
|
r1077 | if type == 'zip': | ||
|
r1165 | tmp = tempfile.mkstemp()[1] | ||
|
r1078 | try: | ||
zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED) | ||||
|
r1112 | for f in files: | ||
|
r1078 | zf.writestr(name + f, self.repo.file(f).read(mf[f])) | ||
zf.close() | ||||
|
r1076 | |||
|
r1078 | f = open(tmp, 'r') | ||
|
r1159 | req.httphdr('application/zip', name[:-1] + '.zip', | ||
|
r1078 | os.path.getsize(tmp)) | ||
|
r1159 | req.write(f.read()) | ||
|
r1078 | f.close() | ||
finally: | ||||
os.unlink(tmp) | ||||
|
r1076 | |||
|
r1077 | else: | ||
|
r1159 | tf = tarfile.TarFile.open(mode='w|' + type, fileobj=req.out) | ||
|
r1078 | mff = self.repo.manifest.readflags(mnode) | ||
|
r1077 | mtime = int(time.time()) | ||
|
r1076 | |||
|
r1185 | if type == "gz": | ||
encoding = "gzip" | ||||
else: | ||||
encoding = "x-bzip2" | ||||
|
r1308 | req.header([('Content-type', 'application/x-tar'), | ||
|
r1185 | ('Content-disposition', 'attachment; filename=%s%s%s' % | ||
(name[:-1], '.tar.', type)), | ||||
('Content-encoding', encoding)]) | ||||
|
r1112 | for fname in files: | ||
|
r1078 | rcont = self.repo.file(fname).read(mf[fname]) | ||
|
r1077 | finfo = tarfile.TarInfo(name + fname) | ||
finfo.mtime = mtime | ||||
finfo.size = len(rcont) | ||||
finfo.mode = mff[fname] and 0755 or 0644 | ||||
tf.addfile(finfo, StringIO.StringIO(rcont)) | ||||
tf.close() | ||||
|
r1076 | |||
|
r138 | # add tags to things | ||
# tags -> list of changesets corresponding to tags | ||||
# find tag, changeset, file | ||||
|
r131 | |||
|
r1159 | def run(self, req=hgrequest()): | ||
|
r1646 | def clean(path): | ||
p = os.path.normpath(path) | ||||
if p[:2] == "..": | ||||
raise "suspicious path" | ||||
return p | ||||
|
r857 | def header(**map): | ||
yield self.t("header", **map) | ||||
def footer(**map): | ||||
yield self.t("footer", **map) | ||||
|
r1409 | |||
|
r1406 | def expand_form(form): | ||
shortcuts = { | ||||
|
r1409 | 'cl': [('cmd', ['changelog']), ('rev', None)], | ||
|
r1406 | 'cs': [('cmd', ['changeset']), ('node', None)], | ||
|
r1409 | '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'])], | ||||
|
r1777 | 'static': [('cmd', ['static']), ('file', None)] | ||
|
r1406 | } | ||
|
r1409 | |||
|
r1406 | 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] | ||||
|
r857 | |||
|
r258 | self.refresh() | ||
|
r132 | |||
|
r1406 | expand_form(req.form) | ||
|
r987 | t = self.repo.ui.config("web", "templates", templatepath()) | ||
|
r1777 | static = self.repo.ui.config("web", "static", os.path.join(t,"static")) | ||
|
r938 | m = os.path.join(t, "map") | ||
|
r986 | style = self.repo.ui.config("web", "style", "") | ||
|
r1159 | if req.form.has_key('style'): | ||
style = req.form['style'][0] | ||||
|
r986 | if style: | ||
b = os.path.basename("map-" + style) | ||||
|
r983 | p = os.path.join(t, b) | ||
|
r1063 | if os.path.isfile(p): | ||
m = p | ||||
|
r515 | |||
|
r1159 | port = req.env["SERVER_PORT"] | ||
|
r601 | port = port != "80" and (":" + port) or "" | ||
|
r1159 | uri = req.env["REQUEST_URI"] | ||
|
r1063 | if "?" in uri: | ||
uri = uri.split("?")[0] | ||||
|
r1159 | url = "http://%s%s%s" % (req.env["SERVER_NAME"], port, uri) | ||
|
r1172 | if not self.reponame: | ||
self.reponame = (self.repo.ui.config("web", "name") | ||||
or uri.strip('/') or self.repo.root) | ||||
|
r601 | |||
|
r941 | self.t = templater(m, common_filters, | ||
|
r1062 | {"url": url, | ||
"repo": self.reponame, | ||||
"header": header, | ||||
"footer": footer, | ||||
|
r1063 | }) | ||
|
r201 | |||
|
r1159 | if not req.form.has_key('cmd'): | ||
req.form['cmd'] = [self.t.cache['default'],] | ||||
|
r937 | |||
|
r1159 | if req.form['cmd'][0] == 'changelog': | ||
|
r538 | c = self.repo.changelog.count() - 1 | ||
hi = c | ||||
|
r1159 | if req.form.has_key('rev'): | ||
hi = req.form['rev'][0] | ||||
|
r166 | try: | ||
hi = self.repo.changelog.rev(self.repo.lookup(hi)) | ||||
|
r1219 | except hg.RepoError: | ||
|
r1159 | req.write(self.search(hi)) | ||
|
r538 | return | ||
|
r575 | |||
|
r1159 | req.write(self.changelog(hi)) | ||
|
r515 | |||
|
r1159 | elif req.form['cmd'][0] == 'changeset': | ||
req.write(self.changeset(req.form['node'][0])) | ||||
|
r138 | |||
|
r1159 | elif req.form['cmd'][0] == 'manifest': | ||
|
r1646 | req.write(self.manifest(req.form['manifest'][0], | ||
clean(req.form['path'][0]))) | ||||
|
r138 | |||
|
r1159 | elif req.form['cmd'][0] == 'tags': | ||
req.write(self.tags()) | ||||
|
r168 | |||
|
r1572 | elif req.form['cmd'][0] == 'summary': | ||
req.write(self.summary()) | ||||
|
r1579 | |||
|
r1159 | elif req.form['cmd'][0] == 'filediff': | ||
|
r1646 | req.write(self.filediff(clean(req.form['file'][0]), | ||
req.form['node'][0])) | ||||
|
r131 | |||
|
r1159 | elif req.form['cmd'][0] == 'file': | ||
|
r1646 | req.write(self.filerevision(clean(req.form['file'][0]), | ||
req.form['filenode'][0])) | ||||
|
r131 | |||
|
r1159 | elif req.form['cmd'][0] == 'annotate': | ||
|
r1646 | req.write(self.fileannotate(clean(req.form['file'][0]), | ||
req.form['filenode'][0])) | ||||
|
r131 | |||
|
r1159 | elif req.form['cmd'][0] == 'filelog': | ||
|
r1646 | req.write(self.filelog(clean(req.form['file'][0]), | ||
req.form['filenode'][0])) | ||||
|
r136 | |||
|
r1159 | elif req.form['cmd'][0] == 'heads': | ||
req.httphdr("application/mercurial-0.1") | ||||
|
r222 | h = self.repo.heads() | ||
|
r1159 | req.write(" ".join(map(hex, h)) + "\n") | ||
|
r222 | |||
|
r1159 | elif req.form['cmd'][0] == 'branches': | ||
req.httphdr("application/mercurial-0.1") | ||||
|
r132 | nodes = [] | ||
|
r1159 | if req.form.has_key('nodes'): | ||
nodes = map(bin, req.form['nodes'][0].split(" ")) | ||||
|
r138 | for b in self.repo.branches(nodes): | ||
|
r1159 | req.write(" ".join(map(hex, b)) + "\n") | ||
|
r131 | |||
|
r1159 | elif req.form['cmd'][0] == 'between': | ||
req.httphdr("application/mercurial-0.1") | ||||
|
r132 | nodes = [] | ||
|
r1159 | if req.form.has_key('pairs'): | ||
|
r1063 | pairs = [map(bin, p.split("-")) | ||
|
r1159 | for p in req.form['pairs'][0].split(" ")] | ||
|
r138 | for b in self.repo.between(pairs): | ||
|
r1159 | req.write(" ".join(map(hex, b)) + "\n") | ||
|
r132 | |||
|
r1159 | elif req.form['cmd'][0] == 'changegroup': | ||
req.httphdr("application/mercurial-0.1") | ||||
|
r132 | nodes = [] | ||
|
r964 | if not self.allowpull: | ||
|
r197 | return | ||
|
r1159 | if req.form.has_key('roots'): | ||
nodes = map(bin, req.form['roots'][0].split(" ")) | ||||
|
r131 | |||
|
r132 | z = zlib.compressobj() | ||
|
r1736 | f = self.repo.changegroup(nodes, 'serve') | ||
|
r635 | while 1: | ||
chunk = f.read(4096) | ||||
|
r1063 | if not chunk: | ||
break | ||||
|
r1159 | req.write(z.compress(chunk)) | ||
|
r132 | |||
|
r1159 | req.write(z.flush()) | ||
|
r131 | |||
|
r1159 | elif req.form['cmd'][0] == 'archive': | ||
|
r1369 | changeset = self.repo.lookup(req.form['node'][0]) | ||
|
r1159 | type = req.form['type'][0] | ||
|
r1078 | if (type in self.archives and | ||
self.repo.ui.configbool("web", "allow" + type, False)): | ||||
|
r1159 | self.archive(req, changeset, type) | ||
|
r1078 | return | ||
|
r1077 | |||
|
r1159 | req.write(self.t("error")) | ||
|
r1076 | |||
|
r1777 | elif req.form['cmd'][0] == 'static': | ||
fname = req.form['file'][0] | ||||
|
r1793 | req.write(staticfile(static, fname) | ||
|
r1796 | or self.t("error", error="%r not found" % fname)) | ||
|
r1777 | |||
|
r132 | else: | ||
|
r1159 | req.write(self.t("error")) | ||
|
r131 | |||
|
r987 | def create_server(repo): | ||
|
r158 | |||
|
r938 | def openlog(opt, default): | ||
if opt and opt != '-': | ||||
return open(opt, 'w') | ||||
return default | ||||
|
r987 | address = repo.ui.config("web", "address", "") | ||
port = int(repo.ui.config("web", "port", 8000)) | ||||
use_ipv6 = repo.ui.configbool("web", "ipv6") | ||||
accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout) | ||||
errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr) | ||||
|
r938 | |||
|
r825 | class IPv6HTTPServer(BaseHTTPServer.HTTPServer): | ||
|
r881 | address_family = getattr(socket, 'AF_INET6', None) | ||
def __init__(self, *args, **kwargs): | ||||
if self.address_family is None: | ||||
|
r1402 | raise hg.RepoError(_('IPv6 not available on this system')) | ||
|
r881 | BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs) | ||
|
r825 | |||
|
r158 | class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler): | ||
|
r605 | def log_error(self, format, *args): | ||
errorlog.write("%s - - [%s] %s\n" % (self.address_string(), | ||||
self.log_date_time_string(), | ||||
format % args)) | ||||
|
r937 | |||
|
r605 | def log_message(self, format, *args): | ||
accesslog.write("%s - - [%s] %s\n" % (self.address_string(), | ||||
self.log_date_time_string(), | ||||
format % args)) | ||||
|
r158 | def do_POST(self): | ||
|
r271 | try: | ||
self.do_hgweb() | ||||
except socket.error, inst: | ||||
|
r1174 | if inst[0] != errno.EPIPE: | ||
|
r1063 | raise | ||
|
r158 | |||
def do_GET(self): | ||||
|
r271 | self.do_POST() | ||
|
r158 | |||
def do_hgweb(self): | ||||
query = "" | ||||
p = self.path.find("?") | ||||
if p: | ||||
query = self.path[p + 1:] | ||||
query = query.replace('+', ' ') | ||||
|
r515 | |||
|
r158 | env = {} | ||
env['GATEWAY_INTERFACE'] = 'CGI/1.1' | ||||
env['REQUEST_METHOD'] = self.command | ||||
|
r599 | env['SERVER_NAME'] = self.server.server_name | ||
env['SERVER_PORT'] = str(self.server.server_port) | ||||
env['REQUEST_URI'] = "/" | ||||
|
r158 | if query: | ||
env['QUERY_STRING'] = query | ||||
host = self.address_string() | ||||
if host != self.client_address[0]: | ||||
env['REMOTE_HOST'] = host | ||||
env['REMOTE_ADDR'] = self.client_address[0] | ||||
if self.headers.typeheader is None: | ||||
env['CONTENT_TYPE'] = self.headers.type | ||||
else: | ||||
env['CONTENT_TYPE'] = self.headers.typeheader | ||||
length = self.headers.getheader('content-length') | ||||
if length: | ||||
env['CONTENT_LENGTH'] = length | ||||
accept = [] | ||||
for line in self.headers.getallmatchingheaders('accept'): | ||||
if line[:1] in "\t\n\r ": | ||||
accept.append(line.strip()) | ||||
else: | ||||
accept = accept + line[7:].split(',') | ||||
env['HTTP_ACCEPT'] = ','.join(accept) | ||||
|
r1180 | req = hgrequest(self.rfile, self.wfile, env) | ||
self.send_response(200, "Script output follows") | ||||
hg.run(req) | ||||
|
r158 | |||
|
r987 | hg = hgweb(repo) | ||
|
r825 | if use_ipv6: | ||
return IPv6HTTPServer((address, port), hgwebhandler) | ||||
else: | ||||
return BaseHTTPServer.HTTPServer((address, port), hgwebhandler) | ||||
|
r603 | |||
|
r941 | # This is a stopgap | ||
|
r1559 | class hgwebdir(object): | ||
|
r941 | def __init__(self, config): | ||
|
r1181 | def cleannames(items): | ||
return [(name.strip('/'), path) for name, path in items] | ||||
|
r1143 | if type(config) == type([]): | ||
|
r1181 | self.repos = cleannames(config) | ||
|
r1143 | elif type(config) == type({}): | ||
|
r1181 | self.repos = cleannames(config.items()) | ||
|
r1143 | self.repos.sort() | ||
else: | ||||
cp = ConfigParser.SafeConfigParser() | ||||
cp.read(config) | ||||
|
r1181 | self.repos = cleannames(cp.items("paths")) | ||
|
r1143 | self.repos.sort() | ||
|
r941 | |||
|
r1159 | def run(self, req=hgrequest()): | ||
|
r941 | def header(**map): | ||
yield tmpl("header", **map) | ||||
def footer(**map): | ||||
yield tmpl("footer", **map) | ||||
|
r1142 | m = os.path.join(templatepath(), "map") | ||
|
r941 | tmpl = templater(m, common_filters, | ||
{"header": header, "footer": footer}) | ||||
def entries(**map): | ||||
parity = 0 | ||||
|
r1141 | for name, path in self.repos: | ||
|
r1213 | u = ui.ui() | ||
|
r1170 | try: | ||
|
r1473 | u.readconfig(os.path.join(path, '.hg', 'hgrc')) | ||
|
r1170 | except IOError: | ||
pass | ||||
|
r1140 | get = u.config | ||
|
r941 | |||
|
r1181 | url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name]) | ||
|
r1142 | .replace("//", "/")) | ||
|
r1022 | |||
|
r1348 | # update time with local timezone | ||
|
r1524 | try: | ||
d = (get_mtime(path), util.makedate()[1]) | ||||
except OSError: | ||||
continue | ||||
|
r1348 | |||
|
r1260 | yield dict(contact=(get("ui", "username") or # preferred | ||
get("web", "contact") or # deprecated | ||||
get("web", "author", "unknown")), # also | ||||
|
r1130 | name=get("web", "name", name), | ||
|
r1062 | url=url, | ||
parity=parity, | ||||
shortdesc=get("web", "description", "unknown"), | ||||
|
r1348 | lastupdate=d) | ||
|
r941 | |||
parity = 1 - parity | ||||
|
r1159 | virtual = req.env.get("PATH_INFO", "").strip('/') | ||
|
r1142 | if virtual: | ||
|
r1141 | real = dict(self.repos).get(virtual) | ||
if real: | ||||
|
r1554 | try: | ||
hgweb(real).run(req) | ||||
except IOError, inst: | ||||
req.write(tmpl("error", error=inst.strerror)) | ||||
except hg.RepoError, inst: | ||||
req.write(tmpl("error", error=str(inst))) | ||||
|
r1123 | else: | ||
|
r1159 | req.write(tmpl("notfound", repo=virtual)) | ||
|
r1142 | else: | ||
|
r1793 | if req.form.has_key('static'): | ||
static = os.path.join(templatepath(), "static") | ||||
fname = req.form['static'][0] | ||||
req.write(staticfile(static, fname) | ||||
or tmpl("error", error="%r not found" % fname)) | ||||
else: | ||||
req.write(tmpl("index", entries=entries)) | ||||