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