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