##// END OF EJS Templates
hgweb: add a status property to file list context...
Jordi Gutiérrez Hermoso -
r44115:33cff871 default
parent child Browse files
Show More
@@ -1,932 +1,940 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 filesadded = ctx.filesadded()
544 for blockno, f in enumerate(ctx.files()):
545 for blockno, f in enumerate(ctx.files()):
545 template = b'filenodelink' if f in ctx else b'filenolink'
546 if f not in ctx:
547 status = b'removed'
548 elif f in filesadded:
549 status = b'added'
550 else:
551 status = b'modified'
552 template = b'filenolink' if status == b'removed' else b'filenodelink'
546 yield context.process(
553 yield context.process(
547 template,
554 template,
548 {
555 {
549 b'node': ctx.hex(),
556 b'node': ctx.hex(),
550 b'file': f,
557 b'file': f,
551 b'blockno': blockno + 1,
558 b'blockno': blockno + 1,
552 b'parity': next(parity),
559 b'parity': next(parity),
560 b'status': status,
553 },
561 },
554 )
562 )
555
563
556
564
557 def changesetentry(web, ctx):
565 def changesetentry(web, ctx):
558 '''Obtain a dictionary to be used to render the "changeset" template.'''
566 '''Obtain a dictionary to be used to render the "changeset" template.'''
559
567
560 showtags = showtag(web.repo, b'changesettag', scmutil.binnode(ctx))
568 showtags = showtag(web.repo, b'changesettag', scmutil.binnode(ctx))
561 showbookmarks = showbookmark(
569 showbookmarks = showbookmark(
562 web.repo, b'changesetbookmark', scmutil.binnode(ctx)
570 web.repo, b'changesetbookmark', scmutil.binnode(ctx)
563 )
571 )
564 showbranch = nodebranchnodefault(ctx)
572 showbranch = nodebranchnodefault(ctx)
565
573
566 basectx = basechangectx(web.repo, web.req)
574 basectx = basechangectx(web.repo, web.req)
567 if basectx is None:
575 if basectx is None:
568 basectx = ctx.p1()
576 basectx = ctx.p1()
569
577
570 style = web.config(b'web', b'style')
578 style = web.config(b'web', b'style')
571 if b'style' in web.req.qsparams:
579 if b'style' in web.req.qsparams:
572 style = web.req.qsparams[b'style']
580 style = web.req.qsparams[b'style']
573
581
574 diff = diffs(web, ctx, basectx, None, style)
582 diff = diffs(web, ctx, basectx, None, style)
575
583
576 parity = paritygen(web.stripecount)
584 parity = paritygen(web.stripecount)
577 diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
585 diffstatsgen = diffstatgen(web.repo.ui, ctx, basectx)
578 diffstats = diffstat(ctx, diffstatsgen, parity)
586 diffstats = diffstat(ctx, diffstatsgen, parity)
579
587
580 return dict(
588 return dict(
581 diff=diff,
589 diff=diff,
582 symrev=symrevorshortnode(web.req, ctx),
590 symrev=symrevorshortnode(web.req, ctx),
583 basenode=basectx.hex(),
591 basenode=basectx.hex(),
584 changesettag=showtags,
592 changesettag=showtags,
585 changesetbookmark=showbookmarks,
593 changesetbookmark=showbookmarks,
586 changesetbranch=showbranch,
594 changesetbranch=showbranch,
587 files=templateutil.mappedgenerator(
595 files=templateutil.mappedgenerator(
588 _listfilesgen, args=(ctx, web.stripecount)
596 _listfilesgen, args=(ctx, web.stripecount)
589 ),
597 ),
590 diffsummary=lambda context, mapping: diffsummary(diffstatsgen),
598 diffsummary=lambda context, mapping: diffsummary(diffstatsgen),
591 diffstat=diffstats,
599 diffstat=diffstats,
592 archives=web.archivelist(ctx.hex()),
600 archives=web.archivelist(ctx.hex()),
593 **pycompat.strkwargs(commonentry(web.repo, ctx))
601 **pycompat.strkwargs(commonentry(web.repo, ctx))
594 )
602 )
595
603
596
604
597 def _listfilediffsgen(context, files, node, max):
605 def _listfilediffsgen(context, files, node, max):
598 for f in files[:max]:
606 for f in files[:max]:
599 yield context.process(b'filedifflink', {b'node': hex(node), b'file': f})
607 yield context.process(b'filedifflink', {b'node': hex(node), b'file': f})
600 if len(files) > max:
608 if len(files) > max:
601 yield context.process(b'fileellipses', {})
609 yield context.process(b'fileellipses', {})
602
610
603
611
604 def listfilediffs(files, node, max):
612 def listfilediffs(files, node, max):
605 return templateutil.mappedgenerator(
613 return templateutil.mappedgenerator(
606 _listfilediffsgen, args=(files, node, max)
614 _listfilediffsgen, args=(files, node, max)
607 )
615 )
608
616
609
617
610 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
618 def _prettyprintdifflines(context, lines, blockno, lineidprefix):
611 for lineno, l in enumerate(lines, 1):
619 for lineno, l in enumerate(lines, 1):
612 difflineno = b"%d.%d" % (blockno, lineno)
620 difflineno = b"%d.%d" % (blockno, lineno)
613 if l.startswith(b'+'):
621 if l.startswith(b'+'):
614 ltype = b"difflineplus"
622 ltype = b"difflineplus"
615 elif l.startswith(b'-'):
623 elif l.startswith(b'-'):
616 ltype = b"difflineminus"
624 ltype = b"difflineminus"
617 elif l.startswith(b'@'):
625 elif l.startswith(b'@'):
618 ltype = b"difflineat"
626 ltype = b"difflineat"
619 else:
627 else:
620 ltype = b"diffline"
628 ltype = b"diffline"
621 yield context.process(
629 yield context.process(
622 ltype,
630 ltype,
623 {
631 {
624 b'line': l,
632 b'line': l,
625 b'lineno': lineno,
633 b'lineno': lineno,
626 b'lineid': lineidprefix + b"l%s" % difflineno,
634 b'lineid': lineidprefix + b"l%s" % difflineno,
627 b'linenumber': b"% 8s" % difflineno,
635 b'linenumber': b"% 8s" % difflineno,
628 },
636 },
629 )
637 )
630
638
631
639
632 def _diffsgen(
640 def _diffsgen(
633 context,
641 context,
634 repo,
642 repo,
635 ctx,
643 ctx,
636 basectx,
644 basectx,
637 files,
645 files,
638 style,
646 style,
639 stripecount,
647 stripecount,
640 linerange,
648 linerange,
641 lineidprefix,
649 lineidprefix,
642 ):
650 ):
643 if files:
651 if files:
644 m = match.exact(files)
652 m = match.exact(files)
645 else:
653 else:
646 m = match.always()
654 m = match.always()
647
655
648 diffopts = patch.diffopts(repo.ui, untrusted=True)
656 diffopts = patch.diffopts(repo.ui, untrusted=True)
649 parity = paritygen(stripecount)
657 parity = paritygen(stripecount)
650
658
651 diffhunks = patch.diffhunks(repo, basectx, ctx, m, opts=diffopts)
659 diffhunks = patch.diffhunks(repo, basectx, ctx, m, opts=diffopts)
652 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
660 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
653 if style != b'raw':
661 if style != b'raw':
654 header = header[1:]
662 header = header[1:]
655 lines = [h + b'\n' for h in header]
663 lines = [h + b'\n' for h in header]
656 for hunkrange, hunklines in hunks:
664 for hunkrange, hunklines in hunks:
657 if linerange is not None and hunkrange is not None:
665 if linerange is not None and hunkrange is not None:
658 s1, l1, s2, l2 = hunkrange
666 s1, l1, s2, l2 = hunkrange
659 if not mdiff.hunkinrange((s2, l2), linerange):
667 if not mdiff.hunkinrange((s2, l2), linerange):
660 continue
668 continue
661 lines.extend(hunklines)
669 lines.extend(hunklines)
662 if lines:
670 if lines:
663 l = templateutil.mappedgenerator(
671 l = templateutil.mappedgenerator(
664 _prettyprintdifflines, args=(lines, blockno, lineidprefix)
672 _prettyprintdifflines, args=(lines, blockno, lineidprefix)
665 )
673 )
666 yield {
674 yield {
667 b'parity': next(parity),
675 b'parity': next(parity),
668 b'blockno': blockno,
676 b'blockno': blockno,
669 b'lines': l,
677 b'lines': l,
670 }
678 }
671
679
672
680
673 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=b''):
681 def diffs(web, ctx, basectx, files, style, linerange=None, lineidprefix=b''):
674 args = (
682 args = (
675 web.repo,
683 web.repo,
676 ctx,
684 ctx,
677 basectx,
685 basectx,
678 files,
686 files,
679 style,
687 style,
680 web.stripecount,
688 web.stripecount,
681 linerange,
689 linerange,
682 lineidprefix,
690 lineidprefix,
683 )
691 )
684 return templateutil.mappinggenerator(
692 return templateutil.mappinggenerator(
685 _diffsgen, args=args, name=b'diffblock'
693 _diffsgen, args=args, name=b'diffblock'
686 )
694 )
687
695
688
696
689 def _compline(type, leftlineno, leftline, rightlineno, rightline):
697 def _compline(type, leftlineno, leftline, rightlineno, rightline):
690 lineid = leftlineno and (b"l%d" % leftlineno) or b''
698 lineid = leftlineno and (b"l%d" % leftlineno) or b''
691 lineid += rightlineno and (b"r%d" % rightlineno) or b''
699 lineid += rightlineno and (b"r%d" % rightlineno) or b''
692 llno = b'%d' % leftlineno if leftlineno else b''
700 llno = b'%d' % leftlineno if leftlineno else b''
693 rlno = b'%d' % rightlineno if rightlineno else b''
701 rlno = b'%d' % rightlineno if rightlineno else b''
694 return {
702 return {
695 b'type': type,
703 b'type': type,
696 b'lineid': lineid,
704 b'lineid': lineid,
697 b'leftlineno': leftlineno,
705 b'leftlineno': leftlineno,
698 b'leftlinenumber': b"% 6s" % llno,
706 b'leftlinenumber': b"% 6s" % llno,
699 b'leftline': leftline or b'',
707 b'leftline': leftline or b'',
700 b'rightlineno': rightlineno,
708 b'rightlineno': rightlineno,
701 b'rightlinenumber': b"% 6s" % rlno,
709 b'rightlinenumber': b"% 6s" % rlno,
702 b'rightline': rightline or b'',
710 b'rightline': rightline or b'',
703 }
711 }
704
712
705
713
706 def _getcompblockgen(context, leftlines, rightlines, opcodes):
714 def _getcompblockgen(context, leftlines, rightlines, opcodes):
707 for type, llo, lhi, rlo, rhi in opcodes:
715 for type, llo, lhi, rlo, rhi in opcodes:
708 type = pycompat.sysbytes(type)
716 type = pycompat.sysbytes(type)
709 len1 = lhi - llo
717 len1 = lhi - llo
710 len2 = rhi - rlo
718 len2 = rhi - rlo
711 count = min(len1, len2)
719 count = min(len1, len2)
712 for i in pycompat.xrange(count):
720 for i in pycompat.xrange(count):
713 yield _compline(
721 yield _compline(
714 type=type,
722 type=type,
715 leftlineno=llo + i + 1,
723 leftlineno=llo + i + 1,
716 leftline=leftlines[llo + i],
724 leftline=leftlines[llo + i],
717 rightlineno=rlo + i + 1,
725 rightlineno=rlo + i + 1,
718 rightline=rightlines[rlo + i],
726 rightline=rightlines[rlo + i],
719 )
727 )
720 if len1 > len2:
728 if len1 > len2:
721 for i in pycompat.xrange(llo + count, lhi):
729 for i in pycompat.xrange(llo + count, lhi):
722 yield _compline(
730 yield _compline(
723 type=type,
731 type=type,
724 leftlineno=i + 1,
732 leftlineno=i + 1,
725 leftline=leftlines[i],
733 leftline=leftlines[i],
726 rightlineno=None,
734 rightlineno=None,
727 rightline=None,
735 rightline=None,
728 )
736 )
729 elif len2 > len1:
737 elif len2 > len1:
730 for i in pycompat.xrange(rlo + count, rhi):
738 for i in pycompat.xrange(rlo + count, rhi):
731 yield _compline(
739 yield _compline(
732 type=type,
740 type=type,
733 leftlineno=None,
741 leftlineno=None,
734 leftline=None,
742 leftline=None,
735 rightlineno=i + 1,
743 rightlineno=i + 1,
736 rightline=rightlines[i],
744 rightline=rightlines[i],
737 )
745 )
738
746
739
747
740 def _getcompblock(leftlines, rightlines, opcodes):
748 def _getcompblock(leftlines, rightlines, opcodes):
741 args = (leftlines, rightlines, opcodes)
749 args = (leftlines, rightlines, opcodes)
742 return templateutil.mappinggenerator(
750 return templateutil.mappinggenerator(
743 _getcompblockgen, args=args, name=b'comparisonline'
751 _getcompblockgen, args=args, name=b'comparisonline'
744 )
752 )
745
753
746
754
747 def _comparegen(context, contextnum, leftlines, rightlines):
755 def _comparegen(context, contextnum, leftlines, rightlines):
748 '''Generator function that provides side-by-side comparison data.'''
756 '''Generator function that provides side-by-side comparison data.'''
749 s = difflib.SequenceMatcher(None, leftlines, rightlines)
757 s = difflib.SequenceMatcher(None, leftlines, rightlines)
750 if contextnum < 0:
758 if contextnum < 0:
751 l = _getcompblock(leftlines, rightlines, s.get_opcodes())
759 l = _getcompblock(leftlines, rightlines, s.get_opcodes())
752 yield {b'lines': l}
760 yield {b'lines': l}
753 else:
761 else:
754 for oc in s.get_grouped_opcodes(n=contextnum):
762 for oc in s.get_grouped_opcodes(n=contextnum):
755 l = _getcompblock(leftlines, rightlines, oc)
763 l = _getcompblock(leftlines, rightlines, oc)
756 yield {b'lines': l}
764 yield {b'lines': l}
757
765
758
766
759 def compare(contextnum, leftlines, rightlines):
767 def compare(contextnum, leftlines, rightlines):
760 args = (contextnum, leftlines, rightlines)
768 args = (contextnum, leftlines, rightlines)
761 return templateutil.mappinggenerator(
769 return templateutil.mappinggenerator(
762 _comparegen, args=args, name=b'comparisonblock'
770 _comparegen, args=args, name=b'comparisonblock'
763 )
771 )
764
772
765
773
766 def diffstatgen(ui, ctx, basectx):
774 def diffstatgen(ui, ctx, basectx):
767 '''Generator function that provides the diffstat data.'''
775 '''Generator function that provides the diffstat data.'''
768
776
769 diffopts = patch.diffopts(ui, {b'noprefix': False})
777 diffopts = patch.diffopts(ui, {b'noprefix': False})
770 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx, opts=diffopts)))
778 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx, opts=diffopts)))
771 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
779 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
772 while True:
780 while True:
773 yield stats, maxname, maxtotal, addtotal, removetotal, binary
781 yield stats, maxname, maxtotal, addtotal, removetotal, binary
774
782
775
783
776 def diffsummary(statgen):
784 def diffsummary(statgen):
777 '''Return a short summary of the diff.'''
785 '''Return a short summary of the diff.'''
778
786
779 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
787 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
780 return _(b' %d files changed, %d insertions(+), %d deletions(-)\n') % (
788 return _(b' %d files changed, %d insertions(+), %d deletions(-)\n') % (
781 len(stats),
789 len(stats),
782 addtotal,
790 addtotal,
783 removetotal,
791 removetotal,
784 )
792 )
785
793
786
794
787 def _diffstattmplgen(context, ctx, statgen, parity):
795 def _diffstattmplgen(context, ctx, statgen, parity):
788 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
796 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
789 files = ctx.files()
797 files = ctx.files()
790
798
791 def pct(i):
799 def pct(i):
792 if maxtotal == 0:
800 if maxtotal == 0:
793 return 0
801 return 0
794 return (float(i) / maxtotal) * 100
802 return (float(i) / maxtotal) * 100
795
803
796 fileno = 0
804 fileno = 0
797 for filename, adds, removes, isbinary in stats:
805 for filename, adds, removes, isbinary in stats:
798 template = b'diffstatlink' if filename in files else b'diffstatnolink'
806 template = b'diffstatlink' if filename in files else b'diffstatnolink'
799 total = adds + removes
807 total = adds + removes
800 fileno += 1
808 fileno += 1
801 yield context.process(
809 yield context.process(
802 template,
810 template,
803 {
811 {
804 b'node': ctx.hex(),
812 b'node': ctx.hex(),
805 b'file': filename,
813 b'file': filename,
806 b'fileno': fileno,
814 b'fileno': fileno,
807 b'total': total,
815 b'total': total,
808 b'addpct': pct(adds),
816 b'addpct': pct(adds),
809 b'removepct': pct(removes),
817 b'removepct': pct(removes),
810 b'parity': next(parity),
818 b'parity': next(parity),
811 },
819 },
812 )
820 )
813
821
814
822
815 def diffstat(ctx, statgen, parity):
823 def diffstat(ctx, statgen, parity):
816 '''Return a diffstat template for each file in the diff.'''
824 '''Return a diffstat template for each file in the diff.'''
817 args = (ctx, statgen, parity)
825 args = (ctx, statgen, parity)
818 return templateutil.mappedgenerator(_diffstattmplgen, args=args)
826 return templateutil.mappedgenerator(_diffstattmplgen, args=args)
819
827
820
828
821 class sessionvars(templateutil.wrapped):
829 class sessionvars(templateutil.wrapped):
822 def __init__(self, vars, start=b'?'):
830 def __init__(self, vars, start=b'?'):
823 self._start = start
831 self._start = start
824 self._vars = vars
832 self._vars = vars
825
833
826 def __getitem__(self, key):
834 def __getitem__(self, key):
827 return self._vars[key]
835 return self._vars[key]
828
836
829 def __setitem__(self, key, value):
837 def __setitem__(self, key, value):
830 self._vars[key] = value
838 self._vars[key] = value
831
839
832 def __copy__(self):
840 def __copy__(self):
833 return sessionvars(copy.copy(self._vars), self._start)
841 return sessionvars(copy.copy(self._vars), self._start)
834
842
835 def contains(self, context, mapping, item):
843 def contains(self, context, mapping, item):
836 item = templateutil.unwrapvalue(context, mapping, item)
844 item = templateutil.unwrapvalue(context, mapping, item)
837 return item in self._vars
845 return item in self._vars
838
846
839 def getmember(self, context, mapping, key):
847 def getmember(self, context, mapping, key):
840 key = templateutil.unwrapvalue(context, mapping, key)
848 key = templateutil.unwrapvalue(context, mapping, key)
841 return self._vars.get(key)
849 return self._vars.get(key)
842
850
843 def getmin(self, context, mapping):
851 def getmin(self, context, mapping):
844 raise error.ParseError(_(b'not comparable'))
852 raise error.ParseError(_(b'not comparable'))
845
853
846 def getmax(self, context, mapping):
854 def getmax(self, context, mapping):
847 raise error.ParseError(_(b'not comparable'))
855 raise error.ParseError(_(b'not comparable'))
848
856
849 def filter(self, context, mapping, select):
857 def filter(self, context, mapping, select):
850 # implement if necessary
858 # implement if necessary
851 raise error.ParseError(_(b'not filterable'))
859 raise error.ParseError(_(b'not filterable'))
852
860
853 def itermaps(self, context):
861 def itermaps(self, context):
854 separator = self._start
862 separator = self._start
855 for key, value in sorted(pycompat.iteritems(self._vars)):
863 for key, value in sorted(pycompat.iteritems(self._vars)):
856 yield {
864 yield {
857 b'name': key,
865 b'name': key,
858 b'value': pycompat.bytestr(value),
866 b'value': pycompat.bytestr(value),
859 b'separator': separator,
867 b'separator': separator,
860 }
868 }
861 separator = b'&'
869 separator = b'&'
862
870
863 def join(self, context, mapping, sep):
871 def join(self, context, mapping, sep):
864 # could be '{separator}{name}={value|urlescape}'
872 # could be '{separator}{name}={value|urlescape}'
865 raise error.ParseError(_(b'not displayable without template'))
873 raise error.ParseError(_(b'not displayable without template'))
866
874
867 def show(self, context, mapping):
875 def show(self, context, mapping):
868 return self.join(context, b'')
876 return self.join(context, b'')
869
877
870 def tobool(self, context, mapping):
878 def tobool(self, context, mapping):
871 return bool(self._vars)
879 return bool(self._vars)
872
880
873 def tovalue(self, context, mapping):
881 def tovalue(self, context, mapping):
874 return self._vars
882 return self._vars
875
883
876
884
877 class wsgiui(uimod.ui):
885 class wsgiui(uimod.ui):
878 # default termwidth breaks under mod_wsgi
886 # default termwidth breaks under mod_wsgi
879 def termwidth(self):
887 def termwidth(self):
880 return 80
888 return 80
881
889
882
890
883 def getwebsubs(repo):
891 def getwebsubs(repo):
884 websubtable = []
892 websubtable = []
885 websubdefs = repo.ui.configitems(b'websub')
893 websubdefs = repo.ui.configitems(b'websub')
886 # we must maintain interhg backwards compatibility
894 # we must maintain interhg backwards compatibility
887 websubdefs += repo.ui.configitems(b'interhg')
895 websubdefs += repo.ui.configitems(b'interhg')
888 for key, pattern in websubdefs:
896 for key, pattern in websubdefs:
889 # grab the delimiter from the character after the "s"
897 # grab the delimiter from the character after the "s"
890 unesc = pattern[1:2]
898 unesc = pattern[1:2]
891 delim = stringutil.reescape(unesc)
899 delim = stringutil.reescape(unesc)
892
900
893 # identify portions of the pattern, taking care to avoid escaped
901 # identify portions of the pattern, taking care to avoid escaped
894 # delimiters. the replace format and flags are optional, but
902 # delimiters. the replace format and flags are optional, but
895 # delimiters are required.
903 # delimiters are required.
896 match = re.match(
904 match = re.match(
897 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
905 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
898 % (delim, delim, delim),
906 % (delim, delim, delim),
899 pattern,
907 pattern,
900 )
908 )
901 if not match:
909 if not match:
902 repo.ui.warn(
910 repo.ui.warn(
903 _(b"websub: invalid pattern for %s: %s\n") % (key, pattern)
911 _(b"websub: invalid pattern for %s: %s\n") % (key, pattern)
904 )
912 )
905 continue
913 continue
906
914
907 # we need to unescape the delimiter for regexp and format
915 # we need to unescape the delimiter for regexp and format
908 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
916 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
909 regexp = delim_re.sub(unesc, match.group(1))
917 regexp = delim_re.sub(unesc, match.group(1))
910 format = delim_re.sub(unesc, match.group(2))
918 format = delim_re.sub(unesc, match.group(2))
911
919
912 # the pattern allows for 6 regexp flags, so set them if necessary
920 # the pattern allows for 6 regexp flags, so set them if necessary
913 flagin = match.group(3)
921 flagin = match.group(3)
914 flags = 0
922 flags = 0
915 if flagin:
923 if flagin:
916 for flag in pycompat.sysstr(flagin.upper()):
924 for flag in pycompat.sysstr(flagin.upper()):
917 flags |= re.__dict__[flag]
925 flags |= re.__dict__[flag]
918
926
919 try:
927 try:
920 regexp = re.compile(regexp, flags)
928 regexp = re.compile(regexp, flags)
921 websubtable.append((regexp, format))
929 websubtable.append((regexp, format))
922 except re.error:
930 except re.error:
923 repo.ui.warn(
931 repo.ui.warn(
924 _(b"websub: invalid regexp for %s: %s\n") % (key, regexp)
932 _(b"websub: invalid regexp for %s: %s\n") % (key, regexp)
925 )
933 )
926 return websubtable
934 return websubtable
927
935
928
936
929 def getgraphnode(repo, ctx):
937 def getgraphnode(repo, ctx):
930 return templatekw.getgraphnodecurrent(
938 return templatekw.getgraphnodecurrent(
931 repo, ctx
939 repo, ctx
932 ) + templatekw.getgraphnodesymbol(ctx)
940 ) + templatekw.getgraphnodesymbol(ctx)
General Comments 0
You need to be logged in to leave comments. Login now