##// END OF EJS Templates
hgweb: convert _siblings to a factory function of mappinggenerator...
Yuya Nishihara -
r37718:495fbeae default
parent child Browse files
Show More
@@ -1,741 +1,735 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(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 class _siblings(object):
200 def _siblings(siblings=None, hiderev=None):
201 def __init__(self, siblings=None, hiderev=None):
202 if siblings is None:
201 if siblings is None:
203 siblings = []
202 siblings = []
204 self.siblings = [s for s in siblings if s.node() != nullid]
203 siblings = [s for s in siblings if s.node() != nullid]
205 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
204 if len(siblings) == 1 and siblings[0].rev() == hiderev:
206 self.siblings = []
205 siblings = []
207
206 return templateutil.mappinggenerator(_ctxsgen, args=(siblings,))
208 def __iter__(self):
209 return _ctxsgen(self.siblings)
210
211 def __len__(self):
212 return len(self.siblings)
213
207
214 def difffeatureopts(req, ui, section):
208 def difffeatureopts(req, ui, section):
215 diffopts = patch.difffeatureopts(ui, untrusted=True,
209 diffopts = patch.difffeatureopts(ui, untrusted=True,
216 section=section, whitespace=True)
210 section=section, whitespace=True)
217
211
218 for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
212 for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
219 v = req.qsparams.get(k)
213 v = req.qsparams.get(k)
220 if v is not None:
214 if v is not None:
221 v = stringutil.parsebool(v)
215 v = stringutil.parsebool(v)
222 setattr(diffopts, k, v if v is not None else True)
216 setattr(diffopts, k, v if v is not None else True)
223
217
224 return diffopts
218 return diffopts
225
219
226 def annotate(req, fctx, ui):
220 def annotate(req, fctx, ui):
227 diffopts = difffeatureopts(req, ui, 'annotate')
221 diffopts = difffeatureopts(req, ui, 'annotate')
228 return fctx.annotate(follow=True, diffopts=diffopts)
222 return fctx.annotate(follow=True, diffopts=diffopts)
229
223
230 def parents(ctx, hide=None):
224 def parents(ctx, hide=None):
231 if isinstance(ctx, context.basefilectx):
225 if isinstance(ctx, context.basefilectx):
232 introrev = ctx.introrev()
226 introrev = ctx.introrev()
233 if ctx.changectx().rev() != introrev:
227 if ctx.changectx().rev() != introrev:
234 return _siblings([ctx.repo()[introrev]], hide)
228 return _siblings([ctx.repo()[introrev]], hide)
235 return _siblings(ctx.parents(), hide)
229 return _siblings(ctx.parents(), hide)
236
230
237 def children(ctx, hide=None):
231 def children(ctx, hide=None):
238 return _siblings(ctx.children(), hide)
232 return _siblings(ctx.children(), hide)
239
233
240 def renamelink(fctx):
234 def renamelink(fctx):
241 r = fctx.renamed()
235 r = fctx.renamed()
242 if r:
236 if r:
243 return [{'file': r[0], 'node': hex(r[1])}]
237 return [{'file': r[0], 'node': hex(r[1])}]
244 return []
238 return []
245
239
246 def nodetagsdict(repo, node):
240 def nodetagsdict(repo, node):
247 return [{"name": i} for i in repo.nodetags(node)]
241 return [{"name": i} for i in repo.nodetags(node)]
248
242
249 def nodebookmarksdict(repo, node):
243 def nodebookmarksdict(repo, node):
250 return [{"name": i} for i in repo.nodebookmarks(node)]
244 return [{"name": i} for i in repo.nodebookmarks(node)]
251
245
252 def nodebranchdict(repo, ctx):
246 def nodebranchdict(repo, ctx):
253 branches = []
247 branches = []
254 branch = ctx.branch()
248 branch = ctx.branch()
255 # If this is an empty repo, ctx.node() == nullid,
249 # If this is an empty repo, ctx.node() == nullid,
256 # ctx.branch() == 'default'.
250 # ctx.branch() == 'default'.
257 try:
251 try:
258 branchnode = repo.branchtip(branch)
252 branchnode = repo.branchtip(branch)
259 except error.RepoLookupError:
253 except error.RepoLookupError:
260 branchnode = None
254 branchnode = None
261 if branchnode == ctx.node():
255 if branchnode == ctx.node():
262 branches.append({"name": branch})
256 branches.append({"name": branch})
263 return branches
257 return branches
264
258
265 def nodeinbranch(repo, ctx):
259 def nodeinbranch(repo, ctx):
266 branches = []
260 branches = []
267 branch = ctx.branch()
261 branch = ctx.branch()
268 try:
262 try:
269 branchnode = repo.branchtip(branch)
263 branchnode = repo.branchtip(branch)
270 except error.RepoLookupError:
264 except error.RepoLookupError:
271 branchnode = None
265 branchnode = None
272 if branch != 'default' and branchnode != ctx.node():
266 if branch != 'default' and branchnode != ctx.node():
273 branches.append({"name": branch})
267 branches.append({"name": branch})
274 return branches
268 return branches
275
269
276 def nodebranchnodefault(ctx):
270 def nodebranchnodefault(ctx):
277 branches = []
271 branches = []
278 branch = ctx.branch()
272 branch = ctx.branch()
279 if branch != 'default':
273 if branch != 'default':
280 branches.append({"name": branch})
274 branches.append({"name": branch})
281 return branches
275 return branches
282
276
283 def showtag(repo, tmpl, t1, node=nullid, **args):
277 def showtag(repo, tmpl, t1, node=nullid, **args):
284 args = pycompat.byteskwargs(args)
278 args = pycompat.byteskwargs(args)
285 for t in repo.nodetags(node):
279 for t in repo.nodetags(node):
286 lm = args.copy()
280 lm = args.copy()
287 lm['tag'] = t
281 lm['tag'] = t
288 yield tmpl.generate(t1, lm)
282 yield tmpl.generate(t1, lm)
289
283
290 def showbookmark(repo, tmpl, t1, node=nullid, **args):
284 def showbookmark(repo, tmpl, t1, node=nullid, **args):
291 args = pycompat.byteskwargs(args)
285 args = pycompat.byteskwargs(args)
292 for t in repo.nodebookmarks(node):
286 for t in repo.nodebookmarks(node):
293 lm = args.copy()
287 lm = args.copy()
294 lm['bookmark'] = t
288 lm['bookmark'] = t
295 yield tmpl.generate(t1, lm)
289 yield tmpl.generate(t1, lm)
296
290
297 def branchentries(repo, stripecount, limit=0):
291 def branchentries(repo, stripecount, limit=0):
298 tips = []
292 tips = []
299 heads = repo.heads()
293 heads = repo.heads()
300 parity = paritygen(stripecount)
294 parity = paritygen(stripecount)
301 sortkey = lambda item: (not item[1], item[0].rev())
295 sortkey = lambda item: (not item[1], item[0].rev())
302
296
303 def entries(**map):
297 def entries(**map):
304 count = 0
298 count = 0
305 if not tips:
299 if not tips:
306 for tag, hs, tip, closed in repo.branchmap().iterbranches():
300 for tag, hs, tip, closed in repo.branchmap().iterbranches():
307 tips.append((repo[tip], closed))
301 tips.append((repo[tip], closed))
308 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
302 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
309 if limit > 0 and count >= limit:
303 if limit > 0 and count >= limit:
310 return
304 return
311 count += 1
305 count += 1
312 if closed:
306 if closed:
313 status = 'closed'
307 status = 'closed'
314 elif ctx.node() not in heads:
308 elif ctx.node() not in heads:
315 status = 'inactive'
309 status = 'inactive'
316 else:
310 else:
317 status = 'open'
311 status = 'open'
318 yield {
312 yield {
319 'parity': next(parity),
313 'parity': next(parity),
320 'branch': ctx.branch(),
314 'branch': ctx.branch(),
321 'status': status,
315 'status': status,
322 'node': ctx.hex(),
316 'node': ctx.hex(),
323 'date': ctx.date()
317 'date': ctx.date()
324 }
318 }
325
319
326 return entries
320 return entries
327
321
328 def cleanpath(repo, path):
322 def cleanpath(repo, path):
329 path = path.lstrip('/')
323 path = path.lstrip('/')
330 return pathutil.canonpath(repo.root, '', path)
324 return pathutil.canonpath(repo.root, '', path)
331
325
332 def changectx(repo, req):
326 def changectx(repo, req):
333 changeid = "tip"
327 changeid = "tip"
334 if 'node' in req.qsparams:
328 if 'node' in req.qsparams:
335 changeid = req.qsparams['node']
329 changeid = req.qsparams['node']
336 ipos = changeid.find(':')
330 ipos = changeid.find(':')
337 if ipos != -1:
331 if ipos != -1:
338 changeid = changeid[(ipos + 1):]
332 changeid = changeid[(ipos + 1):]
339
333
340 return scmutil.revsymbol(repo, changeid)
334 return scmutil.revsymbol(repo, changeid)
341
335
342 def basechangectx(repo, req):
336 def basechangectx(repo, req):
343 if 'node' in req.qsparams:
337 if 'node' in req.qsparams:
344 changeid = req.qsparams['node']
338 changeid = req.qsparams['node']
345 ipos = changeid.find(':')
339 ipos = changeid.find(':')
346 if ipos != -1:
340 if ipos != -1:
347 changeid = changeid[:ipos]
341 changeid = changeid[:ipos]
348 return scmutil.revsymbol(repo, changeid)
342 return scmutil.revsymbol(repo, changeid)
349
343
350 return None
344 return None
351
345
352 def filectx(repo, req):
346 def filectx(repo, req):
353 if 'file' not in req.qsparams:
347 if 'file' not in req.qsparams:
354 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
348 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
355 path = cleanpath(repo, req.qsparams['file'])
349 path = cleanpath(repo, req.qsparams['file'])
356 if 'node' in req.qsparams:
350 if 'node' in req.qsparams:
357 changeid = req.qsparams['node']
351 changeid = req.qsparams['node']
358 elif 'filenode' in req.qsparams:
352 elif 'filenode' in req.qsparams:
359 changeid = req.qsparams['filenode']
353 changeid = req.qsparams['filenode']
360 else:
354 else:
361 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
355 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
362 try:
356 try:
363 fctx = scmutil.revsymbol(repo, changeid)[path]
357 fctx = scmutil.revsymbol(repo, changeid)[path]
364 except error.RepoError:
358 except error.RepoError:
365 fctx = repo.filectx(path, fileid=changeid)
359 fctx = repo.filectx(path, fileid=changeid)
366
360
367 return fctx
361 return fctx
368
362
369 def linerange(req):
363 def linerange(req):
370 linerange = req.qsparams.getall('linerange')
364 linerange = req.qsparams.getall('linerange')
371 if not linerange:
365 if not linerange:
372 return None
366 return None
373 if len(linerange) > 1:
367 if len(linerange) > 1:
374 raise ErrorResponse(HTTP_BAD_REQUEST,
368 raise ErrorResponse(HTTP_BAD_REQUEST,
375 'redundant linerange parameter')
369 'redundant linerange parameter')
376 try:
370 try:
377 fromline, toline = map(int, linerange[0].split(':', 1))
371 fromline, toline = map(int, linerange[0].split(':', 1))
378 except ValueError:
372 except ValueError:
379 raise ErrorResponse(HTTP_BAD_REQUEST,
373 raise ErrorResponse(HTTP_BAD_REQUEST,
380 'invalid linerange parameter')
374 'invalid linerange parameter')
381 try:
375 try:
382 return util.processlinerange(fromline, toline)
376 return util.processlinerange(fromline, toline)
383 except error.ParseError as exc:
377 except error.ParseError as exc:
384 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
378 raise ErrorResponse(HTTP_BAD_REQUEST, pycompat.bytestr(exc))
385
379
386 def formatlinerange(fromline, toline):
380 def formatlinerange(fromline, toline):
387 return '%d:%d' % (fromline + 1, toline)
381 return '%d:%d' % (fromline + 1, toline)
388
382
389 def succsandmarkers(context, mapping):
383 def succsandmarkers(context, mapping):
390 repo = context.resource(mapping, 'repo')
384 repo = context.resource(mapping, 'repo')
391 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
385 itemmappings = templatekw.showsuccsandmarkers(context, mapping)
392 for item in itemmappings.tovalue(context, mapping):
386 for item in itemmappings.tovalue(context, mapping):
393 item['successors'] = _siblings(repo[successor]
387 item['successors'] = _siblings(repo[successor]
394 for successor in item['successors'])
388 for successor in item['successors'])
395 yield item
389 yield item
396
390
397 # teach templater succsandmarkers is switched to (context, mapping) API
391 # teach templater succsandmarkers is switched to (context, mapping) API
398 succsandmarkers._requires = {'repo', 'ctx'}
392 succsandmarkers._requires = {'repo', 'ctx'}
399
393
400 def whyunstable(context, mapping):
394 def whyunstable(context, mapping):
401 repo = context.resource(mapping, 'repo')
395 repo = context.resource(mapping, 'repo')
402 ctx = context.resource(mapping, 'ctx')
396 ctx = context.resource(mapping, 'ctx')
403
397
404 entries = obsutil.whyunstable(repo, ctx)
398 entries = obsutil.whyunstable(repo, ctx)
405 for entry in entries:
399 for entry in entries:
406 if entry.get('divergentnodes'):
400 if entry.get('divergentnodes'):
407 entry['divergentnodes'] = _siblings(entry['divergentnodes'])
401 entry['divergentnodes'] = _siblings(entry['divergentnodes'])
408 yield entry
402 yield entry
409
403
410 whyunstable._requires = {'repo', 'ctx'}
404 whyunstable._requires = {'repo', 'ctx'}
411
405
412 def commonentry(repo, ctx):
406 def commonentry(repo, ctx):
413 node = ctx.node()
407 node = ctx.node()
414 return {
408 return {
415 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
409 # TODO: perhaps ctx.changectx() should be assigned if ctx is a
416 # filectx, but I'm not pretty sure if that would always work because
410 # filectx, but I'm not pretty sure if that would always work because
417 # fctx.parents() != fctx.changectx.parents() for example.
411 # fctx.parents() != fctx.changectx.parents() for example.
418 'ctx': ctx,
412 'ctx': ctx,
419 'rev': ctx.rev(),
413 'rev': ctx.rev(),
420 'node': hex(node),
414 'node': hex(node),
421 'author': ctx.user(),
415 'author': ctx.user(),
422 'desc': ctx.description(),
416 'desc': ctx.description(),
423 'date': ctx.date(),
417 'date': ctx.date(),
424 'extra': ctx.extra(),
418 'extra': ctx.extra(),
425 'phase': ctx.phasestr(),
419 'phase': ctx.phasestr(),
426 'obsolete': ctx.obsolete(),
420 'obsolete': ctx.obsolete(),
427 'succsandmarkers': succsandmarkers,
421 'succsandmarkers': succsandmarkers,
428 'instabilities': [{"instability": i} for i in ctx.instabilities()],
422 'instabilities': [{"instability": i} for i in ctx.instabilities()],
429 'whyunstable': whyunstable,
423 'whyunstable': whyunstable,
430 'branch': nodebranchnodefault(ctx),
424 'branch': nodebranchnodefault(ctx),
431 'inbranch': nodeinbranch(repo, ctx),
425 'inbranch': nodeinbranch(repo, ctx),
432 'branches': nodebranchdict(repo, ctx),
426 'branches': nodebranchdict(repo, ctx),
433 'tags': nodetagsdict(repo, node),
427 'tags': nodetagsdict(repo, node),
434 'bookmarks': nodebookmarksdict(repo, node),
428 'bookmarks': nodebookmarksdict(repo, node),
435 'parent': lambda **x: parents(ctx),
429 'parent': lambda **x: parents(ctx),
436 'child': lambda **x: children(ctx),
430 'child': lambda **x: children(ctx),
437 }
431 }
438
432
439 def changelistentry(web, ctx):
433 def changelistentry(web, ctx):
440 '''Obtain a dictionary to be used for entries in a changelist.
434 '''Obtain a dictionary to be used for entries in a changelist.
441
435
442 This function is called when producing items for the "entries" list passed
436 This function is called when producing items for the "entries" list passed
443 to the "shortlog" and "changelog" templates.
437 to the "shortlog" and "changelog" templates.
444 '''
438 '''
445 repo = web.repo
439 repo = web.repo
446 rev = ctx.rev()
440 rev = ctx.rev()
447 n = ctx.node()
441 n = ctx.node()
448 showtags = showtag(repo, web.tmpl, 'changelogtag', n)
442 showtags = showtag(repo, web.tmpl, 'changelogtag', n)
449 files = listfilediffs(web.tmpl, ctx.files(), n, web.maxfiles)
443 files = listfilediffs(web.tmpl, ctx.files(), n, web.maxfiles)
450
444
451 entry = commonentry(repo, ctx)
445 entry = commonentry(repo, ctx)
452 entry.update(
446 entry.update(
453 allparents=lambda **x: parents(ctx),
447 allparents=lambda **x: parents(ctx),
454 parent=lambda **x: parents(ctx, rev - 1),
448 parent=lambda **x: parents(ctx, rev - 1),
455 child=lambda **x: children(ctx, rev + 1),
449 child=lambda **x: children(ctx, rev + 1),
456 changelogtag=showtags,
450 changelogtag=showtags,
457 files=files,
451 files=files,
458 )
452 )
459 return entry
453 return entry
460
454
461 def symrevorshortnode(req, ctx):
455 def symrevorshortnode(req, ctx):
462 if 'node' in req.qsparams:
456 if 'node' in req.qsparams:
463 return templatefilters.revescape(req.qsparams['node'])
457 return templatefilters.revescape(req.qsparams['node'])
464 else:
458 else:
465 return short(ctx.node())
459 return short(ctx.node())
466
460
467 def changesetentry(web, ctx):
461 def changesetentry(web, ctx):
468 '''Obtain a dictionary to be used to render the "changeset" template.'''
462 '''Obtain a dictionary to be used to render the "changeset" template.'''
469
463
470 showtags = showtag(web.repo, web.tmpl, 'changesettag', ctx.node())
464 showtags = showtag(web.repo, web.tmpl, 'changesettag', ctx.node())
471 showbookmarks = showbookmark(web.repo, web.tmpl, 'changesetbookmark',
465 showbookmarks = showbookmark(web.repo, web.tmpl, 'changesetbookmark',
472 ctx.node())
466 ctx.node())
473 showbranch = nodebranchnodefault(ctx)
467 showbranch = nodebranchnodefault(ctx)
474
468
475 files = []
469 files = []
476 parity = paritygen(web.stripecount)
470 parity = paritygen(web.stripecount)
477 for blockno, f in enumerate(ctx.files()):
471 for blockno, f in enumerate(ctx.files()):
478 template = 'filenodelink' if f in ctx else 'filenolink'
472 template = 'filenodelink' if f in ctx else 'filenolink'
479 files.append(web.tmpl.generate(template, {
473 files.append(web.tmpl.generate(template, {
480 'node': ctx.hex(),
474 'node': ctx.hex(),
481 'file': f,
475 'file': f,
482 'blockno': blockno + 1,
476 'blockno': blockno + 1,
483 'parity': next(parity),
477 'parity': next(parity),
484 }))
478 }))
485
479
486 basectx = basechangectx(web.repo, web.req)
480 basectx = basechangectx(web.repo, web.req)
487 if basectx is None:
481 if basectx is None:
488 basectx = ctx.p1()
482 basectx = ctx.p1()
489
483
490 style = web.config('web', 'style')
484 style = web.config('web', 'style')
491 if 'style' in web.req.qsparams:
485 if 'style' in web.req.qsparams:
492 style = web.req.qsparams['style']
486 style = web.req.qsparams['style']
493
487
494 diff = diffs(web, ctx, basectx, None, style)
488 diff = diffs(web, ctx, basectx, None, style)
495
489
496 parity = paritygen(web.stripecount)
490 parity = paritygen(web.stripecount)
497 diffstatsgen = diffstatgen(ctx, basectx)
491 diffstatsgen = diffstatgen(ctx, basectx)
498 diffstats = diffstat(web.tmpl, ctx, diffstatsgen, parity)
492 diffstats = diffstat(web.tmpl, ctx, diffstatsgen, parity)
499
493
500 return dict(
494 return dict(
501 diff=diff,
495 diff=diff,
502 symrev=symrevorshortnode(web.req, ctx),
496 symrev=symrevorshortnode(web.req, ctx),
503 basenode=basectx.hex(),
497 basenode=basectx.hex(),
504 changesettag=showtags,
498 changesettag=showtags,
505 changesetbookmark=showbookmarks,
499 changesetbookmark=showbookmarks,
506 changesetbranch=showbranch,
500 changesetbranch=showbranch,
507 files=files,
501 files=files,
508 diffsummary=lambda **x: diffsummary(diffstatsgen),
502 diffsummary=lambda **x: diffsummary(diffstatsgen),
509 diffstat=diffstats,
503 diffstat=diffstats,
510 archives=web.archivelist(ctx.hex()),
504 archives=web.archivelist(ctx.hex()),
511 **pycompat.strkwargs(commonentry(web.repo, ctx)))
505 **pycompat.strkwargs(commonentry(web.repo, ctx)))
512
506
513 def listfilediffs(tmpl, files, node, max):
507 def listfilediffs(tmpl, files, node, max):
514 for f in files[:max]:
508 for f in files[:max]:
515 yield tmpl.generate('filedifflink', {'node': hex(node), 'file': f})
509 yield tmpl.generate('filedifflink', {'node': hex(node), 'file': f})
516 if len(files) > max:
510 if len(files) > max:
517 yield tmpl.generate('fileellipses', {})
511 yield tmpl.generate('fileellipses', {})
518
512
519 def diffs(web, ctx, basectx, files, style, linerange=None,
513 def diffs(web, ctx, basectx, files, style, linerange=None,
520 lineidprefix=''):
514 lineidprefix=''):
521
515
522 def prettyprintlines(lines, blockno):
516 def prettyprintlines(lines, blockno):
523 for lineno, l in enumerate(lines, 1):
517 for lineno, l in enumerate(lines, 1):
524 difflineno = "%d.%d" % (blockno, lineno)
518 difflineno = "%d.%d" % (blockno, lineno)
525 if l.startswith('+'):
519 if l.startswith('+'):
526 ltype = "difflineplus"
520 ltype = "difflineplus"
527 elif l.startswith('-'):
521 elif l.startswith('-'):
528 ltype = "difflineminus"
522 ltype = "difflineminus"
529 elif l.startswith('@'):
523 elif l.startswith('@'):
530 ltype = "difflineat"
524 ltype = "difflineat"
531 else:
525 else:
532 ltype = "diffline"
526 ltype = "diffline"
533 yield web.tmpl.generate(ltype, {
527 yield web.tmpl.generate(ltype, {
534 'line': l,
528 'line': l,
535 'lineno': lineno,
529 'lineno': lineno,
536 'lineid': lineidprefix + "l%s" % difflineno,
530 'lineid': lineidprefix + "l%s" % difflineno,
537 'linenumber': "% 8s" % difflineno,
531 'linenumber': "% 8s" % difflineno,
538 })
532 })
539
533
540 repo = web.repo
534 repo = web.repo
541 if files:
535 if files:
542 m = match.exact(repo.root, repo.getcwd(), files)
536 m = match.exact(repo.root, repo.getcwd(), files)
543 else:
537 else:
544 m = match.always(repo.root, repo.getcwd())
538 m = match.always(repo.root, repo.getcwd())
545
539
546 diffopts = patch.diffopts(repo.ui, untrusted=True)
540 diffopts = patch.diffopts(repo.ui, untrusted=True)
547 node1 = basectx.node()
541 node1 = basectx.node()
548 node2 = ctx.node()
542 node2 = ctx.node()
549 parity = paritygen(web.stripecount)
543 parity = paritygen(web.stripecount)
550
544
551 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
545 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
552 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
546 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
553 if style != 'raw':
547 if style != 'raw':
554 header = header[1:]
548 header = header[1:]
555 lines = [h + '\n' for h in header]
549 lines = [h + '\n' for h in header]
556 for hunkrange, hunklines in hunks:
550 for hunkrange, hunklines in hunks:
557 if linerange is not None and hunkrange is not None:
551 if linerange is not None and hunkrange is not None:
558 s1, l1, s2, l2 = hunkrange
552 s1, l1, s2, l2 = hunkrange
559 if not mdiff.hunkinrange((s2, l2), linerange):
553 if not mdiff.hunkinrange((s2, l2), linerange):
560 continue
554 continue
561 lines.extend(hunklines)
555 lines.extend(hunklines)
562 if lines:
556 if lines:
563 yield web.tmpl.generate('diffblock', {
557 yield web.tmpl.generate('diffblock', {
564 'parity': next(parity),
558 'parity': next(parity),
565 'blockno': blockno,
559 'blockno': blockno,
566 'lines': prettyprintlines(lines, blockno),
560 'lines': prettyprintlines(lines, blockno),
567 })
561 })
568
562
569 def compare(tmpl, context, leftlines, rightlines):
563 def compare(tmpl, context, leftlines, rightlines):
570 '''Generator function that provides side-by-side comparison data.'''
564 '''Generator function that provides side-by-side comparison data.'''
571
565
572 def compline(type, leftlineno, leftline, rightlineno, rightline):
566 def compline(type, leftlineno, leftline, rightlineno, rightline):
573 lineid = leftlineno and ("l%d" % leftlineno) or ''
567 lineid = leftlineno and ("l%d" % leftlineno) or ''
574 lineid += rightlineno and ("r%d" % rightlineno) or ''
568 lineid += rightlineno and ("r%d" % rightlineno) or ''
575 llno = '%d' % leftlineno if leftlineno else ''
569 llno = '%d' % leftlineno if leftlineno else ''
576 rlno = '%d' % rightlineno if rightlineno else ''
570 rlno = '%d' % rightlineno if rightlineno else ''
577 return tmpl.generate('comparisonline', {
571 return tmpl.generate('comparisonline', {
578 'type': type,
572 'type': type,
579 'lineid': lineid,
573 'lineid': lineid,
580 'leftlineno': leftlineno,
574 'leftlineno': leftlineno,
581 'leftlinenumber': "% 6s" % llno,
575 'leftlinenumber': "% 6s" % llno,
582 'leftline': leftline or '',
576 'leftline': leftline or '',
583 'rightlineno': rightlineno,
577 'rightlineno': rightlineno,
584 'rightlinenumber': "% 6s" % rlno,
578 'rightlinenumber': "% 6s" % rlno,
585 'rightline': rightline or '',
579 'rightline': rightline or '',
586 })
580 })
587
581
588 def getblock(opcodes):
582 def getblock(opcodes):
589 for type, llo, lhi, rlo, rhi in opcodes:
583 for type, llo, lhi, rlo, rhi in opcodes:
590 len1 = lhi - llo
584 len1 = lhi - llo
591 len2 = rhi - rlo
585 len2 = rhi - rlo
592 count = min(len1, len2)
586 count = min(len1, len2)
593 for i in xrange(count):
587 for i in xrange(count):
594 yield compline(type=type,
588 yield compline(type=type,
595 leftlineno=llo + i + 1,
589 leftlineno=llo + i + 1,
596 leftline=leftlines[llo + i],
590 leftline=leftlines[llo + i],
597 rightlineno=rlo + i + 1,
591 rightlineno=rlo + i + 1,
598 rightline=rightlines[rlo + i])
592 rightline=rightlines[rlo + i])
599 if len1 > len2:
593 if len1 > len2:
600 for i in xrange(llo + count, lhi):
594 for i in xrange(llo + count, lhi):
601 yield compline(type=type,
595 yield compline(type=type,
602 leftlineno=i + 1,
596 leftlineno=i + 1,
603 leftline=leftlines[i],
597 leftline=leftlines[i],
604 rightlineno=None,
598 rightlineno=None,
605 rightline=None)
599 rightline=None)
606 elif len2 > len1:
600 elif len2 > len1:
607 for i in xrange(rlo + count, rhi):
601 for i in xrange(rlo + count, rhi):
608 yield compline(type=type,
602 yield compline(type=type,
609 leftlineno=None,
603 leftlineno=None,
610 leftline=None,
604 leftline=None,
611 rightlineno=i + 1,
605 rightlineno=i + 1,
612 rightline=rightlines[i])
606 rightline=rightlines[i])
613
607
614 s = difflib.SequenceMatcher(None, leftlines, rightlines)
608 s = difflib.SequenceMatcher(None, leftlines, rightlines)
615 if context < 0:
609 if context < 0:
616 yield tmpl.generate('comparisonblock',
610 yield tmpl.generate('comparisonblock',
617 {'lines': getblock(s.get_opcodes())})
611 {'lines': getblock(s.get_opcodes())})
618 else:
612 else:
619 for oc in s.get_grouped_opcodes(n=context):
613 for oc in s.get_grouped_opcodes(n=context):
620 yield tmpl.generate('comparisonblock', {'lines': getblock(oc)})
614 yield tmpl.generate('comparisonblock', {'lines': getblock(oc)})
621
615
622 def diffstatgen(ctx, basectx):
616 def diffstatgen(ctx, basectx):
623 '''Generator function that provides the diffstat data.'''
617 '''Generator function that provides the diffstat data.'''
624
618
625 stats = patch.diffstatdata(
619 stats = patch.diffstatdata(
626 util.iterlines(ctx.diff(basectx, noprefix=False)))
620 util.iterlines(ctx.diff(basectx, noprefix=False)))
627 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
621 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
628 while True:
622 while True:
629 yield stats, maxname, maxtotal, addtotal, removetotal, binary
623 yield stats, maxname, maxtotal, addtotal, removetotal, binary
630
624
631 def diffsummary(statgen):
625 def diffsummary(statgen):
632 '''Return a short summary of the diff.'''
626 '''Return a short summary of the diff.'''
633
627
634 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
628 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
635 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
629 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
636 len(stats), addtotal, removetotal)
630 len(stats), addtotal, removetotal)
637
631
638 def diffstat(tmpl, ctx, statgen, parity):
632 def diffstat(tmpl, ctx, statgen, parity):
639 '''Return a diffstat template for each file in the diff.'''
633 '''Return a diffstat template for each file in the diff.'''
640
634
641 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
635 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
642 files = ctx.files()
636 files = ctx.files()
643
637
644 def pct(i):
638 def pct(i):
645 if maxtotal == 0:
639 if maxtotal == 0:
646 return 0
640 return 0
647 return (float(i) / maxtotal) * 100
641 return (float(i) / maxtotal) * 100
648
642
649 fileno = 0
643 fileno = 0
650 for filename, adds, removes, isbinary in stats:
644 for filename, adds, removes, isbinary in stats:
651 template = 'diffstatlink' if filename in files else 'diffstatnolink'
645 template = 'diffstatlink' if filename in files else 'diffstatnolink'
652 total = adds + removes
646 total = adds + removes
653 fileno += 1
647 fileno += 1
654 yield tmpl.generate(template, {
648 yield tmpl.generate(template, {
655 'node': ctx.hex(),
649 'node': ctx.hex(),
656 'file': filename,
650 'file': filename,
657 'fileno': fileno,
651 'fileno': fileno,
658 'total': total,
652 'total': total,
659 'addpct': pct(adds),
653 'addpct': pct(adds),
660 'removepct': pct(removes),
654 'removepct': pct(removes),
661 'parity': next(parity),
655 'parity': next(parity),
662 })
656 })
663
657
664 class sessionvars(templateutil.wrapped):
658 class sessionvars(templateutil.wrapped):
665 def __init__(self, vars, start='?'):
659 def __init__(self, vars, start='?'):
666 self._start = start
660 self._start = start
667 self._vars = vars
661 self._vars = vars
668
662
669 def __getitem__(self, key):
663 def __getitem__(self, key):
670 return self._vars[key]
664 return self._vars[key]
671
665
672 def __setitem__(self, key, value):
666 def __setitem__(self, key, value):
673 self._vars[key] = value
667 self._vars[key] = value
674
668
675 def __copy__(self):
669 def __copy__(self):
676 return sessionvars(copy.copy(self._vars), self._start)
670 return sessionvars(copy.copy(self._vars), self._start)
677
671
678 def itermaps(self, context):
672 def itermaps(self, context):
679 separator = self._start
673 separator = self._start
680 for key, value in sorted(self._vars.iteritems()):
674 for key, value in sorted(self._vars.iteritems()):
681 yield {'name': key,
675 yield {'name': key,
682 'value': pycompat.bytestr(value),
676 'value': pycompat.bytestr(value),
683 'separator': separator,
677 'separator': separator,
684 }
678 }
685 separator = '&'
679 separator = '&'
686
680
687 def join(self, context, mapping, sep):
681 def join(self, context, mapping, sep):
688 # could be '{separator}{name}={value|urlescape}'
682 # could be '{separator}{name}={value|urlescape}'
689 raise error.ParseError(_('not displayable without template'))
683 raise error.ParseError(_('not displayable without template'))
690
684
691 def show(self, context, mapping):
685 def show(self, context, mapping):
692 return self.join(context, '')
686 return self.join(context, '')
693
687
694 def tovalue(self, context, mapping):
688 def tovalue(self, context, mapping):
695 return self._vars
689 return self._vars
696
690
697 class wsgiui(uimod.ui):
691 class wsgiui(uimod.ui):
698 # default termwidth breaks under mod_wsgi
692 # default termwidth breaks under mod_wsgi
699 def termwidth(self):
693 def termwidth(self):
700 return 80
694 return 80
701
695
702 def getwebsubs(repo):
696 def getwebsubs(repo):
703 websubtable = []
697 websubtable = []
704 websubdefs = repo.ui.configitems('websub')
698 websubdefs = repo.ui.configitems('websub')
705 # we must maintain interhg backwards compatibility
699 # we must maintain interhg backwards compatibility
706 websubdefs += repo.ui.configitems('interhg')
700 websubdefs += repo.ui.configitems('interhg')
707 for key, pattern in websubdefs:
701 for key, pattern in websubdefs:
708 # grab the delimiter from the character after the "s"
702 # grab the delimiter from the character after the "s"
709 unesc = pattern[1:2]
703 unesc = pattern[1:2]
710 delim = re.escape(unesc)
704 delim = re.escape(unesc)
711
705
712 # identify portions of the pattern, taking care to avoid escaped
706 # identify portions of the pattern, taking care to avoid escaped
713 # delimiters. the replace format and flags are optional, but
707 # delimiters. the replace format and flags are optional, but
714 # delimiters are required.
708 # delimiters are required.
715 match = re.match(
709 match = re.match(
716 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
710 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
717 % (delim, delim, delim), pattern)
711 % (delim, delim, delim), pattern)
718 if not match:
712 if not match:
719 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
713 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
720 % (key, pattern))
714 % (key, pattern))
721 continue
715 continue
722
716
723 # we need to unescape the delimiter for regexp and format
717 # we need to unescape the delimiter for regexp and format
724 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
718 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
725 regexp = delim_re.sub(unesc, match.group(1))
719 regexp = delim_re.sub(unesc, match.group(1))
726 format = delim_re.sub(unesc, match.group(2))
720 format = delim_re.sub(unesc, match.group(2))
727
721
728 # the pattern allows for 6 regexp flags, so set them if necessary
722 # the pattern allows for 6 regexp flags, so set them if necessary
729 flagin = match.group(3)
723 flagin = match.group(3)
730 flags = 0
724 flags = 0
731 if flagin:
725 if flagin:
732 for flag in flagin.upper():
726 for flag in flagin.upper():
733 flags |= re.__dict__[flag]
727 flags |= re.__dict__[flag]
734
728
735 try:
729 try:
736 regexp = re.compile(regexp, flags)
730 regexp = re.compile(regexp, flags)
737 websubtable.append((regexp, format))
731 websubtable.append((regexp, format))
738 except re.error:
732 except re.error:
739 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
733 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
740 % (key, regexp))
734 % (key, regexp))
741 return websubtable
735 return websubtable
General Comments 0
You need to be logged in to leave comments. Login now