hgweb.py
895 lines
| 28.9 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. | ||||
|
r825 | import os, cgi, time, re, difflib, socket, sys, zlib | ||
|
r138 | from mercurial.hg import * | ||
|
r215 | from mercurial.ui import * | ||
|
r138 | |||
|
r157 | def templatepath(): | ||
|
r201 | for f in "templates", "../templates": | ||
|
r157 | p = os.path.join(os.path.dirname(__file__), f) | ||
|
r201 | if os.path.isdir(p): return p | ||
|
r157 | |||
|
r138 | def age(t): | ||
def plural(t, c): | ||||
if c == 1: return t | ||||
return t + "s" | ||||
def fmt(t, c): | ||||
return "%d %s" % (c, plural(t, c)) | ||||
now = time.time() | ||||
delta = max(1, int(now - t)) | ||||
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 | ||||
|
r195 | 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): | ||||
|
r533 | return ''.join([ '&#%d;' % ord(c) for c in text ]) | ||
|
r138 | |||
def up(p): | ||||
if p[0] != "/": p = "/" + p | ||||
if p[-1] == "/": p = p[:-1] | ||||
up = os.path.dirname(p) | ||||
if up == "/": | ||||
return "/" | ||||
return up + "/" | ||||
|
r131 | |||
def httphdr(type): | ||||
|
r582 | sys.stdout.write('Content-type: %s\n\n' % type) | ||
|
r131 | |||
|
r135 | def write(*things): | ||
for thing in things: | ||||
if hasattr(thing, "__iter__"): | ||||
for part in thing: | ||||
write(part) | ||||
else: | ||||
sys.stdout.write(str(thing)) | ||||
|
r138 | class templater: | ||
|
r601 | 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: | ||
|
r138 | raise "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) | ||
def template(self, tmpl, filters = {}, **map): | ||||
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 | |||
|
r599 | def rfc822date(x): | ||
|
r601 | return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x)) | ||
|
r599 | |||
|
r941 | common_filters = { | ||
"escape": cgi.escape, | ||||
"age": age, | ||||
"date": (lambda x: time.asctime(time.gmtime(x))), | ||||
"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--"), | ||||
"rfc822date": rfc822date, | ||||
} | ||||
|
r138 | class hgweb: | ||
|
r133 | |||
|
r941 | def __init__(self, path, name=None, templates=""): | ||
|
r938 | self.templates = templates | ||
|
r138 | self.reponame = name | ||
|
r258 | self.path = path | ||
self.mtime = -1 | ||||
|
r197 | self.viewonly = 0 | ||
|
r131 | |||
|
r258 | def refresh(self): | ||
s = os.stat(os.path.join(self.path, ".hg", "00changelog.i")) | ||||
if s.st_mtime != self.mtime: | ||||
|
r322 | self.mtime = s.st_mtime | ||
|
r258 | self.repo = repository(ui(), self.path) | ||
|
r964 | self.maxchanges = self.repo.ui.config("web", "maxchanges", 10) | ||
self.maxfiles = self.repo.ui.config("web", "maxchanges", 10) | ||||
self.allowpull = self.repo.ui.configbool("web", "allowpull", True) | ||||
|
r258 | |||
|
r138 | def date(self, cs): | ||
return time.asctime(time.gmtime(float(cs[2].split(' ')[0]))) | ||||
def listfiles(self, files, mf): | ||||
for f in files[:self.maxfiles]: | ||||
yield self.t("filenodelink", node = hex(mf[f]), file = f) | ||||
if len(files) > self.maxfiles: | ||||
yield self.t("fileellipses") | ||||
def listfilediffs(self, files, changeset): | ||||
for f in files[:self.maxfiles]: | ||||
yield self.t("filedifflink", node = hex(changeset), file = f) | ||||
if len(files) > self.maxfiles: | ||||
yield self.t("fileellipses") | ||||
|
r569 | def parents(self, t1, nodes=[], rev=None,**args): | ||
if not rev: rev = lambda x: "" | ||||
for node in nodes: | ||||
if node != nullid: | ||||
yield self.t(t1, node = hex(node), rev = rev(node), **args) | ||||
|
r568 | def showtag(self, t1, node=nullid, **args): | ||
for t in self.repo.nodetags(node): | ||||
yield self.t(t1, tag = t, **args) | ||||
|
r138 | def diff(self, node1, node2, files): | ||
def filterfiles(list, files): | ||||
l = [ x for x in list if x in files ] | ||||
|
r515 | |||
|
r138 | for f in files: | ||
if f[-1] != os.sep: f += os.sep | ||||
l += [ x for x in list if x.startswith(f) ] | ||||
return l | ||||
|
r131 | |||
|
r172 | parity = [0] | ||
def diffblock(diff, f, fn): | ||||
yield self.t("diffblock", | ||||
lines = prettyprintlines(diff), | ||||
parity = parity[0], | ||||
file = f, | ||||
|
r369 | 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('+'): | ||
yield self.t("difflineplus", line = l) | ||||
elif l.startswith('-'): | ||||
yield self.t("difflineminus", line = l) | ||||
elif l.startswith('@'): | ||||
yield self.t("difflineat", line = l) | ||||
|
r138 | else: | ||
|
r201 | 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]) | ||||
date1 = self.date(change1) | ||||
date2 = self.date(change2) | ||||
|
r131 | |||
|
r539 | c, a, d, u = r.changes(node1, node2) | ||
|
r645 | if files: | ||
c, a, d = map(lambda x: filterfiles(x, files), (c, a, d)) | ||||
|
r131 | |||
|
r138 | for f in c: | ||
to = r.file(f).read(mmap1[f]) | ||||
tn = r.file(f).read(mmap2[f]) | ||||
|
r172 | yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) | ||
|
r138 | for f in a: | ||
|
r265 | to = None | ||
|
r138 | tn = r.file(f).read(mmap2[f]) | ||
|
r172 | yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) | ||
|
r138 | for f in d: | ||
to = r.file(f).read(mmap1[f]) | ||||
|
r265 | tn = None | ||
|
r172 | yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn) | ||
|
r131 | |||
|
r180 | def changelog(self, pos): | ||
|
r857 | def changenav(**map): | ||
|
r138 | def seq(factor = 1): | ||
yield 1 * factor | ||||
|
r173 | yield 3 * factor | ||
#yield 5 * factor | ||||
|
r138 | for f in seq(factor * 10): | ||
yield f | ||||
|
r131 | |||
|
r173 | l = [] | ||
for f in seq(): | ||||
if f < self.maxchanges / 2: continue | ||||
if f > count: break | ||||
r = "%d" % f | ||||
|
r351 | 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 | |||
|
r975 | yield {"label": "tip", "rev": ""} | ||
|
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) | ||||
t = float(changes[2].split(' ')[0]) | ||||
|
r131 | |||
|
r975 | l.insert(0, { | ||
"parity": parity, | ||||
"author": changes[1], | ||||
"parent": self.parents("changelogparent", | ||||
|
r569 | cl.parents(n), cl.rev), | ||
|
r975 | "changelogtag": self.showtag("changelogtag",n), | ||
"manifest": hex(changes[0]), | ||||
"desc": changes[4], | ||||
"date": t, | ||||
"files": self.listfilediffs(changes[3], n), | ||||
"rev": i, | ||||
"node": hn}) | ||||
|
r142 | parity = 1 - parity | ||
|
r138 | |||
|
r975 | 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', | ||
changenav = changenav, | ||||
|
r168 | manifest = hex(mf), | ||
|
r142 | 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) | ||||
l.insert(0, (n, j, changes)) | ||||
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 | ||||
if miss: continue | ||||
count += 1 | ||||
hn = hex(n) | ||||
t = float(changes[2].split(' ')[0]) | ||||
yield self.t( | ||||
'searchentry', | ||||
parity = count & 1, | ||||
author = changes[1], | ||||
|
r570 | parent = self.parents("changelogparent", | ||
|
r569 | cl.parents(n), cl.rev), | ||
|
r568 | changelogtag = self.showtag("changelogtag",n), | ||
|
r538 | manifest = hex(changes[0]), | ||
desc = changes[4], | ||||
date = t, | ||||
files = self.listfilediffs(changes[3], n), | ||||
rev = i, | ||||
node = hn) | ||||
if count >= self.maxchanges: break | ||||
cl = self.repo.changelog | ||||
mf = cl.read(cl.tip())[0] | ||||
yield self.t('search', | ||||
query = query, | ||||
manifest = hex(mf), | ||||
entries = changelist) | ||||
|
r138 | def changeset(self, nodeid): | ||
n = bin(nodeid) | ||||
cl = self.repo.changelog | ||||
changes = cl.read(n) | ||||
|
r598 | p1 = cl.parents(n)[0] | ||
|
r138 | t = float(changes[2].split(' ')[0]) | ||
|
r515 | |||
|
r133 | files = [] | ||
|
r138 | mf = self.repo.manifest.read(changes[0]) | ||
|
r131 | for f in changes[3]: | ||
|
r138 | files.append(self.t("filenodelink", | ||
|
r369 | 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', | ||
diff = diff, | ||||
rev = cl.rev(n), | ||||
node = nodeid, | ||||
|
r570 | parent = self.parents("changesetparent", | ||
|
r569 | cl.parents(n), cl.rev), | ||
|
r568 | changesettag = self.showtag("changesettag",n), | ||
|
r138 | manifest = hex(changes[0]), | ||
|
r201 | author = changes[1], | ||
desc = changes[4], | ||||
date = t, | ||||
|
r138 | files = files) | ||
|
r131 | |||
|
r138 | def filelog(self, f, filenode): | ||
cl = self.repo.changelog | ||||
fl = self.repo.file(f) | ||||
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)) | ||||
t = float(cs[2].split(' ')[0]) | ||||
|
r133 | |||
|
r978 | l.insert(0, {"parity": parity, | ||
"filenode": hex(n), | ||||
"filerev": i, | ||||
"file": f, | ||||
"node": hex(cn), | ||||
"author": cs[1], | ||||
"date": t, | ||||
"parent": self.parents("filelogparent", | ||||
|
r598 | fl.parents(n), fl.rev, file=f), | ||
|
r978 | "desc": cs[4]}) | ||
|
r142 | parity = 1 - parity | ||
|
r138 | |||
|
r978 | for e in l: yield e | ||
|
r138 | |||
yield self.t("filelog", | ||||
file = f, | ||||
filenode = filenode, | ||||
entries = entries) | ||||
|
r131 | |||
|
r138 | def filerevision(self, f, node): | ||
fl = self.repo.file(f) | ||||
n = bin(node) | ||||
|
r201 | text = fl.read(n) | ||
|
r138 | changerev = fl.linkrev(n) | ||
cl = self.repo.changelog | ||||
cn = cl.node(changerev) | ||||
cs = cl.read(cn) | ||||
t = float(cs[2].split(' ')[0]) | ||||
mfn = cs[0] | ||||
|
r142 | |||
def lines(): | ||||
for l, t in enumerate(text.splitlines(1)): | ||||
|
r976 | yield {"line": t, | ||
"linenumber": "% 6d" % (l + 1), | ||||
"parity": l & 1} | ||||
|
r359 | |||
|
r138 | yield self.t("filerevision", file = f, | ||
filenode = node, | ||||
path = up(f), | ||||
|
r142 | text = lines(), | ||
|
r138 | rev = changerev, | ||
node = hex(cn), | ||||
manifest = hex(mfn), | ||||
|
r201 | author = cs[1], | ||
date = t, | ||||
|
r570 | parent = self.parents("filerevparent", | ||
|
r569 | fl.parents(n), fl.rev, file=f), | ||
|
r598 | permissions = self.repo.manifest.readflags(mfn)[f]) | ||
|
r138 | |||
def fileannotate(self, f, node): | ||||
bcache = {} | ||||
ncache = {} | ||||
fl = self.repo.file(f) | ||||
n = bin(node) | ||||
changerev = fl.linkrev(n) | ||||
cl = self.repo.changelog | ||||
cn = cl.node(changerev) | ||||
cs = cl.read(cn) | ||||
t = float(cs[2].split(' ')[0]) | ||||
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) | ||||
name = cl[1] | ||||
f = name.find('@') | ||||
if f >= 0: | ||||
name = name[:f] | ||||
|
r534 | f = name.find('<') | ||
if f >= 0: | ||||
name = name[f+1:] | ||||
|
r138 | bcache[r] = name | ||
|
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", | ||||
file = f, | ||||
filenode = node, | ||||
annotate = annotate, | ||||
path = up(f), | ||||
rev = changerev, | ||||
node = hex(cn), | ||||
manifest = hex(mfn), | ||||
|
r201 | author = cs[1], | ||
date = t, | ||||
|
r570 | parent = self.parents("fileannotateparent", | ||
|
r569 | fl.parents(n), fl.rev, file=f), | ||
|
r598 | permissions = self.repo.manifest.readflags(mfn)[f]) | ||
|
r136 | |||
|
r138 | def manifest(self, mnode, path): | ||
mf = self.repo.manifest.read(bin(mnode)) | ||||
rev = self.repo.manifest.rev(bin(mnode)) | ||||
node = self.repo.changelog.node(rev) | ||||
|
r359 | mff=self.repo.manifest.readflags(bin(mnode)) | ||
|
r138 | |||
files = {} | ||||
|
r515 | |||
|
r138 | p = path[1:] | ||
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", | ||
manifest = mnode, | ||||
rev = rev, | ||||
node = hex(node), | ||||
path = path, | ||||
up = up(path), | ||||
|
r979 | fentries = filelist, | ||
dentries = dirlist) | ||||
|
r131 | |||
|
r168 | def tags(self): | ||
cl = self.repo.changelog | ||||
mf = cl.read(cl.tip())[0] | ||||
|
r343 | i = self.repo.tagslist() | ||
i.reverse() | ||||
|
r168 | |||
|
r857 | def entries(**map): | ||
|
r168 | parity = 0 | ||
for k,n in i: | ||||
|
r974 | yield {"parity": parity, | ||
"tag": k, | ||||
"node": hex(n)} | ||||
|
r168 | parity = 1 - parity | ||
yield self.t("tags", | ||||
manifest = hex(mf), | ||||
entries = entries) | ||||
|
r138 | def filediff(self, file, changeset): | ||
n = bin(changeset) | ||||
cl = self.repo.changelog | ||||
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", | ||
file = file, | ||||
|
r376 | filenode = hex(mf.get(file, nullid)), | ||
|
r138 | node = changeset, | ||
rev = self.repo.changelog.rev(n), | ||||
|
r571 | parent = self.parents("filediffparent", | ||
cl.parents(n), cl.rev), | ||||
|
r138 | diff = diff) | ||
|
r515 | |||
|
r138 | # add tags to things | ||
# tags -> list of changesets corresponding to tags | ||||
# find tag, changeset, file | ||||
|
r131 | |||
|
r132 | def run(self): | ||
|
r857 | def header(**map): | ||
yield self.t("header", **map) | ||||
def footer(**map): | ||||
yield self.t("footer", **map) | ||||
|
r258 | self.refresh() | ||
|
r132 | args = cgi.parse() | ||
|
r938 | t = self.templates or self.repo.ui.config("web", "templates", | ||
templatepath()) | ||||
m = os.path.join(t, "map") | ||||
|
r201 | if args.has_key('style'): | ||
b = os.path.basename("map-" + args['style'][0]) | ||||
p = os.path.join(self.templates, b) | ||||
if os.path.isfile(p): m = p | ||||
|
r515 | |||
|
r601 | port = os.environ["SERVER_PORT"] | ||
port = port != "80" and (":" + port) or "" | ||||
|
r620 | uri = os.environ["REQUEST_URI"] | ||
if "?" in uri: uri = uri.split("?")[0] | ||||
url = "http://%s%s%s" % (os.environ["SERVER_NAME"], port, uri) | ||||
|
r601 | |||
|
r938 | name = self.reponame or self.repo.ui.config("web", "name", os.getcwd()) | ||
|
r941 | self.t = templater(m, common_filters, | ||
|
r601 | {"url":url, | ||
|
r938 | "repo":name, | ||
|
r857 | "header":header, | ||
"footer":footer, | ||||
|
r601 | }) | ||
|
r201 | |||
|
r858 | if not args.has_key('cmd'): | ||
args['cmd'] = [self.t.cache['default'],] | ||||
|
r937 | |||
|
r858 | if args['cmd'][0] == 'changelog': | ||
|
r538 | c = self.repo.changelog.count() - 1 | ||
hi = c | ||||
|
r153 | if args.has_key('rev'): | ||
|
r165 | hi = args['rev'][0] | ||
|
r166 | try: | ||
hi = self.repo.changelog.rev(self.repo.lookup(hi)) | ||||
|
r688 | except RepoError: | ||
|
r538 | write(self.search(hi)) | ||
return | ||||
|
r575 | |||
|
r138 | write(self.changelog(hi)) | ||
|
r515 | |||
|
r138 | elif args['cmd'][0] == 'changeset': | ||
write(self.changeset(args['node'][0])) | ||||
elif args['cmd'][0] == 'manifest': | ||||
write(self.manifest(args['manifest'][0], args['path'][0])) | ||||
|
r168 | elif args['cmd'][0] == 'tags': | ||
write(self.tags()) | ||||
|
r138 | elif args['cmd'][0] == 'filediff': | ||
write(self.filediff(args['file'][0], args['node'][0])) | ||||
|
r131 | |||
|
r132 | elif args['cmd'][0] == 'file': | ||
|
r138 | write(self.filerevision(args['file'][0], args['filenode'][0])) | ||
|
r131 | |||
|
r138 | elif args['cmd'][0] == 'annotate': | ||
write(self.fileannotate(args['file'][0], args['filenode'][0])) | ||||
|
r131 | |||
|
r138 | elif args['cmd'][0] == 'filelog': | ||
write(self.filelog(args['file'][0], args['filenode'][0])) | ||||
|
r136 | |||
|
r222 | elif args['cmd'][0] == 'heads': | ||
|
r751 | httphdr("application/mercurial-0.1") | ||
|
r222 | h = self.repo.heads() | ||
sys.stdout.write(" ".join(map(hex, h)) + "\n") | ||||
|
r132 | elif args['cmd'][0] == 'branches': | ||
|
r751 | httphdr("application/mercurial-0.1") | ||
|
r132 | nodes = [] | ||
if args.has_key('nodes'): | ||||
|
r138 | nodes = map(bin, args['nodes'][0].split(" ")) | ||
for b in self.repo.branches(nodes): | ||||
sys.stdout.write(" ".join(map(hex, b)) + "\n") | ||||
|
r131 | |||
|
r132 | elif args['cmd'][0] == 'between': | ||
|
r753 | httphdr("application/mercurial-0.1") | ||
|
r132 | nodes = [] | ||
if args.has_key('pairs'): | ||||
|
r138 | pairs = [ map(bin, p.split("-")) | ||
|
r132 | for p in args['pairs'][0].split(" ") ] | ||
|
r138 | for b in self.repo.between(pairs): | ||
sys.stdout.write(" ".join(map(hex, b)) + "\n") | ||||
|
r132 | |||
elif args['cmd'][0] == 'changegroup': | ||||
|
r751 | httphdr("application/mercurial-0.1") | ||
|
r132 | nodes = [] | ||
|
r964 | if not self.allowpull: | ||
|
r197 | return | ||
|
r132 | if args.has_key('roots'): | ||
|
r138 | nodes = map(bin, args['roots'][0].split(" ")) | ||
|
r131 | |||
|
r132 | z = zlib.compressobj() | ||
|
r635 | f = self.repo.changegroup(nodes) | ||
while 1: | ||||
chunk = f.read(4096) | ||||
if not chunk: break | ||||
|
r132 | sys.stdout.write(z.compress(chunk)) | ||
sys.stdout.write(z.flush()) | ||||
|
r131 | |||
|
r132 | else: | ||
|
r138 | write(self.t("error")) | ||
|
r131 | |||
|
r825 | def create_server(path, name, templates, address, port, use_ipv6 = False, | ||
|
r605 | accesslog = sys.stdout, errorlog = sys.stderr): | ||
|
r158 | |||
|
r938 | def openlog(opt, default): | ||
if opt and opt != '-': | ||||
return open(opt, 'w') | ||||
return default | ||||
u = ui() | ||||
repo = repository(u, path) | ||||
if not address: | ||||
address = u.config("web", "address", "") | ||||
if not port: | ||||
port = int(u.config("web", "port", 8000)) | ||||
if not use_ipv6: | ||||
use_ipv6 = u.configbool("web", "ipv6") | ||||
accesslog = openlog(accesslog or u.config("web", "accesslog", "-"), | ||||
sys.stdout) | ||||
errorlog = openlog(errorlog or u.config("web", "errorlog", "-"), | ||||
sys.stderr) | ||||
|
r158 | import BaseHTTPServer | ||
|
r825 | class IPv6HTTPServer(BaseHTTPServer.HTTPServer): | ||
|
r881 | address_family = getattr(socket, 'AF_INET6', None) | ||
def __init__(self, *args, **kwargs): | ||||
if self.address_family is None: | ||||
raise RepoError('IPv6 not available on this system') | ||||
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: | ||||
if inst.args[0] != 32: 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) | ||||
os.environ.update(env) | ||||
save = sys.argv, sys.stdin, sys.stdout, sys.stderr | ||||
try: | ||||
sys.stdin = self.rfile | ||||
sys.stdout = self.wfile | ||||
sys.argv = ["hgweb.py"] | ||||
if '=' not in query: | ||||
sys.argv.append(query) | ||||
self.send_response(200, "Script output follows") | ||||
hg.run() | ||||
finally: | ||||
sys.argv, sys.stdin, sys.stdout, sys.stderr = save | ||||
hg = hgweb(path, name, templates) | ||||
|
r825 | if use_ipv6: | ||
return IPv6HTTPServer((address, port), hgwebhandler) | ||||
else: | ||||
return BaseHTTPServer.HTTPServer((address, port), hgwebhandler) | ||||
|
r603 | |||
|
r825 | def server(path, name, templates, address, port, use_ipv6 = False, | ||
|
r605 | accesslog = sys.stdout, errorlog = sys.stderr): | ||
|
r825 | httpd = create_server(path, name, templates, address, port, use_ipv6, | ||
|
r605 | accesslog, errorlog) | ||
|
r158 | httpd.serve_forever() | ||
|
r941 | |||
# This is a stopgap | ||||
class hgwebdir: | ||||
def __init__(self, config): | ||||
self.cp = ConfigParser.SafeConfigParser() | ||||
self.cp.read(config) | ||||
def run(self): | ||||
try: | ||||
virtual = os.environ["PATH_INFO"] | ||||
except: | ||||
virtual = "" | ||||
if virtual: | ||||
real = self.cp.get("paths", virtual[1:]) | ||||
|
r956 | h = hgweb(real) | ||
|
r941 | h.run() | ||
return | ||||
def header(**map): | ||||
yield tmpl("header", **map) | ||||
def footer(**map): | ||||
yield tmpl("footer", **map) | ||||
templates = templatepath() | ||||
m = os.path.join(templates, "map") | ||||
tmpl = templater(m, common_filters, | ||||
{"header": header, "footer": footer}) | ||||
def entries(**map): | ||||
parity = 0 | ||||
|
r957 | l = self.cp.items("paths") | ||
l.sort() | ||||
for v,r in l: | ||||
|
r941 | cp2 = ConfigParser.SafeConfigParser() | ||
cp2.read(os.path.join(r, ".hg", "hgrc")) | ||||
def get(sec, val, default): | ||||
try: | ||||
return cp2.get(sec, val) | ||||
except: | ||||
return default | ||||
|
r982 | yield dict(author = get("web", "author", "unknown"), | ||
|
r941 | name = get("web", "name", v), | ||
url = os.environ["REQUEST_URI"] + "/" + v, | ||||
parity = parity, | ||||
shortdesc = get("web", "description", "unknown"), | ||||
lastupdate = os.stat(os.path.join(r, ".hg", | ||||
"00changelog.d")).st_mtime) | ||||
parity = 1 - parity | ||||
write(tmpl("index", entries = entries)) | ||||