##// END OF EJS Templates
py3: add b'' to make sure regex pattern are bytes in hgweb/webutil.py...
Pulkit Goyal -
r36201:44a519ec default
parent child Browse files
Show More
@@ -1,654 +1,654 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, str(exc))
350 raise ErrorResponse(HTTP_BAD_REQUEST, str(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(repo, ctx):
355 def succsandmarkers(repo, ctx):
356 for item in templatekw.showsuccsandmarkers(repo, ctx):
356 for item in templatekw.showsuccsandmarkers(repo, ctx):
357 item['successors'] = _siblings(repo[successor]
357 item['successors'] = _siblings(repo[successor]
358 for successor in item['successors'])
358 for successor in item['successors'])
359 yield item
359 yield item
360
360
361 def commonentry(repo, ctx):
361 def commonentry(repo, ctx):
362 node = ctx.node()
362 node = ctx.node()
363 return {
363 return {
364 'rev': ctx.rev(),
364 'rev': ctx.rev(),
365 'node': hex(node),
365 'node': hex(node),
366 'author': ctx.user(),
366 'author': ctx.user(),
367 'desc': ctx.description(),
367 'desc': ctx.description(),
368 'date': ctx.date(),
368 'date': ctx.date(),
369 'extra': ctx.extra(),
369 'extra': ctx.extra(),
370 'phase': ctx.phasestr(),
370 'phase': ctx.phasestr(),
371 'obsolete': ctx.obsolete(),
371 'obsolete': ctx.obsolete(),
372 'succsandmarkers': lambda **x: succsandmarkers(repo, ctx),
372 'succsandmarkers': lambda **x: succsandmarkers(repo, ctx),
373 'instabilities': [{"instability": i} for i in ctx.instabilities()],
373 'instabilities': [{"instability": i} for i in ctx.instabilities()],
374 'branch': nodebranchnodefault(ctx),
374 'branch': nodebranchnodefault(ctx),
375 'inbranch': nodeinbranch(repo, ctx),
375 'inbranch': nodeinbranch(repo, ctx),
376 'branches': nodebranchdict(repo, ctx),
376 'branches': nodebranchdict(repo, ctx),
377 'tags': nodetagsdict(repo, node),
377 'tags': nodetagsdict(repo, node),
378 'bookmarks': nodebookmarksdict(repo, node),
378 'bookmarks': nodebookmarksdict(repo, node),
379 'parent': lambda **x: parents(ctx),
379 'parent': lambda **x: parents(ctx),
380 'child': lambda **x: children(ctx),
380 'child': lambda **x: children(ctx),
381 }
381 }
382
382
383 def changelistentry(web, ctx, tmpl):
383 def changelistentry(web, ctx, tmpl):
384 '''Obtain a dictionary to be used for entries in a changelist.
384 '''Obtain a dictionary to be used for entries in a changelist.
385
385
386 This function is called when producing items for the "entries" list passed
386 This function is called when producing items for the "entries" list passed
387 to the "shortlog" and "changelog" templates.
387 to the "shortlog" and "changelog" templates.
388 '''
388 '''
389 repo = web.repo
389 repo = web.repo
390 rev = ctx.rev()
390 rev = ctx.rev()
391 n = ctx.node()
391 n = ctx.node()
392 showtags = showtag(repo, tmpl, 'changelogtag', n)
392 showtags = showtag(repo, tmpl, 'changelogtag', n)
393 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
393 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
394
394
395 entry = commonentry(repo, ctx)
395 entry = commonentry(repo, ctx)
396 entry.update(
396 entry.update(
397 allparents=lambda **x: parents(ctx),
397 allparents=lambda **x: parents(ctx),
398 parent=lambda **x: parents(ctx, rev - 1),
398 parent=lambda **x: parents(ctx, rev - 1),
399 child=lambda **x: children(ctx, rev + 1),
399 child=lambda **x: children(ctx, rev + 1),
400 changelogtag=showtags,
400 changelogtag=showtags,
401 files=files,
401 files=files,
402 )
402 )
403 return entry
403 return entry
404
404
405 def symrevorshortnode(req, ctx):
405 def symrevorshortnode(req, ctx):
406 if 'node' in req.form:
406 if 'node' in req.form:
407 return templatefilters.revescape(req.form['node'][0])
407 return templatefilters.revescape(req.form['node'][0])
408 else:
408 else:
409 return short(ctx.node())
409 return short(ctx.node())
410
410
411 def changesetentry(web, req, tmpl, ctx):
411 def changesetentry(web, req, tmpl, ctx):
412 '''Obtain a dictionary to be used to render the "changeset" template.'''
412 '''Obtain a dictionary to be used to render the "changeset" template.'''
413
413
414 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
414 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
415 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
415 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
416 ctx.node())
416 ctx.node())
417 showbranch = nodebranchnodefault(ctx)
417 showbranch = nodebranchnodefault(ctx)
418
418
419 files = []
419 files = []
420 parity = paritygen(web.stripecount)
420 parity = paritygen(web.stripecount)
421 for blockno, f in enumerate(ctx.files()):
421 for blockno, f in enumerate(ctx.files()):
422 template = 'filenodelink' if f in ctx else 'filenolink'
422 template = 'filenodelink' if f in ctx else 'filenolink'
423 files.append(tmpl(template,
423 files.append(tmpl(template,
424 node=ctx.hex(), file=f, blockno=blockno + 1,
424 node=ctx.hex(), file=f, blockno=blockno + 1,
425 parity=next(parity)))
425 parity=next(parity)))
426
426
427 basectx = basechangectx(web.repo, req)
427 basectx = basechangectx(web.repo, req)
428 if basectx is None:
428 if basectx is None:
429 basectx = ctx.p1()
429 basectx = ctx.p1()
430
430
431 style = web.config('web', 'style')
431 style = web.config('web', 'style')
432 if 'style' in req.form:
432 if 'style' in req.form:
433 style = req.form['style'][0]
433 style = req.form['style'][0]
434
434
435 diff = diffs(web, tmpl, ctx, basectx, None, style)
435 diff = diffs(web, tmpl, ctx, basectx, None, style)
436
436
437 parity = paritygen(web.stripecount)
437 parity = paritygen(web.stripecount)
438 diffstatsgen = diffstatgen(ctx, basectx)
438 diffstatsgen = diffstatgen(ctx, basectx)
439 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
439 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
440
440
441 return dict(
441 return dict(
442 diff=diff,
442 diff=diff,
443 symrev=symrevorshortnode(req, ctx),
443 symrev=symrevorshortnode(req, ctx),
444 basenode=basectx.hex(),
444 basenode=basectx.hex(),
445 changesettag=showtags,
445 changesettag=showtags,
446 changesetbookmark=showbookmarks,
446 changesetbookmark=showbookmarks,
447 changesetbranch=showbranch,
447 changesetbranch=showbranch,
448 files=files,
448 files=files,
449 diffsummary=lambda **x: diffsummary(diffstatsgen),
449 diffsummary=lambda **x: diffsummary(diffstatsgen),
450 diffstat=diffstats,
450 diffstat=diffstats,
451 archives=web.archivelist(ctx.hex()),
451 archives=web.archivelist(ctx.hex()),
452 **commonentry(web.repo, ctx))
452 **commonentry(web.repo, ctx))
453
453
454 def listfilediffs(tmpl, files, node, max):
454 def listfilediffs(tmpl, files, node, max):
455 for f in files[:max]:
455 for f in files[:max]:
456 yield tmpl('filedifflink', node=hex(node), file=f)
456 yield tmpl('filedifflink', node=hex(node), file=f)
457 if len(files) > max:
457 if len(files) > max:
458 yield tmpl('fileellipses')
458 yield tmpl('fileellipses')
459
459
460 def diffs(web, tmpl, ctx, basectx, files, style, linerange=None,
460 def diffs(web, tmpl, ctx, basectx, files, style, linerange=None,
461 lineidprefix=''):
461 lineidprefix=''):
462
462
463 def prettyprintlines(lines, blockno):
463 def prettyprintlines(lines, blockno):
464 for lineno, l in enumerate(lines, 1):
464 for lineno, l in enumerate(lines, 1):
465 difflineno = "%d.%d" % (blockno, lineno)
465 difflineno = "%d.%d" % (blockno, lineno)
466 if l.startswith('+'):
466 if l.startswith('+'):
467 ltype = "difflineplus"
467 ltype = "difflineplus"
468 elif l.startswith('-'):
468 elif l.startswith('-'):
469 ltype = "difflineminus"
469 ltype = "difflineminus"
470 elif l.startswith('@'):
470 elif l.startswith('@'):
471 ltype = "difflineat"
471 ltype = "difflineat"
472 else:
472 else:
473 ltype = "diffline"
473 ltype = "diffline"
474 yield tmpl(ltype,
474 yield tmpl(ltype,
475 line=l,
475 line=l,
476 lineno=lineno,
476 lineno=lineno,
477 lineid=lineidprefix + "l%s" % difflineno,
477 lineid=lineidprefix + "l%s" % difflineno,
478 linenumber="% 8s" % difflineno)
478 linenumber="% 8s" % difflineno)
479
479
480 repo = web.repo
480 repo = web.repo
481 if files:
481 if files:
482 m = match.exact(repo.root, repo.getcwd(), files)
482 m = match.exact(repo.root, repo.getcwd(), files)
483 else:
483 else:
484 m = match.always(repo.root, repo.getcwd())
484 m = match.always(repo.root, repo.getcwd())
485
485
486 diffopts = patch.diffopts(repo.ui, untrusted=True)
486 diffopts = patch.diffopts(repo.ui, untrusted=True)
487 node1 = basectx.node()
487 node1 = basectx.node()
488 node2 = ctx.node()
488 node2 = ctx.node()
489 parity = paritygen(web.stripecount)
489 parity = paritygen(web.stripecount)
490
490
491 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
491 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
492 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
492 for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
493 if style != 'raw':
493 if style != 'raw':
494 header = header[1:]
494 header = header[1:]
495 lines = [h + '\n' for h in header]
495 lines = [h + '\n' for h in header]
496 for hunkrange, hunklines in hunks:
496 for hunkrange, hunklines in hunks:
497 if linerange is not None and hunkrange is not None:
497 if linerange is not None and hunkrange is not None:
498 s1, l1, s2, l2 = hunkrange
498 s1, l1, s2, l2 = hunkrange
499 if not mdiff.hunkinrange((s2, l2), linerange):
499 if not mdiff.hunkinrange((s2, l2), linerange):
500 continue
500 continue
501 lines.extend(hunklines)
501 lines.extend(hunklines)
502 if lines:
502 if lines:
503 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
503 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
504 lines=prettyprintlines(lines, blockno))
504 lines=prettyprintlines(lines, blockno))
505
505
506 def compare(tmpl, context, leftlines, rightlines):
506 def compare(tmpl, context, leftlines, rightlines):
507 '''Generator function that provides side-by-side comparison data.'''
507 '''Generator function that provides side-by-side comparison data.'''
508
508
509 def compline(type, leftlineno, leftline, rightlineno, rightline):
509 def compline(type, leftlineno, leftline, rightlineno, rightline):
510 lineid = leftlineno and ("l%s" % leftlineno) or ''
510 lineid = leftlineno and ("l%s" % leftlineno) or ''
511 lineid += rightlineno and ("r%s" % rightlineno) or ''
511 lineid += rightlineno and ("r%s" % rightlineno) or ''
512 return tmpl('comparisonline',
512 return tmpl('comparisonline',
513 type=type,
513 type=type,
514 lineid=lineid,
514 lineid=lineid,
515 leftlineno=leftlineno,
515 leftlineno=leftlineno,
516 leftlinenumber="% 6s" % (leftlineno or ''),
516 leftlinenumber="% 6s" % (leftlineno or ''),
517 leftline=leftline or '',
517 leftline=leftline or '',
518 rightlineno=rightlineno,
518 rightlineno=rightlineno,
519 rightlinenumber="% 6s" % (rightlineno or ''),
519 rightlinenumber="% 6s" % (rightlineno or ''),
520 rightline=rightline or '')
520 rightline=rightline or '')
521
521
522 def getblock(opcodes):
522 def getblock(opcodes):
523 for type, llo, lhi, rlo, rhi in opcodes:
523 for type, llo, lhi, rlo, rhi in opcodes:
524 len1 = lhi - llo
524 len1 = lhi - llo
525 len2 = rhi - rlo
525 len2 = rhi - rlo
526 count = min(len1, len2)
526 count = min(len1, len2)
527 for i in xrange(count):
527 for i in xrange(count):
528 yield compline(type=type,
528 yield compline(type=type,
529 leftlineno=llo + i + 1,
529 leftlineno=llo + i + 1,
530 leftline=leftlines[llo + i],
530 leftline=leftlines[llo + i],
531 rightlineno=rlo + i + 1,
531 rightlineno=rlo + i + 1,
532 rightline=rightlines[rlo + i])
532 rightline=rightlines[rlo + i])
533 if len1 > len2:
533 if len1 > len2:
534 for i in xrange(llo + count, lhi):
534 for i in xrange(llo + count, lhi):
535 yield compline(type=type,
535 yield compline(type=type,
536 leftlineno=i + 1,
536 leftlineno=i + 1,
537 leftline=leftlines[i],
537 leftline=leftlines[i],
538 rightlineno=None,
538 rightlineno=None,
539 rightline=None)
539 rightline=None)
540 elif len2 > len1:
540 elif len2 > len1:
541 for i in xrange(rlo + count, rhi):
541 for i in xrange(rlo + count, rhi):
542 yield compline(type=type,
542 yield compline(type=type,
543 leftlineno=None,
543 leftlineno=None,
544 leftline=None,
544 leftline=None,
545 rightlineno=i + 1,
545 rightlineno=i + 1,
546 rightline=rightlines[i])
546 rightline=rightlines[i])
547
547
548 s = difflib.SequenceMatcher(None, leftlines, rightlines)
548 s = difflib.SequenceMatcher(None, leftlines, rightlines)
549 if context < 0:
549 if context < 0:
550 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
550 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
551 else:
551 else:
552 for oc in s.get_grouped_opcodes(n=context):
552 for oc in s.get_grouped_opcodes(n=context):
553 yield tmpl('comparisonblock', lines=getblock(oc))
553 yield tmpl('comparisonblock', lines=getblock(oc))
554
554
555 def diffstatgen(ctx, basectx):
555 def diffstatgen(ctx, basectx):
556 '''Generator function that provides the diffstat data.'''
556 '''Generator function that provides the diffstat data.'''
557
557
558 stats = patch.diffstatdata(
558 stats = patch.diffstatdata(
559 util.iterlines(ctx.diff(basectx, noprefix=False)))
559 util.iterlines(ctx.diff(basectx, noprefix=False)))
560 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
560 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
561 while True:
561 while True:
562 yield stats, maxname, maxtotal, addtotal, removetotal, binary
562 yield stats, maxname, maxtotal, addtotal, removetotal, binary
563
563
564 def diffsummary(statgen):
564 def diffsummary(statgen):
565 '''Return a short summary of the diff.'''
565 '''Return a short summary of the diff.'''
566
566
567 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
567 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
568 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
568 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
569 len(stats), addtotal, removetotal)
569 len(stats), addtotal, removetotal)
570
570
571 def diffstat(tmpl, ctx, statgen, parity):
571 def diffstat(tmpl, ctx, statgen, parity):
572 '''Return a diffstat template for each file in the diff.'''
572 '''Return a diffstat template for each file in the diff.'''
573
573
574 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
574 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
575 files = ctx.files()
575 files = ctx.files()
576
576
577 def pct(i):
577 def pct(i):
578 if maxtotal == 0:
578 if maxtotal == 0:
579 return 0
579 return 0
580 return (float(i) / maxtotal) * 100
580 return (float(i) / maxtotal) * 100
581
581
582 fileno = 0
582 fileno = 0
583 for filename, adds, removes, isbinary in stats:
583 for filename, adds, removes, isbinary in stats:
584 template = 'diffstatlink' if filename in files else 'diffstatnolink'
584 template = 'diffstatlink' if filename in files else 'diffstatnolink'
585 total = adds + removes
585 total = adds + removes
586 fileno += 1
586 fileno += 1
587 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
587 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
588 total=total, addpct=pct(adds), removepct=pct(removes),
588 total=total, addpct=pct(adds), removepct=pct(removes),
589 parity=next(parity))
589 parity=next(parity))
590
590
591 class sessionvars(object):
591 class sessionvars(object):
592 def __init__(self, vars, start='?'):
592 def __init__(self, vars, start='?'):
593 self.start = start
593 self.start = start
594 self.vars = vars
594 self.vars = vars
595 def __getitem__(self, key):
595 def __getitem__(self, key):
596 return self.vars[key]
596 return self.vars[key]
597 def __setitem__(self, key, value):
597 def __setitem__(self, key, value):
598 self.vars[key] = value
598 self.vars[key] = value
599 def __copy__(self):
599 def __copy__(self):
600 return sessionvars(copy.copy(self.vars), self.start)
600 return sessionvars(copy.copy(self.vars), self.start)
601 def __iter__(self):
601 def __iter__(self):
602 separator = self.start
602 separator = self.start
603 for key, value in sorted(self.vars.iteritems()):
603 for key, value in sorted(self.vars.iteritems()):
604 yield {'name': key,
604 yield {'name': key,
605 'value': pycompat.bytestr(value),
605 'value': pycompat.bytestr(value),
606 'separator': separator,
606 'separator': separator,
607 }
607 }
608 separator = '&'
608 separator = '&'
609
609
610 class wsgiui(uimod.ui):
610 class wsgiui(uimod.ui):
611 # default termwidth breaks under mod_wsgi
611 # default termwidth breaks under mod_wsgi
612 def termwidth(self):
612 def termwidth(self):
613 return 80
613 return 80
614
614
615 def getwebsubs(repo):
615 def getwebsubs(repo):
616 websubtable = []
616 websubtable = []
617 websubdefs = repo.ui.configitems('websub')
617 websubdefs = repo.ui.configitems('websub')
618 # we must maintain interhg backwards compatibility
618 # we must maintain interhg backwards compatibility
619 websubdefs += repo.ui.configitems('interhg')
619 websubdefs += repo.ui.configitems('interhg')
620 for key, pattern in websubdefs:
620 for key, pattern in websubdefs:
621 # grab the delimiter from the character after the "s"
621 # grab the delimiter from the character after the "s"
622 unesc = pattern[1:2]
622 unesc = pattern[1:2]
623 delim = re.escape(unesc)
623 delim = re.escape(unesc)
624
624
625 # identify portions of the pattern, taking care to avoid escaped
625 # identify portions of the pattern, taking care to avoid escaped
626 # delimiters. the replace format and flags are optional, but
626 # delimiters. the replace format and flags are optional, but
627 # delimiters are required.
627 # delimiters are required.
628 match = re.match(
628 match = re.match(
629 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
629 br'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
630 % (delim, delim, delim), pattern)
630 % (delim, delim, delim), pattern)
631 if not match:
631 if not match:
632 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
632 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
633 % (key, pattern))
633 % (key, pattern))
634 continue
634 continue
635
635
636 # we need to unescape the delimiter for regexp and format
636 # we need to unescape the delimiter for regexp and format
637 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
637 delim_re = re.compile(br'(?<!\\)\\%s' % delim)
638 regexp = delim_re.sub(unesc, match.group(1))
638 regexp = delim_re.sub(unesc, match.group(1))
639 format = delim_re.sub(unesc, match.group(2))
639 format = delim_re.sub(unesc, match.group(2))
640
640
641 # the pattern allows for 6 regexp flags, so set them if necessary
641 # the pattern allows for 6 regexp flags, so set them if necessary
642 flagin = match.group(3)
642 flagin = match.group(3)
643 flags = 0
643 flags = 0
644 if flagin:
644 if flagin:
645 for flag in flagin.upper():
645 for flag in flagin.upper():
646 flags |= re.__dict__[flag]
646 flags |= re.__dict__[flag]
647
647
648 try:
648 try:
649 regexp = re.compile(regexp, flags)
649 regexp = re.compile(regexp, flags)
650 websubtable.append((regexp, format))
650 websubtable.append((regexp, format))
651 except re.error:
651 except re.error:
652 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
652 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
653 % (key, regexp))
653 % (key, regexp))
654 return websubtable
654 return websubtable
General Comments 0
You need to be logged in to leave comments. Login now