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