webutil.py
666 lines
| 21.2 KiB
| text/x-python
|
PythonLexer
Dirkjan Ochtman
|
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
|
r8225 | # This software may be used and distributed according to the terms of the | ||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Dirkjan Ochtman
|
r6392 | |||
Yuya Nishihara
|
r27046 | from __future__ import absolute_import | ||
import copy | ||||
import difflib | ||||
import os | ||||
Gregory Szorc
|
r26162 | import re | ||
Yuya Nishihara
|
r27046 | |||
from ..i18n import _ | ||||
from ..node import hex, nullid, short | ||||
from .common import ( | ||||
ErrorResponse, | ||||
Denis Laxalde
|
r31665 | HTTP_BAD_REQUEST, | ||
Yuya Nishihara
|
r27046 | HTTP_NOT_FOUND, | ||
paritygen, | ||||
) | ||||
from .. import ( | ||||
context, | ||||
error, | ||||
match, | ||||
Denis Laxalde
|
r31808 | mdiff, | ||
Yuya Nishihara
|
r27046 | patch, | ||
pathutil, | ||||
Augie Fackler
|
r34808 | pycompat, | ||
Yuya Nishihara
|
r27046 | templatefilters, | ||
r35501 | templatekw, | |||
Yuya Nishihara
|
r27046 | ui as uimod, | ||
util, | ||||
) | ||||
Dirkjan Ochtman
|
r6392 | |||
Dirkjan Ochtman
|
r6393 | def up(p): | ||
Augie Fackler
|
r36731 | if p[0:1] != "/": | ||
Dirkjan Ochtman
|
r6393 | p = "/" + p | ||
Augie Fackler
|
r36731 | if p[-1:] == "/": | ||
Dirkjan Ochtman
|
r6393 | p = p[:-1] | ||
up = os.path.dirname(p) | ||||
if up == "/": | ||||
return "/" | ||||
return up + "/" | ||||
Pierre-Yves David
|
r18391 | def _navseq(step, firststep=None): | ||
if firststep: | ||||
yield firststep | ||||
if firststep >= 20 and firststep <= 40: | ||||
Pierre-Yves David
|
r18392 | firststep = 50 | ||
yield firststep | ||||
assert step > 0 | ||||
assert firststep > 0 | ||||
while step <= firststep: | ||||
step *= 10 | ||||
Pierre-Yves David
|
r18390 | while True: | ||
Pierre-Yves David
|
r18391 | yield 1 * step | ||
yield 3 * step | ||||
step *= 10 | ||||
Pierre-Yves David
|
r18389 | |||
Pierre-Yves David
|
r18403 | class revnav(object): | ||
Pierre-Yves David
|
r18320 | |||
Pierre-Yves David
|
r18409 | def __init__(self, repo): | ||
Pierre-Yves David
|
r18404 | """Navigation generation object | ||
Pierre-Yves David
|
r18409 | :repo: repo object we generate nav for | ||
Pierre-Yves David
|
r18404 | """ | ||
Pierre-Yves David
|
r18409 | # used for hex generation | ||
self._revlog = repo.changelog | ||||
Pierre-Yves David
|
r18404 | |||
Pierre-Yves David
|
r18406 | def __nonzero__(self): | ||
"""return True if any revision to navigate over""" | ||||
Pierre-Yves David
|
r19094 | return self._first() is not None | ||
Gregory Szorc
|
r31476 | __bool__ = __nonzero__ | ||
Pierre-Yves David
|
r19094 | def _first(self): | ||
"""return the minimum non-filtered changeset or None""" | ||||
try: | ||||
timeless
|
r29216 | return next(iter(self._revlog)) | ||
Pierre-Yves David
|
r19094 | except StopIteration: | ||
return None | ||||
Pierre-Yves David
|
r18406 | |||
Pierre-Yves David
|
r18405 | def hex(self, rev): | ||
Pierre-Yves David
|
r18409 | return hex(self._revlog.node(rev)) | ||
Pierre-Yves David
|
r18405 | |||
Pierre-Yves David
|
r18404 | def gen(self, pos, pagelen, limit): | ||
Pierre-Yves David
|
r18403 | """computes label and revision id for navigation link | ||
Pierre-Yves David
|
r18320 | |||
Pierre-Yves David
|
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
|
r6393 | |||
Pierre-Yves David
|
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
|
r18406 | if not self: | ||
# empty repo | ||||
return ({'before': (), 'after': ()},) | ||||
Dirkjan Ochtman
|
r6393 | |||
Pierre-Yves David
|
r18425 | targets = [] | ||
Pierre-Yves David
|
r18403 | for f in _navseq(1, pagelen): | ||
if f > limit: | ||||
break | ||||
Pierre-Yves David
|
r18425 | targets.append(pos + f) | ||
targets.append(pos - f) | ||||
targets.sort() | ||||
Pierre-Yves David
|
r19094 | first = self._first() | ||
navbefore = [("(%i)" % first, self.hex(first))] | ||||
Pierre-Yves David
|
r18425 | navafter = [] | ||
for rev in targets: | ||||
Pierre-Yves David
|
r18426 | if rev not in self._revlog: | ||
continue | ||||
Pierre-Yves David
|
r18425 | if pos < rev < limit: | ||
Pierre-Yves David
|
r18503 | navafter.append(("+%d" % abs(rev - pos), self.hex(rev))) | ||
Pierre-Yves David
|
r18425 | if 0 < rev < pos: | ||
Pierre-Yves David
|
r18503 | navbefore.append(("-%d" % abs(rev - pos), self.hex(rev))) | ||
Pierre-Yves David
|
r18425 | |||
Nicolas Dumazet
|
r10254 | |||
Pierre-Yves David
|
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
|
r6393 | |||
Pierre-Yves David
|
r18408 | class filerevnav(revnav): | ||
Pierre-Yves David
|
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))) | ||||
r27023 | class _siblings(object): | |||
Gregory Szorc
|
r31391 | def __init__(self, siblings=None, hiderev=None): | ||
Pierre-Yves David
|
r31434 | if siblings is None: | ||
siblings = [] | ||||
self.siblings = [s for s in siblings if s.node() != nullid] | ||||
r27023 | if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev: | |||
self.siblings = [] | ||||
Pierre-Yves David
|
r18408 | |||
r27023 | def __iter__(self): | |||
for s in self.siblings: | ||||
d = { | ||||
'node': s.hex(), | ||||
'rev': s.rev(), | ||||
'user': s.user(), | ||||
'date': s.date(), | ||||
'description': s.description(), | ||||
'branch': s.branch(), | ||||
} | ||||
if util.safehasattr(s, 'path'): | ||||
d['file'] = s.path() | ||||
yield d | ||||
def __len__(self): | ||||
return len(self.siblings) | ||||
Dirkjan Ochtman
|
r6392 | |||
Gregory Szorc
|
r34391 | def difffeatureopts(req, ui, section): | ||
Jun Wu
|
r30081 | diffopts = patch.difffeatureopts(ui, untrusted=True, | ||
Gregory Szorc
|
r34391 | section=section, whitespace=True) | ||
for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'): | ||||
Gregory Szorc
|
r36902 | v = req.qsparams.get(k) | ||
Gregory Szorc
|
r34391 | if v is not None: | ||
Gregory Szorc
|
r34404 | v = util.parsebool(v) | ||
setattr(diffopts, k, v if v is not None else True) | ||||
Gregory Szorc
|
r34391 | |||
return diffopts | ||||
def annotate(req, fctx, ui): | ||||
diffopts = difffeatureopts(req, ui, 'annotate') | ||||
Jun Wu
|
r30081 | return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts) | ||
Dirkjan Ochtman
|
r7671 | def parents(ctx, hide=None): | ||
Anton Shestakov
|
r24136 | if isinstance(ctx, context.basefilectx): | ||
introrev = ctx.introrev() | ||||
if ctx.changectx().rev() != introrev: | ||||
Matt Harbison
|
r24340 | return _siblings([ctx.repo()[introrev]], hide) | ||
Dirkjan Ochtman
|
r7671 | return _siblings(ctx.parents(), hide) | ||
def children(ctx, hide=None): | ||||
return _siblings(ctx.children(), hide) | ||||
Matt Mackall
|
r6434 | def renamelink(fctx): | ||
Matt Mackall
|
r6437 | r = fctx.renamed() | ||
Dirkjan Ochtman
|
r6392 | if r: | ||
Augie Fackler
|
r20681 | return [{'file': r[0], 'node': hex(r[1])}] | ||
Dirkjan Ochtman
|
r6392 | return [] | ||
def nodetagsdict(repo, node): | ||||
return [{"name": i} for i in repo.nodetags(node)] | ||||
Alexander Solovyov
|
r13596 | def nodebookmarksdict(repo, node): | ||
return [{"name": i} for i in repo.nodebookmarks(node)] | ||||
Dirkjan Ochtman
|
r6392 | def nodebranchdict(repo, ctx): | ||
branches = [] | ||||
branch = ctx.branch() | ||||
# If this is an empty repo, ctx.node() == nullid, | ||||
Brodie Rao
|
r16719 | # ctx.branch() == 'default'. | ||
try: | ||||
branchnode = repo.branchtip(branch) | ||||
except error.RepoLookupError: | ||||
branchnode = None | ||||
if branchnode == ctx.node(): | ||||
Dirkjan Ochtman
|
r6392 | branches.append({"name": branch}) | ||
return branches | ||||
def nodeinbranch(repo, ctx): | ||||
branches = [] | ||||
branch = ctx.branch() | ||||
Brodie Rao
|
r16719 | try: | ||
branchnode = repo.branchtip(branch) | ||||
except error.RepoLookupError: | ||||
branchnode = None | ||||
if branch != 'default' and branchnode != ctx.node(): | ||||
Dirkjan Ochtman
|
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
|
r13596 | def showbookmark(repo, tmpl, t1, node=nullid, **args): | ||
for t in repo.nodebookmarks(node): | ||||
yield tmpl(t1, bookmark=t, **args) | ||||
r26129 | def branchentries(repo, stripecount, limit=0): | |||
tips = [] | ||||
heads = repo.heads() | ||||
parity = paritygen(stripecount) | ||||
sortkey = lambda item: (not item[1], item[0].rev()) | ||||
def entries(**map): | ||||
count = 0 | ||||
if not tips: | ||||
for tag, hs, tip, closed in repo.branchmap().iterbranches(): | ||||
tips.append((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 { | ||||
timeless
|
r29216 | 'parity': next(parity), | ||
r26129 | 'branch': ctx.branch(), | |||
'status': status, | ||||
'node': ctx.hex(), | ||||
'date': ctx.date() | ||||
} | ||||
return entries | ||||
Dirkjan Ochtman
|
r6392 | def cleanpath(repo, path): | ||
path = path.lstrip('/') | ||||
Augie Fackler
|
r20033 | return pathutil.canonpath(repo.root, '', path) | ||
Dirkjan Ochtman
|
r6392 | |||
r25999 | def changeidctx(repo, changeid): | |||
Dirkjan Ochtman
|
r6392 | try: | ||
Matt Mackall
|
r6747 | ctx = repo[changeid] | ||
Matt Mackall
|
r7637 | except error.RepoError: | ||
Durham Goode
|
r30375 | man = repo.manifestlog._revlog | ||
Matt Mackall
|
r7361 | ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))] | ||
Dirkjan Ochtman
|
r6392 | |||
return ctx | ||||
r25999 | def changectx(repo, req): | |||
Weiwen
|
r17991 | changeid = "tip" | ||
Gregory Szorc
|
r36902 | if 'node' in req.qsparams: | ||
changeid = req.qsparams['node'] | ||||
r25999 | ipos = changeid.find(':') | |||
Weiwen
|
r17991 | if ipos != -1: | ||
changeid = changeid[(ipos + 1):] | ||||
Gregory Szorc
|
r36902 | elif 'manifest' in req.qsparams: | ||
changeid = req.qsparams['manifest'] | ||||
Weiwen
|
r17991 | |||
return changeidctx(repo, changeid) | ||||
def basechangectx(repo, req): | ||||
Gregory Szorc
|
r36902 | if 'node' in req.qsparams: | ||
changeid = req.qsparams['node'] | ||||
r25999 | ipos = changeid.find(':') | |||
Weiwen
|
r17991 | if ipos != -1: | ||
changeid = changeid[:ipos] | ||||
return changeidctx(repo, changeid) | ||||
return None | ||||
Dirkjan Ochtman
|
r6392 | def filectx(repo, req): | ||
Gregory Szorc
|
r36902 | if 'file' not in req.qsparams: | ||
Ross Lagerwall
|
r17289 | raise ErrorResponse(HTTP_NOT_FOUND, 'file not given') | ||
Gregory Szorc
|
r36902 | path = cleanpath(repo, req.qsparams['file']) | ||
if 'node' in req.qsparams: | ||||
changeid = req.qsparams['node'] | ||||
elif 'filenode' in req.qsparams: | ||||
changeid = req.qsparams['filenode'] | ||||
Dirkjan Ochtman
|
r6392 | else: | ||
Ross Lagerwall
|
r17289 | raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given') | ||
Dirkjan Ochtman
|
r6392 | try: | ||
Matt Mackall
|
r6747 | fctx = repo[changeid][path] | ||
Matt Mackall
|
r7637 | except error.RepoError: | ||
Dirkjan Ochtman
|
r6392 | fctx = repo.filectx(path, fileid=changeid) | ||
return fctx | ||||
Dirkjan Ochtman
|
r7310 | |||
Denis Laxalde
|
r31665 | def linerange(req): | ||
Gregory Szorc
|
r36902 | linerange = req.qsparams.getall('linerange') | ||
Gregory Szorc
|
r36881 | if not linerange: | ||
Denis Laxalde
|
r31665 | return None | ||
if len(linerange) > 1: | ||||
raise ErrorResponse(HTTP_BAD_REQUEST, | ||||
'redundant linerange parameter') | ||||
try: | ||||
fromline, toline = map(int, linerange[0].split(':', 1)) | ||||
except ValueError: | ||||
raise ErrorResponse(HTTP_BAD_REQUEST, | ||||
'invalid linerange parameter') | ||||
try: | ||||
return util.processlinerange(fromline, toline) | ||||
except error.ParseError as exc: | ||||
Augie Fackler
|
r36272 | raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc)) | ||
Denis Laxalde
|
r31665 | |||
def formatlinerange(fromline, toline): | ||||
return '%d:%d' % (fromline + 1, toline) | ||||
Yuya Nishihara
|
r36612 | def succsandmarkers(context, mapping): | ||
repo = context.resource(mapping, 'repo') | ||||
for item in templatekw.showsuccsandmarkers(context, mapping): | ||||
r35502 | item['successors'] = _siblings(repo[successor] | |||
for successor in item['successors']) | ||||
yield item | ||||
r35501 | ||||
Yuya Nishihara
|
r36612 | # teach templater succsandmarkers is switched to (context, mapping) API | ||
succsandmarkers._requires = {'repo', 'ctx', 'templ'} | ||||
r27294 | def commonentry(repo, ctx): | |||
node = ctx.node() | ||||
return { | ||||
Yuya Nishihara
|
r36535 | # TODO: perhaps ctx.changectx() should be assigned if ctx is a | ||
# filectx, but I'm not pretty sure if that would always work because | ||||
# fctx.parents() != fctx.changectx.parents() for example. | ||||
'ctx': ctx, | ||||
'revcache': {}, | ||||
r27294 | 'rev': ctx.rev(), | |||
'node': hex(node), | ||||
'author': ctx.user(), | ||||
'desc': ctx.description(), | ||||
'date': ctx.date(), | ||||
'extra': ctx.extra(), | ||||
'phase': ctx.phasestr(), | ||||
r35087 | 'obsolete': ctx.obsolete(), | |||
Yuya Nishihara
|
r36535 | 'succsandmarkers': succsandmarkers, | ||
r35129 | 'instabilities': [{"instability": i} for i in ctx.instabilities()], | |||
r27294 | 'branch': nodebranchnodefault(ctx), | |||
'inbranch': nodeinbranch(repo, ctx), | ||||
'branches': nodebranchdict(repo, ctx), | ||||
'tags': nodetagsdict(repo, node), | ||||
'bookmarks': nodebookmarksdict(repo, node), | ||||
'parent': lambda **x: parents(ctx), | ||||
'child': lambda **x: children(ctx), | ||||
} | ||||
Gregory Szorc
|
r36901 | def changelistentry(web, ctx): | ||
Gregory Szorc
|
r23745 | '''Obtain a dictionary to be used for entries in a changelist. | ||
This function is called when producing items for the "entries" list passed | ||||
to the "shortlog" and "changelog" templates. | ||||
''' | ||||
repo = web.repo | ||||
rev = ctx.rev() | ||||
n = ctx.node() | ||||
Gregory Szorc
|
r36901 | showtags = showtag(repo, web.tmpl, 'changelogtag', n) | ||
files = listfilediffs(web.tmpl, ctx.files(), n, web.maxfiles) | ||||
Gregory Szorc
|
r23745 | |||
r27294 | entry = commonentry(repo, ctx) | |||
entry.update( | ||||
r28709 | allparents=lambda **x: parents(ctx), | |||
r27294 | parent=lambda **x: parents(ctx, rev - 1), | |||
child=lambda **x: children(ctx, rev + 1), | ||||
changelogtag=showtags, | ||||
files=files, | ||||
) | ||||
return entry | ||||
Gregory Szorc
|
r23745 | |||
r25602 | def symrevorshortnode(req, ctx): | |||
Gregory Szorc
|
r36902 | if 'node' in req.qsparams: | ||
return templatefilters.revescape(req.qsparams['node']) | ||||
r25602 | else: | |||
return short(ctx.node()) | ||||
Gregory Szorc
|
r36902 | def changesetentry(web, ctx): | ||
Gregory Szorc
|
r24177 | '''Obtain a dictionary to be used to render the "changeset" template.''' | ||
Gregory Szorc
|
r36901 | showtags = showtag(web.repo, web.tmpl, 'changesettag', ctx.node()) | ||
showbookmarks = showbookmark(web.repo, web.tmpl, 'changesetbookmark', | ||||
Gregory Szorc
|
r24177 | ctx.node()) | ||
showbranch = nodebranchnodefault(ctx) | ||||
files = [] | ||||
parity = paritygen(web.stripecount) | ||||
for blockno, f in enumerate(ctx.files()): | ||||
r35315 | template = 'filenodelink' if f in ctx else 'filenolink' | |||
Gregory Szorc
|
r36901 | files.append(web.tmpl(template, | ||
node=ctx.hex(), file=f, blockno=blockno + 1, | ||||
parity=next(parity))) | ||||
Gregory Szorc
|
r24177 | |||
Gregory Szorc
|
r36902 | basectx = basechangectx(web.repo, web.req) | ||
Gregory Szorc
|
r24177 | if basectx is None: | ||
basectx = ctx.p1() | ||||
Boris Feld
|
r34243 | style = web.config('web', 'style') | ||
Gregory Szorc
|
r36902 | if 'style' in web.req.qsparams: | ||
style = web.req.qsparams['style'] | ||||
Gregory Szorc
|
r24177 | |||
Gregory Szorc
|
r36901 | diff = diffs(web, ctx, basectx, None, style) | ||
Gregory Szorc
|
r24177 | |||
parity = paritygen(web.stripecount) | ||||
diffstatsgen = diffstatgen(ctx, basectx) | ||||
Gregory Szorc
|
r36901 | diffstats = diffstat(web.tmpl, ctx, diffstatsgen, parity) | ||
Gregory Szorc
|
r24177 | |||
return dict( | ||||
diff=diff, | ||||
Gregory Szorc
|
r36902 | symrev=symrevorshortnode(web.req, ctx), | ||
Gregory Szorc
|
r24177 | basenode=basectx.hex(), | ||
changesettag=showtags, | ||||
changesetbookmark=showbookmarks, | ||||
changesetbranch=showbranch, | ||||
files=files, | ||||
diffsummary=lambda **x: diffsummary(diffstatsgen), | ||||
diffstat=diffstats, | ||||
archives=web.archivelist(ctx.hex()), | ||||
Pulkit Goyal
|
r36452 | **pycompat.strkwargs(commonentry(web.repo, ctx))) | ||
Gregory Szorc
|
r24177 | |||
Dirkjan Ochtman
|
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') | ||||
Gregory Szorc
|
r36901 | def diffs(web, ctx, basectx, files, style, linerange=None, | ||
Denis Laxalde
|
r31727 | lineidprefix=''): | ||
Dirkjan Ochtman
|
r7310 | |||
Denis Laxalde
|
r31276 | def prettyprintlines(lines, blockno): | ||
for lineno, l in enumerate(lines, 1): | ||||
Denis Laxalde
|
r31275 | difflineno = "%d.%d" % (blockno, lineno) | ||
Dirkjan Ochtman
|
r7310 | if l.startswith('+'): | ||
ltype = "difflineplus" | ||||
elif l.startswith('-'): | ||||
ltype = "difflineminus" | ||||
elif l.startswith('@'): | ||||
ltype = "difflineat" | ||||
else: | ||||
ltype = "diffline" | ||||
Gregory Szorc
|
r36901 | yield web.tmpl( | ||
ltype, | ||||
line=l, | ||||
lineno=lineno, | ||||
lineid=lineidprefix + "l%s" % difflineno, | ||||
linenumber="% 8s" % difflineno) | ||||
Dirkjan Ochtman
|
r7310 | |||
Denis Laxalde
|
r31660 | repo = web.repo | ||
Dirkjan Ochtman
|
r7310 | 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) | ||||
Denis Laxalde
|
r31082 | node1 = basectx.node() | ||
Dirkjan Ochtman
|
r7310 | node2 = ctx.node() | ||
Denis Laxalde
|
r31660 | parity = paritygen(web.stripecount) | ||
Dirkjan Ochtman
|
r7310 | |||
Denis Laxalde
|
r31276 | diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts) | ||
Denis Laxalde
|
r34856 | for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1): | ||
Denis Laxalde
|
r31276 | if style != 'raw': | ||
header = header[1:] | ||||
lines = [h + '\n' for h in header] | ||||
for hunkrange, hunklines in hunks: | ||||
Denis Laxalde
|
r31666 | if linerange is not None and hunkrange is not None: | ||
s1, l1, s2, l2 = hunkrange | ||||
Denis Laxalde
|
r31808 | if not mdiff.hunkinrange((s2, l2), linerange): | ||
Denis Laxalde
|
r31666 | continue | ||
Denis Laxalde
|
r31276 | lines.extend(hunklines) | ||
if lines: | ||||
Gregory Szorc
|
r36901 | yield web.tmpl('diffblock', parity=next(parity), blockno=blockno, | ||
lines=prettyprintlines(lines, blockno)) | ||||
Dirkjan Ochtman
|
r7345 | |||
wujek srujek
|
r17302 | def compare(tmpl, context, leftlines, rightlines): | ||
wujek srujek
|
r17202 | '''Generator function that provides side-by-side comparison data.''' | ||
def compline(type, leftlineno, leftline, rightlineno, rightline): | ||||
Augie Fackler
|
r36728 | lineid = leftlineno and ("l%d" % leftlineno) or '' | ||
lineid += rightlineno and ("r%d" % rightlineno) or '' | ||||
llno = '%d' % leftlineno if leftlineno else '' | ||||
rlno = '%d' % rightlineno if rightlineno else '' | ||||
wujek srujek
|
r17202 | return tmpl('comparisonline', | ||
type=type, | ||||
lineid=lineid, | ||||
Gregory Szorc
|
r24712 | leftlineno=leftlineno, | ||
Augie Fackler
|
r36728 | leftlinenumber="% 6s" % llno, | ||
wujek srujek
|
r17202 | leftline=leftline or '', | ||
Gregory Szorc
|
r24712 | rightlineno=rightlineno, | ||
Augie Fackler
|
r36728 | rightlinenumber="% 6s" % rlno, | ||
wujek srujek
|
r17202 | 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
|
r17302 | yield tmpl('comparisonblock', lines=getblock(s.get_opcodes())) | ||
wujek srujek
|
r17202 | else: | ||
wujek srujek
|
r17302 | for oc in s.get_grouped_opcodes(n=context): | ||
yield tmpl('comparisonblock', lines=getblock(oc)) | ||||
wujek srujek
|
r17202 | |||
Weiwen
|
r17991 | def diffstatgen(ctx, basectx): | ||
Steven Brown
|
r14570 | '''Generator function that provides the diffstat data.''' | ||
Steven Brown
|
r14490 | |||
Yuya Nishihara
|
r35445 | stats = patch.diffstatdata( | ||
util.iterlines(ctx.diff(basectx, noprefix=False))) | ||||
Steven Brown
|
r14490 | maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats) | ||
Steven Brown
|
r14570 | while True: | ||
yield stats, maxname, maxtotal, addtotal, removetotal, binary | ||||
def diffsummary(statgen): | ||||
'''Return a short summary of the diff.''' | ||||
timeless
|
r29216 | stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen) | ||
Steven Brown
|
r14570 | 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.''' | ||||
timeless
|
r29216 | stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen) | ||
Steven Brown
|
r14561 | files = ctx.files() | ||
Steven Brown
|
r14490 | |||
Steven Brown
|
r14561 | def pct(i): | ||
if maxtotal == 0: | ||||
return 0 | ||||
return (float(i) / maxtotal) * 100 | ||||
Steven Brown
|
r14490 | |||
Steven Brown
|
r14562 | fileno = 0 | ||
Steven Brown
|
r14561 | for filename, adds, removes, isbinary in stats: | ||
r35315 | template = 'diffstatlink' if filename in files else 'diffstatnolink' | |||
Steven Brown
|
r14561 | total = adds + removes | ||
Steven Brown
|
r14562 | fileno += 1 | ||
yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno, | ||||
Steven Brown
|
r14561 | total=total, addpct=pct(adds), removepct=pct(removes), | ||
timeless
|
r29216 | parity=next(parity)) | ||
Steven Brown
|
r14490 | |||
Dirkjan Ochtman
|
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
|
r18367 | for key, value in sorted(self.vars.iteritems()): | ||
Augie Fackler
|
r34808 | yield {'name': key, | ||
'value': pycompat.bytestr(value), | ||||
'separator': separator, | ||||
} | ||||
Dirkjan Ochtman
|
r7345 | separator = '&' | ||
Augie Fackler
|
r12691 | |||
Yuya Nishihara
|
r27007 | class wsgiui(uimod.ui): | ||
Augie Fackler
|
r12691 | # default termwidth breaks under mod_wsgi | ||
def termwidth(self): | ||||
return 80 | ||||
Gregory Szorc
|
r26162 | |||
def getwebsubs(repo): | ||||
websubtable = [] | ||||
websubdefs = repo.ui.configitems('websub') | ||||
# we must maintain interhg backwards compatibility | ||||
websubdefs += repo.ui.configitems('interhg') | ||||
for key, pattern in websubdefs: | ||||
# grab the delimiter from the character after the "s" | ||||
Pulkit Goyal
|
r36198 | unesc = pattern[1:2] | ||
Gregory Szorc
|
r26162 | delim = re.escape(unesc) | ||
# identify portions of the pattern, taking care to avoid escaped | ||||
# delimiters. the replace format and flags are optional, but | ||||
# delimiters are required. | ||||
match = re.match( | ||||
Pulkit Goyal
|
r36201 | br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$' | ||
Gregory Szorc
|
r26162 | % (delim, delim, delim), pattern) | ||
if not match: | ||||
repo.ui.warn(_("websub: invalid pattern for %s: %s\n") | ||||
% (key, pattern)) | ||||
continue | ||||
# we need to unescape the delimiter for regexp and format | ||||
Pulkit Goyal
|
r36201 | delim_re = re.compile(br'(?<!\\)\\%s' % delim) | ||
Gregory Szorc
|
r26162 | regexp = delim_re.sub(unesc, match.group(1)) | ||
format = delim_re.sub(unesc, match.group(2)) | ||||
# the pattern allows for 6 regexp flags, so set them if necessary | ||||
flagin = match.group(3) | ||||
flags = 0 | ||||
if flagin: | ||||
for flag in flagin.upper(): | ||||
flags |= re.__dict__[flag] | ||||
try: | ||||
regexp = re.compile(regexp, flags) | ||||
websubtable.append((regexp, format)) | ||||
except re.error: | ||||
repo.ui.warn(_("websub: invalid regexp for %s: %s\n") | ||||
% (key, regexp)) | ||||
return websubtable | ||||