##// END OF EJS Templates
match: match explicit file using a set...
match: match explicit file using a set The matcher as all the logic to do quick comparison against explicit patterns, however the pattern matcher was shadowing the code using that set and used the compiled regex pattern in all cases, which is quite slow. We restore the usage of the set based matching to boost performance. Building the regexp is still consuming a large amount of time (actually, the majority of the time), which is still silly. Maybe using re2 would help that, but this is a quest for another adventure. Another path to improve this is to have a pattern type dedicated to match the exact path to a file only (not a directory). This pattern could use the set matching only and be skipped in the regex all together. Benchmarks ========== In the following benchmark we are comparing the `hg cat` and `hg files` run time when matching against all files in the repository. They are run: - without the rust extensions - with the standard python engine (so without re2) Performance improvement in this series -------------------------------------- ###### hg files ############################################################### ### mercurial-2018-08-01-zstd-sparse-revlog ### sorted base-changeset: 0.230092 seconds prev-changeset: 0.230069 seconds this-changeset: 0.211425 seconds (-8.36%) ### mercurial-2018-08-01-zstd-sparse-revlog ### shuffled base-changeset: 0.234235 seconds prev-changeset: 0.231165 seconds (-1.38%) this-changeset: 0.212300 seconds (-9.43%) ### pypy-2018-08-01-zstd-sparse-revlog ### sorted base-changeset: 0.613567 seconds prev-changeset: 0.616799 seconds this-changeset: 0.510852 seconds (-16.82%) ### pypy-2018-08-01-zstd-sparse-revlog ### shuffled base-changeset: 0.801880 seconds prev-changeset: 0.616393 seconds (-23.22%) this-changeset: 0.511903 seconds (-36.23%) ### netbeans-2018-08-01-zstd-sparse-revlog ### sorted base-changeset: 21.541828 seconds prev-changeset: 21.586773 seconds this-changeset: 13.648347 seconds (-36.76%) ### netbeans-2018-08-01-zstd-sparse-revlog ### shuffled base-changeset: 172.759857 seconds prev-changeset: 21.908197 seconds (-87.32%) this-changeset: 13.945110 seconds (-91.93%) ### mozilla-central-2018-08-01-zstd-sparse-revlog ### sorted base-changeset: 62.474221 seconds prev-changeset: 61.279490 seconds (-1.22%) this-changeset: 29.529469 seconds (-52.40%) ### mozilla-central-2018-08-01-zstd-sparse-revlog ### shuffled base-changeset: 1364.180218 seconds prev-changeset: 62.473549 seconds (-95.40%) this-changeset: 30.625249 seconds (-97.75%) ###### hg cat ################################################################# ### mercurial-2018-08-01-zstd-sparse-revlog ### sorted base-changeset: 0.764407 seconds prev-changeset: 0.763883 seconds this-changeset: 0.737326 seconds (-3.68%) ### mercurial-2018-08-01-zstd-sparse-revlog ### shuffled base-changeset: 0.768924 seconds prev-changeset: 0.765848 seconds this-changeset: 0.174d0b seconds (-4.44%) ### pypy-2018-08-01-zstd-sparse-revlog ### sorted base-changeset: 2.065220 seconds prev-changeset: 2.070498 seconds this-changeset: 1.939482 seconds (-6.08%) ### pypy-2018-08-01-zstd-sparse-revlog ### shuffled base-changeset: 2.276388 seconds prev-changeset: 2.069197 seconds (-9.15%) this-changeset: 1.931746 seconds (-15.19%) ### netbeans-2018-08-01-zstd-sparse-revlog ### sorted base-changeset: 40.967983 seconds prev-changeset: 41.392423 seconds this-changeset: 32.181681 seconds (-22.20%) ### netbeans-2018-08-01-zstd-sparse-revlog ### shuffled base-changeset: 216.388709 seconds prev-changeset: 41.648689 seconds (-80.88%) this-changeset: 32.580817 seconds (-85.04%) ### mozilla-central-2018-08-01-zstd-sparse-revlog ### sorted base-changeset: 105.228510 seconds prev-changeset: 103.315670 seconds (-1.23%) this-changeset: 69.416118 seconds (-33.64%) ### mozilla-central-2018-08-01-zstd-sparse-revlog ### shuffled base-changeset: 1448.722784 seconds prev-changeset: 104.369358 seconds (-92.80%) this-changeset: 70.554789 seconds (-95.13%) Different way to list the same data with this revision ------------------------------------------------------ ###### hg files ############################################################### ### mercurial-2018-08-01-zstd-sparse-revlog root: 0.119182 seconds glob: 0.120697 seconds (+1.27%) sorted: 0.211425 seconds (+77.40%) shuffled: 0.212300 seconds (+78.13%) ### pypy-2018-08-01-zstd-sparse-revlog root: 0.121986 seconds glob: 0.124822 seconds (+2.32%) sorted: 0.510852 seconds (+318.78%) shuffled: 0.511903 seconds (+319.64%) ### netbeans-2018-08-01-zstd-sparse-revlog root: 0.173984 seconds glob: 0.227203 seconds (+30.59%) sorted: 13.648347 seconds (+7744.59%) shuffled: 13.945110 seconds (+7915.16%) ### mozilla-central-2018-08-01-zstd-sparse-revlog root: 0.366463 seconds glob: 0.491030 seconds (+33.99%) sorted: 29.529469 seconds (+7957.96%) shuffled: 30.625249 seconds (+8256.97%) ###### hg cat ################################################################# ### mercurial-2018-08-01-zstd-sparse-revlog glob: 0.647471 seconds root: 0.643120 seconds shuffled: 0.174d0b seconds (+13.92%) sorted: 0.737326 seconds (+13.88%) ### mozilla-central-2018-08-01-zstd-sparse-revlog glob: 40.596983 seconds root: 40.129136 seconds shuffled: 70.554789 seconds (+73.79%) sorted: 69.416118 seconds (+70.99%) ### netbeans-2018-08-01-zstd-sparse-revlog glob: 18.777924 seconds root: 18.613905 seconds shuffled: 32.580817 seconds (+73.51%) sorted: 32.181681 seconds (+71.38%) ### pypy-2018-08-01-zstd-sparse-revlog glob: 1.555319 seconds root: 1.536534 seconds shuffled: 1.931746 seconds (+24.20%) sorted: 1.939482 seconds (+24.70%)

