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