##// END OF EJS Templates
hgweb: ensure template mapping keys are bytes...
Gregory Szorc -
r41356:eff0a7d3 default
parent child Browse files
Show More
@@ -1,815 +1,815 b''
1 # hgweb/webutil.py - utility library for the web interface.
1 # hgweb/webutil.py - utility library for the web interface.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import copy
11 import copy
12 import difflib
12 import difflib
13 import os
13 import os
14 import re
14 import re
15
15
16 from ..i18n import _
16 from ..i18n import _
17 from ..node import hex, nullid, short
17 from ..node import hex, nullid, short
18
18
19 from .common import (
19 from .common import (
20 ErrorResponse,
20 ErrorResponse,
21 HTTP_BAD_REQUEST,
21 HTTP_BAD_REQUEST,
22 HTTP_NOT_FOUND,
22 HTTP_NOT_FOUND,
23 paritygen,
23 paritygen,
24 )
24 )
25
25
26 from .. import (
26 from .. import (
27 context,
27 context,
28 diffutil,
28 diffutil,
29 error,
29 error,
30 match,
30 match,
31 mdiff,
31 mdiff,
32 obsutil,
32 obsutil,
33 patch,
33 patch,
34 pathutil,
34 pathutil,
35 pycompat,
35 pycompat,
36 scmutil,
36 scmutil,
37 templatefilters,
37 templatefilters,
38 templatekw,
38 templatekw,
39 templateutil,
39 templateutil,
40 ui as uimod,
40 ui as uimod,
41 util,
41 util,
42 )
42 )
43
43
44 from ..utils import (
44 from ..utils import (
45 stringutil,
45 stringutil,
46 )
46 )
47
47
48 archivespecs = util.sortdict((
48 archivespecs = util.sortdict((
49 ('zip', ('application/zip', 'zip', '.zip', None)),
49 ('zip', ('application/zip', 'zip', '.zip', None)),
50 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
50 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
51 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
51 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
52 ))
52 ))
53
53
54 def archivelist(ui, nodeid, url=None):
54 def archivelist(ui, nodeid, url=None):
55 allowed = ui.configlist('web', 'allow-archive', untrusted=True)
55 allowed = ui.configlist('web', 'allow-archive', untrusted=True)
56 archives = []
56 archives = []
57
57
58 for typ, spec in archivespecs.iteritems():
58 for typ, spec in archivespecs.iteritems():
59 if typ in allowed or ui.configbool('web', 'allow' + typ,
59 if typ in allowed or ui.configbool('web', 'allow' + typ,
60 untrusted=True):
60 untrusted=True):
61 archives.append({
61 archives.append({
62 'type': typ,
62 'type': typ,
63 'extension': spec[2],
63 'extension': spec[2],
64 'node': nodeid,
64 'node': nodeid,
65 'url': url,
65 'url': url,
66 })
66 })
67
67
68 return templateutil.mappinglist(archives)
68 return templateutil.mappinglist(archives)
69
69
70 def up(p):
70 def up(p):
71 if p[0:1] != "/":
71 if p[0:1] != "/":
72 p = "/" + p
72 p = "/" + p
73 if p[-1:] == "/":
73 if p[-1:] == "/":
74 p = p[:-1]
74 p = p[:-1]
75 up = os.path.dirname(p)
75 up = os.path.dirname(p)
76 if up == "/":
76 if up == "/":
77 return "/"
77 return "/"
78 return up + "/"
78 return up + "/"
79
79
80 def _navseq(step, firststep=None):
80 def _navseq(step, firststep=None):
81 if firststep:
81 if firststep:
82 yield firststep
82 yield firststep
83 if firststep >= 20 and firststep <= 40:
83 if firststep >= 20 and firststep <= 40:
84 firststep = 50
84 firststep = 50
85 yield firststep
85 yield firststep
86 assert step > 0
86 assert step > 0
87 assert firststep > 0
87 assert firststep > 0
88 while step <= firststep:
88 while step <= firststep:
89 step *= 10
89 step *= 10
90 while True:
90 while True:
91 yield 1 * step
91 yield 1 * step
92 yield 3 * step
92 yield 3 * step
93 step *= 10
93 step *= 10
94
94
95 class revnav(object):
95 class revnav(object):
96
96
97 def __init__(self, repo):
97 def __init__(self, repo):
98 """Navigation generation object
98 """Navigation generation object
99
99
100 :repo: repo object we generate nav for
100 :repo: repo object we generate nav for
101 """
101 """
102 # used for hex generation
102 # used for hex generation
103 self._revlog = repo.changelog
103 self._revlog = repo.changelog
104
104
105 def __nonzero__(self):
105 def __nonzero__(self):
106 """return True if any revision to navigate over"""
106 """return True if any revision to navigate over"""
107 return self._first() is not None
107 return self._first() is not None
108
108
109 __bool__ = __nonzero__
109 __bool__ = __nonzero__
110
110
111 def _first(self):
111 def _first(self):
112 """return the minimum non-filtered changeset or None"""
112 """return the minimum non-filtered changeset or None"""
113 try:
113 try:
114 return next(iter(self._revlog))
114 return next(iter(self._revlog))
115 except StopIteration:
115 except StopIteration:
116 return None
116 return None
117
117
118 def hex(self, rev):
118 def hex(self, rev):
119 return hex(self._revlog.node(rev))
119 return hex(self._revlog.node(rev))
120
120
121 def gen(self, pos, pagelen, limit):
121 def gen(self, pos, pagelen, limit):
122 """computes label and revision id for navigation link
122 """computes label and revision id for navigation link
123
123
124 :pos: is the revision relative to which we generate navigation.
124 :pos: is the revision relative to which we generate navigation.
125 :pagelen: the size of each navigation page
125 :pagelen: the size of each navigation page
126 :limit: how far shall we link
126 :limit: how far shall we link
127
127
128 The return is:
128 The return is:
129 - a single element mappinglist
129 - a single element mappinglist
130 - containing a dictionary with a `before` and `after` key
130 - containing a dictionary with a `before` and `after` key
131 - values are dictionaries with `label` and `node` keys
131 - values are dictionaries with `label` and `node` keys
132 """
132 """
133 if not self:
133 if not self:
134 # empty repo
134 # empty repo
135 return templateutil.mappinglist([
135 return templateutil.mappinglist([
136 {'before': templateutil.mappinglist([]),
136 {'before': templateutil.mappinglist([]),
137 'after': templateutil.mappinglist([])},
137 'after': templateutil.mappinglist([])},
138 ])
138 ])
139
139
140 targets = []
140 targets = []
141 for f in _navseq(1, pagelen):
141 for f in _navseq(1, pagelen):
142 if f > limit:
142 if f > limit:
143 break
143 break
144 targets.append(pos + f)
144 targets.append(pos + f)
145 targets.append(pos - f)
145 targets.append(pos - f)
146 targets.sort()
146 targets.sort()
147
147
148 first = self._first()
148 first = self._first()
149 navbefore = [{'label': '(%i)' % first, 'node': self.hex(first)}]
149 navbefore = [{'label': '(%i)' % first, 'node': self.hex(first)}]
150 navafter = []
150 navafter = []
151 for rev in targets:
151 for rev in targets:
152 if rev not in self._revlog:
152 if rev not in self._revlog:
153 continue
153 continue
154 if pos < rev < limit:
154 if pos < rev < limit:
155 navafter.append({'label': '+%d' % abs(rev - pos),
155 navafter.append({'label': '+%d' % abs(rev - pos),
156 'node': self.hex(rev)})
156 'node': self.hex(rev)})
157 if 0 < rev < pos:
157 if 0 < rev < pos:
158 navbefore.append({'label': '-%d' % abs(rev - pos),
158 navbefore.append({'label': '-%d' % abs(rev - pos),
159 'node': self.hex(rev)})
159 'node': self.hex(rev)})
160
160
161 navafter.append({'label': 'tip', 'node': 'tip'})
161 navafter.append({'label': 'tip', 'node': 'tip'})
162
162
163 # TODO: maybe this can be a scalar object supporting tomap()
163 # TODO: maybe this can be a scalar object supporting tomap()
164 return templateutil.mappinglist([
164 return templateutil.mappinglist([
165 {'before': templateutil.mappinglist(navbefore),
165 {'before': templateutil.mappinglist(navbefore),
166 'after': templateutil.mappinglist(navafter)},
166 'after': templateutil.mappinglist(navafter)},
167 ])
167 ])
168
168
169 class filerevnav(revnav):
169 class filerevnav(revnav):
170
170
171 def __init__(self, repo, path):
171 def __init__(self, repo, path):
172 """Navigation generation object
172 """Navigation generation object
173
173
174 :repo: repo object we generate nav for
174 :repo: repo object we generate nav for
175 :path: path of the file we generate nav for
175 :path: path of the file we generate nav for
176 """
176 """
177 # used for iteration
177 # used for iteration
178 self._changelog = repo.unfiltered().changelog
178 self._changelog = repo.unfiltered().changelog
179 # used for hex generation
179 # used for hex generation
180 self._revlog = repo.file(path)
180 self._revlog = repo.file(path)
181
181
182 def hex(self, rev):
182 def hex(self, rev):
183 return hex(self._changelog.node(self._revlog.linkrev(rev)))
183 return hex(self._changelog.node(self._revlog.linkrev(rev)))
184
184
185 # TODO: maybe this can be a wrapper class for changectx/filectx list, which
185 # TODO: maybe this can be a wrapper class for changectx/filectx list, which
186 # yields {'ctx': ctx}
186 # yields {'ctx': ctx}
187 def _ctxsgen(context, ctxs):
187 def _ctxsgen(context, ctxs):
188 for s in ctxs:
188 for s in ctxs:
189 d = {
189 d = {
190 'node': s.hex(),
190 'node': s.hex(),
191 'rev': s.rev(),
191 'rev': s.rev(),
192 'user': s.user(),
192 'user': s.user(),
193 'date': s.date(),
193 'date': s.date(),
194 'description': s.description(),
194 'description': s.description(),
195 'branch': s.branch(),
195 'branch': s.branch(),
196 }
196 }
197 if util.safehasattr(s, 'path'):
197 if util.safehasattr(s, 'path'):
198 d['file'] = s.path()
198 d['file'] = s.path()
199 yield d
199 yield d
200
200
201 def _siblings(siblings=None, hiderev=None):
201 def _siblings(siblings=None, hiderev=None):
202 if siblings is None:
202 if siblings is None:
203 siblings = []
203 siblings = []
204 siblings = [s for s in siblings if s.node() != nullid]
204 siblings = [s for s in siblings if s.node() != nullid]
205 if len(siblings) == 1 and siblings[0].rev() == hiderev:
205 if len(siblings) == 1 and siblings[0].rev() == hiderev:
206 siblings = []
206 siblings = []
207 return templateutil.mappinggenerator(_ctxsgen, args=(siblings,))
207 return templateutil.mappinggenerator(_ctxsgen, args=(siblings,))
208
208
209 def difffeatureopts(req, ui, section):
209 def difffeatureopts(req, ui, section):
210 diffopts = diffutil.difffeatureopts(ui, untrusted=True,
210 diffopts = diffutil.difffeatureopts(ui, untrusted=True,
211 section=section, whitespace=True)
211 section=section, whitespace=True)
212
212
213 for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
213 for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
214 v = req.qsparams.get(k)
214 v = req.qsparams.get(k)
215 if v is not None:
215 if v is not None:
216 v = stringutil.parsebool(v)
216 v = stringutil.parsebool(v)
217 setattr(diffopts, k, v if v is not None else True)
217 setattr(diffopts, k, v if v is not None else True)
218
218
219 return diffopts
219 return diffopts
220
220
221 def annotate(req, fctx, ui):
221 def annotate(req, fctx, ui):
222 diffopts = difffeatureopts(req, ui, 'annotate')
222 diffopts = difffeatureopts(req, ui, 'annotate')
223 return fctx.annotate(follow=True, diffopts=diffopts)
223 return fctx.annotate(follow=True, diffopts=diffopts)
224
224
225 def parents(ctx, hide=None):
225 def parents(ctx, hide=None):
226 if isinstance(ctx, context.basefilectx):
226 if isinstance(ctx, context.basefilectx):
227 introrev = ctx.introrev()
227 introrev = ctx.introrev()
228 if ctx.changectx().rev() != introrev:
228 if ctx.changectx().rev() != introrev:
229 return _siblings([ctx.repo()[introrev]], hide)
229 return _siblings([ctx.repo()[introrev]], hide)
230 return _siblings(ctx.parents(), hide)
230 return _siblings(ctx.parents(), hide)
231
231
232 def children(ctx, hide=None):
232 def children(ctx, hide=None):
233 return _siblings(ctx.children(), hide)
233 return _siblings(ctx.children(), hide)
234
234
235 def renamelink(fctx):
235 def renamelink(fctx):
236 r = fctx.renamed()
236 r = fctx.renamed()
237 if r:
237 if r:
238 return templateutil.mappinglist([{'file': r[0], 'node': hex(r[1])}])
238 return templateutil.mappinglist([{'file': r[0], 'node': hex(r[1])}])
239 return templateutil.mappinglist([])
239 return templateutil.mappinglist([])
240
240
241 def nodetagsdict(repo, node):
241 def nodetagsdict(repo, node):
242 return templateutil.hybridlist(repo.nodetags(node), name='name')
242 return templateutil.hybridlist(repo.nodetags(node), name='name')
243
243
244 def nodebookmarksdict(repo, node):
244 def nodebookmarksdict(repo, node):
245 return templateutil.hybridlist(repo.nodebookmarks(node), name='name')
245 return templateutil.hybridlist(repo.nodebookmarks(node), name='name')
246
246
247 def nodebranchdict(repo, ctx):
247 def nodebranchdict(repo, ctx):
248 branches = []
248 branches = []
249 branch = ctx.branch()
249 branch = ctx.branch()
250 # If this is an empty repo, ctx.node() == nullid,
250 # If this is an empty repo, ctx.node() == nullid,
251 # ctx.branch() == 'default'.
251 # ctx.branch() == 'default'.
252 try:
252 try:
253 branchnode = repo.branchtip(branch)
253 branchnode = repo.branchtip(branch)
254 except error.RepoLookupError:
254 except error.RepoLookupError:
255 branchnode = None
255 branchnode = None
256 if branchnode == ctx.node():
256 if branchnode == ctx.node():
257 branches.append(branch)
257 branches.append(branch)
258 return templateutil.hybridlist(branches, name='name')
258 return templateutil.hybridlist(branches, name='name')
259
259
260 def nodeinbranch(repo, ctx):
260 def nodeinbranch(repo, ctx):
261 branches = []
261 branches = []
262 branch = ctx.branch()
262 branch = ctx.branch()
263 try:
263 try:
264 branchnode = repo.branchtip(branch)
264 branchnode = repo.branchtip(branch)
265 except error.RepoLookupError:
265 except error.RepoLookupError:
266 branchnode = None
266 branchnode = None
267 if branch != 'default' and branchnode != ctx.node():
267 if branch != 'default' and branchnode != ctx.node():
268 branches.append(branch)
268 branches.append(branch)
269 return templateutil.hybridlist(branches, name='name')
269 return templateutil.hybridlist(branches, name='name')
270
270
271 def nodebranchnodefault(ctx):
271 def nodebranchnodefault(ctx):
272 branches = []
272 branches = []
273 branch = ctx.branch()
273 branch = ctx.branch()
274 if branch != 'default':
274 if branch != 'default':
275 branches.append(branch)
275 branches.append(branch)
276 return templateutil.hybridlist(branches, name='name')
276 return templateutil.hybridlist(branches, name='name')
277
277
278 def _nodenamesgen(context, f, node, name):
278 def _nodenamesgen(context, f, node, name):
279 for t in f(node):
279 for t in f(node):
280 yield {name: t}
280 yield {name: t}
281
281
282 def showtag(repo, t1, node=nullid):
282 def showtag(repo, t1, node=nullid):
283 args = (repo.nodetags, node, 'tag')
283 args = (repo.nodetags, node, 'tag')
284 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
284 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
285
285
286 def showbookmark(repo, t1, node=nullid):
286 def showbookmark(repo, t1, node=nullid):
287 args = (repo.nodebookmarks, node, 'bookmark')
287 args = (repo.nodebookmarks, node, 'bookmark')
288 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
288 return templateutil.mappinggenerator(_nodenamesgen, args=args, name=t1)
289
289
290 def branchentries(repo, stripecount, limit=0):
290 def branchentries(repo, stripecount, limit=0):
291 tips = []
291 tips = []
292 heads = repo.heads()
292 heads = repo.heads()
293 parity = paritygen(stripecount)
293 parity = paritygen(stripecount)
294 sortkey = lambda item: (not item[1], item[0].rev())
294 sortkey = lambda item: (not item[1], item[0].rev())
295
295
296 def entries(context):
296 def entries(context):
297 count = 0
297 count = 0
298 if not tips:
298 if not tips:
299 for tag, hs, tip, closed in repo.branchmap().iterbranches():
299 for tag, hs, tip, closed in repo.branchmap().iterbranches():
300 tips.append((repo[tip], closed))
300 tips.append((repo[tip], closed))
301 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
301 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
302 if limit > 0 and count >= limit:
302 if limit > 0 and count >= limit:
303 return
303 return
304 count += 1
304 count += 1
305 if closed:
305 if closed:
306 status = 'closed'
306 status = 'closed'
307 elif ctx.node() not in heads:
307 elif ctx.node() not in heads:
308 status = 'inactive'
308 status = 'inactive'
309 else:
309 else:
310 status = 'open'
310 status = 'open'
311 yield {
311 yield {
312 'parity': next(parity),
312 'parity': next(parity),
313 'branch': ctx.branch(),
313 'branch': ctx.branch(),
314 'status': status,
314 'status': status,
315 'node': ctx.hex(),
315 'node': ctx.hex(),
316 'date': ctx.date()
316 'date': ctx.date()
317 }
317 }
318
318
319 return templateutil.mappinggenerator(entries)
319 return templateutil.mappinggenerator(entries)
320
320
321 def cleanpath(repo, path):
321 def cleanpath(repo, path):
322 path = path.lstrip('/')
322 path = path.lstrip('/')
323 auditor = pathutil.pathauditor(repo.root, realfs=False)
323 auditor = pathutil.pathauditor(repo.root, realfs=False)
324 return pathutil.canonpath(repo.root, '', path, auditor=auditor)
324 return pathutil.canonpath(repo.root, '', path, auditor=auditor)
325
325
326 def changectx(repo, req):
326 def changectx(repo, req):
327 changeid = "tip"
327 changeid = "tip"
328 if 'node' in req.qsparams:
328 if 'node' in req.qsparams:
329 changeid = req.qsparams['node']
329 changeid = req.qsparams['node']
330 ipos = changeid.find(':')
330 ipos = changeid.find(':')
331 if ipos != -1:
331 if ipos != -1:
332 changeid = changeid[(ipos + 1):]
332 changeid = changeid[(ipos + 1):]
333
333
334 return scmutil.revsymbol(repo, changeid)
334 return scmutil.revsymbol(repo, changeid)
335
335
336 def basechangectx(repo, req):
336 def basechangectx(repo, req):
337 if 'node' in req.qsparams:
337 if 'node' in req.qsparams:
338 changeid = req.qsparams['node']
338 changeid = req.qsparams['node']
339 ipos = changeid.find(':')
339 ipos = changeid.find(':')
340 if ipos != -1:
340 if ipos != -1:
341 changeid = changeid[:ipos]
341 changeid = changeid[:ipos]
342 return scmutil.revsymbol(repo, changeid)
342 return scmutil.revsymbol(repo, changeid)
343
343
344 return None
344 return None
345
345
346 def filectx(repo, req):
346 def filectx(repo, req):
347 if 'file' not in req.qsparams:
347 if 'file' not in req.qsparams:
348 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
348 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
349 path = cleanpath(repo, req.qsparams['file'])
349 path = cleanpath(repo, req.qsparams['file'])
350 if 'node' in req.qsparams:
350 if 'node' in req.qsparams:
351 changeid = req.qsparams['node']
351 changeid = req.qsparams['node']
352 elif 'filenode' in req.qsparams:
352 elif 'filenode' in req.qsparams:
353 changeid = req.qsparams['filenode']
353 changeid = req.qsparams['filenode']
354 else:
354 else:
355 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
355 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
356 try:
356 try:
357 fctx = scmutil.revsymbol(repo, changeid)[path]
357 fctx = scmutil.revsymbol(repo, changeid)[path]
358 except error.RepoError:
358 except error.RepoError:
359 fctx = repo.filectx(path, fileid=changeid)
359 fctx = repo.filectx(path, fileid=changeid)
360
360
361 return fctx
361 return fctx
362
362
363 def linerange(req):
363 def linerange(req):
364 linerange = req.qsparams.getall('linerange')
364 linerange = req.qsparams.getall('linerange')
365 if not linerange:
365 if not linerange:
366 return None
366 return None
367 if len(linerange) > 1:
367 if len(linerange) > 1:
368 raise ErrorResponse(HTTP_BAD_REQUEST,
368 raise ErrorResponse(HTTP_BAD_REQUEST,
369 'redundant linerange parameter')
369 'redundant linerange parameter')
370 try:
370 try:
371 fromline, toline = map(int, linerange[0].split(':', 1))
371 fromline, toline = map(int, linerange[0].split(':', 1))
372 except ValueError:
372 except ValueError:
373 raise ErrorResponse(HTTP_BAD_REQUEST,
373 raise ErrorResponse(HTTP_BAD_REQUEST,
374 'invalid linerange parameter')
374 'invalid linerange parameter')
375 try:
375 try:
376 return util.processlinerange(fromline, toline)
376 return util.processlinerange(fromline, toline)
377 except error.ParseError as exc:
377 except error.ParseError as exc:
378 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
378 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
379
379
380 def formatlinerange(fromline, toline):
380 def formatlinerange(fromline, toline):
381 return '%d:%d' % (fromline + 1, toline)
381 return '%d:%d' % (fromline + 1, toline)
382
382
383 def _succsandmarkersgen(context, mapping):
383 def _succsandmarkersgen(context, mapping):
384 repo = context.resource(mapping, 'repo')
384 repo = context.resource(mapping, 'repo')
385 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
385 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
386 for item in itemmappings.tovalue(context, mapping):
386 for item in itemmappings.tovalue(context, mapping):
387 item['successors'] = _siblings(repo[successor]
387 item['successors'] = _siblings(repo[successor]
388 for successor in item['successors'])
388 for successor in item['successors'])
389 yield item
389 yield item
390
390
391 def succsandmarkers(context, mapping):
391 def succsandmarkers(context, mapping):
392 return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
392 return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
393
393
394 # teach templater succsandmarkers is switched to (context, mapping) API
394 # teach templater succsandmarkers is switched to (context, mapping) API
395 succsandmarkers._requires = {'repo', 'ctx'}
395 succsandmarkers._requires = {'repo', 'ctx'}
396
396
397 def _whyunstablegen(context, mapping):
397 def _whyunstablegen(context, mapping):
398 repo = context.resource(mapping, 'repo')
398 repo = context.resource(mapping, 'repo')
399 ctx = context.resource(mapping, 'ctx')
399 ctx = context.resource(mapping, 'ctx')
400
400
401 entries = obsutil.whyunstable(repo, ctx)
401 entries = obsutil.whyunstable(repo, ctx)
402 for entry in entries:
402 for entry in entries:
403 if entry.get('divergentnodes'):
403 if entry.get('divergentnodes'):
404 entry['divergentnodes'] = _siblings(entry['divergentnodes'])
404 entry['divergentnodes'] = _siblings(entry['divergentnodes'])
405 yield entry
405 yield entry
406
406
407 def whyunstable(context, mapping):
407 def whyunstable(context, mapping):
408 return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
408 return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
409
409
410 whyunstable._requires = {'repo', 'ctx'}
410 whyunstable._requires = {'repo', 'ctx'}
411
411
412 # helper to mark a function as a new-style template keyword; can be removed
412 # helper to mark a function as a new-style template keyword; can be removed
413 # once old-style function gets unsupported and new-style becomes the default
413 # once old-style function gets unsupported and new-style becomes the default
414 def _kwfunc(f):
414 def _kwfunc(f):
415 f._requires = ()
415 f._requires = ()
416 return f
416 return f
417
417
418 def commonentry(repo, ctx):
418 def commonentry(repo, ctx):
419 node = scmutil.binnode(ctx)
419 node = scmutil.binnode(ctx)
420 return {
420 return {
421 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
421 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
422 # filectx, but I'm not pretty sure if that would always work because
422 # filectx, but I'm not pretty sure if that would always work because
423 # fctx.parents() != fctx.changectx.parents() for example.
423 # fctx.parents() != fctx.changectx.parents() for example.
424 'ctx': ctx,
424 'ctx': ctx,
425 'rev': ctx.rev(),
425 'rev': ctx.rev(),
426 'node': hex(node),
426 'node': hex(node),
427 'author': ctx.user(),
427 'author': ctx.user(),
428 'desc': ctx.description(),
428 'desc': ctx.description(),
429 'date': ctx.date(),
429 'date': ctx.date(),
430 'extra': ctx.extra(),
430 'extra': ctx.extra(),
431 'phase': ctx.phasestr(),
431 'phase': ctx.phasestr(),
432 'obsolete': ctx.obsolete(),
432 'obsolete': ctx.obsolete(),
433 'succsandmarkers': succsandmarkers,
433 'succsandmarkers': succsandmarkers,
434 'instabilities': templateutil.hybridlist(ctx.instabilities(),
434 'instabilities': templateutil.hybridlist(ctx.instabilities(),
435 name='instability'),
435 name='instability'),
436 'whyunstable': whyunstable,
436 'whyunstable': whyunstable,
437 'branch': nodebranchnodefault(ctx),
437 'branch': nodebranchnodefault(ctx),
438 'inbranch': nodeinbranch(repo, ctx),
438 'inbranch': nodeinbranch(repo, ctx),
439 'branches': nodebranchdict(repo, ctx),
439 'branches': nodebranchdict(repo, ctx),
440 'tags': nodetagsdict(repo, node),
440 'tags': nodetagsdict(repo, node),
441 'bookmarks': nodebookmarksdict(repo, node),
441 'bookmarks': nodebookmarksdict(repo, node),
442 'parent': _kwfunc(lambda context, mapping: parents(ctx)),
442 'parent': _kwfunc(lambda context, mapping: parents(ctx)),
443 'child': _kwfunc(lambda context, mapping: children(ctx)),
443 'child': _kwfunc(lambda context, mapping: children(ctx)),
444 }
444 }
445
445
446 def changelistentry(web, ctx):
446 def changelistentry(web, ctx):
447 '''Obtain a dictionary to be used for entries in a changelist.
447 '''Obtain a dictionary to be used for entries in a changelist.
448
448
449 This function is called when producing items for the "entries" list passed
449 This function is called when producing items for the "entries" list passed
450 to the "shortlog" and "changelog" templates.
450 to the "shortlog" and "changelog" templates.
451 '''
451 '''
452 repo = web.repo
452 repo = web.repo
453 rev = ctx.rev()
453 rev = ctx.rev()
454 n = scmutil.binnode(ctx)
454 n = scmutil.binnode(ctx)
455 showtags = showtag(repo, 'changelogtag', n)
455 showtags = showtag(repo, 'changelogtag', n)
456 files = listfilediffs(ctx.files(), n, web.maxfiles)
456 files = listfilediffs(ctx.files(), n, web.maxfiles)
457
457
458 entry = commonentry(repo, ctx)
458 entry = commonentry(repo, ctx)
459 entry.update(
459 entry.update({
460 allparents=_kwfunc(lambda context, mapping: parents(ctx)),
460 'allparents': _kwfunc(lambda context, mapping: parents(ctx)),
461 parent=_kwfunc(lambda context, mapping: parents(ctx, rev - 1)),
461 'parent': _kwfunc(lambda context, mapping: parents(ctx, rev - 1)),
462 child=_kwfunc(lambda context, mapping: children(ctx, rev + 1)),
462 'child': _kwfunc(lambda context, mapping: children(ctx, rev + 1)),
463 changelogtag=showtags,
463 'changelogtag': showtags,
464 files=files,
464 'files': files,
465 )
465 })
466 return entry
466 return entry
467
467
468 def changelistentries(web, revs, maxcount, parityfn):
468 def changelistentries(web, revs, maxcount, parityfn):
469 """Emit up to N records for an iterable of revisions."""
469 """Emit up to N records for an iterable of revisions."""
470 repo = web.repo
470 repo = web.repo
471
471
472 count = 0
472 count = 0
473 for rev in revs:
473 for rev in revs:
474 if count >= maxcount:
474 if count >= maxcount:
475 break
475 break
476
476
477 count += 1
477 count += 1
478
478
479 entry = changelistentry(web, repo[rev])
479 entry = changelistentry(web, repo[rev])
480 entry['parity'] = next(parityfn)
480 entry['parity'] = next(parityfn)
481
481
482 yield entry
482 yield entry
483
483
484 def symrevorshortnode(req, ctx):
484 def symrevorshortnode(req, ctx):
485 if 'node' in req.qsparams:
485 if 'node' in req.qsparams:
486 return templatefilters.revescape(req.qsparams['node'])
486 return templatefilters.revescape(req.qsparams['node'])
487 else:
487 else:
488 return short(scmutil.binnode(ctx))
488 return short(scmutil.binnode(ctx))
489
489
490 def _listfilesgen(context, ctx, stripecount):
490 def _listfilesgen(context, ctx, stripecount):
491 parity = paritygen(stripecount)
491 parity = paritygen(stripecount)
492 for blockno, f in enumerate(ctx.files()):
492 for blockno, f in enumerate(ctx.files()):
493 template = 'filenodelink' if f in ctx else 'filenolink'
493 template = 'filenodelink' if f in ctx else 'filenolink'
494 yield context.process(template, {
494 yield context.process(template, {
495 'node': ctx.hex(),
495 'node': ctx.hex(),
496 'file': f,
496 'file': f,
497 'blockno': blockno + 1,
497 'blockno': blockno + 1,
498 'parity': next(parity),
498 'parity': next(parity),
499 })
499 })
500
500
501 def changesetentry(web, ctx):
501 def changesetentry(web, ctx):
502 '''Obtain a dictionary to be used to render the "changeset" template.'''
502 '''Obtain a dictionary to be used to render the "changeset" template.'''
503
503
504 showtags = showtag(web.repo, 'changesettag', scmutil.binnode(ctx))
504 showtags = showtag(web.repo, 'changesettag', scmutil.binnode(ctx))
505 showbookmarks = showbookmark(web.repo, 'changesetbookmark',
505 showbookmarks = showbookmark(web.repo, 'changesetbookmark',
506 scmutil.binnode(ctx))
506 scmutil.binnode(ctx))
507 showbranch = nodebranchnodefault(ctx)
507 showbranch = nodebranchnodefault(ctx)
508
508
509 basectx = basechangectx(web.repo, web.req)
509 basectx = basechangectx(web.repo, web.req)
510 if basectx is None:
510 if basectx is None:
511 basectx = ctx.p1()
511 basectx = ctx.p1()
512
512
513 style = web.config('web', 'style')
513 style = web.config('web', 'style')
514 if 'style' in web.req.qsparams:
514 if 'style' in web.req.qsparams:
515 style = web.req.qsparams['style']
515 style = web.req.qsparams['style']
516
516
517 diff = diffs(web, ctx, basectx, None, style)
517 diff = diffs(web, ctx, basectx, None, style)
518
518
519 parity = paritygen(web.stripecount)
519 parity = paritygen(web.stripecount)
520 diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
520 diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
521 diffstats = diffstat(ctx, diffstatsgen, parity)
521 diffstats = diffstat(ctx, diffstatsgen, parity)
522
522
523 return dict(
523 return dict(
524 diff=diff,
524 diff=diff,
525 symrev=symrevorshortnode(web.req, ctx),
525 symrev=symrevorshortnode(web.req, ctx),
526 basenode=basectx.hex(),
526 basenode=basectx.hex(),
527 changesettag=showtags,
527 changesettag=showtags,
528 changesetbookmark=showbookmarks,
528 changesetbookmark=showbookmarks,
529 changesetbranch=showbranch,
529 changesetbranch=showbranch,
530 files=templateutil.mappedgenerator(_listfilesgen,
530 files=templateutil.mappedgenerator(_listfilesgen,
531 args=(ctx, web.stripecount)),
531 args=(ctx, web.stripecount)),
532 diffsummary=_kwfunc(lambda context, mapping: diffsummary(diffstatsgen)),
532 diffsummary=_kwfunc(lambda context, mapping: diffsummary(diffstatsgen)),
533 diffstat=diffstats,
533 diffstat=diffstats,
534 archives=web.archivelist(ctx.hex()),
534 archives=web.archivelist(ctx.hex()),
535 **pycompat.strkwargs(commonentry(web.repo, ctx)))
535 **pycompat.strkwargs(commonentry(web.repo, ctx)))
536
536
537 def _listfilediffsgen(context, files, node, max):
537 def _listfilediffsgen(context, files, node, max):
538 for f in files[:max]:
538 for f in files[:max]:
539 yield context.process('filedifflink', {'node': hex(node), 'file': f})
539 yield context.process('filedifflink', {'node': hex(node), 'file': f})
540 if len(files) > max:
540 if len(files) > max:
541 yield context.process('fileellipses', {})
541 yield context.process('fileellipses', {})
542
542
543 def listfilediffs(files, node, max):
543 def listfilediffs(files, node, max):
544 return templateutil.mappedgenerator(_listfilediffsgen,
544 return templateutil.mappedgenerator(_listfilediffsgen,
545 args=(files, node, max))
545 args=(files, node, max))
546
546
547 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
547 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
548 for lineno, l in enumerate(lines, 1):
548 for lineno, l in enumerate(lines, 1):
549 difflineno = "%d.%d" % (blockno, lineno)
549 difflineno = "%d.%d" % (blockno, lineno)
550 if l.startswith('+'):
550 if l.startswith('+'):
551 ltype = "difflineplus"
551 ltype = "difflineplus"
552 elif l.startswith('-'):
552 elif l.startswith('-'):
553 ltype = "difflineminus"
553 ltype = "difflineminus"
554 elif l.startswith('@'):
554 elif l.startswith('@'):
555 ltype = "difflineat"
555 ltype = "difflineat"
556 else:
556 else:
557 ltype = "diffline"
557 ltype = "diffline"
558 yield context.process(ltype, {
558 yield context.process(ltype, {
559 'line': l,
559 'line': l,
560 'lineno': lineno,
560 'lineno': lineno,
561 'lineid': lineidprefix + "l%s" % difflineno,
561 'lineid': lineidprefix + "l%s" % difflineno,
562 'linenumber': "% 8s" % difflineno,
562 'linenumber': "% 8s" % difflineno,
563 })
563 })
564
564
565 def _diffsgen(context, repo, ctx, basectx, files, style, stripecount,
565 def _diffsgen(context, repo, ctx, basectx, files, style, stripecount,
566 linerange, lineidprefix):
566 linerange, lineidprefix):
567 if files:
567 if files:
568 m = match.exact(repo.root, repo.getcwd(), files)
568 m = match.exact(repo.root, repo.getcwd(), files)
569 else:
569 else:
570 m = match.always(repo.root, repo.getcwd())
570 m = match.always(repo.root, repo.getcwd())
571
571
572 diffopts = patch.diffopts(repo.ui, untrusted=True)
572 diffopts = patch.diffopts(repo.ui, untrusted=True)
573 node1 = basectx.node()
573 node1 = basectx.node()
574 node2 = ctx.node()
574 node2 = ctx.node()
575 parity = paritygen(stripecount)
575 parity = paritygen(stripecount)
576
576
577 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
577 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
578 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
578 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
579 if style != 'raw':
579 if style != 'raw':
580 header = header[1:]
580 header = header[1:]
581 lines = [h + '\n' for h in header]
581 lines = [h + '\n' for h in header]
582 for hunkrange, hunklines in hunks:
582 for hunkrange, hunklines in hunks:
583 if linerange is not None and hunkrange is not None:
583 if linerange is not None and hunkrange is not None:
584 s1, l1, s2, l2 = hunkrange
584 s1, l1, s2, l2 = hunkrange
585 if not mdiff.hunkinrange((s2, l2), linerange):
585 if not mdiff.hunkinrange((s2, l2), linerange):
586 continue
586 continue
587 lines.extend(hunklines)
587 lines.extend(hunklines)
588 if lines:
588 if lines:
589 l = templateutil.mappedgenerator(_prettyprintdifflines,
589 l = templateutil.mappedgenerator(_prettyprintdifflines,
590 args=(lines, blockno,
590 args=(lines, blockno,
591 lineidprefix))
591 lineidprefix))
592 yield {
592 yield {
593 'parity': next(parity),
593 'parity': next(parity),
594 'blockno': blockno,
594 'blockno': blockno,
595 'lines': l,
595 'lines': l,
596 }
596 }
597
597
598 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=''):
598 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=''):
599 args = (web.repo, ctx, basectx, files, style, web.stripecount,
599 args = (web.repo, ctx, basectx, files, style, web.stripecount,
600 linerange, lineidprefix)
600 linerange, lineidprefix)
601 return templateutil.mappinggenerator(_diffsgen, args=args, name='diffblock')
601 return templateutil.mappinggenerator(_diffsgen, args=args, name='diffblock')
602
602
603 def _compline(type, leftlineno, leftline, rightlineno, rightline):
603 def _compline(type, leftlineno, leftline, rightlineno, rightline):
604 lineid = leftlineno and ("l%d" % leftlineno) or ''
604 lineid = leftlineno and ("l%d" % leftlineno) or ''
605 lineid += rightlineno and ("r%d" % rightlineno) or ''
605 lineid += rightlineno and ("r%d" % rightlineno) or ''
606 llno = '%d' % leftlineno if leftlineno else ''
606 llno = '%d' % leftlineno if leftlineno else ''
607 rlno = '%d' % rightlineno if rightlineno else ''
607 rlno = '%d' % rightlineno if rightlineno else ''
608 return {
608 return {
609 'type': type,
609 'type': type,
610 'lineid': lineid,
610 'lineid': lineid,
611 'leftlineno': leftlineno,
611 'leftlineno': leftlineno,
612 'leftlinenumber': "% 6s" % llno,
612 'leftlinenumber': "% 6s" % llno,
613 'leftline': leftline or '',
613 'leftline': leftline or '',
614 'rightlineno': rightlineno,
614 'rightlineno': rightlineno,
615 'rightlinenumber': "% 6s" % rlno,
615 'rightlinenumber': "% 6s" % rlno,
616 'rightline': rightline or '',
616 'rightline': rightline or '',
617 }
617 }
618
618
619 def _getcompblockgen(context, leftlines, rightlines, opcodes):
619 def _getcompblockgen(context, leftlines, rightlines, opcodes):
620 for type, llo, lhi, rlo, rhi in opcodes:
620 for type, llo, lhi, rlo, rhi in opcodes:
621 type = pycompat.sysbytes(type)
621 type = pycompat.sysbytes(type)
622 len1 = lhi - llo
622 len1 = lhi - llo
623 len2 = rhi - rlo
623 len2 = rhi - rlo
624 count = min(len1, len2)
624 count = min(len1, len2)
625 for i in pycompat.xrange(count):
625 for i in pycompat.xrange(count):
626 yield _compline(type=type,
626 yield _compline(type=type,
627 leftlineno=llo + i + 1,
627 leftlineno=llo + i + 1,
628 leftline=leftlines[llo + i],
628 leftline=leftlines[llo + i],
629 rightlineno=rlo + i + 1,
629 rightlineno=rlo + i + 1,
630 rightline=rightlines[rlo + i])
630 rightline=rightlines[rlo + i])
631 if len1 > len2:
631 if len1 > len2:
632 for i in pycompat.xrange(llo + count, lhi):
632 for i in pycompat.xrange(llo + count, lhi):
633 yield _compline(type=type,
633 yield _compline(type=type,
634 leftlineno=i + 1,
634 leftlineno=i + 1,
635 leftline=leftlines[i],
635 leftline=leftlines[i],
636 rightlineno=None,
636 rightlineno=None,
637 rightline=None)
637 rightline=None)
638 elif len2 > len1:
638 elif len2 > len1:
639 for i in pycompat.xrange(rlo + count, rhi):
639 for i in pycompat.xrange(rlo + count, rhi):
640 yield _compline(type=type,
640 yield _compline(type=type,
641 leftlineno=None,
641 leftlineno=None,
642 leftline=None,
642 leftline=None,
643 rightlineno=i + 1,
643 rightlineno=i + 1,
644 rightline=rightlines[i])
644 rightline=rightlines[i])
645
645
646 def _getcompblock(leftlines, rightlines, opcodes):
646 def _getcompblock(leftlines, rightlines, opcodes):
647 args = (leftlines, rightlines, opcodes)
647 args = (leftlines, rightlines, opcodes)
648 return templateutil.mappinggenerator(_getcompblockgen, args=args,
648 return templateutil.mappinggenerator(_getcompblockgen, args=args,
649 name='comparisonline')
649 name='comparisonline')
650
650
651 def _comparegen(context, contextnum, leftlines, rightlines):
651 def _comparegen(context, contextnum, leftlines, rightlines):
652 '''Generator function that provides side-by-side comparison data.'''
652 '''Generator function that provides side-by-side comparison data.'''
653 s = difflib.SequenceMatcher(None, leftlines, rightlines)
653 s = difflib.SequenceMatcher(None, leftlines, rightlines)
654 if contextnum < 0:
654 if contextnum < 0:
655 l = _getcompblock(leftlines, rightlines, s.get_opcodes())
655 l = _getcompblock(leftlines, rightlines, s.get_opcodes())
656 yield {'lines': l}
656 yield {'lines': l}
657 else:
657 else:
658 for oc in s.get_grouped_opcodes(n=contextnum):
658 for oc in s.get_grouped_opcodes(n=contextnum):
659 l = _getcompblock(leftlines, rightlines, oc)
659 l = _getcompblock(leftlines, rightlines, oc)
660 yield {'lines': l}
660 yield {'lines': l}
661
661
662 def compare(contextnum, leftlines, rightlines):
662 def compare(contextnum, leftlines, rightlines):
663 args = (contextnum, leftlines, rightlines)
663 args = (contextnum, leftlines, rightlines)
664 return templateutil.mappinggenerator(_comparegen, args=args,
664 return templateutil.mappinggenerator(_comparegen, args=args,
665 name='comparisonblock')
665 name='comparisonblock')
666
666
667 def diffstatgen(ui, ctx, basectx):
667 def diffstatgen(ui, ctx, basectx):
668 '''Generator function that provides the diffstat data.'''
668 '''Generator function that provides the diffstat data.'''
669
669
670 diffopts = patch.diffopts(ui, {'noprefix': False})
670 diffopts = patch.diffopts(ui, {'noprefix': False})
671 stats = patch.diffstatdata(
671 stats = patch.diffstatdata(
672 util.iterlines(ctx.diff(basectx, opts=diffopts)))
672 util.iterlines(ctx.diff(basectx, opts=diffopts)))
673 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
673 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
674 while True:
674 while True:
675 yield stats, maxname, maxtotal, addtotal, removetotal, binary
675 yield stats, maxname, maxtotal, addtotal, removetotal, binary
676
676
677 def diffsummary(statgen):
677 def diffsummary(statgen):
678 '''Return a short summary of the diff.'''
678 '''Return a short summary of the diff.'''
679
679
680 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
680 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
681 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
681 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
682 len(stats), addtotal, removetotal)
682 len(stats), addtotal, removetotal)
683
683
684 def _diffstattmplgen(context, ctx, statgen, parity):
684 def _diffstattmplgen(context, ctx, statgen, parity):
685 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
685 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
686 files = ctx.files()
686 files = ctx.files()
687
687
688 def pct(i):
688 def pct(i):
689 if maxtotal == 0:
689 if maxtotal == 0:
690 return 0
690 return 0
691 return (float(i) / maxtotal) * 100
691 return (float(i) / maxtotal) * 100
692
692
693 fileno = 0
693 fileno = 0
694 for filename, adds, removes, isbinary in stats:
694 for filename, adds, removes, isbinary in stats:
695 template = 'diffstatlink' if filename in files else 'diffstatnolink'
695 template = 'diffstatlink' if filename in files else 'diffstatnolink'
696 total = adds + removes
696 total = adds + removes
697 fileno += 1
697 fileno += 1
698 yield context.process(template, {
698 yield context.process(template, {
699 'node': ctx.hex(),
699 'node': ctx.hex(),
700 'file': filename,
700 'file': filename,
701 'fileno': fileno,
701 'fileno': fileno,
702 'total': total,
702 'total': total,
703 'addpct': pct(adds),
703 'addpct': pct(adds),
704 'removepct': pct(removes),
704 'removepct': pct(removes),
705 'parity': next(parity),
705 'parity': next(parity),
706 })
706 })
707
707
708 def diffstat(ctx, statgen, parity):
708 def diffstat(ctx, statgen, parity):
709 '''Return a diffstat template for each file in the diff.'''
709 '''Return a diffstat template for each file in the diff.'''
710 args = (ctx, statgen, parity)
710 args = (ctx, statgen, parity)
711 return templateutil.mappedgenerator(_diffstattmplgen, args=args)
711 return templateutil.mappedgenerator(_diffstattmplgen, args=args)
712
712
713 class sessionvars(templateutil.wrapped):
713 class sessionvars(templateutil.wrapped):
714 def __init__(self, vars, start='?'):
714 def __init__(self, vars, start='?'):
715 self._start = start
715 self._start = start
716 self._vars = vars
716 self._vars = vars
717
717
718 def __getitem__(self, key):
718 def __getitem__(self, key):
719 return self._vars[key]
719 return self._vars[key]
720
720
721 def __setitem__(self, key, value):
721 def __setitem__(self, key, value):
722 self._vars[key] = value
722 self._vars[key] = value
723
723
724 def __copy__(self):
724 def __copy__(self):
725 return sessionvars(copy.copy(self._vars), self._start)
725 return sessionvars(copy.copy(self._vars), self._start)
726
726
727 def contains(self, context, mapping, item):
727 def contains(self, context, mapping, item):
728 item = templateutil.unwrapvalue(context, mapping, item)
728 item = templateutil.unwrapvalue(context, mapping, item)
729 return item in self._vars
729 return item in self._vars
730
730
731 def getmember(self, context, mapping, key):
731 def getmember(self, context, mapping, key):
732 key = templateutil.unwrapvalue(context, mapping, key)
732 key = templateutil.unwrapvalue(context, mapping, key)
733 return self._vars.get(key)
733 return self._vars.get(key)
734
734
735 def getmin(self, context, mapping):
735 def getmin(self, context, mapping):
736 raise error.ParseError(_('not comparable'))
736 raise error.ParseError(_('not comparable'))
737
737
738 def getmax(self, context, mapping):
738 def getmax(self, context, mapping):
739 raise error.ParseError(_('not comparable'))
739 raise error.ParseError(_('not comparable'))
740
740
741 def filter(self, context, mapping, select):
741 def filter(self, context, mapping, select):
742 # implement if necessary
742 # implement if necessary
743 raise error.ParseError(_('not filterable'))
743 raise error.ParseError(_('not filterable'))
744
744
745 def itermaps(self, context):
745 def itermaps(self, context):
746 separator = self._start
746 separator = self._start
747 for key, value in sorted(self._vars.iteritems()):
747 for key, value in sorted(self._vars.iteritems()):
748 yield {'name': key,
748 yield {'name': key,
749 'value': pycompat.bytestr(value),
749 'value': pycompat.bytestr(value),
750 'separator': separator,
750 'separator': separator,
751 }
751 }
752 separator = '&'
752 separator = '&'
753
753
754 def join(self, context, mapping, sep):
754 def join(self, context, mapping, sep):
755 # could be '{separator}{name}={value|urlescape}'
755 # could be '{separator}{name}={value|urlescape}'
756 raise error.ParseError(_('not displayable without template'))
756 raise error.ParseError(_('not displayable without template'))
757
757
758 def show(self, context, mapping):
758 def show(self, context, mapping):
759 return self.join(context, '')
759 return self.join(context, '')
760
760
761 def tobool(self, context, mapping):
761 def tobool(self, context, mapping):
762 return bool(self._vars)
762 return bool(self._vars)
763
763
764 def tovalue(self, context, mapping):
764 def tovalue(self, context, mapping):
765 return self._vars
765 return self._vars
766
766
767 class wsgiui(uimod.ui):
767 class wsgiui(uimod.ui):
768 # default termwidth breaks under mod_wsgi
768 # default termwidth breaks under mod_wsgi
769 def termwidth(self):
769 def termwidth(self):
770 return 80
770 return 80
771
771
772 def getwebsubs(repo):
772 def getwebsubs(repo):
773 websubtable = []
773 websubtable = []
774 websubdefs = repo.ui.configitems('websub')
774 websubdefs = repo.ui.configitems('websub')
775 # we must maintain interhg backwards compatibility
775 # we must maintain interhg backwards compatibility
776 websubdefs += repo.ui.configitems('interhg')
776 websubdefs += repo.ui.configitems('interhg')
777 for key, pattern in websubdefs:
777 for key, pattern in websubdefs:
778 # grab the delimiter from the character after the "s"
778 # grab the delimiter from the character after the "s"
779 unesc = pattern[1:2]
779 unesc = pattern[1:2]
780 delim = stringutil.reescape(unesc)
780 delim = stringutil.reescape(unesc)
781
781
782 # identify portions of the pattern, taking care to avoid escaped
782 # identify portions of the pattern, taking care to avoid escaped
783 # delimiters. the replace format and flags are optional, but
783 # delimiters. the replace format and flags are optional, but
784 # delimiters are required.
784 # delimiters are required.
785 match = re.match(
785 match = re.match(
786 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
786 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
787 % (delim, delim, delim), pattern)
787 % (delim, delim, delim), pattern)
788 if not match:
788 if not match:
789 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
789 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
790 % (key, pattern))
790 % (key, pattern))
791 continue
791 continue
792
792
793 # we need to unescape the delimiter for regexp and format
793 # we need to unescape the delimiter for regexp and format
794 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
794 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
795 regexp = delim_re.sub(unesc, match.group(1))
795 regexp = delim_re.sub(unesc, match.group(1))
796 format = delim_re.sub(unesc, match.group(2))
796 format = delim_re.sub(unesc, match.group(2))
797
797
798 # the pattern allows for 6 regexp flags, so set them if necessary
798 # the pattern allows for 6 regexp flags, so set them if necessary
799 flagin = match.group(3)
799 flagin = match.group(3)
800 flags = 0
800 flags = 0
801 if flagin:
801 if flagin:
802 for flag in flagin.upper():
802 for flag in flagin.upper():
803 flags |= re.__dict__[flag]
803 flags |= re.__dict__[flag]
804
804
805 try:
805 try:
806 regexp = re.compile(regexp, flags)
806 regexp = re.compile(regexp, flags)
807 websubtable.append((regexp, format))
807 websubtable.append((regexp, format))
808 except re.error:
808 except re.error:
809 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
809 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
810 % (key, regexp))
810 % (key, regexp))
811 return websubtable
811 return websubtable
812
812
813 def getgraphnode(repo, ctx):
813 def getgraphnode(repo, ctx):
814 return (templatekw.getgraphnodecurrent(repo, ctx) +
814 return (templatekw.getgraphnodecurrent(repo, ctx) +
815 templatekw.getgraphnodesymbol(ctx))
815 templatekw.getgraphnodesymbol(ctx))
General Comments 0
You need to be logged in to leave comments. Login now