webutil.py
350 lines
| 11.0 KiB
| text/x-python
|
PythonLexer
|
r6392 | # hgweb/webutil.py - utility library for the web interface. | ||
# | ||||
# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net> | ||||
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
|
r8225 | # This software may be used and distributed according to the terms of the | ||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r6392 | |||
|
r17302 | import os, copy | ||
|
r14490 | from mercurial import match, patch, scmutil, error, ui, util | ||
|
r14570 | from mercurial.i18n import _ | ||
|
r6392 | from mercurial.node import hex, nullid | ||
|
r17289 | from common import ErrorResponse | ||
from common import HTTP_NOT_FOUND | ||||
|
r17202 | import difflib | ||
|
r6392 | |||
|
r6393 | def up(p): | ||
if p[0] != "/": | ||||
p = "/" + p | ||||
if p[-1] == "/": | ||||
p = p[:-1] | ||||
up = os.path.dirname(p) | ||||
if up == "/": | ||||
return "/" | ||||
return up + "/" | ||||
def revnavgen(pos, pagelen, limit, nodefunc): | ||||
def seq(factor, limit=None): | ||||
if limit: | ||||
yield limit | ||||
if limit >= 20 and limit <= 40: | ||||
yield 50 | ||||
else: | ||||
yield 1 * factor | ||||
yield 3 * factor | ||||
for f in seq(factor * 10): | ||||
yield f | ||||
|
r10254 | navbefore = [] | ||
navafter = [] | ||||
|
r6393 | |||
|
r10254 | last = 0 | ||
for f in seq(1, pagelen): | ||||
if f < pagelen or f <= last: | ||||
continue | ||||
if f > limit: | ||||
break | ||||
last = f | ||||
if pos + f < limit: | ||||
navafter.append(("+%d" % f, hex(nodefunc(pos + f).node()))) | ||||
if pos - f >= 0: | ||||
navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node()))) | ||||
|
r6393 | |||
|
r10254 | navafter.append(("tip", "tip")) | ||
try: | ||||
navbefore.insert(0, ("(0)", hex(nodefunc('0').node()))) | ||||
except error.RepoError: | ||||
pass | ||||
def gen(l): | ||||
def f(**map): | ||||
|
r6393 | for label, node in l: | ||
yield {"label": label, "node": node} | ||||
|
r10254 | return f | ||
|
r6393 | |||
|
r10282 | return (dict(before=gen(navbefore), after=gen(navafter)),) | ||
|
r6393 | |||
|
r7671 | def _siblings(siblings=[], hiderev=None): | ||
|
r6392 | siblings = [s for s in siblings if s.node() != nullid] | ||
if len(siblings) == 1 and siblings[0].rev() == hiderev: | ||||
return | ||||
for s in siblings: | ||||
|
r14055 | d = {'node': s.hex(), 'rev': s.rev()} | ||
|
r7294 | d['user'] = s.user() | ||
d['date'] = s.date() | ||||
d['description'] = s.description() | ||||
|
r7717 | d['branch'] = s.branch() | ||
|
r14957 | if util.safehasattr(s, 'path'): | ||
|
r6392 | d['file'] = s.path() | ||
yield d | ||||
|
r7671 | def parents(ctx, hide=None): | ||
return _siblings(ctx.parents(), hide) | ||||
def children(ctx, hide=None): | ||||
return _siblings(ctx.children(), hide) | ||||
|
r6434 | def renamelink(fctx): | ||
|
r6437 | r = fctx.renamed() | ||
|
r6392 | if r: | ||
return [dict(file=r[0], node=hex(r[1]))] | ||||
return [] | ||||
def nodetagsdict(repo, node): | ||||
return [{"name": i} for i in repo.nodetags(node)] | ||||
|
r13596 | def nodebookmarksdict(repo, node): | ||
return [{"name": i} for i in repo.nodebookmarks(node)] | ||||
|
r6392 | def nodebranchdict(repo, ctx): | ||
branches = [] | ||||
branch = ctx.branch() | ||||
# If this is an empty repo, ctx.node() == nullid, | ||||
|
r16719 | # ctx.branch() == 'default'. | ||
try: | ||||
branchnode = repo.branchtip(branch) | ||||
except error.RepoLookupError: | ||||
branchnode = None | ||||
if branchnode == ctx.node(): | ||||
|
r6392 | branches.append({"name": branch}) | ||
return branches | ||||
def nodeinbranch(repo, ctx): | ||||
branches = [] | ||||
branch = ctx.branch() | ||||
|
r16719 | try: | ||
branchnode = repo.branchtip(branch) | ||||
except error.RepoLookupError: | ||||
branchnode = None | ||||
if branch != 'default' and branchnode != ctx.node(): | ||||
|
r6392 | branches.append({"name": branch}) | ||
return branches | ||||
def nodebranchnodefault(ctx): | ||||
branches = [] | ||||
branch = ctx.branch() | ||||
if branch != 'default': | ||||
branches.append({"name": branch}) | ||||
return branches | ||||
def showtag(repo, tmpl, t1, node=nullid, **args): | ||||
for t in repo.nodetags(node): | ||||
yield tmpl(t1, tag=t, **args) | ||||
|
r13596 | def showbookmark(repo, tmpl, t1, node=nullid, **args): | ||
for t in repo.nodebookmarks(node): | ||||
yield tmpl(t1, bookmark=t, **args) | ||||
|
r6392 | def cleanpath(repo, path): | ||
path = path.lstrip('/') | ||||
|
r13971 | return scmutil.canonpath(repo.root, '', path) | ||
|
r6392 | |||
|
r17991 | def changeidctx (repo, changeid): | ||
|
r6392 | try: | ||
|
r6747 | ctx = repo[changeid] | ||
|
r7637 | except error.RepoError: | ||
|
r6392 | man = repo.manifest | ||
|
r7361 | ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))] | ||
|
r6392 | |||
return ctx | ||||
|
r17991 | def changectx (repo, req): | ||
changeid = "tip" | ||||
if 'node' in req.form: | ||||
changeid = req.form['node'][0] | ||||
ipos=changeid.find(':') | ||||
if ipos != -1: | ||||
changeid = changeid[(ipos + 1):] | ||||
elif 'manifest' in req.form: | ||||
changeid = req.form['manifest'][0] | ||||
return changeidctx(repo, changeid) | ||||
def basechangectx(repo, req): | ||||
if 'node' in req.form: | ||||
changeid = req.form['node'][0] | ||||
ipos=changeid.find(':') | ||||
if ipos != -1: | ||||
changeid = changeid[:ipos] | ||||
return changeidctx(repo, changeid) | ||||
return None | ||||
|
r6392 | def filectx(repo, req): | ||
|
r17289 | if 'file' not in req.form: | ||
raise ErrorResponse(HTTP_NOT_FOUND, 'file not given') | ||||
|
r6392 | path = cleanpath(repo, req.form['file'][0]) | ||
if 'node' in req.form: | ||||
changeid = req.form['node'][0] | ||||
|
r17289 | elif 'filenode' in req.form: | ||
changeid = req.form['filenode'][0] | ||||
|
r6392 | else: | ||
|
r17289 | raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given') | ||
|
r6392 | try: | ||
|
r6747 | fctx = repo[changeid][path] | ||
|
r7637 | except error.RepoError: | ||
|
r6392 | fctx = repo.filectx(path, fileid=changeid) | ||
return fctx | ||||
|
r7310 | |||
|
r7311 | def listfilediffs(tmpl, files, node, max): | ||
for f in files[:max]: | ||||
yield tmpl('filedifflink', node=hex(node), file=f) | ||||
if len(files) > max: | ||||
yield tmpl('fileellipses') | ||||
|
r17991 | def diffs(repo, tmpl, ctx, basectx, files, parity, style): | ||
|
r7310 | |||
def countgen(): | ||||
start = 1 | ||||
while True: | ||||
yield start | ||||
start += 1 | ||||
blockcount = countgen() | ||||
|
r16308 | def prettyprintlines(diff, blockno): | ||
|
r7310 | for lineno, l in enumerate(diff.splitlines(True)): | ||
lineno = "%d.%d" % (blockno, lineno + 1) | ||||
if l.startswith('+'): | ||||
ltype = "difflineplus" | ||||
elif l.startswith('-'): | ||||
ltype = "difflineminus" | ||||
elif l.startswith('@'): | ||||
ltype = "difflineat" | ||||
else: | ||||
ltype = "diffline" | ||||
yield tmpl(ltype, | ||||
line=l, | ||||
lineid="l%s" % lineno, | ||||
linenumber="% 8s" % lineno) | ||||
if files: | ||||
m = match.exact(repo.root, repo.getcwd(), files) | ||||
else: | ||||
m = match.always(repo.root, repo.getcwd()) | ||||
diffopts = patch.diffopts(repo.ui, untrusted=True) | ||||
|
r17991 | if basectx is None: | ||
parents = ctx.parents() | ||||
node1 = parents and parents[0].node() or nullid | ||||
else: | ||||
node1 = basectx.node() | ||||
|
r7310 | node2 = ctx.node() | ||
block = [] | ||||
for chunk in patch.diff(repo, node1, node2, m, opts=diffopts): | ||||
if chunk.startswith('diff') and block: | ||||
|
r16308 | blockno = blockcount.next() | ||
yield tmpl('diffblock', parity=parity.next(), blockno=blockno, | ||||
lines=prettyprintlines(''.join(block), blockno)) | ||||
|
r7310 | block = [] | ||
|
r9402 | if chunk.startswith('diff') and style != 'raw': | ||
|
r7310 | chunk = ''.join(chunk.splitlines(True)[1:]) | ||
block.append(chunk) | ||||
|
r16308 | blockno = blockcount.next() | ||
yield tmpl('diffblock', parity=parity.next(), blockno=blockno, | ||||
lines=prettyprintlines(''.join(block), blockno)) | ||||
|
r7345 | |||
|
r17302 | def compare(tmpl, context, leftlines, rightlines): | ||
|
r17202 | '''Generator function that provides side-by-side comparison data.''' | ||
def compline(type, leftlineno, leftline, rightlineno, rightline): | ||||
lineid = leftlineno and ("l%s" % leftlineno) or '' | ||||
lineid += rightlineno and ("r%s" % rightlineno) or '' | ||||
return tmpl('comparisonline', | ||||
type=type, | ||||
lineid=lineid, | ||||
leftlinenumber="% 6s" % (leftlineno or ''), | ||||
leftline=leftline or '', | ||||
rightlinenumber="% 6s" % (rightlineno or ''), | ||||
rightline=rightline or '') | ||||
def getblock(opcodes): | ||||
for type, llo, lhi, rlo, rhi in opcodes: | ||||
len1 = lhi - llo | ||||
len2 = rhi - rlo | ||||
count = min(len1, len2) | ||||
for i in xrange(count): | ||||
yield compline(type=type, | ||||
leftlineno=llo + i + 1, | ||||
leftline=leftlines[llo + i], | ||||
rightlineno=rlo + i + 1, | ||||
rightline=rightlines[rlo + i]) | ||||
if len1 > len2: | ||||
for i in xrange(llo + count, lhi): | ||||
yield compline(type=type, | ||||
leftlineno=i + 1, | ||||
leftline=leftlines[i], | ||||
rightlineno=None, | ||||
rightline=None) | ||||
elif len2 > len1: | ||||
for i in xrange(rlo + count, rhi): | ||||
yield compline(type=type, | ||||
leftlineno=None, | ||||
leftline=None, | ||||
rightlineno=i + 1, | ||||
rightline=rightlines[i]) | ||||
s = difflib.SequenceMatcher(None, leftlines, rightlines) | ||||
if context < 0: | ||||
|
r17302 | yield tmpl('comparisonblock', lines=getblock(s.get_opcodes())) | ||
|
r17202 | else: | ||
|
r17302 | for oc in s.get_grouped_opcodes(n=context): | ||
yield tmpl('comparisonblock', lines=getblock(oc)) | ||||
|
r17202 | |||
|
r17991 | def diffstatgen(ctx, basectx): | ||
|
r14570 | '''Generator function that provides the diffstat data.''' | ||
|
r14490 | |||
|
r17991 | stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx))) | ||
|
r14490 | maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats) | ||
|
r14570 | while True: | ||
yield stats, maxname, maxtotal, addtotal, removetotal, binary | ||||
def diffsummary(statgen): | ||||
'''Return a short summary of the diff.''' | ||||
stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next() | ||||
return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % ( | ||||
len(stats), addtotal, removetotal) | ||||
def diffstat(tmpl, ctx, statgen, parity): | ||||
'''Return a diffstat template for each file in the diff.''' | ||||
stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next() | ||||
|
r14561 | files = ctx.files() | ||
|
r14490 | |||
|
r14561 | def pct(i): | ||
if maxtotal == 0: | ||||
return 0 | ||||
return (float(i) / maxtotal) * 100 | ||||
|
r14490 | |||
|
r14562 | fileno = 0 | ||
|
r14561 | for filename, adds, removes, isbinary in stats: | ||
template = filename in files and 'diffstatlink' or 'diffstatnolink' | ||||
total = adds + removes | ||||
|
r14562 | fileno += 1 | ||
yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno, | ||||
|
r14561 | total=total, addpct=pct(adds), removepct=pct(removes), | ||
parity=parity.next()) | ||||
|
r14490 | |||
|
r7345 | class sessionvars(object): | ||
def __init__(self, vars, start='?'): | ||||
self.start = start | ||||
self.vars = vars | ||||
def __getitem__(self, key): | ||||
return self.vars[key] | ||||
def __setitem__(self, key, value): | ||||
self.vars[key] = value | ||||
def __copy__(self): | ||||
return sessionvars(copy.copy(self.vars), self.start) | ||||
def __iter__(self): | ||||
separator = self.start | ||||
for key, value in self.vars.iteritems(): | ||||
yield {'name': key, 'value': str(value), 'separator': separator} | ||||
separator = '&' | ||||
|
r12691 | |||
class wsgiui(ui.ui): | ||||
# default termwidth breaks under mod_wsgi | ||||
def termwidth(self): | ||||
return 80 | ||||