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