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