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