##// END OF EJS Templates
hgweb: mark all lambda template keywords as new-style function...
Yuya Nishihara -
r38965:d7e6e109 default
parent child Browse files
Show More
@@ -1,806 +1,812 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 return pathutil.canonpath(repo.root, '', path)
323 return pathutil.canonpath(repo.root, '', path)
324
324
325 def changectx(repo, req):
325 def changectx(repo, req):
326 changeid = "tip"
326 changeid = "tip"
327 if 'node' in req.qsparams:
327 if 'node' in req.qsparams:
328 changeid = req.qsparams['node']
328 changeid = req.qsparams['node']
329 ipos = changeid.find(':')
329 ipos = changeid.find(':')
330 if ipos != -1:
330 if ipos != -1:
331 changeid = changeid[(ipos + 1):]
331 changeid = changeid[(ipos + 1):]
332
332
333 return scmutil.revsymbol(repo, changeid)
333 return scmutil.revsymbol(repo, changeid)
334
334
335 def basechangectx(repo, req):
335 def basechangectx(repo, req):
336 if 'node' in req.qsparams:
336 if 'node' in req.qsparams:
337 changeid = req.qsparams['node']
337 changeid = req.qsparams['node']
338 ipos = changeid.find(':')
338 ipos = changeid.find(':')
339 if ipos != -1:
339 if ipos != -1:
340 changeid = changeid[:ipos]
340 changeid = changeid[:ipos]
341 return scmutil.revsymbol(repo, changeid)
341 return scmutil.revsymbol(repo, changeid)
342
342
343 return None
343 return None
344
344
345 def filectx(repo, req):
345 def filectx(repo, req):
346 if 'file' not in req.qsparams:
346 if 'file' not in req.qsparams:
347 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
347 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
348 path = cleanpath(repo, req.qsparams['file'])
348 path = cleanpath(repo, req.qsparams['file'])
349 if 'node' in req.qsparams:
349 if 'node' in req.qsparams:
350 changeid = req.qsparams['node']
350 changeid = req.qsparams['node']
351 elif 'filenode' in req.qsparams:
351 elif 'filenode' in req.qsparams:
352 changeid = req.qsparams['filenode']
352 changeid = req.qsparams['filenode']
353 else:
353 else:
354 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
354 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
355 try:
355 try:
356 fctx = scmutil.revsymbol(repo, changeid)[path]
356 fctx = scmutil.revsymbol(repo, changeid)[path]
357 except error.RepoError:
357 except error.RepoError:
358 fctx = repo.filectx(path, fileid=changeid)
358 fctx = repo.filectx(path, fileid=changeid)
359
359
360 return fctx
360 return fctx
361
361
362 def linerange(req):
362 def linerange(req):
363 linerange = req.qsparams.getall('linerange')
363 linerange = req.qsparams.getall('linerange')
364 if not linerange:
364 if not linerange:
365 return None
365 return None
366 if len(linerange) > 1:
366 if len(linerange) > 1:
367 raise ErrorResponse(HTTP_BAD_REQUEST,
367 raise ErrorResponse(HTTP_BAD_REQUEST,
368 'redundant linerange parameter')
368 'redundant linerange parameter')
369 try:
369 try:
370 fromline, toline = map(int, linerange[0].split(':', 1))
370 fromline, toline = map(int, linerange[0].split(':', 1))
371 except ValueError:
371 except ValueError:
372 raise ErrorResponse(HTTP_BAD_REQUEST,
372 raise ErrorResponse(HTTP_BAD_REQUEST,
373 'invalid linerange parameter')
373 'invalid linerange parameter')
374 try:
374 try:
375 return util.processlinerange(fromline, toline)
375 return util.processlinerange(fromline, toline)
376 except error.ParseError as exc:
376 except error.ParseError as exc:
377 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
377 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
378
378
379 def formatlinerange(fromline, toline):
379 def formatlinerange(fromline, toline):
380 return '%d:%d' % (fromline + 1, toline)
380 return '%d:%d' % (fromline + 1, toline)
381
381
382 def _succsandmarkersgen(context, mapping):
382 def _succsandmarkersgen(context, mapping):
383 repo = context.resource(mapping, 'repo')
383 repo = context.resource(mapping, 'repo')
384 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
384 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
385 for item in itemmappings.tovalue(context, mapping):
385 for item in itemmappings.tovalue(context, mapping):
386 item['successors'] = _siblings(repo[successor]
386 item['successors'] = _siblings(repo[successor]
387 for successor in item['successors'])
387 for successor in item['successors'])
388 yield item
388 yield item
389
389
390 def succsandmarkers(context, mapping):
390 def succsandmarkers(context, mapping):
391 return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
391 return templateutil.mappinggenerator(_succsandmarkersgen, args=(mapping,))
392
392
393 # teach templater succsandmarkers is switched to (context, mapping) API
393 # teach templater succsandmarkers is switched to (context, mapping) API
394 succsandmarkers._requires = {'repo', 'ctx'}
394 succsandmarkers._requires = {'repo', 'ctx'}
395
395
396 def _whyunstablegen(context, mapping):
396 def _whyunstablegen(context, mapping):
397 repo = context.resource(mapping, 'repo')
397 repo = context.resource(mapping, 'repo')
398 ctx = context.resource(mapping, 'ctx')
398 ctx = context.resource(mapping, 'ctx')
399
399
400 entries = obsutil.whyunstable(repo, ctx)
400 entries = obsutil.whyunstable(repo, ctx)
401 for entry in entries:
401 for entry in entries:
402 if entry.get('divergentnodes'):
402 if entry.get('divergentnodes'):
403 entry['divergentnodes'] = _siblings(entry['divergentnodes'])
403 entry['divergentnodes'] = _siblings(entry['divergentnodes'])
404 yield entry
404 yield entry
405
405
406 def whyunstable(context, mapping):
406 def whyunstable(context, mapping):
407 return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
407 return templateutil.mappinggenerator(_whyunstablegen, args=(mapping,))
408
408
409 whyunstable._requires = {'repo', 'ctx'}
409 whyunstable._requires = {'repo', 'ctx'}
410
410
411 # helper to mark a function as a new-style template keyword; can be removed
412 # once old-style function gets unsupported and new-style becomes the default
413 def _kwfunc(f):
414 f._requires = ()
415 return f
416
411 def commonentry(repo, ctx):
417 def commonentry(repo, ctx):
412 node = ctx.node()
418 node = ctx.node()
413 return {
419 return {
414 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
420 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
415 # filectx, but I'm not pretty sure if that would always work because
421 # filectx, but I'm not pretty sure if that would always work because
416 # fctx.parents() != fctx.changectx.parents() for example.
422 # fctx.parents() != fctx.changectx.parents() for example.
417 'ctx': ctx,
423 'ctx': ctx,
418 'rev': ctx.rev(),
424 'rev': ctx.rev(),
419 'node': hex(node),
425 'node': hex(node),
420 'author': ctx.user(),
426 'author': ctx.user(),
421 'desc': ctx.description(),
427 'desc': ctx.description(),
422 'date': ctx.date(),
428 'date': ctx.date(),
423 'extra': ctx.extra(),
429 'extra': ctx.extra(),
424 'phase': ctx.phasestr(),
430 'phase': ctx.phasestr(),
425 'obsolete': ctx.obsolete(),
431 'obsolete': ctx.obsolete(),
426 'succsandmarkers': succsandmarkers,
432 'succsandmarkers': succsandmarkers,
427 'instabilities': templateutil.hybridlist(ctx.instabilities(),
433 'instabilities': templateutil.hybridlist(ctx.instabilities(),
428 name='instability'),
434 name='instability'),
429 'whyunstable': whyunstable,
435 'whyunstable': whyunstable,
430 'branch': nodebranchnodefault(ctx),
436 'branch': nodebranchnodefault(ctx),
431 'inbranch': nodeinbranch(repo, ctx),
437 'inbranch': nodeinbranch(repo, ctx),
432 'branches': nodebranchdict(repo, ctx),
438 'branches': nodebranchdict(repo, ctx),
433 'tags': nodetagsdict(repo, node),
439 'tags': nodetagsdict(repo, node),
434 'bookmarks': nodebookmarksdict(repo, node),
440 'bookmarks': nodebookmarksdict(repo, node),
435 'parent': lambda **x: parents(ctx),
441 'parent': _kwfunc(lambda context, mapping: parents(ctx)),
436 'child': lambda **x: children(ctx),
442 'child': _kwfunc(lambda context, mapping: children(ctx)),
437 }
443 }
438
444
439 def changelistentry(web, ctx):
445 def changelistentry(web, ctx):
440 '''Obtain a dictionary to be used for entries in a changelist.
446 '''Obtain a dictionary to be used for entries in a changelist.
441
447
442 This function is called when producing items for the "entries" list passed
448 This function is called when producing items for the "entries" list passed
443 to the "shortlog" and "changelog" templates.
449 to the "shortlog" and "changelog" templates.
444 '''
450 '''
445 repo = web.repo
451 repo = web.repo
446 rev = ctx.rev()
452 rev = ctx.rev()
447 n = ctx.node()
453 n = ctx.node()
448 showtags = showtag(repo, 'changelogtag', n)
454 showtags = showtag(repo, 'changelogtag', n)
449 files = listfilediffs(ctx.files(), n, web.maxfiles)
455 files = listfilediffs(ctx.files(), n, web.maxfiles)
450
456
451 entry = commonentry(repo, ctx)
457 entry = commonentry(repo, ctx)
452 entry.update(
458 entry.update(
453 allparents=lambda **x: parents(ctx),
459 allparents=_kwfunc(lambda context, mapping: parents(ctx)),
454 parent=lambda **x: parents(ctx, rev - 1),
460 parent=_kwfunc(lambda context, mapping: parents(ctx, rev - 1)),
455 child=lambda **x: children(ctx, rev + 1),
461 child=_kwfunc(lambda context, mapping: children(ctx, rev + 1)),
456 changelogtag=showtags,
462 changelogtag=showtags,
457 files=files,
463 files=files,
458 )
464 )
459 return entry
465 return entry
460
466
461 def changelistentries(web, revs, maxcount, parityfn):
467 def changelistentries(web, revs, maxcount, parityfn):
462 """Emit up to N records for an iterable of revisions."""
468 """Emit up to N records for an iterable of revisions."""
463 repo = web.repo
469 repo = web.repo
464
470
465 count = 0
471 count = 0
466 for rev in revs:
472 for rev in revs:
467 if count >= maxcount:
473 if count >= maxcount:
468 break
474 break
469
475
470 count += 1
476 count += 1
471
477
472 entry = changelistentry(web, repo[rev])
478 entry = changelistentry(web, repo[rev])
473 entry['parity'] = next(parityfn)
479 entry['parity'] = next(parityfn)
474
480
475 yield entry
481 yield entry
476
482
477 def symrevorshortnode(req, ctx):
483 def symrevorshortnode(req, ctx):
478 if 'node' in req.qsparams:
484 if 'node' in req.qsparams:
479 return templatefilters.revescape(req.qsparams['node'])
485 return templatefilters.revescape(req.qsparams['node'])
480 else:
486 else:
481 return short(ctx.node())
487 return short(ctx.node())
482
488
483 def _listfilesgen(context, ctx, stripecount):
489 def _listfilesgen(context, ctx, stripecount):
484 parity = paritygen(stripecount)
490 parity = paritygen(stripecount)
485 for blockno, f in enumerate(ctx.files()):
491 for blockno, f in enumerate(ctx.files()):
486 template = 'filenodelink' if f in ctx else 'filenolink'
492 template = 'filenodelink' if f in ctx else 'filenolink'
487 yield context.process(template, {
493 yield context.process(template, {
488 'node': ctx.hex(),
494 'node': ctx.hex(),
489 'file': f,
495 'file': f,
490 'blockno': blockno + 1,
496 'blockno': blockno + 1,
491 'parity': next(parity),
497 'parity': next(parity),
492 })
498 })
493
499
494 def changesetentry(web, ctx):
500 def changesetentry(web, ctx):
495 '''Obtain a dictionary to be used to render the "changeset" template.'''
501 '''Obtain a dictionary to be used to render the "changeset" template.'''
496
502
497 showtags = showtag(web.repo, 'changesettag', ctx.node())
503 showtags = showtag(web.repo, 'changesettag', ctx.node())
498 showbookmarks = showbookmark(web.repo, 'changesetbookmark', ctx.node())
504 showbookmarks = showbookmark(web.repo, 'changesetbookmark', ctx.node())
499 showbranch = nodebranchnodefault(ctx)
505 showbranch = nodebranchnodefault(ctx)
500
506
501 basectx = basechangectx(web.repo, web.req)
507 basectx = basechangectx(web.repo, web.req)
502 if basectx is None:
508 if basectx is None:
503 basectx = ctx.p1()
509 basectx = ctx.p1()
504
510
505 style = web.config('web', 'style')
511 style = web.config('web', 'style')
506 if 'style' in web.req.qsparams:
512 if 'style' in web.req.qsparams:
507 style = web.req.qsparams['style']
513 style = web.req.qsparams['style']
508
514
509 diff = diffs(web, ctx, basectx, None, style)
515 diff = diffs(web, ctx, basectx, None, style)
510
516
511 parity = paritygen(web.stripecount)
517 parity = paritygen(web.stripecount)
512 diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
518 diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
513 diffstats = diffstat(ctx, diffstatsgen, parity)
519 diffstats = diffstat(ctx, diffstatsgen, parity)
514
520
515 return dict(
521 return dict(
516 diff=diff,
522 diff=diff,
517 symrev=symrevorshortnode(web.req, ctx),
523 symrev=symrevorshortnode(web.req, ctx),
518 basenode=basectx.hex(),
524 basenode=basectx.hex(),
519 changesettag=showtags,
525 changesettag=showtags,
520 changesetbookmark=showbookmarks,
526 changesetbookmark=showbookmarks,
521 changesetbranch=showbranch,
527 changesetbranch=showbranch,
522 files=templateutil.mappedgenerator(_listfilesgen,
528 files=templateutil.mappedgenerator(_listfilesgen,
523 args=(ctx, web.stripecount)),
529 args=(ctx, web.stripecount)),
524 diffsummary=lambda **x: diffsummary(diffstatsgen),
530 diffsummary=_kwfunc(lambda context, mapping: diffsummary(diffstatsgen)),
525 diffstat=diffstats,
531 diffstat=diffstats,
526 archives=web.archivelist(ctx.hex()),
532 archives=web.archivelist(ctx.hex()),
527 **pycompat.strkwargs(commonentry(web.repo, ctx)))
533 **pycompat.strkwargs(commonentry(web.repo, ctx)))
528
534
529 def _listfilediffsgen(context, files, node, max):
535 def _listfilediffsgen(context, files, node, max):
530 for f in files[:max]:
536 for f in files[:max]:
531 yield context.process('filedifflink', {'node': hex(node), 'file': f})
537 yield context.process('filedifflink', {'node': hex(node), 'file': f})
532 if len(files) > max:
538 if len(files) > max:
533 yield context.process('fileellipses', {})
539 yield context.process('fileellipses', {})
534
540
535 def listfilediffs(files, node, max):
541 def listfilediffs(files, node, max):
536 return templateutil.mappedgenerator(_listfilediffsgen,
542 return templateutil.mappedgenerator(_listfilediffsgen,
537 args=(files, node, max))
543 args=(files, node, max))
538
544
539 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
545 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
540 for lineno, l in enumerate(lines, 1):
546 for lineno, l in enumerate(lines, 1):
541 difflineno = "%d.%d" % (blockno, lineno)
547 difflineno = "%d.%d" % (blockno, lineno)
542 if l.startswith('+'):
548 if l.startswith('+'):
543 ltype = "difflineplus"
549 ltype = "difflineplus"
544 elif l.startswith('-'):
550 elif l.startswith('-'):
545 ltype = "difflineminus"
551 ltype = "difflineminus"
546 elif l.startswith('@'):
552 elif l.startswith('@'):
547 ltype = "difflineat"
553 ltype = "difflineat"
548 else:
554 else:
549 ltype = "diffline"
555 ltype = "diffline"
550 yield context.process(ltype, {
556 yield context.process(ltype, {
551 'line': l,
557 'line': l,
552 'lineno': lineno,
558 'lineno': lineno,
553 'lineid': lineidprefix + "l%s" % difflineno,
559 'lineid': lineidprefix + "l%s" % difflineno,
554 'linenumber': "% 8s" % difflineno,
560 'linenumber': "% 8s" % difflineno,
555 })
561 })
556
562
557 def _diffsgen(context, repo, ctx, basectx, files, style, stripecount,
563 def _diffsgen(context, repo, ctx, basectx, files, style, stripecount,
558 linerange, lineidprefix):
564 linerange, lineidprefix):
559 if files:
565 if files:
560 m = match.exact(repo.root, repo.getcwd(), files)
566 m = match.exact(repo.root, repo.getcwd(), files)
561 else:
567 else:
562 m = match.always(repo.root, repo.getcwd())
568 m = match.always(repo.root, repo.getcwd())
563
569
564 diffopts = patch.diffopts(repo.ui, untrusted=True)
570 diffopts = patch.diffopts(repo.ui, untrusted=True)
565 node1 = basectx.node()
571 node1 = basectx.node()
566 node2 = ctx.node()
572 node2 = ctx.node()
567 parity = paritygen(stripecount)
573 parity = paritygen(stripecount)
568
574
569 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
575 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
570 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
576 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
571 if style != 'raw':
577 if style != 'raw':
572 header = header[1:]
578 header = header[1:]
573 lines = [h + '\n' for h in header]
579 lines = [h + '\n' for h in header]
574 for hunkrange, hunklines in hunks:
580 for hunkrange, hunklines in hunks:
575 if linerange is not None and hunkrange is not None:
581 if linerange is not None and hunkrange is not None:
576 s1, l1, s2, l2 = hunkrange
582 s1, l1, s2, l2 = hunkrange
577 if not mdiff.hunkinrange((s2, l2), linerange):
583 if not mdiff.hunkinrange((s2, l2), linerange):
578 continue
584 continue
579 lines.extend(hunklines)
585 lines.extend(hunklines)
580 if lines:
586 if lines:
581 l = templateutil.mappedgenerator(_prettyprintdifflines,
587 l = templateutil.mappedgenerator(_prettyprintdifflines,
582 args=(lines, blockno,
588 args=(lines, blockno,
583 lineidprefix))
589 lineidprefix))
584 yield {
590 yield {
585 'parity': next(parity),
591 'parity': next(parity),
586 'blockno': blockno,
592 'blockno': blockno,
587 'lines': l,
593 'lines': l,
588 }
594 }
589
595
590 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=''):
596 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=''):
591 args = (web.repo, ctx, basectx, files, style, web.stripecount,
597 args = (web.repo, ctx, basectx, files, style, web.stripecount,
592 linerange, lineidprefix)
598 linerange, lineidprefix)
593 return templateutil.mappinggenerator(_diffsgen, args=args, name='diffblock')
599 return templateutil.mappinggenerator(_diffsgen, args=args, name='diffblock')
594
600
595 def _compline(type, leftlineno, leftline, rightlineno, rightline):
601 def _compline(type, leftlineno, leftline, rightlineno, rightline):
596 lineid = leftlineno and ("l%d" % leftlineno) or ''
602 lineid = leftlineno and ("l%d" % leftlineno) or ''
597 lineid += rightlineno and ("r%d" % rightlineno) or ''
603 lineid += rightlineno and ("r%d" % rightlineno) or ''
598 llno = '%d' % leftlineno if leftlineno else ''
604 llno = '%d' % leftlineno if leftlineno else ''
599 rlno = '%d' % rightlineno if rightlineno else ''
605 rlno = '%d' % rightlineno if rightlineno else ''
600 return {
606 return {
601 'type': type,
607 'type': type,
602 'lineid': lineid,
608 'lineid': lineid,
603 'leftlineno': leftlineno,
609 'leftlineno': leftlineno,
604 'leftlinenumber': "% 6s" % llno,
610 'leftlinenumber': "% 6s" % llno,
605 'leftline': leftline or '',
611 'leftline': leftline or '',
606 'rightlineno': rightlineno,
612 'rightlineno': rightlineno,
607 'rightlinenumber': "% 6s" % rlno,
613 'rightlinenumber': "% 6s" % rlno,
608 'rightline': rightline or '',
614 'rightline': rightline or '',
609 }
615 }
610
616
611 def _getcompblockgen(context, leftlines, rightlines, opcodes):
617 def _getcompblockgen(context, leftlines, rightlines, opcodes):
612 for type, llo, lhi, rlo, rhi in opcodes:
618 for type, llo, lhi, rlo, rhi in opcodes:
613 len1 = lhi - llo
619 len1 = lhi - llo
614 len2 = rhi - rlo
620 len2 = rhi - rlo
615 count = min(len1, len2)
621 count = min(len1, len2)
616 for i in pycompat.xrange(count):
622 for i in pycompat.xrange(count):
617 yield _compline(type=type,
623 yield _compline(type=type,
618 leftlineno=llo + i + 1,
624 leftlineno=llo + i + 1,
619 leftline=leftlines[llo + i],
625 leftline=leftlines[llo + i],
620 rightlineno=rlo + i + 1,
626 rightlineno=rlo + i + 1,
621 rightline=rightlines[rlo + i])
627 rightline=rightlines[rlo + i])
622 if len1 > len2:
628 if len1 > len2:
623 for i in pycompat.xrange(llo + count, lhi):
629 for i in pycompat.xrange(llo + count, lhi):
624 yield _compline(type=type,
630 yield _compline(type=type,
625 leftlineno=i + 1,
631 leftlineno=i + 1,
626 leftline=leftlines[i],
632 leftline=leftlines[i],
627 rightlineno=None,
633 rightlineno=None,
628 rightline=None)
634 rightline=None)
629 elif len2 > len1:
635 elif len2 > len1:
630 for i in pycompat.xrange(rlo + count, rhi):
636 for i in pycompat.xrange(rlo + count, rhi):
631 yield _compline(type=type,
637 yield _compline(type=type,
632 leftlineno=None,
638 leftlineno=None,
633 leftline=None,
639 leftline=None,
634 rightlineno=i + 1,
640 rightlineno=i + 1,
635 rightline=rightlines[i])
641 rightline=rightlines[i])
636
642
637 def _getcompblock(leftlines, rightlines, opcodes):
643 def _getcompblock(leftlines, rightlines, opcodes):
638 args = (leftlines, rightlines, opcodes)
644 args = (leftlines, rightlines, opcodes)
639 return templateutil.mappinggenerator(_getcompblockgen, args=args,
645 return templateutil.mappinggenerator(_getcompblockgen, args=args,
640 name='comparisonline')
646 name='comparisonline')
641
647
642 def _comparegen(context, contextnum, leftlines, rightlines):
648 def _comparegen(context, contextnum, leftlines, rightlines):
643 '''Generator function that provides side-by-side comparison data.'''
649 '''Generator function that provides side-by-side comparison data.'''
644 s = difflib.SequenceMatcher(None, leftlines, rightlines)
650 s = difflib.SequenceMatcher(None, leftlines, rightlines)
645 if contextnum < 0:
651 if contextnum < 0:
646 l = _getcompblock(leftlines, rightlines, s.get_opcodes())
652 l = _getcompblock(leftlines, rightlines, s.get_opcodes())
647 yield {'lines': l}
653 yield {'lines': l}
648 else:
654 else:
649 for oc in s.get_grouped_opcodes(n=contextnum):
655 for oc in s.get_grouped_opcodes(n=contextnum):
650 l = _getcompblock(leftlines, rightlines, oc)
656 l = _getcompblock(leftlines, rightlines, oc)
651 yield {'lines': l}
657 yield {'lines': l}
652
658
653 def compare(contextnum, leftlines, rightlines):
659 def compare(contextnum, leftlines, rightlines):
654 args = (contextnum, leftlines, rightlines)
660 args = (contextnum, leftlines, rightlines)
655 return templateutil.mappinggenerator(_comparegen, args=args,
661 return templateutil.mappinggenerator(_comparegen, args=args,
656 name='comparisonblock')
662 name='comparisonblock')
657
663
658 def diffstatgen(ui, ctx, basectx):
664 def diffstatgen(ui, ctx, basectx):
659 '''Generator function that provides the diffstat data.'''
665 '''Generator function that provides the diffstat data.'''
660
666
661 diffopts = patch.diffopts(ui, {'noprefix': False})
667 diffopts = patch.diffopts(ui, {'noprefix': False})
662 stats = patch.diffstatdata(
668 stats = patch.diffstatdata(
663 util.iterlines(ctx.diff(basectx, opts=diffopts)))
669 util.iterlines(ctx.diff(basectx, opts=diffopts)))
664 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
670 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
665 while True:
671 while True:
666 yield stats, maxname, maxtotal, addtotal, removetotal, binary
672 yield stats, maxname, maxtotal, addtotal, removetotal, binary
667
673
668 def diffsummary(statgen):
674 def diffsummary(statgen):
669 '''Return a short summary of the diff.'''
675 '''Return a short summary of the diff.'''
670
676
671 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
677 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
672 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
678 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
673 len(stats), addtotal, removetotal)
679 len(stats), addtotal, removetotal)
674
680
675 def _diffstattmplgen(context, ctx, statgen, parity):
681 def _diffstattmplgen(context, ctx, statgen, parity):
676 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
682 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
677 files = ctx.files()
683 files = ctx.files()
678
684
679 def pct(i):
685 def pct(i):
680 if maxtotal == 0:
686 if maxtotal == 0:
681 return 0
687 return 0
682 return (float(i) / maxtotal) * 100
688 return (float(i) / maxtotal) * 100
683
689
684 fileno = 0
690 fileno = 0
685 for filename, adds, removes, isbinary in stats:
691 for filename, adds, removes, isbinary in stats:
686 template = 'diffstatlink' if filename in files else 'diffstatnolink'
692 template = 'diffstatlink' if filename in files else 'diffstatnolink'
687 total = adds + removes
693 total = adds + removes
688 fileno += 1
694 fileno += 1
689 yield context.process(template, {
695 yield context.process(template, {
690 'node': ctx.hex(),
696 'node': ctx.hex(),
691 'file': filename,
697 'file': filename,
692 'fileno': fileno,
698 'fileno': fileno,
693 'total': total,
699 'total': total,
694 'addpct': pct(adds),
700 'addpct': pct(adds),
695 'removepct': pct(removes),
701 'removepct': pct(removes),
696 'parity': next(parity),
702 'parity': next(parity),
697 })
703 })
698
704
699 def diffstat(ctx, statgen, parity):
705 def diffstat(ctx, statgen, parity):
700 '''Return a diffstat template for each file in the diff.'''
706 '''Return a diffstat template for each file in the diff.'''
701 args = (ctx, statgen, parity)
707 args = (ctx, statgen, parity)
702 return templateutil.mappedgenerator(_diffstattmplgen, args=args)
708 return templateutil.mappedgenerator(_diffstattmplgen, args=args)
703
709
704 class sessionvars(templateutil.wrapped):
710 class sessionvars(templateutil.wrapped):
705 def __init__(self, vars, start='?'):
711 def __init__(self, vars, start='?'):
706 self._start = start
712 self._start = start
707 self._vars = vars
713 self._vars = vars
708
714
709 def __getitem__(self, key):
715 def __getitem__(self, key):
710 return self._vars[key]
716 return self._vars[key]
711
717
712 def __setitem__(self, key, value):
718 def __setitem__(self, key, value):
713 self._vars[key] = value
719 self._vars[key] = value
714
720
715 def __copy__(self):
721 def __copy__(self):
716 return sessionvars(copy.copy(self._vars), self._start)
722 return sessionvars(copy.copy(self._vars), self._start)
717
723
718 def contains(self, context, mapping, item):
724 def contains(self, context, mapping, item):
719 item = templateutil.unwrapvalue(context, mapping, item)
725 item = templateutil.unwrapvalue(context, mapping, item)
720 return item in self._vars
726 return item in self._vars
721
727
722 def getmember(self, context, mapping, key):
728 def getmember(self, context, mapping, key):
723 key = templateutil.unwrapvalue(context, mapping, key)
729 key = templateutil.unwrapvalue(context, mapping, key)
724 return self._vars.get(key)
730 return self._vars.get(key)
725
731
726 def getmin(self, context, mapping):
732 def getmin(self, context, mapping):
727 raise error.ParseError(_('not comparable'))
733 raise error.ParseError(_('not comparable'))
728
734
729 def getmax(self, context, mapping):
735 def getmax(self, context, mapping):
730 raise error.ParseError(_('not comparable'))
736 raise error.ParseError(_('not comparable'))
731
737
732 def filter(self, context, mapping, select):
738 def filter(self, context, mapping, select):
733 # implement if necessary
739 # implement if necessary
734 raise error.ParseError(_('not filterable'))
740 raise error.ParseError(_('not filterable'))
735
741
736 def itermaps(self, context):
742 def itermaps(self, context):
737 separator = self._start
743 separator = self._start
738 for key, value in sorted(self._vars.iteritems()):
744 for key, value in sorted(self._vars.iteritems()):
739 yield {'name': key,
745 yield {'name': key,
740 'value': pycompat.bytestr(value),
746 'value': pycompat.bytestr(value),
741 'separator': separator,
747 'separator': separator,
742 }
748 }
743 separator = '&'
749 separator = '&'
744
750
745 def join(self, context, mapping, sep):
751 def join(self, context, mapping, sep):
746 # could be '{separator}{name}={value|urlescape}'
752 # could be '{separator}{name}={value|urlescape}'
747 raise error.ParseError(_('not displayable without template'))
753 raise error.ParseError(_('not displayable without template'))
748
754
749 def show(self, context, mapping):
755 def show(self, context, mapping):
750 return self.join(context, '')
756 return self.join(context, '')
751
757
752 def tobool(self, context, mapping):
758 def tobool(self, context, mapping):
753 return bool(self._vars)
759 return bool(self._vars)
754
760
755 def tovalue(self, context, mapping):
761 def tovalue(self, context, mapping):
756 return self._vars
762 return self._vars
757
763
758 class wsgiui(uimod.ui):
764 class wsgiui(uimod.ui):
759 # default termwidth breaks under mod_wsgi
765 # default termwidth breaks under mod_wsgi
760 def termwidth(self):
766 def termwidth(self):
761 return 80
767 return 80
762
768
763 def getwebsubs(repo):
769 def getwebsubs(repo):
764 websubtable = []
770 websubtable = []
765 websubdefs = repo.ui.configitems('websub')
771 websubdefs = repo.ui.configitems('websub')
766 # we must maintain interhg backwards compatibility
772 # we must maintain interhg backwards compatibility
767 websubdefs += repo.ui.configitems('interhg')
773 websubdefs += repo.ui.configitems('interhg')
768 for key, pattern in websubdefs:
774 for key, pattern in websubdefs:
769 # grab the delimiter from the character after the "s"
775 # grab the delimiter from the character after the "s"
770 unesc = pattern[1:2]
776 unesc = pattern[1:2]
771 delim = stringutil.reescape(unesc)
777 delim = stringutil.reescape(unesc)
772
778
773 # identify portions of the pattern, taking care to avoid escaped
779 # identify portions of the pattern, taking care to avoid escaped
774 # delimiters. the replace format and flags are optional, but
780 # delimiters. the replace format and flags are optional, but
775 # delimiters are required.
781 # delimiters are required.
776 match = re.match(
782 match = re.match(
777 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
783 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
778 % (delim, delim, delim), pattern)
784 % (delim, delim, delim), pattern)
779 if not match:
785 if not match:
780 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
786 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
781 % (key, pattern))
787 % (key, pattern))
782 continue
788 continue
783
789
784 # we need to unescape the delimiter for regexp and format
790 # we need to unescape the delimiter for regexp and format
785 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
791 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
786 regexp = delim_re.sub(unesc, match.group(1))
792 regexp = delim_re.sub(unesc, match.group(1))
787 format = delim_re.sub(unesc, match.group(2))
793 format = delim_re.sub(unesc, match.group(2))
788
794
789 # the pattern allows for 6 regexp flags, so set them if necessary
795 # the pattern allows for 6 regexp flags, so set them if necessary
790 flagin = match.group(3)
796 flagin = match.group(3)
791 flags = 0
797 flags = 0
792 if flagin:
798 if flagin:
793 for flag in flagin.upper():
799 for flag in flagin.upper():
794 flags |= re.__dict__[flag]
800 flags |= re.__dict__[flag]
795
801
796 try:
802 try:
797 regexp = re.compile(regexp, flags)
803 regexp = re.compile(regexp, flags)
798 websubtable.append((regexp, format))
804 websubtable.append((regexp, format))
799 except re.error:
805 except re.error:
800 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
806 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
801 % (key, regexp))
807 % (key, regexp))
802 return websubtable
808 return websubtable
803
809
804 def getgraphnode(repo, ctx):
810 def getgraphnode(repo, ctx):
805 return (templatekw.getgraphnodecurrent(repo, ctx) +
811 return (templatekw.getgraphnodecurrent(repo, ctx) +
806 templatekw.getgraphnodesymbol(ctx))
812 templatekw.getgraphnodesymbol(ctx))
General Comments 0
You need to be logged in to leave comments. Login now