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