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