File last commit:

r50179:d44e3c45 default
r51286:81c7d04f stable
Show More
webutil.py
943 lines | 26.9 KiB | text/x-python | PythonLexer
# hgweb/webutil.py - utility library for the web interface.
#
# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
# Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
import copy
import difflib
import os
import re
from ..i18n import _
from ..node import hex, short
from ..pycompat import setattr
from .common import (
ErrorResponse,
HTTP_BAD_REQUEST,
HTTP_NOT_FOUND,
paritygen,
)
from .. import (
context,
diffutil,
error,
match,
mdiff,
obsutil,
patch,
pathutil,
pycompat,
scmutil,
templatefilters,
templatekw,
templateutil,
ui as uimod,
util,
)
from ..utils import stringutil
archivespecs = util.sortdict(
(
(b'zip', (b'application/zip', b'zip', b'.zip', None)),
(b'gz', (b'application/x-gzip', b'tgz', b'.tar.gz', None)),
(b'bz2', (b'application/x-bzip2', b'tbz2', b'.tar.bz2', None)),
)
)
def archivelist(ui, nodeid, url=None):
allowed = ui.configlist(b'web', b'allow-archive', untrusted=True)
archives = []
for typ, spec in archivespecs.items():
if typ in allowed or ui.configbool(
b'web', b'allow' + typ, untrusted=True
):
archives.append(
{
b'type': typ,
b'extension': spec[2],
b'node': nodeid,
b'url': url,
}
)
return templateutil.mappinglist(archives)
def up(p):
if p[0:1] != b"/":
p = b"/" + p
if p[-1:] == b"/":
p = p[:-1]
up = os.path.dirname(p)
if up == b"/":
return b"/"
return up + b"/"
def _navseq(step, firststep=None):
if firststep:
yield firststep
if firststep >= 20 and firststep <= 40:
firststep = 50
yield firststep
assert step > 0
assert firststep > 0
while step <= firststep:
step *= 10
while True:
yield 1 * step
yield 3 * step
step *= 10
class revnav:
def __init__(self, repo):
"""Navigation generation object
:repo: repo object we generate nav for
"""
# used for hex generation
self._revlog = repo.changelog
def __nonzero__(self):
"""return True if any revision to navigate over"""
return self._first() is not None
__bool__ = __nonzero__
def _first(self):
"""return the minimum non-filtered changeset or None"""
try:
return next(iter(self._revlog))
except StopIteration:
return None
def hex(self, rev):
return hex(self._revlog.node(rev))
def gen(self, pos, pagelen, limit):
"""computes label and revision id for navigation link
:pos: is the revision relative to which we generate navigation.
:pagelen: the size of each navigation page
:limit: how far shall we link
The return is:
- a single element mappinglist
- containing a dictionary with a `before` and `after` key
- values are dictionaries with `label` and `node` keys
"""
if not self:
# empty repo
return templateutil.mappinglist(
[
{
b'before': templateutil.mappinglist([]),
b'after': templateutil.mappinglist([]),
},
]
)
targets = []
for f in _navseq(1, pagelen):
if f > limit:
break
targets.append(pos + f)
targets.append(pos - f)
targets.sort()
first = self._first()
navbefore = [{b'label': b'(%i)' % first, b'node': self.hex(first)}]
navafter = []
for rev in targets:
if rev not in self._revlog:
continue
if pos < rev < limit:
navafter.append(
{b'label': b'+%d' % abs(rev - pos), b'node': self.hex(rev)}
)
if 0 < rev < pos:
navbefore.append(
{b'label': b'-%d' % abs(rev - pos), b'node': self.hex(rev)}
)
navafter.append({b'label': b'tip', b'node': b'tip'})
# TODO: maybe this can be a scalar object supporting tomap()
return templateutil.mappinglist(
[
{
b'before': templateutil.mappinglist(navbefore),
b'after': templateutil.mappinglist(navafter),
},
]
)
class filerevnav(revnav):
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)))
# TODO: maybe this can be a wrapper class for changectx/filectx list, which
# yields {'ctx': ctx}
def _ctxsgen(context, ctxs):
for s in ctxs:
d = {
b'node': s.hex(),
b'rev': s.rev(),
b'user': s.user(),
b'date': s.date(),
b'description': s.description(),
b'branch': s.branch(),
}
if util.safehasattr(s, b'path'):
d[b'file'] = s.path()
yield d
def _siblings(siblings=None, hiderev=None):
if siblings is None:
siblings = []
siblings = [s for s in siblings if s.node() != s.repo().nullid]
if len(siblings) == 1 and siblings[0].rev() == hiderev:
siblings = []
return templateutil.mappinggenerator(_ctxsgen, args=(siblings,))
def difffeatureopts(req, ui, section):
diffopts = diffutil.difffeatureopts(
ui, untrusted=True, section=section, whitespace=True
)
for k in (
b'ignorews',
b'ignorewsamount',
b'ignorewseol',
b'ignoreblanklines',
):
v = req.qsparams.get(k)
if v is not None:
v = stringutil.parsebool(v)
setattr(diffopts, k, v if v is not None else True)
return diffopts
def annotate(req, fctx, ui):
diffopts = difffeatureopts(req, ui, b'annotate')
return fctx.annotate(follow=True, diffopts=diffopts)
def parents(ctx, hide=None):
if isinstance(ctx, context.basefilectx):
introrev = ctx.introrev()
if ctx.changectx().rev() != introrev:
return _siblings([ctx.repo()[introrev]], hide)
return _siblings(ctx.parents(), hide)
def children(ctx, hide=None):
return _siblings(ctx.children(), hide)
def renamelink(fctx):
r = fctx.renamed()
if r:
return templateutil.mappinglist([{b'file': r[0], b'node': hex(r[1])}])
return templateutil.mappinglist([])
def nodetagsdict(repo, node):
return templateutil.hybridlist(repo.nodetags(node), name=b'name')
def nodebookmarksdict(repo, node):
return templateutil.hybridlist(repo.nodebookmarks(node), name=b'name')
def nodebranchdict(repo, ctx):
branches = []
branch = ctx.branch()
# If this is an empty repo, ctx.node() == nullid,
# ctx.branch() == 'default'.
try:
branchnode = repo.branchtip(branch)
except error.RepoLookupError:
branchnode = None
if branchnode == ctx.node():
branches.append(branch)
return templateutil.hybridlist(branches, name=b'name')
def nodeinbranch(repo, ctx):
branches = []
branch = ctx.branch()
try:
branchnode = repo.branchtip(branch)
except error.RepoLookupError:
branchnode = None
if branch != b'default' and branchnode != ctx.node():
branches.append(branch)
return templateutil.hybridlist(branches, name=b'name')
def nodebranchnodefault(ctx):
branches = []
branch = ctx.branch()
if branch != b'default':
branches.append(branch)
return templateutil.hybridlist(branches, name=b'name')
def _nodenamesgen(context, f, node, name):
for t in f(node):
yield {name: t}
def showtag(repo, t1, node=None):
if node is None:
node = repo.nullid
args = (repo.nodetags, node, b'tag')
return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
def showbookmark(repo, t1, node=None):
if node is None:
node = repo.nullid
args = (repo.nodebookmarks, node, b'bookmark')
return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
def branchentries(repo, stripecount, limit=0):
tips = []
heads = repo.heads()
parity = paritygen(stripecount)
sortkey = lambda item: (not item[1], item[0].rev())
def entries(context):
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 = b'closed'
elif ctx.node() not in heads:
status = b'inactive'
else:
status = b'open'
yield {
b'parity': next(parity),
b'branch': ctx.branch(),
b'status': status,
b'node': ctx.hex(),
b'date': ctx.date(),
}
return templateutil.mappinggenerator(entries)
def cleanpath(repo, path):
path = path.lstrip(b'/')
auditor = pathutil.pathauditor(repo.root, realfs=False)
return pathutil.canonpath(repo.root, b'', path, auditor=auditor)
def changectx(repo, req):
changeid = b"tip"
if b'node' in req.qsparams:
changeid = req.qsparams[b'node']
ipos = changeid.find(b':')
if ipos != -1:
changeid = changeid[(ipos + 1) :]
return scmutil.revsymbol(repo, changeid)
def basechangectx(repo, req):
if b'node' in req.qsparams:
changeid = req.qsparams[b'node']
ipos = changeid.find(b':')
if ipos != -1:
changeid = changeid[:ipos]
return scmutil.revsymbol(repo, changeid)
return None
def filectx(repo, req):
if b'file' not in req.qsparams:
raise ErrorResponse(HTTP_NOT_FOUND, b'file not given')
path = cleanpath(repo, req.qsparams[b'file'])
if b'node' in req.qsparams:
changeid = req.qsparams[b'node']
elif b'filenode' in req.qsparams:
changeid = req.qsparams[b'filenode']
else:
raise ErrorResponse(HTTP_NOT_FOUND, b'node or filenode not given')
try:
fctx = scmutil.revsymbol(repo, changeid)[path]
except error.RepoError:
fctx = repo.filectx(path, fileid=changeid)
return fctx
def linerange(req):
linerange = req.qsparams.getall(b'linerange')
if not linerange:
return None
if len(linerange) > 1:
raise ErrorResponse(HTTP_BAD_REQUEST, b'redundant linerange parameter')
try:
fromline, toline = map(int, linerange[0].split(b':', 1))
except ValueError:
raise ErrorResponse(HTTP_BAD_REQUEST, b'invalid linerange parameter')
try:
return util.processlinerange(fromline, toline)
except error.ParseError as exc:
raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
def formatlinerange(fromline, toline):
return b'%d:%d' % (fromline + 1, toline)
def _succsandmarkersgen(context, mapping):
repo = context.resource(mapping, b'repo')
itemmappings = templatekw.showsuccsandmarkers(context, mapping)
for item in itemmappings.tovalue(context, mapping):
item[b'successors'] = _siblings(
repo[successor] for successor in item[b'successors']
)
yield item
def succsandmarkers(context, mapping):
return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
# teach templater succsandmarkers is switched to (context, mapping) API
succsandmarkers._requires = {b'repo', b'ctx'}
def _whyunstablegen(context, mapping):
repo = context.resource(mapping, b'repo')
ctx = context.resource(mapping, b'ctx')
entries = obsutil.whyunstable(repo, ctx)
for entry in entries:
if entry.get(b'divergentnodes'):
entry[b'divergentnodes'] = _siblings(entry[b'divergentnodes'])
yield entry
def whyunstable(context, mapping):
return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
whyunstable._requires = {b'repo', b'ctx'}
def commonentry(repo, ctx):
node = scmutil.binnode(ctx)
return {
# 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.
b'ctx': ctx,
b'rev': ctx.rev(),
b'node': hex(node),
b'author': ctx.user(),
b'desc': ctx.description(),
b'date': ctx.date(),
b'extra': ctx.extra(),
b'phase': ctx.phasestr(),
b'obsolete': ctx.obsolete(),
b'succsandmarkers': succsandmarkers,
b'instabilities': templateutil.hybridlist(
ctx.instabilities(), name=b'instability'
),
b'whyunstable': whyunstable,
b'branch': nodebranchnodefault(ctx),
b'inbranch': nodeinbranch(repo, ctx),
b'branches': nodebranchdict(repo, ctx),
b'tags': nodetagsdict(repo, node),
b'bookmarks': nodebookmarksdict(repo, node),
b'parent': lambda context, mapping: parents(ctx),
b'child': lambda context, mapping: children(ctx),
}
def changelistentry(web, ctx):
"""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 = scmutil.binnode(ctx)
showtags = showtag(repo, b'changelogtag', n)
files = listfilediffs(ctx.files(), n, web.maxfiles)
entry = commonentry(repo, ctx)
entry.update(
{
b'allparents': lambda context, mapping: parents(ctx),
b'parent': lambda context, mapping: parents(ctx, rev - 1),
b'child': lambda context, mapping: children(ctx, rev + 1),
b'changelogtag': showtags,
b'files': files,
}
)
return entry
def changelistentries(web, revs, maxcount, parityfn):
"""Emit up to N records for an iterable of revisions."""
repo = web.repo
count = 0
for rev in revs:
if count >= maxcount:
break
count += 1
entry = changelistentry(web, repo[rev])
entry[b'parity'] = next(parityfn)
yield entry
def symrevorshortnode(req, ctx):
if b'node' in req.qsparams:
return templatefilters.revescape(req.qsparams[b'node'])
else:
return short(scmutil.binnode(ctx))
def _listfilesgen(context, ctx, stripecount):
parity = paritygen(stripecount)
filesadded = ctx.filesadded()
for blockno, f in enumerate(ctx.files()):
if f not in ctx:
status = b'removed'
elif f in filesadded:
status = b'added'
else:
status = b'modified'
template = b'filenolink' if status == b'removed' else b'filenodelink'
yield context.process(
template,
{
b'node': ctx.hex(),
b'file': f,
b'blockno': blockno + 1,
b'parity': next(parity),
b'status': status,
},
)
def changesetentry(web, ctx):
'''Obtain a dictionary to be used to render the "changeset" template.'''
showtags = showtag(web.repo, b'changesettag', scmutil.binnode(ctx))
showbookmarks = showbookmark(
web.repo, b'changesetbookmark', scmutil.binnode(ctx)
)
showbranch = nodebranchnodefault(ctx)
basectx = basechangectx(web.repo, web.req)
if basectx is None:
basectx = ctx.p1()
style = web.config(b'web', b'style')
if b'style' in web.req.qsparams:
style = web.req.qsparams[b'style']
diff = diffs(web, ctx, basectx, None, style)
parity = paritygen(web.stripecount)
diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
diffstats = diffstat(ctx, diffstatsgen, parity)
return dict(
diff=diff,
symrev=symrevorshortnode(web.req, ctx),
basenode=basectx.hex(),
changesettag=showtags,
changesetbookmark=showbookmarks,
changesetbranch=showbranch,
files=templateutil.mappedgenerator(
_listfilesgen, args=(ctx, web.stripecount)
),
diffsummary=lambda context, mapping: diffsummary(diffstatsgen),
diffstat=diffstats,
archives=web.archivelist(ctx.hex()),
**pycompat.strkwargs(commonentry(web.repo, ctx))
)
def _listfilediffsgen(context, files, node, max):
for f in files[:max]:
yield context.process(b'filedifflink', {b'node': hex(node), b'file': f})
if len(files) > max:
yield context.process(b'fileellipses', {})
def listfilediffs(files, node, max):
return templateutil.mappedgenerator(
_listfilediffsgen, args=(files, node, max)
)
def _prettyprintdifflines(context, lines, blockno, lineidprefix):
for lineno, l in enumerate(lines, 1):
difflineno = b"%d.%d" % (blockno, lineno)
if l.startswith(b'+'):
ltype = b"difflineplus"
elif l.startswith(b'-'):
ltype = b"difflineminus"
elif l.startswith(b'@'):
ltype = b"difflineat"
else:
ltype = b"diffline"
yield context.process(
ltype,
{
b'line': l,
b'lineno': lineno,
b'lineid': lineidprefix + b"l%s" % difflineno,
b'linenumber': b"% 8s" % difflineno,
},
)
def _diffsgen(
context,
repo,
ctx,
basectx,
files,
style,
stripecount,
linerange,
lineidprefix,
):
if files:
m = match.exact(files)
else:
m = match.always()
diffopts = patch.diffopts(repo.ui, untrusted=True)
parity = paritygen(stripecount)
diffhunks = patch.diffhunks(repo, basectx, ctx, m, opts=diffopts)
for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
if style != b'raw':
header = header[1:]
lines = [h + b'\n' for h in header]
for hunkrange, hunklines in hunks:
if linerange is not None and hunkrange is not None:
s1, l1, s2, l2 = hunkrange
if not mdiff.hunkinrange((s2, l2), linerange):
continue
lines.extend(hunklines)
if lines:
l = templateutil.mappedgenerator(
_prettyprintdifflines, args=(lines, blockno, lineidprefix)
)
yield {
b'parity': next(parity),
b'blockno': blockno,
b'lines': l,
}
def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=b''):
args = (
web.repo,
ctx,
basectx,
files,
style,
web.stripecount,
linerange,
lineidprefix,
)
return templateutil.mappinggenerator(
_diffsgen, args=args, name=b'diffblock'
)
def _compline(type, leftlineno, leftline, rightlineno, rightline):
lineid = leftlineno and (b"l%d" % leftlineno) or b''
lineid += rightlineno and (b"r%d" % rightlineno) or b''
llno = b'%d' % leftlineno if leftlineno else b''
rlno = b'%d' % rightlineno if rightlineno else b''
return {
b'type': type,
b'lineid': lineid,
b'leftlineno': leftlineno,
b'leftlinenumber': b"% 6s" % llno,
b'leftline': leftline or b'',
b'rightlineno': rightlineno,
b'rightlinenumber': b"% 6s" % rlno,
b'rightline': rightline or b'',
}
def _getcompblockgen(context, leftlines, rightlines, opcodes):
for type, llo, lhi, rlo, rhi in opcodes:
type = pycompat.sysbytes(type)
len1 = lhi - llo
len2 = rhi - rlo
count = min(len1, len2)
for i in range(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 range(llo + count, lhi):
yield _compline(
type=type,
leftlineno=i + 1,
leftline=leftlines[i],
rightlineno=None,
rightline=None,
)
elif len2 > len1:
for i in range(rlo + count, rhi):
yield _compline(
type=type,
leftlineno=None,
leftline=None,
rightlineno=i + 1,
rightline=rightlines[i],
)
def _getcompblock(leftlines, rightlines, opcodes):
args = (leftlines, rightlines, opcodes)
return templateutil.mappinggenerator(
_getcompblockgen, args=args, name=b'comparisonline'
)
def _comparegen(context, contextnum, leftlines, rightlines):
'''Generator function that provides side-by-side comparison data.'''
s = difflib.SequenceMatcher(None, leftlines, rightlines)
if contextnum < 0:
l = _getcompblock(leftlines, rightlines, s.get_opcodes())
yield {b'lines': l}
else:
for oc in s.get_grouped_opcodes(n=contextnum):
l = _getcompblock(leftlines, rightlines, oc)
yield {b'lines': l}
def compare(contextnum, leftlines, rightlines):
args = (contextnum, leftlines, rightlines)
return templateutil.mappinggenerator(
_comparegen, args=args, name=b'comparisonblock'
)
def diffstatgen(ui, ctx, basectx):
'''Generator function that provides the diffstat data.'''
diffopts = patch.diffopts(ui, {b'noprefix': False})
stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx, opts=diffopts)))
maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
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 = next(statgen)
return _(b' %d files changed, %d insertions(+), %d deletions(-)\n') % (
len(stats),
addtotal,
removetotal,
)
def _diffstattmplgen(context, ctx, statgen, parity):
stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
files = ctx.files()
def pct(i):
if maxtotal == 0:
return 0
return (float(i) / maxtotal) * 100
fileno = 0
for filename, adds, removes, isbinary in stats:
template = b'diffstatlink' if filename in files else b'diffstatnolink'
total = adds + removes
fileno += 1
yield context.process(
template,
{
b'node': ctx.hex(),
b'file': filename,
b'fileno': fileno,
b'total': total,
b'addpct': pct(adds),
b'removepct': pct(removes),
b'parity': next(parity),
},
)
def diffstat(ctx, statgen, parity):
'''Return a diffstat template for each file in the diff.'''
args = (ctx, statgen, parity)
return templateutil.mappedgenerator(_diffstattmplgen, args=args)
class sessionvars(templateutil.wrapped):
def __init__(self, vars, start=b'?'):
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 contains(self, context, mapping, item):
item = templateutil.unwrapvalue(context, mapping, item)
return item in self._vars
def getmember(self, context, mapping, key):
key = templateutil.unwrapvalue(context, mapping, key)
return self._vars.get(key)
def getmin(self, context, mapping):
raise error.ParseError(_(b'not comparable'))
def getmax(self, context, mapping):
raise error.ParseError(_(b'not comparable'))
def filter(self, context, mapping, select):
# implement if necessary
raise error.ParseError(_(b'not filterable'))
def itermaps(self, context):
separator = self._start
for key, value in sorted(self._vars.items()):
yield {
b'name': key,
b'value': pycompat.bytestr(value),
b'separator': separator,
}
separator = b'&'
def join(self, context, mapping, sep):
# could be '{separator}{name}={value|urlescape}'
raise error.ParseError(_(b'not displayable without template'))
def show(self, context, mapping):
return self.join(context, mapping, b'')
def tobool(self, context, mapping):
return bool(self._vars)
def tovalue(self, context, mapping):
return self._vars
class wsgiui(uimod.ui):
# default termwidth breaks under mod_wsgi
def termwidth(self):
return 80
def getwebsubs(repo):
websubtable = []
websubdefs = repo.ui.configitems(b'websub')
# we must maintain interhg backwards compatibility
websubdefs += repo.ui.configitems(b'interhg')
for key, pattern in websubdefs:
# grab the delimiter from the character after the "s"
unesc = pattern[1:2]
delim = stringutil.reescape(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(
br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
% (delim, delim, delim),
pattern,
)
if not match:
repo.ui.warn(
_(b"websub: invalid pattern for %s: %s\n") % (key, pattern)
)
continue
# we need to unescape the delimiter for regexp and format
delim_re = re.compile(br'(?<!\\)\\%s' % delim)
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 pycompat.sysstr(flagin.upper()):
flags |= re.__dict__[flag]
try:
regexp = re.compile(regexp, flags)
websubtable.append((regexp, format))
except re.error:
repo.ui.warn(
_(b"websub: invalid regexp for %s: %s\n") % (key, regexp)
)
return websubtable
def getgraphnode(repo, ctx):
return templatekw.getgraphnodecurrent(
repo, ctx, {}
) + templatekw.getgraphnodesymbol(ctx)