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