##// END OF EJS Templates
obsolete: add __eq__ and __hash__ to marker to make set() deduplication work
obsolete: add __eq__ and __hash__ to marker to make set() deduplication work

File last commit:

r19094:fc1b77db stable
r20031:6c1adf20 default
Show More
webutil.py
408 lines | 12.9 KiB | text/x-python | PythonLexer
Dirkjan Ochtman
hgweb: separate out utility functions
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>
#
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Dirkjan Ochtman
hgweb: separate out utility functions
r6392
wujek srujek
hgweb: fixes traceback for invalid files by removing top-level template...
r17302 import os, copy
Steven Brown
web: provide diffstat to the changeset page...
r14490 from mercurial import match, patch, scmutil, error, ui, util
Steven Brown
web: provide diff summary to the changeset page...
r14570 from mercurial.i18n import _
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 from mercurial.node import hex, nullid
Ross Lagerwall
hgweb: avoid traceback when file or node parameters are missing...
r17289 from common import ErrorResponse
from common import HTTP_NOT_FOUND
wujek srujek
hgweb: side-by-side comparison functionality...
r17202 import difflib
Dirkjan Ochtman
hgweb: separate out utility functions
r6392
Dirkjan Ochtman
hgweb: refactor hgweb code
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 + "/"
Pierre-Yves David
hgweb: better names for _navseq arguments...
r18391 def _navseq(step, firststep=None):
if firststep:
yield firststep
if firststep >= 20 and firststep <= 40:
Pierre-Yves David
hgweb: ensure _navseq yield strictly increasing numbers...
r18392 firststep = 50
yield firststep
assert step > 0
assert firststep > 0
while step <= firststep:
step *= 10
Pierre-Yves David
hgweb: drop recursivity in _navseq...
r18390 while True:
Pierre-Yves David
hgweb: better names for _navseq arguments...
r18391 yield 1 * step
yield 3 * step
step *= 10
Pierre-Yves David
hgweb: move the `seq` function out of the revnavgen scope...
r18389
Pierre-Yves David
hgweb: move revnavgen into an object...
r18403 class revnav(object):
Pierre-Yves David
hgweb: document the revnavgen function
r18320
Pierre-Yves David
hgweb: pass repo object to revnav construction...
r18409 def __init__(self, repo):
Pierre-Yves David
hgweb: pass nodefunc to the revnav object...
r18404 """Navigation generation object
Pierre-Yves David
hgweb: pass repo object to revnav construction...
r18409 :repo: repo object we generate nav for
Pierre-Yves David
hgweb: pass nodefunc to the revnav object...
r18404 """
Pierre-Yves David
hgweb: pass repo object to revnav construction...
r18409 # used for hex generation
self._revlog = repo.changelog
Pierre-Yves David
hgweb: pass nodefunc to the revnav object...
r18404
Pierre-Yves David
hgweb: simplify the handling of empty repo...
r18406 def __nonzero__(self):
"""return True if any revision to navigate over"""
Pierre-Yves David
hgweb: handle filtered "0" rev in navigation...
r19094 return self._first() is not None
def _first(self):
"""return the minimum non-filtered changeset or None"""
try:
return iter(self._revlog).next()
except StopIteration:
return None
Pierre-Yves David
hgweb: simplify the handling of empty repo...
r18406
Pierre-Yves David
hgweb: move hex creation into an object method...
r18405 def hex(self, rev):
Pierre-Yves David
hgweb: pass repo object to revnav construction...
r18409 return hex(self._revlog.node(rev))
Pierre-Yves David
hgweb: move hex creation into an object method...
r18405
Pierre-Yves David
hgweb: pass nodefunc to the revnav object...
r18404 def gen(self, pos, pagelen, limit):
Pierre-Yves David
hgweb: move revnavgen into an object...
r18403 """computes label and revision id for navigation link
Pierre-Yves David
hgweb: document the revnavgen function
r18320
Pierre-Yves David
hgweb: move revnavgen into an object...
r18403 :pos: is the revision relative to which we generate navigation.
:pagelen: the size of each navigation page
:limit: how far shall we link
Dirkjan Ochtman
hgweb: refactor hgweb code
r6393
Pierre-Yves David
hgweb: move revnavgen into an object...
r18403 The return is:
- a single element tuple
- containing a dictionary with a `before` and `after` key
- values are generator functions taking arbitrary number of kwargs
- yield items are dictionaries with `label` and `node` keys
"""
Pierre-Yves David
hgweb: simplify the handling of empty repo...
r18406 if not self:
# empty repo
return ({'before': (), 'after': ()},)
Dirkjan Ochtman
hgweb: refactor hgweb code
r6393
Pierre-Yves David
hgweb: generate revnav in two phase...
r18425 targets = []
Pierre-Yves David
hgweb: move revnavgen into an object...
r18403 for f in _navseq(1, pagelen):
if f > limit:
break
Pierre-Yves David
hgweb: generate revnav in two phase...
r18425 targets.append(pos + f)
targets.append(pos - f)
targets.sort()
Pierre-Yves David
hgweb: handle filtered "0" rev in navigation...
r19094 first = self._first()
navbefore = [("(%i)" % first, self.hex(first))]
Pierre-Yves David
hgweb: generate revnav in two phase...
r18425 navafter = []
for rev in targets:
Pierre-Yves David
hgweb: ignore filtered revision in revnav...
r18426 if rev not in self._revlog:
continue
Pierre-Yves David
hgweb: generate revnav in two phase...
r18425 if pos < rev < limit:
Pierre-Yves David
hgweb: fix navigation label (issue3792)...
r18503 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
Pierre-Yves David
hgweb: generate revnav in two phase...
r18425 if 0 < rev < pos:
Pierre-Yves David
hgweb: fix navigation label (issue3792)...
r18503 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
Pierre-Yves David
hgweb: generate revnav in two phase...
r18425
Nicolas Dumazet
hgweb: changenav: separate pages before and after the current position...
r10254
Pierre-Yves David
hgweb: move revnavgen into an object...
r18403 navafter.append(("tip", "tip"))
data = lambda i: {"label": i[0], "node": i[1]}
return ({'before': lambda **map: (data(i) for i in navbefore),
'after': lambda **map: (data(i) for i in navafter)},)
Dirkjan Ochtman
hgweb: refactor hgweb code
r6393
Pierre-Yves David
hgweb: introduction a filerevnav subclass...
r18408 class filerevnav(revnav):
Pierre-Yves David
hgweb: pass repo object to revnav construction...
r18409
def __init__(self, repo, path):
"""Navigation generation object
:repo: repo object we generate nav for
:path: path of the file we generate nav for
"""
# used for iteration
self._changelog = repo.unfiltered().changelog
# used for hex generation
self._revlog = repo.file(path)
def hex(self, rev):
return hex(self._changelog.node(self._revlog.linkrev(rev)))
Pierre-Yves David
hgweb: introduction a filerevnav subclass...
r18408
Dirkjan Ochtman
hgweb: simplify parents/children generation code
r7671 def _siblings(siblings=[], hiderev=None):
Dirkjan Ochtman
hgweb: separate out utility functions
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:
Alexander Solovyov
drop {short,hex}(ctx.node()) calls in favor of ctx methods
r14055 d = {'node': s.hex(), 'rev': s.rev()}
Dirkjan Ochtman
hgweb: pass more information about parent/child csets to templates
r7294 d['user'] = s.user()
d['date'] = s.date()
d['description'] = s.description()
Dirkjan Ochtman
hgweb: expose sibling branches to templater
r7717 d['branch'] = s.branch()
Augie Fackler
hgweb: move remaining hasattr calls to safehasattr
r14957 if util.safehasattr(s, 'path'):
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 d['file'] = s.path()
yield d
Dirkjan Ochtman
hgweb: simplify parents/children generation code
r7671 def parents(ctx, hide=None):
return _siblings(ctx.parents(), hide)
def children(ctx, hide=None):
return _siblings(ctx.children(), hide)
Matt Mackall
hgweb: minor improvements for new web style...
r6434 def renamelink(fctx):
Matt Mackall
hgweb: fix merge breakage
r6437 r = fctx.renamed()
Dirkjan Ochtman
hgweb: separate out utility functions
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)]
Alexander Solovyov
hgweb: add display of bookmarks for changelog and changeset
r13596 def nodebookmarksdict(repo, node):
return [{"name": i} for i in repo.nodebookmarks(node)]
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 def nodebranchdict(repo, ctx):
branches = []
branch = ctx.branch()
# If this is an empty repo, ctx.node() == nullid,
Brodie Rao
localrepo: add branchtip() method for faster single-branch lookups...
r16719 # ctx.branch() == 'default'.
try:
branchnode = repo.branchtip(branch)
except error.RepoLookupError:
branchnode = None
if branchnode == ctx.node():
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 branches.append({"name": branch})
return branches
def nodeinbranch(repo, ctx):
branches = []
branch = ctx.branch()
Brodie Rao
localrepo: add branchtip() method for faster single-branch lookups...
r16719 try:
branchnode = repo.branchtip(branch)
except error.RepoLookupError:
branchnode = None
if branch != 'default' and branchnode != ctx.node():
Dirkjan Ochtman
hgweb: separate out utility functions
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)
Alexander Solovyov
hgweb: add display of bookmarks for changelog and changeset
r13596 def showbookmark(repo, tmpl, t1, node=nullid, **args):
for t in repo.nodebookmarks(node):
yield tmpl(t1, bookmark=t, **args)
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 def cleanpath(repo, path):
path = path.lstrip('/')
Adrian Buehlmann
move canonpath from util to scmutil
r13971 return scmutil.canonpath(repo.root, '', path)
Dirkjan Ochtman
hgweb: separate out utility functions
r6392
Weiwen
hgweb: display diff for a changeset against any parents (issue2810)...
r17991 def changeidctx (repo, changeid):
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 try:
Matt Mackall
use repo[changeid] to get a changectx
r6747 ctx = repo[changeid]
Matt Mackall
error: move repo errors...
r7637 except error.RepoError:
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 man = repo.manifest
Matt Mackall
linkrev: take a revision number rather than a hash
r7361 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
Dirkjan Ochtman
hgweb: separate out utility functions
r6392
return ctx
Weiwen
hgweb: display diff for a changeset against any parents (issue2810)...
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
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 def filectx(repo, req):
Ross Lagerwall
hgweb: avoid traceback when file or node parameters are missing...
r17289 if 'file' not in req.form:
raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 path = cleanpath(repo, req.form['file'][0])
if 'node' in req.form:
changeid = req.form['node'][0]
Ross Lagerwall
hgweb: avoid traceback when file or node parameters are missing...
r17289 elif 'filenode' in req.form:
changeid = req.form['filenode'][0]
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 else:
Ross Lagerwall
hgweb: avoid traceback when file or node parameters are missing...
r17289 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 try:
Matt Mackall
use repo[changeid] to get a changectx
r6747 fctx = repo[changeid][path]
Matt Mackall
error: move repo errors...
r7637 except error.RepoError:
Dirkjan Ochtman
hgweb: separate out utility functions
r6392 fctx = repo.filectx(path, fileid=changeid)
return fctx
Dirkjan Ochtman
hgweb: move the diffs() generator into webutil
r7310
Dirkjan Ochtman
hgweb: move another utility function into the webutil module
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')
Weiwen
hgweb: display diff for a changeset against any parents (issue2810)...
r17991 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
Dirkjan Ochtman
hgweb: move the diffs() generator into webutil
r7310
def countgen():
start = 1
while True:
yield start
start += 1
blockcount = countgen()
Paul Boddie
hgweb: add block numbers to diff regions and related links...
r16308 def prettyprintlines(diff, blockno):
Dirkjan Ochtman
hgweb: move the diffs() generator into webutil
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)
Weiwen
hgweb: display diff for a changeset against any parents (issue2810)...
r17991 if basectx is None:
parents = ctx.parents()
node1 = parents and parents[0].node() or nullid
else:
node1 = basectx.node()
Dirkjan Ochtman
hgweb: move the diffs() generator into webutil
r7310 node2 = ctx.node()
block = []
for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
if chunk.startswith('diff') and block:
Paul Boddie
hgweb: add block numbers to diff regions and related links...
r16308 blockno = blockcount.next()
yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
lines=prettyprintlines(''.join(block), blockno))
Dirkjan Ochtman
hgweb: move the diffs() generator into webutil
r7310 block = []
Dirkjan Ochtman
hgweb: show diff header line in raw diffs
r9402 if chunk.startswith('diff') and style != 'raw':
Dirkjan Ochtman
hgweb: move the diffs() generator into webutil
r7310 chunk = ''.join(chunk.splitlines(True)[1:])
block.append(chunk)
Paul Boddie
hgweb: add block numbers to diff regions and related links...
r16308 blockno = blockcount.next()
yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
lines=prettyprintlines(''.join(block), blockno))
Dirkjan Ochtman
hgweb: fix up the less/more links on the graph page...
r7345
wujek srujek
hgweb: fixes traceback for invalid files by removing top-level template...
r17302 def compare(tmpl, context, leftlines, rightlines):
wujek srujek
hgweb: side-by-side comparison functionality...
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:
wujek srujek
hgweb: fixes traceback for invalid files by removing top-level template...
r17302 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
wujek srujek
hgweb: side-by-side comparison functionality...
r17202 else:
wujek srujek
hgweb: fixes traceback for invalid files by removing top-level template...
r17302 for oc in s.get_grouped_opcodes(n=context):
yield tmpl('comparisonblock', lines=getblock(oc))
wujek srujek
hgweb: side-by-side comparison functionality...
r17202
Weiwen
hgweb: display diff for a changeset against any parents (issue2810)...
r17991 def diffstatgen(ctx, basectx):
Steven Brown
web: provide diff summary to the changeset page...
r14570 '''Generator function that provides the diffstat data.'''
Steven Brown
web: provide diffstat to the changeset page...
r14490
Weiwen
hgweb: display diff for a changeset against any parents (issue2810)...
r17991 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
Steven Brown
web: provide diffstat to the changeset page...
r14490 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
Steven Brown
web: provide diff summary to the changeset page...
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()
Steven Brown
web: include all files in the diffstat...
r14561 files = ctx.files()
Steven Brown
web: provide diffstat to the changeset page...
r14490
Steven Brown
web: include all files in the diffstat...
r14561 def pct(i):
if maxtotal == 0:
return 0
return (float(i) / maxtotal) * 100
Steven Brown
web: provide diffstat to the changeset page...
r14490
Steven Brown
web: provide the file number to the diffstat templates...
r14562 fileno = 0
Steven Brown
web: include all files in the diffstat...
r14561 for filename, adds, removes, isbinary in stats:
template = filename in files and 'diffstatlink' or 'diffstatnolink'
total = adds + removes
Steven Brown
web: provide the file number to the diffstat templates...
r14562 fileno += 1
yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
Steven Brown
web: include all files in the diffstat...
r14561 total=total, addpct=pct(adds), removepct=pct(removes),
parity=parity.next())
Steven Brown
web: provide diffstat to the changeset page...
r14490
Dirkjan Ochtman
hgweb: fix up the less/more links on the graph page...
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
Mads Kiilerich
hgweb: generate query strings with parameters sorted by key
r18367 for key, value in sorted(self.vars.iteritems()):
Dirkjan Ochtman
hgweb: fix up the less/more links on the graph page...
r7345 yield {'name': key, 'value': str(value), 'separator': separator}
separator = '&'
Augie Fackler
hgweb: fix hgweb_mod as well as hgwebdir_mod
r12691
class wsgiui(ui.ui):
# default termwidth breaks under mod_wsgi
def termwidth(self):
return 80