sapling/mercurial/hgweb/webutil.py

543 lines
17 KiB
Python
Raw Normal View History

2008-03-28 21:37:28 +03:00
# 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>
#
# 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.
2008-03-28 21:37:28 +03:00
import os, copy
from mercurial import match, patch, error, ui, util, pathutil, context
from mercurial.i18n import _
hgweb: provide symrev (symbolic revision) property to the templates One of the features of hgweb is that current position in repo history is remembered between separate requests. That is, links from /rev/<node_hash> lead to /file/<node_hash> or /log/<node_hash>, so it's easy to dig deep into the history. However, such links could only use node hashes and local revision numbers, so while staying at one exact revision is easy, staying on top of the changes is not, because hashes presumably can't change (local revision numbers can, but probably not in a way you'd find useful for navigating). So while you could use 'tip' or 'default' in a url, links on that page would be permanent. This is not always desired (think /rev/tip or /graph/stable or /log/@) and is sometimes just confusing (i.e. /log/<not the tip hash>, when recent history is not displayed). And if user changed url deliberately to say default instead of <some node hash>, the page ignores that fact and uses node hash in its links, which means that navigation is, in a way, broken. This new property, symrev, is used for storing current revision the way it was specified, so then templates can use it in links and thus "not dereference" the symbolic revision. It is an additional way to produce links, so not every link needs to drop {node|short} in favor of {symrev}, many will still use node hash (log and filelog entries, annotate lines, etc). Some pages (e.g. summary, tags) always use the tip changeset for their context, in such cases symrev is set to 'tip'. This is needed in case the pages want to provide archive links. highlight extension needs to be updated, since _filerevision now takes an additional positional argument (signature "web, req, tmpl" is used by most of webcommands.py functions). More references to symbolic revisions and related gripes: issue2296, issue2826, issue3594, issue3634.
2015-06-15 21:07:25 +03:00
from mercurial.node import hex, nullid, short
from mercurial.templatefilters import revescape
from common import ErrorResponse, paritygen
from common import HTTP_NOT_FOUND
import difflib
2008-03-28 21:37:28 +03:00
2008-03-28 21:40:44 +03:00
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 _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(object):
2013-01-14 00:36:35 +04:00
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
def _first(self):
"""return the minimum non-filtered changeset or None"""
try:
return iter(self._revlog).next()
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
2013-01-14 00:36:35 +04:00
:pos: is the revision relative to which we generate navigation.
:pagelen: the size of each navigation page
:limit: how far shall we link
2008-03-28 21:40:44 +03:00
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
"""
if not self:
# empty repo
return ({'before': (), 'after': ()},)
2008-03-28 21:40:44 +03:00
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 = [("(%i)" % first, self.hex(first))]
navafter = []
for rev in targets:
if rev not in self._revlog:
continue
if pos < rev < limit:
navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
if 0 < rev < pos:
navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
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)},)
2008-03-28 21:40:44 +03:00
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)))
def _siblings(siblings=[], hiderev=None):
2008-03-28 21:37:28 +03:00
siblings = [s for s in siblings if s.node() != nullid]
if len(siblings) == 1 and siblings[0].rev() == hiderev:
return
for s in siblings:
d = {'node': s.hex(), 'rev': s.rev()}
d['user'] = s.user()
d['date'] = s.date()
d['description'] = s.description()
d['branch'] = s.branch()
if util.safehasattr(s, 'path'):
2008-03-28 21:37:28 +03:00
d['file'] = s.path()
yield d
def parents(ctx, hide=None):
hgweb: use introrev() for finding parents (issue4506) The issue is titled "filtered revision 'XXX' (not in 'served' subset)" and that is the error message you sometimes get when trying to look at a file (/file or /annotate) in hgweb. For example: http://hg.intevation.org/mercurial/crew/file/8414f8487b33/mercurial/cmdutil.py This happens when a parent revision for a file is hidden, thus it is not 'served' and isn't accessible in hgweb by default. When hgweb tries to access such changeset, it produces the error and HTTP status code 404. Another detail is that the parents() function, that is used in multiple places in hgweb, sometimes returned changesets that were obsoleted by the current changeset for the file. For example, when using rebase with evolve and rebasing a divergent changeset that introduces a file on top of current branch. Or grafting a change and making the new grafted changeset obsolete the source (shown in the test case). The result is the same - the obsoleted changeset was mistakingly returned from parents(), even though it's not a parent and the only link to the new changeset is an obsoletion marker (and rebase/graft metadata? not sure it matters). The problem is fixed by using introrev() instead of linkrev() for finding parents. This prevents parents() function from returning unrelated obsolete changesets. The test case prepares a separate repo because (afaict) all other test cases never reuse file names, so there are no files that were changed in multiple changesets. So no previously available files have obsolete changesets in their history.
2015-02-19 14:32:06 +03:00
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):
2008-04-01 08:14:25 +04:00
r = fctx.renamed()
2008-03-28 21:37:28 +03:00
if r:
return [{'file': r[0], 'node': hex(r[1])}]
2008-03-28 21:37:28 +03:00
return []
def nodetagsdict(repo, node):
return [{"name": i} for i in repo.nodetags(node)]
def nodebookmarksdict(repo, node):
return [{"name": i} for i in repo.nodebookmarks(node)]
2008-03-28 21:37:28 +03:00
def nodebranchdict(repo, ctx):
branches = []
branch = ctx.branch()
# If this is an empty repo, ctx.node() == nullid,
localrepo: add branchtip() method for faster single-branch lookups For the PyPy repo with 744 branches and 843 branch heads, this brings hg log -r default over NFS from: CallCount Recursive Total(ms) Inline(ms) module:lineno(function) 3249 0 1.3222 1.3222 <open> 3244 0 0.6211 0.6211 <method 'close' of 'file' objects> 3243 0 0.0800 0.0800 <method 'read' of 'file' objects> 3241 0 0.0660 0.0660 <method 'seek' of 'file' objects> 3905 0 0.0476 0.0476 <zlib.decompress> 3281 0 2.6756 0.0472 mercurial.changelog:182(read) +3281 0 2.5256 0.0453 +mercurial.revlog:881(revision) +3276 0 0.0389 0.0196 +mercurial.changelog:28(decodeextra) +6562 0 0.0123 0.0123 +<method 'split' of 'str' objects> +6562 0 0.0408 0.0073 +mercurial.encoding:61(tolocal) +3281 0 0.0054 0.0054 +<method 'index' of 'str' objects> 3241 0 2.2464 0.0456 mercurial.revlog:818(_loadchunk) +3241 0 0.6205 0.6205 +<method 'close' of 'file' objects> +3241 0 0.0765 0.0765 +<method 'read' of 'file' objects> +3241 0 0.0660 0.0660 +<method 'seek' of 'file' objects> +3241 0 1.4209 0.0135 +mercurial.store:374(__call__) +3241 0 0.0122 0.0107 +mercurial.revlog:810(_addchunk) 3281 0 2.5256 0.0453 mercurial.revlog:881(revision) +3280 0 0.0175 0.0175 +mercurial.revlog:305(rev) +3281 0 2.2819 0.0119 +mercurial.revlog:847(_chunkraw) +3281 0 0.0603 0.0083 +mercurial.revlog:945(_checkhash) +3281 0 0.0051 0.0051 +mercurial.revlog:349(flags) +3281 0 0.0040 0.0040 +<mercurial.mpatch.patches> 13682 0 0.0479 0.0248 <method 'decode' of 'str' objects> +7418 0 0.0228 0.0076 +encodings.utf_8:15(decode) +1 0 0.0003 0.0000 +encodings:71(search_function) 3248 0 1.3995 0.0246 mercurial.scmutil:218(__call__) +3248 0 1.3222 1.3222 +<open> +3248 0 0.0235 0.0184 +os.path:80(split) +3248 0 0.0084 0.0068 +mercurial.scmutil:92(__call__) Time: real 2.750 secs (user 0.680+0.000 sys 0.360+0.000) down to: CallCount Recursive Total(ms) Inline(ms) module:lineno(function) 55 31 0.0197 0.0163 <__import__> +1 0 0.0006 0.0002 +mercurial.context:8(<module>) +1 0 0.0042 0.0001 +mercurial.revlog:12(<module>) +1 0 0.0002 0.0001 +mercurial.match:8(<module>) +1 0 0.0003 0.0001 +mercurial.dirstate:7(<module>) +1 0 0.0057 0.0001 +mercurial.changelog:8(<module>) 1 0 0.0117 0.0032 mercurial.localrepo:525(_readbranchcache) +844 0 0.0015 0.0015 +<binascii.unhexlify> +845 0 0.0010 0.0010 +<method 'split' of 'str' objects> +843 0 0.0045 0.0009 +mercurial.encoding:61(tolocal) +843 0 0.0004 0.0004 +<method 'setdefault' of 'dict' objects> +1 0 0.0003 0.0003 +<method 'close' of 'file' objects> 3 0 0.0029 0.0029 <method 'read' of 'file' objects> 9 0 0.0018 0.0018 <open> 990 0 0.0017 0.0017 <binascii.unhexlify> 53 0 0.0016 0.0016 mercurial.demandimport:43(__init__) 862 0 0.0015 0.0015 <_codecs.utf_8_decode> 862 0 0.0037 0.0014 <method 'decode' of 'str' objects> +862 0 0.0023 0.0008 +encodings.utf_8:15(decode) 981 0 0.0011 0.0011 <method 'split' of 'str' objects> 861 0 0.0046 0.0009 mercurial.encoding:61(tolocal) +861 0 0.0037 0.0014 +<method 'decode' of 'str' objects> 862 0 0.0023 0.0008 encodings.utf_8:15(decode) +862 0 0.0015 0.0015 +<_codecs.utf_8_decode> 4 0 0.0008 0.0008 <method 'close' of 'file' objects> 179 154 0.0202 0.0004 mercurial.demandimport:83(__getattribute__) +36 11 0.0199 0.0003 +mercurial.demandimport:55(_load) +72 0 0.0001 0.0001 +mercurial.demandimport:83(__getattribute__) +36 0 0.0000 0.0000 +<getattr> 1 0 0.0015 0.0004 mercurial.tags:148(_readtagcache) Time: real 0.060 secs (user 0.030+0.000 sys 0.010+0.000)
2012-05-13 16:04:04 +04:00
# ctx.branch() == 'default'.
try:
branchnode = repo.branchtip(branch)
except error.RepoLookupError:
branchnode = None
if branchnode == ctx.node():
2008-03-28 21:37:28 +03:00
branches.append({"name": branch})
return branches
def nodeinbranch(repo, ctx):
branches = []
branch = ctx.branch()
localrepo: add branchtip() method for faster single-branch lookups For the PyPy repo with 744 branches and 843 branch heads, this brings hg log -r default over NFS from: CallCount Recursive Total(ms) Inline(ms) module:lineno(function) 3249 0 1.3222 1.3222 <open> 3244 0 0.6211 0.6211 <method 'close' of 'file' objects> 3243 0 0.0800 0.0800 <method 'read' of 'file' objects> 3241 0 0.0660 0.0660 <method 'seek' of 'file' objects> 3905 0 0.0476 0.0476 <zlib.decompress> 3281 0 2.6756 0.0472 mercurial.changelog:182(read) +3281 0 2.5256 0.0453 +mercurial.revlog:881(revision) +3276 0 0.0389 0.0196 +mercurial.changelog:28(decodeextra) +6562 0 0.0123 0.0123 +<method 'split' of 'str' objects> +6562 0 0.0408 0.0073 +mercurial.encoding:61(tolocal) +3281 0 0.0054 0.0054 +<method 'index' of 'str' objects> 3241 0 2.2464 0.0456 mercurial.revlog:818(_loadchunk) +3241 0 0.6205 0.6205 +<method 'close' of 'file' objects> +3241 0 0.0765 0.0765 +<method 'read' of 'file' objects> +3241 0 0.0660 0.0660 +<method 'seek' of 'file' objects> +3241 0 1.4209 0.0135 +mercurial.store:374(__call__) +3241 0 0.0122 0.0107 +mercurial.revlog:810(_addchunk) 3281 0 2.5256 0.0453 mercurial.revlog:881(revision) +3280 0 0.0175 0.0175 +mercurial.revlog:305(rev) +3281 0 2.2819 0.0119 +mercurial.revlog:847(_chunkraw) +3281 0 0.0603 0.0083 +mercurial.revlog:945(_checkhash) +3281 0 0.0051 0.0051 +mercurial.revlog:349(flags) +3281 0 0.0040 0.0040 +<mercurial.mpatch.patches> 13682 0 0.0479 0.0248 <method 'decode' of 'str' objects> +7418 0 0.0228 0.0076 +encodings.utf_8:15(decode) +1 0 0.0003 0.0000 +encodings:71(search_function) 3248 0 1.3995 0.0246 mercurial.scmutil:218(__call__) +3248 0 1.3222 1.3222 +<open> +3248 0 0.0235 0.0184 +os.path:80(split) +3248 0 0.0084 0.0068 +mercurial.scmutil:92(__call__) Time: real 2.750 secs (user 0.680+0.000 sys 0.360+0.000) down to: CallCount Recursive Total(ms) Inline(ms) module:lineno(function) 55 31 0.0197 0.0163 <__import__> +1 0 0.0006 0.0002 +mercurial.context:8(<module>) +1 0 0.0042 0.0001 +mercurial.revlog:12(<module>) +1 0 0.0002 0.0001 +mercurial.match:8(<module>) +1 0 0.0003 0.0001 +mercurial.dirstate:7(<module>) +1 0 0.0057 0.0001 +mercurial.changelog:8(<module>) 1 0 0.0117 0.0032 mercurial.localrepo:525(_readbranchcache) +844 0 0.0015 0.0015 +<binascii.unhexlify> +845 0 0.0010 0.0010 +<method 'split' of 'str' objects> +843 0 0.0045 0.0009 +mercurial.encoding:61(tolocal) +843 0 0.0004 0.0004 +<method 'setdefault' of 'dict' objects> +1 0 0.0003 0.0003 +<method 'close' of 'file' objects> 3 0 0.0029 0.0029 <method 'read' of 'file' objects> 9 0 0.0018 0.0018 <open> 990 0 0.0017 0.0017 <binascii.unhexlify> 53 0 0.0016 0.0016 mercurial.demandimport:43(__init__) 862 0 0.0015 0.0015 <_codecs.utf_8_decode> 862 0 0.0037 0.0014 <method 'decode' of 'str' objects> +862 0 0.0023 0.0008 +encodings.utf_8:15(decode) 981 0 0.0011 0.0011 <method 'split' of 'str' objects> 861 0 0.0046 0.0009 mercurial.encoding:61(tolocal) +861 0 0.0037 0.0014 +<method 'decode' of 'str' objects> 862 0 0.0023 0.0008 encodings.utf_8:15(decode) +862 0 0.0015 0.0015 +<_codecs.utf_8_decode> 4 0 0.0008 0.0008 <method 'close' of 'file' objects> 179 154 0.0202 0.0004 mercurial.demandimport:83(__getattribute__) +36 11 0.0199 0.0003 +mercurial.demandimport:55(_load) +72 0 0.0001 0.0001 +mercurial.demandimport:83(__getattribute__) +36 0 0.0000 0.0000 +<getattr> 1 0 0.0015 0.0004 mercurial.tags:148(_readtagcache) Time: real 0.060 secs (user 0.030+0.000 sys 0.010+0.000)
2012-05-13 16:04:04 +04:00
try:
branchnode = repo.branchtip(branch)
except error.RepoLookupError:
branchnode = None
if branch != 'default' and branchnode != ctx.node():
2008-03-28 21:37:28 +03:00
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)
def showbookmark(repo, tmpl, t1, node=nullid, **args):
for t in repo.nodebookmarks(node):
yield tmpl(t1, bookmark=t, **args)
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 {
'parity': parity.next(),
'branch': ctx.branch(),
'status': status,
'node': ctx.hex(),
'date': ctx.date()
}
return entries
2008-03-28 21:37:28 +03:00
def cleanpath(repo, path):
path = path.lstrip('/')
return pathutil.canonpath(repo.root, '', path)
2008-03-28 21:37:28 +03:00
def changeidctx(repo, changeid):
2008-03-28 21:37:28 +03:00
try:
2008-06-26 23:35:46 +04:00
ctx = repo[changeid]
except error.RepoError:
2008-03-28 21:37:28 +03:00
man = repo.manifest
ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
2008-03-28 21:37:28 +03:00
return ctx
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
2008-03-28 21:37:28 +03:00
def filectx(repo, req):
if 'file' not in req.form:
raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
2008-03-28 21:37:28 +03:00
path = cleanpath(repo, req.form['file'][0])
if 'node' in req.form:
changeid = req.form['node'][0]
elif 'filenode' in req.form:
2008-03-28 21:37:28 +03:00
changeid = req.form['filenode'][0]
else:
raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
2008-03-28 21:37:28 +03:00
try:
2008-06-26 23:35:46 +04:00
fctx = repo[changeid][path]
except error.RepoError:
2008-03-28 21:37:28 +03:00
fctx = repo.filectx(path, fileid=changeid)
return fctx
def changelistentry(web, ctx, tmpl):
'''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()
showtags = showtag(repo, tmpl, 'changelogtag', n)
files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
return {
"author": ctx.user(),
"parent": parents(ctx, rev - 1),
"child": children(ctx, rev + 1),
"changelogtag": showtags,
"desc": ctx.description(),
"extra": ctx.extra(),
"date": ctx.date(),
"files": files,
"rev": rev,
"node": hex(n),
"tags": nodetagsdict(repo, n),
"bookmarks": nodebookmarksdict(repo, n),
"inbranch": nodeinbranch(repo, ctx),
"branches": nodebranchdict(repo, ctx)
}
hgweb: provide symrev (symbolic revision) property to the templates One of the features of hgweb is that current position in repo history is remembered between separate requests. That is, links from /rev/<node_hash> lead to /file/<node_hash> or /log/<node_hash>, so it's easy to dig deep into the history. However, such links could only use node hashes and local revision numbers, so while staying at one exact revision is easy, staying on top of the changes is not, because hashes presumably can't change (local revision numbers can, but probably not in a way you'd find useful for navigating). So while you could use 'tip' or 'default' in a url, links on that page would be permanent. This is not always desired (think /rev/tip or /graph/stable or /log/@) and is sometimes just confusing (i.e. /log/<not the tip hash>, when recent history is not displayed). And if user changed url deliberately to say default instead of <some node hash>, the page ignores that fact and uses node hash in its links, which means that navigation is, in a way, broken. This new property, symrev, is used for storing current revision the way it was specified, so then templates can use it in links and thus "not dereference" the symbolic revision. It is an additional way to produce links, so not every link needs to drop {node|short} in favor of {symrev}, many will still use node hash (log and filelog entries, annotate lines, etc). Some pages (e.g. summary, tags) always use the tip changeset for their context, in such cases symrev is set to 'tip'. This is needed in case the pages want to provide archive links. highlight extension needs to be updated, since _filerevision now takes an additional positional argument (signature "web, req, tmpl" is used by most of webcommands.py functions). More references to symbolic revisions and related gripes: issue2296, issue2826, issue3594, issue3634.
2015-06-15 21:07:25 +03:00
def symrevorshortnode(req, ctx):
if 'node' in req.form:
return revescape(req.form['node'][0])
hgweb: provide symrev (symbolic revision) property to the templates One of the features of hgweb is that current position in repo history is remembered between separate requests. That is, links from /rev/<node_hash> lead to /file/<node_hash> or /log/<node_hash>, so it's easy to dig deep into the history. However, such links could only use node hashes and local revision numbers, so while staying at one exact revision is easy, staying on top of the changes is not, because hashes presumably can't change (local revision numbers can, but probably not in a way you'd find useful for navigating). So while you could use 'tip' or 'default' in a url, links on that page would be permanent. This is not always desired (think /rev/tip or /graph/stable or /log/@) and is sometimes just confusing (i.e. /log/<not the tip hash>, when recent history is not displayed). And if user changed url deliberately to say default instead of <some node hash>, the page ignores that fact and uses node hash in its links, which means that navigation is, in a way, broken. This new property, symrev, is used for storing current revision the way it was specified, so then templates can use it in links and thus "not dereference" the symbolic revision. It is an additional way to produce links, so not every link needs to drop {node|short} in favor of {symrev}, many will still use node hash (log and filelog entries, annotate lines, etc). Some pages (e.g. summary, tags) always use the tip changeset for their context, in such cases symrev is set to 'tip'. This is needed in case the pages want to provide archive links. highlight extension needs to be updated, since _filerevision now takes an additional positional argument (signature "web, req, tmpl" is used by most of webcommands.py functions). More references to symbolic revisions and related gripes: issue2296, issue2826, issue3594, issue3634.
2015-06-15 21:07:25 +03:00
else:
return short(ctx.node())
def changesetentry(web, req, tmpl, ctx):
'''Obtain a dictionary to be used to render the "changeset" template.'''
showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
ctx.node())
showbranch = nodebranchnodefault(ctx)
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,
parity=parity.next()))
basectx = basechangectx(web.repo, req)
if basectx is None:
basectx = ctx.p1()
style = web.config('web', 'style', 'paper')
if 'style' in req.form:
style = req.form['style'][0]
parity = paritygen(web.stripecount)
diff = diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
parity = paritygen(web.stripecount)
diffstatsgen = diffstatgen(ctx, basectx)
diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
return dict(
diff=diff,
rev=ctx.rev(),
node=ctx.hex(),
hgweb: provide symrev (symbolic revision) property to the templates One of the features of hgweb is that current position in repo history is remembered between separate requests. That is, links from /rev/<node_hash> lead to /file/<node_hash> or /log/<node_hash>, so it's easy to dig deep into the history. However, such links could only use node hashes and local revision numbers, so while staying at one exact revision is easy, staying on top of the changes is not, because hashes presumably can't change (local revision numbers can, but probably not in a way you'd find useful for navigating). So while you could use 'tip' or 'default' in a url, links on that page would be permanent. This is not always desired (think /rev/tip or /graph/stable or /log/@) and is sometimes just confusing (i.e. /log/<not the tip hash>, when recent history is not displayed). And if user changed url deliberately to say default instead of <some node hash>, the page ignores that fact and uses node hash in its links, which means that navigation is, in a way, broken. This new property, symrev, is used for storing current revision the way it was specified, so then templates can use it in links and thus "not dereference" the symbolic revision. It is an additional way to produce links, so not every link needs to drop {node|short} in favor of {symrev}, many will still use node hash (log and filelog entries, annotate lines, etc). Some pages (e.g. summary, tags) always use the tip changeset for their context, in such cases symrev is set to 'tip'. This is needed in case the pages want to provide archive links. highlight extension needs to be updated, since _filerevision now takes an additional positional argument (signature "web, req, tmpl" is used by most of webcommands.py functions). More references to symbolic revisions and related gripes: issue2296, issue2826, issue3594, issue3634.
2015-06-15 21:07:25 +03:00
symrev=symrevorshortnode(req, ctx),
parent=tuple(parents(ctx)),
child=children(ctx),
basenode=basectx.hex(),
changesettag=showtags,
changesetbookmark=showbookmarks,
changesetbranch=showbranch,
author=ctx.user(),
desc=ctx.description(),
extra=ctx.extra(),
date=ctx.date(),
phase=ctx.phasestr(),
files=files,
diffsummary=lambda **x: diffsummary(diffstatsgen),
diffstat=diffstats,
archives=web.archivelist(ctx.hex()),
tags=nodetagsdict(web.repo, ctx.node()),
bookmarks=nodebookmarksdict(web.repo, ctx.node()),
branch=showbranch,
inbranch=nodeinbranch(web.repo, ctx),
branches=nodebranchdict(web.repo, ctx))
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')
def diffs(repo, tmpl, ctx, basectx, files, parity, style):
def countgen():
start = 1
while True:
yield start
start += 1
blockcount = countgen()
def prettyprintlines(diff, blockno):
for lineno, l in enumerate(diff.splitlines(True)):
difflineno = "%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,
lineno=lineno + 1,
lineid="l%s" % difflineno,
linenumber="% 8s" % difflineno)
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)
if basectx is None:
parents = ctx.parents()
if parents:
node1 = parents[0].node()
else:
node1 = nullid
else:
node1 = basectx.node()
node2 = ctx.node()
block = []
for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
if chunk.startswith('diff') and block:
blockno = blockcount.next()
yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
lines=prettyprintlines(''.join(block), blockno))
block = []
if chunk.startswith('diff') and style != 'raw':
chunk = ''.join(chunk.splitlines(True)[1:])
block.append(chunk)
blockno = blockcount.next()
yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
lines=prettyprintlines(''.join(block), blockno))
def compare(tmpl, context, leftlines, rightlines):
'''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,
leftlineno=leftlineno,
leftlinenumber="% 6s" % (leftlineno or ''),
leftline=leftline or '',
rightlineno=rightlineno,
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:
yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
else:
for oc in s.get_grouped_opcodes(n=context):
yield tmpl('comparisonblock', lines=getblock(oc))
def diffstatgen(ctx, basectx):
'''Generator function that provides the diffstat data.'''
stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
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 = 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()
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 = filename in files and 'diffstatlink' or 'diffstatnolink'
total = adds + removes
fileno += 1
yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
total=total, addpct=pct(adds), removepct=pct(removes),
parity=parity.next())
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 sorted(self.vars.iteritems()):
yield {'name': key, 'value': str(value), 'separator': separator}
separator = '&'
class wsgiui(ui.ui):
# default termwidth breaks under mod_wsgi
def termwidth(self):
return 80