##// END OF EJS Templates
hgweb: drop recursivity in _navseq...
Pierre-Yves David -
r18390:28fa9443 default
parent child Browse files
Show More
@@ -1,360 +1,360 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 import os, copy
9 import os, copy
10 from mercurial import match, patch, scmutil, error, ui, util
10 from mercurial import match, patch, scmutil, error, ui, util
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial.node import hex, nullid
12 from mercurial.node import hex, nullid
13 from common import ErrorResponse
13 from common import ErrorResponse
14 from common import HTTP_NOT_FOUND
14 from common import HTTP_NOT_FOUND
15 import difflib
15 import difflib
16
16
17 def up(p):
17 def up(p):
18 if p[0] != "/":
18 if p[0] != "/":
19 p = "/" + p
19 p = "/" + p
20 if p[-1] == "/":
20 if p[-1] == "/":
21 p = p[:-1]
21 p = p[:-1]
22 up = os.path.dirname(p)
22 up = os.path.dirname(p)
23 if up == "/":
23 if up == "/":
24 return "/"
24 return "/"
25 return up + "/"
25 return up + "/"
26
26
27 def _navseq(factor, limit=None):
27 def _navseq(factor, limit=None):
28 if limit:
28 if limit:
29 yield limit
29 yield limit
30 if limit >= 20 and limit <= 40:
30 if limit >= 20 and limit <= 40:
31 yield 50
31 yield 50
32 else:
32 factor *= 10
33 while True:
33 yield 1 * factor
34 yield 1 * factor
34 yield 3 * factor
35 yield 3 * factor
35 for f in _navseq(factor * 10):
36 factor *= 10
36 yield f
37
37
38 def revnavgen(pos, pagelen, limit, nodefunc):
38 def revnavgen(pos, pagelen, limit, nodefunc):
39 """computes label and revision id for navigation link
39 """computes label and revision id for navigation link
40
40
41 :pos: is the revision relative to which we generate navigation.
41 :pos: is the revision relative to which we generate navigation.
42 :pagelen: the size of each navigation page
42 :pagelen: the size of each navigation page
43 :limit: how far shall we link
43 :limit: how far shall we link
44 :nodefun: factory for a changectx from a revision
44 :nodefun: factory for a changectx from a revision
45
45
46 The return is:
46 The return is:
47 - a single element tuple
47 - a single element tuple
48 - containing a dictionary with a `before` and `after` key
48 - containing a dictionary with a `before` and `after` key
49 - values are generator functions taking an arbitrary number of kwargs
49 - values are generator functions taking an arbitrary number of kwargs
50 - yield items are dictionaries with `label` and `node` keys
50 - yield items are dictionaries with `label` and `node` keys
51 """
51 """
52
52
53 navbefore = []
53 navbefore = []
54 navafter = []
54 navafter = []
55
55
56 last = 0
56 last = 0
57 for f in _navseq(1, pagelen):
57 for f in _navseq(1, pagelen):
58 if f < pagelen or f <= last:
58 if f < pagelen or f <= last:
59 continue
59 continue
60 if f > limit:
60 if f > limit:
61 break
61 break
62 last = f
62 last = f
63 if pos + f < limit:
63 if pos + f < limit:
64 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
64 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
65 if pos - f >= 0:
65 if pos - f >= 0:
66 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
66 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
67
67
68 navafter.append(("tip", "tip"))
68 navafter.append(("tip", "tip"))
69 try:
69 try:
70 navbefore.insert(0, ("(0)", hex(nodefunc(0).node())))
70 navbefore.insert(0, ("(0)", hex(nodefunc(0).node())))
71 except error.RepoError:
71 except error.RepoError:
72 pass
72 pass
73
73
74 data = lambda i: {"label": i[0], "node": i[1]}
74 data = lambda i: {"label": i[0], "node": i[1]}
75 return ({'before': lambda **map: (data(i) for i in navbefore),
75 return ({'before': lambda **map: (data(i) for i in navbefore),
76 'after': lambda **map: (data(i) for i in navafter)},)
76 'after': lambda **map: (data(i) for i in navafter)},)
77
77
78 def _siblings(siblings=[], hiderev=None):
78 def _siblings(siblings=[], hiderev=None):
79 siblings = [s for s in siblings if s.node() != nullid]
79 siblings = [s for s in siblings if s.node() != nullid]
80 if len(siblings) == 1 and siblings[0].rev() == hiderev:
80 if len(siblings) == 1 and siblings[0].rev() == hiderev:
81 return
81 return
82 for s in siblings:
82 for s in siblings:
83 d = {'node': s.hex(), 'rev': s.rev()}
83 d = {'node': s.hex(), 'rev': s.rev()}
84 d['user'] = s.user()
84 d['user'] = s.user()
85 d['date'] = s.date()
85 d['date'] = s.date()
86 d['description'] = s.description()
86 d['description'] = s.description()
87 d['branch'] = s.branch()
87 d['branch'] = s.branch()
88 if util.safehasattr(s, 'path'):
88 if util.safehasattr(s, 'path'):
89 d['file'] = s.path()
89 d['file'] = s.path()
90 yield d
90 yield d
91
91
92 def parents(ctx, hide=None):
92 def parents(ctx, hide=None):
93 return _siblings(ctx.parents(), hide)
93 return _siblings(ctx.parents(), hide)
94
94
95 def children(ctx, hide=None):
95 def children(ctx, hide=None):
96 return _siblings(ctx.children(), hide)
96 return _siblings(ctx.children(), hide)
97
97
98 def renamelink(fctx):
98 def renamelink(fctx):
99 r = fctx.renamed()
99 r = fctx.renamed()
100 if r:
100 if r:
101 return [dict(file=r[0], node=hex(r[1]))]
101 return [dict(file=r[0], node=hex(r[1]))]
102 return []
102 return []
103
103
104 def nodetagsdict(repo, node):
104 def nodetagsdict(repo, node):
105 return [{"name": i} for i in repo.nodetags(node)]
105 return [{"name": i} for i in repo.nodetags(node)]
106
106
107 def nodebookmarksdict(repo, node):
107 def nodebookmarksdict(repo, node):
108 return [{"name": i} for i in repo.nodebookmarks(node)]
108 return [{"name": i} for i in repo.nodebookmarks(node)]
109
109
110 def nodebranchdict(repo, ctx):
110 def nodebranchdict(repo, ctx):
111 branches = []
111 branches = []
112 branch = ctx.branch()
112 branch = ctx.branch()
113 # If this is an empty repo, ctx.node() == nullid,
113 # If this is an empty repo, ctx.node() == nullid,
114 # ctx.branch() == 'default'.
114 # ctx.branch() == 'default'.
115 try:
115 try:
116 branchnode = repo.branchtip(branch)
116 branchnode = repo.branchtip(branch)
117 except error.RepoLookupError:
117 except error.RepoLookupError:
118 branchnode = None
118 branchnode = None
119 if branchnode == ctx.node():
119 if branchnode == ctx.node():
120 branches.append({"name": branch})
120 branches.append({"name": branch})
121 return branches
121 return branches
122
122
123 def nodeinbranch(repo, ctx):
123 def nodeinbranch(repo, ctx):
124 branches = []
124 branches = []
125 branch = ctx.branch()
125 branch = ctx.branch()
126 try:
126 try:
127 branchnode = repo.branchtip(branch)
127 branchnode = repo.branchtip(branch)
128 except error.RepoLookupError:
128 except error.RepoLookupError:
129 branchnode = None
129 branchnode = None
130 if branch != 'default' and branchnode != ctx.node():
130 if branch != 'default' and branchnode != ctx.node():
131 branches.append({"name": branch})
131 branches.append({"name": branch})
132 return branches
132 return branches
133
133
134 def nodebranchnodefault(ctx):
134 def nodebranchnodefault(ctx):
135 branches = []
135 branches = []
136 branch = ctx.branch()
136 branch = ctx.branch()
137 if branch != 'default':
137 if branch != 'default':
138 branches.append({"name": branch})
138 branches.append({"name": branch})
139 return branches
139 return branches
140
140
141 def showtag(repo, tmpl, t1, node=nullid, **args):
141 def showtag(repo, tmpl, t1, node=nullid, **args):
142 for t in repo.nodetags(node):
142 for t in repo.nodetags(node):
143 yield tmpl(t1, tag=t, **args)
143 yield tmpl(t1, tag=t, **args)
144
144
145 def showbookmark(repo, tmpl, t1, node=nullid, **args):
145 def showbookmark(repo, tmpl, t1, node=nullid, **args):
146 for t in repo.nodebookmarks(node):
146 for t in repo.nodebookmarks(node):
147 yield tmpl(t1, bookmark=t, **args)
147 yield tmpl(t1, bookmark=t, **args)
148
148
149 def cleanpath(repo, path):
149 def cleanpath(repo, path):
150 path = path.lstrip('/')
150 path = path.lstrip('/')
151 return scmutil.canonpath(repo.root, '', path)
151 return scmutil.canonpath(repo.root, '', path)
152
152
153 def changeidctx (repo, changeid):
153 def changeidctx (repo, changeid):
154 try:
154 try:
155 ctx = repo[changeid]
155 ctx = repo[changeid]
156 except error.RepoError:
156 except error.RepoError:
157 man = repo.manifest
157 man = repo.manifest
158 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
158 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
159
159
160 return ctx
160 return ctx
161
161
162 def changectx (repo, req):
162 def changectx (repo, req):
163 changeid = "tip"
163 changeid = "tip"
164 if 'node' in req.form:
164 if 'node' in req.form:
165 changeid = req.form['node'][0]
165 changeid = req.form['node'][0]
166 ipos=changeid.find(':')
166 ipos=changeid.find(':')
167 if ipos != -1:
167 if ipos != -1:
168 changeid = changeid[(ipos + 1):]
168 changeid = changeid[(ipos + 1):]
169 elif 'manifest' in req.form:
169 elif 'manifest' in req.form:
170 changeid = req.form['manifest'][0]
170 changeid = req.form['manifest'][0]
171
171
172 return changeidctx(repo, changeid)
172 return changeidctx(repo, changeid)
173
173
174 def basechangectx(repo, req):
174 def basechangectx(repo, req):
175 if 'node' in req.form:
175 if 'node' in req.form:
176 changeid = req.form['node'][0]
176 changeid = req.form['node'][0]
177 ipos=changeid.find(':')
177 ipos=changeid.find(':')
178 if ipos != -1:
178 if ipos != -1:
179 changeid = changeid[:ipos]
179 changeid = changeid[:ipos]
180 return changeidctx(repo, changeid)
180 return changeidctx(repo, changeid)
181
181
182 return None
182 return None
183
183
184 def filectx(repo, req):
184 def filectx(repo, req):
185 if 'file' not in req.form:
185 if 'file' not in req.form:
186 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
186 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
187 path = cleanpath(repo, req.form['file'][0])
187 path = cleanpath(repo, req.form['file'][0])
188 if 'node' in req.form:
188 if 'node' in req.form:
189 changeid = req.form['node'][0]
189 changeid = req.form['node'][0]
190 elif 'filenode' in req.form:
190 elif 'filenode' in req.form:
191 changeid = req.form['filenode'][0]
191 changeid = req.form['filenode'][0]
192 else:
192 else:
193 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
193 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
194 try:
194 try:
195 fctx = repo[changeid][path]
195 fctx = repo[changeid][path]
196 except error.RepoError:
196 except error.RepoError:
197 fctx = repo.filectx(path, fileid=changeid)
197 fctx = repo.filectx(path, fileid=changeid)
198
198
199 return fctx
199 return fctx
200
200
201 def listfilediffs(tmpl, files, node, max):
201 def listfilediffs(tmpl, files, node, max):
202 for f in files[:max]:
202 for f in files[:max]:
203 yield tmpl('filedifflink', node=hex(node), file=f)
203 yield tmpl('filedifflink', node=hex(node), file=f)
204 if len(files) > max:
204 if len(files) > max:
205 yield tmpl('fileellipses')
205 yield tmpl('fileellipses')
206
206
207 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
207 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
208
208
209 def countgen():
209 def countgen():
210 start = 1
210 start = 1
211 while True:
211 while True:
212 yield start
212 yield start
213 start += 1
213 start += 1
214
214
215 blockcount = countgen()
215 blockcount = countgen()
216 def prettyprintlines(diff, blockno):
216 def prettyprintlines(diff, blockno):
217 for lineno, l in enumerate(diff.splitlines(True)):
217 for lineno, l in enumerate(diff.splitlines(True)):
218 lineno = "%d.%d" % (blockno, lineno + 1)
218 lineno = "%d.%d" % (blockno, lineno + 1)
219 if l.startswith('+'):
219 if l.startswith('+'):
220 ltype = "difflineplus"
220 ltype = "difflineplus"
221 elif l.startswith('-'):
221 elif l.startswith('-'):
222 ltype = "difflineminus"
222 ltype = "difflineminus"
223 elif l.startswith('@'):
223 elif l.startswith('@'):
224 ltype = "difflineat"
224 ltype = "difflineat"
225 else:
225 else:
226 ltype = "diffline"
226 ltype = "diffline"
227 yield tmpl(ltype,
227 yield tmpl(ltype,
228 line=l,
228 line=l,
229 lineid="l%s" % lineno,
229 lineid="l%s" % lineno,
230 linenumber="% 8s" % lineno)
230 linenumber="% 8s" % lineno)
231
231
232 if files:
232 if files:
233 m = match.exact(repo.root, repo.getcwd(), files)
233 m = match.exact(repo.root, repo.getcwd(), files)
234 else:
234 else:
235 m = match.always(repo.root, repo.getcwd())
235 m = match.always(repo.root, repo.getcwd())
236
236
237 diffopts = patch.diffopts(repo.ui, untrusted=True)
237 diffopts = patch.diffopts(repo.ui, untrusted=True)
238 if basectx is None:
238 if basectx is None:
239 parents = ctx.parents()
239 parents = ctx.parents()
240 node1 = parents and parents[0].node() or nullid
240 node1 = parents and parents[0].node() or nullid
241 else:
241 else:
242 node1 = basectx.node()
242 node1 = basectx.node()
243 node2 = ctx.node()
243 node2 = ctx.node()
244
244
245 block = []
245 block = []
246 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
246 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
247 if chunk.startswith('diff') and block:
247 if chunk.startswith('diff') and block:
248 blockno = blockcount.next()
248 blockno = blockcount.next()
249 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
249 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
250 lines=prettyprintlines(''.join(block), blockno))
250 lines=prettyprintlines(''.join(block), blockno))
251 block = []
251 block = []
252 if chunk.startswith('diff') and style != 'raw':
252 if chunk.startswith('diff') and style != 'raw':
253 chunk = ''.join(chunk.splitlines(True)[1:])
253 chunk = ''.join(chunk.splitlines(True)[1:])
254 block.append(chunk)
254 block.append(chunk)
255 blockno = blockcount.next()
255 blockno = blockcount.next()
256 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
256 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
257 lines=prettyprintlines(''.join(block), blockno))
257 lines=prettyprintlines(''.join(block), blockno))
258
258
259 def compare(tmpl, context, leftlines, rightlines):
259 def compare(tmpl, context, leftlines, rightlines):
260 '''Generator function that provides side-by-side comparison data.'''
260 '''Generator function that provides side-by-side comparison data.'''
261
261
262 def compline(type, leftlineno, leftline, rightlineno, rightline):
262 def compline(type, leftlineno, leftline, rightlineno, rightline):
263 lineid = leftlineno and ("l%s" % leftlineno) or ''
263 lineid = leftlineno and ("l%s" % leftlineno) or ''
264 lineid += rightlineno and ("r%s" % rightlineno) or ''
264 lineid += rightlineno and ("r%s" % rightlineno) or ''
265 return tmpl('comparisonline',
265 return tmpl('comparisonline',
266 type=type,
266 type=type,
267 lineid=lineid,
267 lineid=lineid,
268 leftlinenumber="% 6s" % (leftlineno or ''),
268 leftlinenumber="% 6s" % (leftlineno or ''),
269 leftline=leftline or '',
269 leftline=leftline or '',
270 rightlinenumber="% 6s" % (rightlineno or ''),
270 rightlinenumber="% 6s" % (rightlineno or ''),
271 rightline=rightline or '')
271 rightline=rightline or '')
272
272
273 def getblock(opcodes):
273 def getblock(opcodes):
274 for type, llo, lhi, rlo, rhi in opcodes:
274 for type, llo, lhi, rlo, rhi in opcodes:
275 len1 = lhi - llo
275 len1 = lhi - llo
276 len2 = rhi - rlo
276 len2 = rhi - rlo
277 count = min(len1, len2)
277 count = min(len1, len2)
278 for i in xrange(count):
278 for i in xrange(count):
279 yield compline(type=type,
279 yield compline(type=type,
280 leftlineno=llo + i + 1,
280 leftlineno=llo + i + 1,
281 leftline=leftlines[llo + i],
281 leftline=leftlines[llo + i],
282 rightlineno=rlo + i + 1,
282 rightlineno=rlo + i + 1,
283 rightline=rightlines[rlo + i])
283 rightline=rightlines[rlo + i])
284 if len1 > len2:
284 if len1 > len2:
285 for i in xrange(llo + count, lhi):
285 for i in xrange(llo + count, lhi):
286 yield compline(type=type,
286 yield compline(type=type,
287 leftlineno=i + 1,
287 leftlineno=i + 1,
288 leftline=leftlines[i],
288 leftline=leftlines[i],
289 rightlineno=None,
289 rightlineno=None,
290 rightline=None)
290 rightline=None)
291 elif len2 > len1:
291 elif len2 > len1:
292 for i in xrange(rlo + count, rhi):
292 for i in xrange(rlo + count, rhi):
293 yield compline(type=type,
293 yield compline(type=type,
294 leftlineno=None,
294 leftlineno=None,
295 leftline=None,
295 leftline=None,
296 rightlineno=i + 1,
296 rightlineno=i + 1,
297 rightline=rightlines[i])
297 rightline=rightlines[i])
298
298
299 s = difflib.SequenceMatcher(None, leftlines, rightlines)
299 s = difflib.SequenceMatcher(None, leftlines, rightlines)
300 if context < 0:
300 if context < 0:
301 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
301 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
302 else:
302 else:
303 for oc in s.get_grouped_opcodes(n=context):
303 for oc in s.get_grouped_opcodes(n=context):
304 yield tmpl('comparisonblock', lines=getblock(oc))
304 yield tmpl('comparisonblock', lines=getblock(oc))
305
305
306 def diffstatgen(ctx, basectx):
306 def diffstatgen(ctx, basectx):
307 '''Generator function that provides the diffstat data.'''
307 '''Generator function that provides the diffstat data.'''
308
308
309 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
309 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
310 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
310 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
311 while True:
311 while True:
312 yield stats, maxname, maxtotal, addtotal, removetotal, binary
312 yield stats, maxname, maxtotal, addtotal, removetotal, binary
313
313
314 def diffsummary(statgen):
314 def diffsummary(statgen):
315 '''Return a short summary of the diff.'''
315 '''Return a short summary of the diff.'''
316
316
317 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
317 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
318 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
318 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
319 len(stats), addtotal, removetotal)
319 len(stats), addtotal, removetotal)
320
320
321 def diffstat(tmpl, ctx, statgen, parity):
321 def diffstat(tmpl, ctx, statgen, parity):
322 '''Return a diffstat template for each file in the diff.'''
322 '''Return a diffstat template for each file in the diff.'''
323
323
324 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
324 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
325 files = ctx.files()
325 files = ctx.files()
326
326
327 def pct(i):
327 def pct(i):
328 if maxtotal == 0:
328 if maxtotal == 0:
329 return 0
329 return 0
330 return (float(i) / maxtotal) * 100
330 return (float(i) / maxtotal) * 100
331
331
332 fileno = 0
332 fileno = 0
333 for filename, adds, removes, isbinary in stats:
333 for filename, adds, removes, isbinary in stats:
334 template = filename in files and 'diffstatlink' or 'diffstatnolink'
334 template = filename in files and 'diffstatlink' or 'diffstatnolink'
335 total = adds + removes
335 total = adds + removes
336 fileno += 1
336 fileno += 1
337 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
337 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
338 total=total, addpct=pct(adds), removepct=pct(removes),
338 total=total, addpct=pct(adds), removepct=pct(removes),
339 parity=parity.next())
339 parity=parity.next())
340
340
341 class sessionvars(object):
341 class sessionvars(object):
342 def __init__(self, vars, start='?'):
342 def __init__(self, vars, start='?'):
343 self.start = start
343 self.start = start
344 self.vars = vars
344 self.vars = vars
345 def __getitem__(self, key):
345 def __getitem__(self, key):
346 return self.vars[key]
346 return self.vars[key]
347 def __setitem__(self, key, value):
347 def __setitem__(self, key, value):
348 self.vars[key] = value
348 self.vars[key] = value
349 def __copy__(self):
349 def __copy__(self):
350 return sessionvars(copy.copy(self.vars), self.start)
350 return sessionvars(copy.copy(self.vars), self.start)
351 def __iter__(self):
351 def __iter__(self):
352 separator = self.start
352 separator = self.start
353 for key, value in sorted(self.vars.iteritems()):
353 for key, value in sorted(self.vars.iteritems()):
354 yield {'name': key, 'value': str(value), 'separator': separator}
354 yield {'name': key, 'value': str(value), 'separator': separator}
355 separator = '&'
355 separator = '&'
356
356
357 class wsgiui(ui.ui):
357 class wsgiui(ui.ui):
358 # default termwidth breaks under mod_wsgi
358 # default termwidth breaks under mod_wsgi
359 def termwidth(self):
359 def termwidth(self):
360 return 80
360 return 80
General Comments 0
You need to be logged in to leave comments. Login now