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