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