sapling/mercurial/hgweb/webcommands.py

1124 lines
37 KiB
Python
Raw Normal View History

#
# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
2010-01-20 07:20:08 +03:00
# GNU General Public License version 2 or any later version.
import os, mimetypes, re, cgi, copy
2008-03-28 21:37:28 +03:00
import webutil
from mercurial import error, encoding, archival, templater, templatefilters
from mercurial.node import short, hex
from mercurial import util
2008-03-28 21:40:44 +03:00
from common import paritygen, staticfile, get_contact, ErrorResponse
from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
from mercurial import graphmod, patch
from mercurial import help as helpmod
from mercurial import scmutil
from mercurial.i18n import _
from mercurial.error import ParseError, RepoLookupError, Abort
from mercurial import revset
__all__ = []
class webcommand(object):
"""Decorator used to register a web command handler.
The decorator takes as its positional arguments the name/path the
command should be accessible under.
Usage:
@webcommand('mycommand')
def mycommand(web, req, tmpl):
pass
"""
def __init__(self, name):
self.name = name
def __call__(self, func):
__all__.append(self.name)
return func
@webcommand('log')
def log(web, req, tmpl):
2008-01-20 16:39:25 +03:00
if 'file' in req.form and req.form['file'][0]:
2008-01-28 17:10:17 +03:00
return filelog(web, req, tmpl)
else:
2008-01-28 17:10:17 +03:00
return changelog(web, req, tmpl)
@webcommand('rawfile')
2008-01-18 21:53:38 +03:00
def rawfile(web, req, tmpl):
guessmime = web.configbool('web', 'guessmime', False)
2008-03-28 21:37:28 +03:00
path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
2008-01-18 21:53:38 +03:00
if not path:
2008-03-28 21:40:44 +03:00
content = manifest(web, req, tmpl)
2008-02-01 12:31:13 +03:00
req.respond(HTTP_OK, web.ctype)
return content
2008-01-18 21:53:38 +03:00
try:
2008-03-28 21:37:28 +03:00
fctx = webutil.filectx(web.repo, req)
except error.LookupError, inst:
2008-03-24 01:08:18 +03:00
try:
2008-03-28 21:40:44 +03:00
content = manifest(web, req, tmpl)
2008-03-24 01:08:18 +03:00
req.respond(HTTP_OK, web.ctype)
return content
except ErrorResponse:
raise inst
2008-01-18 21:53:38 +03:00
path = fctx.path()
text = fctx.data()
mt = 'application/binary'
if guessmime:
mt = mimetypes.guess_type(path)[0]
if mt is None:
mt = util.binary(text) and 'application/binary' or 'text/plain'
if mt.startswith('text/'):
mt += '; charset="%s"' % encoding.encoding
2008-01-18 21:53:38 +03:00
req.respond(HTTP_OK, mt, path, body=text)
return []
2008-01-18 21:53:38 +03:00
2008-03-28 21:40:44 +03:00
def _filerevision(web, tmpl, fctx):
f = fctx.path()
text = fctx.data()
parity = paritygen(web.stripecount)
if util.binary(text):
2008-03-28 21:40:44 +03:00
mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
text = '(binary:%s)' % mt
def lines():
for lineno, t in enumerate(text.splitlines(True)):
2008-03-28 21:40:44 +03:00
yield {"line": t,
"lineid": "l%d" % (lineno + 1),
"linenumber": "% 6d" % (lineno + 1),
"parity": parity.next()}
return tmpl("filerevision",
file=f,
path=webutil.up(f),
text=lines(),
rev=fctx.rev(),
node=fctx.hex(),
2008-03-28 21:40:44 +03:00
author=fctx.user(),
date=fctx.date(),
desc=fctx.description(),
extra=fctx.extra(),
2008-03-28 21:40:44 +03:00
branch=webutil.nodebranchnodefault(fctx),
parent=webutil.parents(fctx),
child=webutil.children(fctx),
rename=webutil.renamelink(fctx),
2008-03-28 21:40:44 +03:00
permissions=fctx.manifest().flags(f))
@webcommand('file')
def file(web, req, tmpl):
2008-03-28 21:37:28 +03:00
path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
if not path:
2008-03-28 21:40:44 +03:00
return manifest(web, req, tmpl)
try:
2008-08-02 16:08:21 +04:00
return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
except error.LookupError, inst:
try:
2008-08-02 16:08:21 +04:00
return manifest(web, req, tmpl)
except ErrorResponse:
raise inst
def _search(web, req, tmpl):
MODE_REVISION = 'rev'
MODE_KEYWORD = 'keyword'
MODE_REVSET = 'revset'
def revsearch(ctx):
yield ctx
def keywordsearch(query):
lower = encoding.lower
qw = lower(query).split()
2008-03-28 21:40:44 +03:00
def revgen():
cl = web.repo.changelog
for i in xrange(len(web.repo) - 1, 0, -100):
2008-03-28 21:40:44 +03:00
l = []
for j in cl.revs(max(0, i - 99), i):
2008-06-26 23:35:46 +04:00
ctx = web.repo[j]
2008-03-28 21:40:44 +03:00
l.append(ctx)
l.reverse()
for e in l:
yield e
for ctx in revgen():
miss = 0
for q in qw:
if not (q in lower(ctx.user()) or
q in lower(ctx.description()) or
q in lower(" ".join(ctx.files()))):
2008-03-28 21:40:44 +03:00
miss = 1
break
if miss:
continue
yield ctx
def revsetsearch(revs):
for r in revs:
yield web.repo[r]
searchfuncs = {
MODE_REVISION: (revsearch, 'exact revision search'),
MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
MODE_REVSET: (revsetsearch, 'revset expression search'),
}
def getsearchmode(query):
try:
ctx = web.repo[query]
except (error.RepoError, error.LookupError):
# query is not an exact revision pointer, need to
2013-10-23 21:49:56 +04:00
# decide if it's a revset expression or keywords
pass
else:
return MODE_REVISION, ctx
revdef = 'reverse(%s)' % query
try:
tree, pos = revset.parse(revdef)
except ParseError:
# can't parse to a revset tree
return MODE_KEYWORD, query
if revset.depth(tree) <= 2:
# no revset syntax used
return MODE_KEYWORD, query
if util.any((token, (value or '')[:3]) == ('string', 're:')
for token, value, pos in revset.tokenize(revdef)):
return MODE_KEYWORD, query
funcsused = revset.funcsused(tree)
if not funcsused.issubset(revset.safesymbols):
return MODE_KEYWORD, query
mfunc = revset.match(web.repo.ui, revdef)
try:
revs = mfunc(web.repo, revset.spanset(web.repo))
return MODE_REVSET, revs
# ParseError: wrongly placed tokens, wrongs arguments, etc
# RepoLookupError: no such revision, e.g. in 'revision:'
# Abort: bookmark/tag not exists
# LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
except (ParseError, RepoLookupError, Abort, LookupError):
return MODE_KEYWORD, query
def changelist(**map):
count = 0
for ctx in searchfunc[0](funcarg):
count += 1
2008-03-28 21:40:44 +03:00
n = ctx.node()
showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
2008-03-28 21:40:44 +03:00
yield tmpl('searchentry',
parity=parity.next(),
author=ctx.user(),
parent=webutil.parents(ctx),
child=webutil.children(ctx),
2008-03-28 21:40:44 +03:00
changelogtag=showtags,
desc=ctx.description(),
extra=ctx.extra(),
2008-03-28 21:40:44 +03:00
date=ctx.date(),
files=files,
2008-03-28 21:40:44 +03:00
rev=ctx.rev(),
node=hex(n),
tags=webutil.nodetagsdict(web.repo, n),
bookmarks=webutil.nodebookmarksdict(web.repo, n),
2008-03-28 21:40:44 +03:00
inbranch=webutil.nodeinbranch(web.repo, ctx),
branches=webutil.nodebranchdict(web.repo, ctx))
if count >= revcount:
2008-03-28 21:40:44 +03:00
break
query = req.form['rev'][0]
revcount = web.maxchanges
if 'revcount' in req.form:
try:
revcount = int(req.form.get('revcount', [revcount])[0])
revcount = max(revcount, 1)
tmpl.defaults['sessionvars']['revcount'] = revcount
except ValueError:
pass
lessvars = copy.copy(tmpl.defaults['sessionvars'])
lessvars['revcount'] = max(revcount / 2, 1)
lessvars['rev'] = query
morevars = copy.copy(tmpl.defaults['sessionvars'])
morevars['revcount'] = revcount * 2
morevars['rev'] = query
mode, funcarg = getsearchmode(query)
if 'forcekw' in req.form:
showforcekw = ''
showunforcekw = searchfuncs[mode][1]
mode = MODE_KEYWORD
funcarg = query
else:
if mode != MODE_KEYWORD:
showforcekw = searchfuncs[MODE_KEYWORD][1]
else:
showforcekw = ''
showunforcekw = ''
searchfunc = searchfuncs[mode]
tip = web.repo['tip']
2008-03-28 21:40:44 +03:00
parity = paritygen(web.stripecount)
return tmpl('search', query=query, node=tip.hex(),
entries=changelist, archives=web.archivelist("tip"),
morevars=morevars, lessvars=lessvars,
modedesc=searchfunc[1],
showforcekw=showforcekw, showunforcekw=showunforcekw)
@webcommand('changelog')
def changelog(web, req, tmpl, shortlog=False):
2008-03-28 21:40:44 +03:00
query = ''
2008-01-20 16:39:25 +03:00
if 'node' in req.form:
2008-03-28 21:37:28 +03:00
ctx = webutil.changectx(web.repo, req)
elif 'rev' in req.form:
return _search(web, req, tmpl)
else:
ctx = web.repo['tip']
2008-03-28 21:40:44 +03:00
def changelist():
revs = []
if pos != -1:
revs = web.repo.changelog.revs(pos, 0)
curcount = 0
for rev in revs:
curcount += 1
if curcount > revcount + 1:
break
entry = webutil.changelistentry(web, web.repo[rev], tmpl)
entry['parity'] = parity.next()
yield entry
2008-03-28 21:40:44 +03:00
revcount = shortlog and web.maxshortchanges or web.maxchanges
if 'revcount' in req.form:
try:
revcount = int(req.form.get('revcount', [revcount])[0])
revcount = max(revcount, 1)
tmpl.defaults['sessionvars']['revcount'] = revcount
except ValueError:
pass
lessvars = copy.copy(tmpl.defaults['sessionvars'])
lessvars['revcount'] = max(revcount / 2, 1)
morevars = copy.copy(tmpl.defaults['sessionvars'])
morevars['revcount'] = revcount * 2
count = len(web.repo)
2008-03-28 21:40:44 +03:00
pos = ctx.rev()
parity = paritygen(web.stripecount)
2008-03-28 21:40:44 +03:00
changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
2008-03-28 21:40:44 +03:00
entries = list(changelist())
latestentry = entries[:1]
if len(entries) > revcount:
nextentry = entries[-1:]
entries = entries[:-1]
else:
nextentry = []
return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
node=ctx.hex(), rev=pos, changesets=count,
entries=entries,
latestentry=latestentry, nextentry=nextentry,
archives=web.archivelist("tip"), revcount=revcount,
morevars=morevars, lessvars=lessvars, query=query)
@webcommand('shortlog')
def shortlog(web, req, tmpl):
return changelog(web, req, tmpl, shortlog=True)
@webcommand('changeset')
def changeset(web, req, tmpl):
2008-03-28 21:40:44 +03:00
ctx = webutil.changectx(web.repo, req)
basectx = webutil.basechangectx(web.repo, req)
if basectx is None:
basectx = ctx.p1()
showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
ctx.node())
showbranch = webutil.nodebranchnodefault(ctx)
2008-03-28 21:40:44 +03:00
files = []
parity = paritygen(web.stripecount)
for blockno, f in enumerate(ctx.files()):
template = f in ctx and 'filenodelink' or 'filenolink'
files.append(tmpl(template,
node=ctx.hex(), file=f, blockno=blockno + 1,
2008-03-28 21:40:44 +03:00
parity=parity.next()))
style = web.config('web', 'style', 'paper')
if 'style' in req.form:
style = req.form['style'][0]
parity = paritygen(web.stripecount)
diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
parity = paritygen(web.stripecount)
diffstatgen = webutil.diffstatgen(ctx, basectx)
diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
2008-03-28 21:40:44 +03:00
return tmpl('changeset',
diff=diffs,
rev=ctx.rev(),
node=ctx.hex(),
parent=tuple(webutil.parents(ctx)),
child=webutil.children(ctx),
basenode=basectx.hex(),
2008-03-28 21:40:44 +03:00
changesettag=showtags,
changesetbookmark=showbookmarks,
changesetbranch=showbranch,
2008-03-28 21:40:44 +03:00
author=ctx.user(),
desc=ctx.description(),
extra=ctx.extra(),
2008-03-28 21:40:44 +03:00
date=ctx.date(),
files=files,
diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
diffstat=diffstat,
archives=web.archivelist(ctx.hex()),
tags=webutil.nodetagsdict(web.repo, ctx.node()),
bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
2008-03-28 21:40:44 +03:00
branch=webutil.nodebranchnodefault(ctx),
inbranch=webutil.nodeinbranch(web.repo, ctx),
branches=webutil.nodebranchdict(web.repo, ctx))
rev = webcommand('rev')(changeset)
def decodepath(path):
"""Hook for mapping a path in the repository to a path in the
working copy.
Extensions (e.g., largefiles) can override this to remap files in
the virtual file system presented by the manifest command below."""
return path
@webcommand('manifest')
def manifest(web, req, tmpl):
2008-03-28 21:40:44 +03:00
ctx = webutil.changectx(web.repo, req)
path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
mf = ctx.manifest()
node = ctx.node()
files = {}
dirs = {}
2008-03-28 21:40:44 +03:00
parity = paritygen(web.stripecount)
if path and path[-1] != "/":
path += "/"
l = len(path)
abspath = "/" + path
for full, n in mf.iteritems():
# the virtual path (working copy path) used for the full
# (repository) path
f = decodepath(full)
2008-03-28 21:40:44 +03:00
if f[:l] != path:
continue
remain = f[l:]
elements = remain.split('/')
if len(elements) == 1:
files[remain] = full
else:
h = dirs # need to retain ref to dirs (root)
for elem in elements[0:-1]:
if elem not in h:
h[elem] = {}
h = h[elem]
if len(h) > 1:
break
h[None] = None # denotes files present
2008-03-28 21:40:44 +03:00
if mf and not files and not dirs:
2008-03-28 21:40:44 +03:00
raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
def filelist(**map):
for f in sorted(files):
full = files[f]
2008-03-28 21:40:44 +03:00
fctx = ctx.filectx(full)
yield {"file": full,
"parity": parity.next(),
"basename": f,
2008-06-26 23:35:46 +04:00
"date": fctx.date(),
2008-03-28 21:40:44 +03:00
"size": fctx.size(),
"permissions": mf.flags(full)}
def dirlist(**map):
for d in sorted(dirs):
emptydirs = []
h = dirs[d]
while isinstance(h, dict) and len(h) == 1:
2010-01-25 09:05:27 +03:00
k, v = h.items()[0]
if v:
emptydirs.append(k)
h = v
2008-03-28 21:40:44 +03:00
path = "%s%s" % (abspath, d)
2008-03-28 21:40:44 +03:00
yield {"parity": parity.next(),
"path": path,
"emptydirs": "/".join(emptydirs),
"basename": d}
2008-03-28 21:40:44 +03:00
return tmpl("manifest",
rev=ctx.rev(),
node=hex(node),
path=abspath,
up=webutil.up(abspath),
upparity=parity.next(),
fentries=filelist,
dentries=dirlist,
archives=web.archivelist(hex(node)),
tags=webutil.nodetagsdict(web.repo, node),
bookmarks=webutil.nodebookmarksdict(web.repo, node),
2008-03-28 21:40:44 +03:00
inbranch=webutil.nodeinbranch(web.repo, ctx),
branches=webutil.nodebranchdict(web.repo, ctx))
@webcommand('tags')
def tags(web, req, tmpl):
i = list(reversed(web.repo.tagslist()))
2008-03-28 21:40:44 +03:00
parity = paritygen(web.stripecount)
def entries(notip, latestonly, **map):
t = i
if notip:
t = [(k, n) for k, n in i if k != "tip"]
if latestonly:
t = t[:1]
for k, n in t:
2008-03-28 21:40:44 +03:00
yield {"parity": parity.next(),
"tag": k,
2008-06-26 23:35:46 +04:00
"date": web.repo[n].date(),
2008-03-28 21:40:44 +03:00
"node": hex(n)}
return tmpl("tags",
node=hex(web.repo.changelog.tip()),
entries=lambda **x: entries(False, False, **x),
entriesnotip=lambda **x: entries(True, False, **x),
latestentry=lambda **x: entries(True, True, **x))
@webcommand('bookmarks')
def bookmarks(web, req, tmpl):
i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
parity = paritygen(web.stripecount)
def entries(latestonly, **map):
if latestonly:
t = [min(i)]
else:
t = sorted(i)
for k, n in t:
yield {"parity": parity.next(),
"bookmark": k,
"date": web.repo[n].date(),
"node": hex(n)}
return tmpl("bookmarks",
node=hex(web.repo.changelog.tip()),
entries=lambda **x: entries(latestonly=False, **x),
latestentry=lambda **x: entries(latestonly=True, **x))
@webcommand('branches')
def branches(web, req, tmpl):
tips = []
heads = web.repo.heads()
parity = paritygen(web.stripecount)
sortkey = lambda item: (not item[1], item[0].rev())
def entries(limit, **map):
count = 0
if not tips:
for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
tips.append((web.repo[tip], closed))
for ctx, closed in sorted(tips, key=sortkey, reverse=True):
if limit > 0 and count >= limit:
return
count += 1
if closed:
status = 'closed'
elif ctx.node() not in heads:
status = 'inactive'
else:
status = 'open'
yield {'parity': parity.next(),
'branch': ctx.branch(),
'status': status,
'node': ctx.hex(),
'date': ctx.date()}
return tmpl('branches', node=hex(web.repo.changelog.tip()),
entries=lambda **x: entries(0, **x),
latestentry=lambda **x: entries(1, **x))
@webcommand('summary')
def summary(web, req, tmpl):
i = reversed(web.repo.tagslist())
2008-03-28 21:40:44 +03:00
def tagentries(**map):
parity = paritygen(web.stripecount)
count = 0
for k, n in i:
if k == "tip": # skip tip
continue
count += 1
2008-03-28 21:40:44 +03:00
if count > 10: # limit to 10 tags
break
yield tmpl("tagentry",
parity=parity.next(),
tag=k,
node=hex(n),
2008-06-26 23:35:46 +04:00
date=web.repo[n].date())
2008-03-28 21:40:44 +03:00
def bookmarks(**map):
parity = paritygen(web.stripecount)
marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
yield {'parity': parity.next(),
'bookmark': k,
'date': web.repo[n].date(),
'node': hex(n)}
2008-03-28 21:40:44 +03:00
def branches(**map):
parity = paritygen(web.stripecount)
b = web.repo.branchmap()
l = [(-web.repo.changelog.rev(tip), tip, tag)
for tag, heads, tip, closed in b.iterbranches()]
2010-01-25 09:05:27 +03:00
for r, n, t in sorted(l):
2008-03-28 21:40:44 +03:00
yield {'parity': parity.next(),
'branch': t,
'node': hex(n),
2008-06-26 23:35:46 +04:00
'date': web.repo[n].date()}
2008-03-28 21:40:44 +03:00
def changelist(**map):
2010-01-25 09:05:27 +03:00
parity = paritygen(web.stripecount, offset=start - end)
2008-03-28 21:40:44 +03:00
l = [] # build a list in forward order for efficiency
revs = []
if start < end:
revs = web.repo.changelog.revs(start, end - 1)
for i in revs:
2008-06-26 23:35:46 +04:00
ctx = web.repo[i]
2008-03-28 21:40:44 +03:00
n = ctx.node()
hn = hex(n)
l.append(tmpl(
2008-03-28 21:40:44 +03:00
'shortlogentry',
parity=parity.next(),
author=ctx.user(),
desc=ctx.description(),
extra=ctx.extra(),
2008-03-28 21:40:44 +03:00
date=ctx.date(),
rev=i,
node=hn,
tags=webutil.nodetagsdict(web.repo, n),
bookmarks=webutil.nodebookmarksdict(web.repo, n),
2008-03-28 21:40:44 +03:00
inbranch=webutil.nodeinbranch(web.repo, ctx),
branches=webutil.nodebranchdict(web.repo, ctx)))
l.reverse()
2008-03-28 21:40:44 +03:00
yield l
tip = web.repo['tip']
count = len(web.repo)
2008-03-28 21:40:44 +03:00
start = max(0, count - web.maxchanges)
end = min(count, start + web.maxchanges)
return tmpl("summary",
desc=web.config("web", "description", "unknown"),
owner=get_contact(web.config) or "unknown",
lastchange=tip.date(),
2008-03-28 21:40:44 +03:00
tags=tagentries,
bookmarks=bookmarks,
2008-03-28 21:40:44 +03:00
branches=branches,
shortlog=changelist,
node=tip.hex(),
2008-03-28 21:40:44 +03:00
archives=web.archivelist("tip"))
@webcommand('filediff')
def filediff(web, req, tmpl):
2008-10-20 16:13:37 +04:00
fctx, ctx = None, None
try:
fctx = webutil.filectx(web.repo, req)
2008-10-28 21:25:26 +03:00
except LookupError:
2008-10-20 16:13:37 +04:00
ctx = webutil.changectx(web.repo, req)
path = webutil.cleanpath(web.repo, req.form['file'][0])
if path not in ctx.files():
raise
if fctx is not None:
n = fctx.node()
path = fctx.path()
ctx = fctx.changectx()
2008-10-20 16:13:37 +04:00
else:
n = ctx.node()
# path already defined in except clause
2008-03-28 21:40:44 +03:00
parity = paritygen(web.stripecount)
style = web.config('web', 'style', 'paper')
if 'style' in req.form:
style = req.form['style'][0]
diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
2008-10-20 16:13:37 +04:00
rename = fctx and webutil.renamelink(fctx) or []
ctx = fctx and fctx or ctx
2008-03-28 21:40:44 +03:00
return tmpl("filediff",
file=path,
node=hex(n),
2008-10-20 16:13:37 +04:00
rev=ctx.rev(),
date=ctx.date(),
desc=ctx.description(),
extra=ctx.extra(),
2008-10-20 16:13:37 +04:00
author=ctx.user(),
rename=rename,
branch=webutil.nodebranchnodefault(ctx),
parent=webutil.parents(ctx),
child=webutil.children(ctx),
2008-03-28 21:40:44 +03:00
diff=diffs)
diff = webcommand('diff')(filediff)
@webcommand('comparison')
def comparison(web, req, tmpl):
ctx = webutil.changectx(web.repo, req)
if 'file' not in req.form:
raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
path = webutil.cleanpath(web.repo, req.form['file'][0])
rename = path in ctx and webutil.renamelink(ctx[path]) or []
parsecontext = lambda v: v == 'full' and -1 or int(v)
if 'context' in req.form:
context = parsecontext(req.form['context'][0])
else:
context = parsecontext(web.config('web', 'comparisoncontext', '5'))
def filelines(f):
if util.binary(f.data()):
mt = mimetypes.guess_type(f.path())[0]
if not mt:
mt = 'application/octet-stream'
return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
return f.data().splitlines()
parent = ctx.p1()
leftrev = parent.rev()
leftnode = parent.node()
rightrev = ctx.rev()
rightnode = ctx.node()
if path in ctx:
fctx = ctx[path]
rightlines = filelines(fctx)
if path not in parent:
leftlines = ()
else:
pfctx = parent[path]
leftlines = filelines(pfctx)
else:
rightlines = ()
fctx = ctx.parents()[0][path]
leftlines = filelines(fctx)
comparison = webutil.compare(tmpl, context, leftlines, rightlines)
return tmpl('filecomparison',
file=path,
node=hex(ctx.node()),
rev=ctx.rev(),
date=ctx.date(),
desc=ctx.description(),
extra=ctx.extra(),
author=ctx.user(),
rename=rename,
branch=webutil.nodebranchnodefault(ctx),
parent=webutil.parents(fctx),
child=webutil.children(fctx),
leftrev=leftrev,
leftnode=hex(leftnode),
rightrev=rightrev,
rightnode=hex(rightnode),
comparison=comparison)
@webcommand('annotate')
def annotate(web, req, tmpl):
2008-03-28 21:40:44 +03:00
fctx = webutil.filectx(web.repo, req)
f = fctx.path()
parity = paritygen(web.stripecount)
diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
section='annotate', whitespace=True)
2008-03-28 21:40:44 +03:00
def annotate(**map):
last = None
if util.binary(fctx.data()):
2008-03-28 21:40:44 +03:00
mt = (mimetypes.guess_type(fctx.path())[0]
or 'application/octet-stream')
lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
'(binary:%s)' % mt)])
else:
lines = enumerate(fctx.annotate(follow=True, linenumber=True,
diffopts=diffopts))
2008-03-28 21:40:44 +03:00
for lineno, ((f, targetline), l) in lines:
fnode = f.filenode()
if last != fnode:
last = fnode
yield {"parity": parity.next(),
"node": f.hex(),
2008-03-28 21:40:44 +03:00
"rev": f.rev(),
"author": f.user(),
"desc": f.description(),
"extra": f.extra(),
2008-03-28 21:40:44 +03:00
"file": f.path(),
"targetline": targetline,
"line": l,
"lineid": "l%d" % (lineno + 1),
"linenumber": "% 6d" % (lineno + 1),
"revdate": f.date()}
2008-03-28 21:40:44 +03:00
return tmpl("fileannotate",
file=f,
annotate=annotate,
path=webutil.up(f),
rev=fctx.rev(),
node=fctx.hex(),
2008-03-28 21:40:44 +03:00
author=fctx.user(),
date=fctx.date(),
desc=fctx.description(),
extra=fctx.extra(),
rename=webutil.renamelink(fctx),
2008-03-28 21:40:44 +03:00
branch=webutil.nodebranchnodefault(fctx),
parent=webutil.parents(fctx),
child=webutil.children(fctx),
2008-03-28 21:40:44 +03:00
permissions=fctx.manifest().flags(f))
@webcommand('filelog')
def filelog(web, req, tmpl):
try:
fctx = webutil.filectx(web.repo, req)
f = fctx.path()
fl = fctx.filelog()
except error.LookupError:
f = webutil.cleanpath(web.repo, req.form['file'][0])
fl = web.repo.file(f)
numrevs = len(fl)
if not numrevs: # file doesn't exist at all
raise
rev = webutil.changectx(web.repo, req).rev()
first = fl.linkrev(0)
if rev < first: # current rev is from before file existed
raise
frev = numrevs - 1
while fl.linkrev(frev) > rev:
frev -= 1
fctx = web.repo.filectx(f, fl.linkrev(frev))
revcount = web.maxshortchanges
if 'revcount' in req.form:
try:
revcount = int(req.form.get('revcount', [revcount])[0])
revcount = max(revcount, 1)
tmpl.defaults['sessionvars']['revcount'] = revcount
except ValueError:
pass
lessvars = copy.copy(tmpl.defaults['sessionvars'])
lessvars['revcount'] = max(revcount / 2, 1)
morevars = copy.copy(tmpl.defaults['sessionvars'])
morevars['revcount'] = revcount * 2
count = fctx.filerev() + 1
start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
end = min(count, start + revcount) # last rev on this page
2010-01-25 09:05:27 +03:00
parity = paritygen(web.stripecount, offset=start - end)
2008-03-28 21:40:44 +03:00
def entries():
2008-03-28 21:40:44 +03:00
l = []
repo = web.repo
revs = fctx.filelog().revs(start, end - 1)
for i in revs:
iterfctx = fctx.filectx(i)
2008-03-28 21:40:44 +03:00
l.append({"parity": parity.next(),
"filerev": i,
"file": f,
"node": iterfctx.hex(),
"author": iterfctx.user(),
"date": iterfctx.date(),
"rename": webutil.renamelink(iterfctx),
"parent": webutil.parents(iterfctx),
"child": webutil.children(iterfctx),
"desc": iterfctx.description(),
"extra": iterfctx.extra(),
"tags": webutil.nodetagsdict(repo, iterfctx.node()),
"bookmarks": webutil.nodebookmarksdict(
repo, iterfctx.node()),
"branch": webutil.nodebranchnodefault(iterfctx),
"inbranch": webutil.nodeinbranch(repo, iterfctx),
"branches": webutil.nodebranchdict(repo, iterfctx)})
for e in reversed(l):
2008-03-28 21:40:44 +03:00
yield e
entries = list(entries())
latestentry = entries[:1]
revnav = webutil.filerevnav(web.repo, fctx.path())
nav = revnav.gen(end - 1, revcount, count)
return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
entries=entries,
latestentry=latestentry,
revcount=revcount, morevars=morevars, lessvars=lessvars)
@webcommand('archive')
def archive(web, req, tmpl):
type_ = req.form.get('type', [None])[0]
allowed = web.configlist("web", "allow_archive")
2008-03-28 21:40:44 +03:00
key = req.form['node'][0]
if type_ not in web.archives:
2008-03-28 21:40:44 +03:00
msg = 'Unsupported archive type: %s' % type_
raise ErrorResponse(HTTP_NOT_FOUND, msg)
if not ((type_ in allowed or
web.configbool("web", "allow" + type_, False))):
msg = 'Archive type not allowed: %s' % type_
raise ErrorResponse(HTTP_FORBIDDEN, msg)
2008-03-28 21:40:44 +03:00
reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
cnode = web.repo.lookup(key)
arch_version = key
if cnode == key or key == 'tip':
arch_version = short(cnode)
name = "%s-%s" % (reponame, arch_version)
ctx = webutil.changectx(web.repo, req)
pats = []
matchfn = scmutil.match(ctx, [])
file = req.form.get('file', None)
if file:
pats = ['path:' + file[0]]
matchfn = scmutil.match(ctx, pats, default='path')
if pats:
files = [f for f in ctx.manifest().keys() if matchfn(f)]
if not files:
raise ErrorResponse(HTTP_NOT_FOUND,
'file(s) not found: %s' % file[0])
2008-03-28 21:40:44 +03:00
mimetype, artype, extension, encoding = web.archive_specs[type_]
headers = [
('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
]
2008-03-28 21:40:44 +03:00
if encoding:
headers.append(('Content-Encoding', encoding))
req.headers.extend(headers)
req.respond(HTTP_OK, mimetype)
archival.archive(web.repo, req, cnode, artype, prefix=name,
matchfn=matchfn,
subrepos=web.configbool("web", "archivesubrepos"))
2008-03-28 21:40:44 +03:00
return []
@webcommand('static')
def static(web, req, tmpl):
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 = web.config("web", "static", None, untrusted=False)
if not static:
tp = web.templatepath or templater.templatepaths()
if isinstance(tp, str):
tp = [tp]
static = [os.path.join(p, 'static') for p in tp]
staticfile(static, fname, req)
return []
2008-06-18 09:06:41 +04:00
@webcommand('graph')
2008-06-18 09:06:41 +04:00
def graph(web, req, tmpl):
ctx = webutil.changectx(web.repo, req)
rev = ctx.rev()
2008-06-18 09:06:41 +04:00
bg_height = 39
revcount = web.maxshortchanges
if 'revcount' in req.form:
try:
revcount = int(req.form.get('revcount', [revcount])[0])
revcount = max(revcount, 1)
tmpl.defaults['sessionvars']['revcount'] = revcount
except ValueError:
pass
lessvars = copy.copy(tmpl.defaults['sessionvars'])
lessvars['revcount'] = max(revcount / 2, 1)
morevars = copy.copy(tmpl.defaults['sessionvars'])
morevars['revcount'] = revcount * 2
count = len(web.repo)
pos = rev
uprev = min(max(0, count - 1), rev + revcount)
downrev = max(0, rev - revcount)
changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
2008-06-18 09:06:41 +04:00
tree = []
if pos != -1:
allrevs = web.repo.changelog.revs(pos, 0)
revs = []
for i in allrevs:
revs.append(i)
if len(revs) >= revcount:
break
# We have to feed a baseset to dagwalker as it is expecting smartset
# object. This does not have a big impact on hgweb performance itself
# since hgweb graphing code is not itself lazy yet.
dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
# As we said one line above... not lazy.
tree = list(graphmod.colored(dag, web.repo))
def getcolumns(tree):
cols = 0
for (id, type, ctx, vtx, edges) in tree:
if type != graphmod.CHANGESET:
continue
cols = max(cols, max([edge[0] for edge in edges] or [0]),
max([edge[1] for edge in edges] or [0]))
return cols
def graphdata(usetuples, **map):
data = []
row = 0
for (id, type, ctx, vtx, edges) in tree:
if type != graphmod.CHANGESET:
continue
node = str(ctx)
age = templatefilters.age(ctx.date())
desc = templatefilters.firstline(ctx.description())
desc = cgi.escape(templatefilters.nonempty(desc))
user = cgi.escape(templatefilters.person(ctx.user()))
branch = cgi.escape(ctx.branch())
try:
branchnode = web.repo.branchtip(branch)
except error.RepoLookupError:
branchnode = None
branch = branch, branchnode == ctx.node()
if usetuples:
data.append((node, vtx, edges, desc, user, age, branch,
[cgi.escape(x) for x in ctx.tags()],
[cgi.escape(x) for x in ctx.bookmarks()]))
else:
edgedata = [{'col': edge[0], 'nextcol': edge[1],
'color': (edge[2] - 1) % 6 + 1,
'width': edge[3], 'bcolor': edge[4]}
for edge in edges]
data.append(
{'node': node,
'col': vtx[0],
'color': (vtx[1] - 1) % 6 + 1,
'edges': edgedata,
'row': row,
'nextrow': row + 1,
'desc': desc,
'user': user,
'age': age,
'bookmarks': webutil.nodebookmarksdict(
web.repo, ctx.node()),
'branches': webutil.nodebranchdict(web.repo, ctx),
'inbranch': webutil.nodeinbranch(web.repo, ctx),
'tags': webutil.nodetagsdict(web.repo, ctx.node())})
row += 1
return data
cols = getcolumns(tree)
rows = len(tree)
canvasheight = (rows + 1) * bg_height - 27
2008-06-18 09:06:41 +04:00
return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
lessvars=lessvars, morevars=morevars, downrev=downrev,
cols=cols, rows=rows,
canvaswidth=(cols + 1) * bg_height,
truecanvasheight=rows * bg_height,
canvasheight=canvasheight, bg_height=bg_height,
jsdata=lambda **x: graphdata(True, **x),
nodes=lambda **x: graphdata(False, **x),
node=ctx.hex(), changenav=changenav)
def _getdoc(e):
doc = e[0].__doc__
if doc:
doc = _(doc).split('\n')[0]
else:
doc = _('(no help text available)')
return doc
@webcommand('help')
def help(web, req, tmpl):
from mercurial import commands # avoid cycle
topicname = req.form.get('node', [None])[0]
if not topicname:
def topics(**map):
for entries, summary, _doc in helpmod.helptable:
yield {'topic': entries[0], 'summary': summary}
early, other = [], []
primary = lambda s: s.split('|')[0]
for c, e in commands.table.iteritems():
doc = _getdoc(e)
if 'DEPRECATED' in doc or c.startswith('debug'):
continue
cmd = primary(c)
if cmd.startswith('^'):
early.append((cmd[1:], doc))
else:
other.append((cmd, doc))
early.sort()
other.sort()
def earlycommands(**map):
for c, doc in early:
yield {'topic': c, 'summary': doc}
def othercommands(**map):
for c, doc in other:
yield {'topic': c, 'summary': doc}
return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
othercommands=othercommands, title='Index')
u = webutil.wsgiui()
u.verbose = True
try:
doc = helpmod.help_(u, topicname)
except error.UnknownCommand:
raise ErrorResponse(HTTP_NOT_FOUND)
return tmpl('help', topic=topicname, doc=doc)