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