##// END OF EJS Templates
hgweb: avoid traceback when file or node parameters are missing...
Ross Lagerwall -
r17289:f2d6b4f8 stable
parent child Browse files
Show More
@@ -1,934 +1,936
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import os, mimetypes, re, cgi, copy
9 9 import webutil
10 10 from mercurial import error, encoding, archival, templater, templatefilters
11 11 from mercurial.node import short, hex
12 12 from mercurial.util import binary
13 13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 15 from mercurial import graphmod, patch
16 16 from mercurial import help as helpmod
17 17 from mercurial.i18n import _
18 18
19 19 # __all__ is populated with the allowed commands. Be sure to add to it if
20 20 # you're adding a new command, or the new command won't work.
21 21
22 22 __all__ = [
23 23 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
24 24 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
25 25 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
26 26 ]
27 27
28 28 def log(web, req, tmpl):
29 29 if 'file' in req.form and req.form['file'][0]:
30 30 return filelog(web, req, tmpl)
31 31 else:
32 32 return changelog(web, req, tmpl)
33 33
34 34 def rawfile(web, req, tmpl):
35 35 guessmime = web.configbool('web', 'guessmime', False)
36 36
37 37 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
38 38 if not path:
39 39 content = manifest(web, req, tmpl)
40 40 req.respond(HTTP_OK, web.ctype)
41 41 return content
42 42
43 43 try:
44 44 fctx = webutil.filectx(web.repo, req)
45 45 except error.LookupError, inst:
46 46 try:
47 47 content = manifest(web, req, tmpl)
48 48 req.respond(HTTP_OK, web.ctype)
49 49 return content
50 50 except ErrorResponse:
51 51 raise inst
52 52
53 53 path = fctx.path()
54 54 text = fctx.data()
55 55 mt = 'application/binary'
56 56 if guessmime:
57 57 mt = mimetypes.guess_type(path)[0]
58 58 if mt is None:
59 59 mt = binary(text) and 'application/binary' or 'text/plain'
60 60 if mt.startswith('text/'):
61 61 mt += '; charset="%s"' % encoding.encoding
62 62
63 63 req.respond(HTTP_OK, mt, path, len(text))
64 64 return [text]
65 65
66 66 def _filerevision(web, tmpl, fctx):
67 67 f = fctx.path()
68 68 text = fctx.data()
69 69 parity = paritygen(web.stripecount)
70 70
71 71 if binary(text):
72 72 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
73 73 text = '(binary:%s)' % mt
74 74
75 75 def lines():
76 76 for lineno, t in enumerate(text.splitlines(True)):
77 77 yield {"line": t,
78 78 "lineid": "l%d" % (lineno + 1),
79 79 "linenumber": "% 6d" % (lineno + 1),
80 80 "parity": parity.next()}
81 81
82 82 return tmpl("filerevision",
83 83 file=f,
84 84 path=webutil.up(f),
85 85 text=lines(),
86 86 rev=fctx.rev(),
87 87 node=fctx.hex(),
88 88 author=fctx.user(),
89 89 date=fctx.date(),
90 90 desc=fctx.description(),
91 91 branch=webutil.nodebranchnodefault(fctx),
92 92 parent=webutil.parents(fctx),
93 93 child=webutil.children(fctx),
94 94 rename=webutil.renamelink(fctx),
95 95 permissions=fctx.manifest().flags(f))
96 96
97 97 def file(web, req, tmpl):
98 98 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
99 99 if not path:
100 100 return manifest(web, req, tmpl)
101 101 try:
102 102 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
103 103 except error.LookupError, inst:
104 104 try:
105 105 return manifest(web, req, tmpl)
106 106 except ErrorResponse:
107 107 raise inst
108 108
109 109 def _search(web, req, tmpl):
110 110
111 111 query = req.form['rev'][0]
112 112 revcount = web.maxchanges
113 113 if 'revcount' in req.form:
114 114 revcount = int(req.form.get('revcount', [revcount])[0])
115 115 revcount = max(revcount, 1)
116 116 tmpl.defaults['sessionvars']['revcount'] = revcount
117 117
118 118 lessvars = copy.copy(tmpl.defaults['sessionvars'])
119 119 lessvars['revcount'] = max(revcount / 2, 1)
120 120 lessvars['rev'] = query
121 121 morevars = copy.copy(tmpl.defaults['sessionvars'])
122 122 morevars['revcount'] = revcount * 2
123 123 morevars['rev'] = query
124 124
125 125 def changelist(**map):
126 126 count = 0
127 127 lower = encoding.lower
128 128 qw = lower(query).split()
129 129
130 130 def revgen():
131 131 for i in xrange(len(web.repo) - 1, 0, -100):
132 132 l = []
133 133 for j in xrange(max(0, i - 100), i + 1):
134 134 ctx = web.repo[j]
135 135 l.append(ctx)
136 136 l.reverse()
137 137 for e in l:
138 138 yield e
139 139
140 140 for ctx in revgen():
141 141 miss = 0
142 142 for q in qw:
143 143 if not (q in lower(ctx.user()) or
144 144 q in lower(ctx.description()) or
145 145 q in lower(" ".join(ctx.files()))):
146 146 miss = 1
147 147 break
148 148 if miss:
149 149 continue
150 150
151 151 count += 1
152 152 n = ctx.node()
153 153 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
154 154 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
155 155
156 156 yield tmpl('searchentry',
157 157 parity=parity.next(),
158 158 author=ctx.user(),
159 159 parent=webutil.parents(ctx),
160 160 child=webutil.children(ctx),
161 161 changelogtag=showtags,
162 162 desc=ctx.description(),
163 163 date=ctx.date(),
164 164 files=files,
165 165 rev=ctx.rev(),
166 166 node=hex(n),
167 167 tags=webutil.nodetagsdict(web.repo, n),
168 168 bookmarks=webutil.nodebookmarksdict(web.repo, n),
169 169 inbranch=webutil.nodeinbranch(web.repo, ctx),
170 170 branches=webutil.nodebranchdict(web.repo, ctx))
171 171
172 172 if count >= revcount:
173 173 break
174 174
175 175 tip = web.repo['tip']
176 176 parity = paritygen(web.stripecount)
177 177
178 178 return tmpl('search', query=query, node=tip.hex(),
179 179 entries=changelist, archives=web.archivelist("tip"),
180 180 morevars=morevars, lessvars=lessvars)
181 181
182 182 def changelog(web, req, tmpl, shortlog=False):
183 183
184 184 if 'node' in req.form:
185 185 ctx = webutil.changectx(web.repo, req)
186 186 else:
187 187 if 'rev' in req.form:
188 188 hi = req.form['rev'][0]
189 189 else:
190 190 hi = len(web.repo) - 1
191 191 try:
192 192 ctx = web.repo[hi]
193 193 except error.RepoError:
194 194 return _search(web, req, tmpl) # XXX redirect to 404 page?
195 195
196 196 def changelist(limit=0, **map):
197 197 l = [] # build a list in forward order for efficiency
198 198 for i in xrange(start, end):
199 199 ctx = web.repo[i]
200 200 n = ctx.node()
201 201 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
202 202 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
203 203
204 204 l.insert(0, {"parity": parity.next(),
205 205 "author": ctx.user(),
206 206 "parent": webutil.parents(ctx, i - 1),
207 207 "child": webutil.children(ctx, i + 1),
208 208 "changelogtag": showtags,
209 209 "desc": ctx.description(),
210 210 "date": ctx.date(),
211 211 "files": files,
212 212 "rev": i,
213 213 "node": hex(n),
214 214 "tags": webutil.nodetagsdict(web.repo, n),
215 215 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
216 216 "inbranch": webutil.nodeinbranch(web.repo, ctx),
217 217 "branches": webutil.nodebranchdict(web.repo, ctx)
218 218 })
219 219
220 220 if limit > 0:
221 221 l = l[:limit]
222 222
223 223 for e in l:
224 224 yield e
225 225
226 226 revcount = shortlog and web.maxshortchanges or web.maxchanges
227 227 if 'revcount' in req.form:
228 228 revcount = int(req.form.get('revcount', [revcount])[0])
229 229 revcount = max(revcount, 1)
230 230 tmpl.defaults['sessionvars']['revcount'] = revcount
231 231
232 232 lessvars = copy.copy(tmpl.defaults['sessionvars'])
233 233 lessvars['revcount'] = max(revcount / 2, 1)
234 234 morevars = copy.copy(tmpl.defaults['sessionvars'])
235 235 morevars['revcount'] = revcount * 2
236 236
237 237 count = len(web.repo)
238 238 pos = ctx.rev()
239 239 start = max(0, pos - revcount + 1)
240 240 end = min(count, start + revcount)
241 241 pos = end - 1
242 242 parity = paritygen(web.stripecount, offset=start - end)
243 243
244 244 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
245 245
246 246 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
247 247 node=ctx.hex(), rev=pos, changesets=count,
248 248 entries=lambda **x: changelist(limit=0,**x),
249 249 latestentry=lambda **x: changelist(limit=1,**x),
250 250 archives=web.archivelist("tip"), revcount=revcount,
251 251 morevars=morevars, lessvars=lessvars)
252 252
253 253 def shortlog(web, req, tmpl):
254 254 return changelog(web, req, tmpl, shortlog = True)
255 255
256 256 def changeset(web, req, tmpl):
257 257 ctx = webutil.changectx(web.repo, req)
258 258 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
259 259 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
260 260 ctx.node())
261 261 showbranch = webutil.nodebranchnodefault(ctx)
262 262
263 263 files = []
264 264 parity = paritygen(web.stripecount)
265 265 for blockno, f in enumerate(ctx.files()):
266 266 template = f in ctx and 'filenodelink' or 'filenolink'
267 267 files.append(tmpl(template,
268 268 node=ctx.hex(), file=f, blockno=blockno + 1,
269 269 parity=parity.next()))
270 270
271 271 style = web.config('web', 'style', 'paper')
272 272 if 'style' in req.form:
273 273 style = req.form['style'][0]
274 274
275 275 parity = paritygen(web.stripecount)
276 276 diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style)
277 277
278 278 parity = paritygen(web.stripecount)
279 279 diffstatgen = webutil.diffstatgen(ctx)
280 280 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
281 281
282 282 return tmpl('changeset',
283 283 diff=diffs,
284 284 rev=ctx.rev(),
285 285 node=ctx.hex(),
286 286 parent=webutil.parents(ctx),
287 287 child=webutil.children(ctx),
288 288 changesettag=showtags,
289 289 changesetbookmark=showbookmarks,
290 290 changesetbranch=showbranch,
291 291 author=ctx.user(),
292 292 desc=ctx.description(),
293 293 date=ctx.date(),
294 294 files=files,
295 295 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
296 296 diffstat=diffstat,
297 297 archives=web.archivelist(ctx.hex()),
298 298 tags=webutil.nodetagsdict(web.repo, ctx.node()),
299 299 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
300 300 branch=webutil.nodebranchnodefault(ctx),
301 301 inbranch=webutil.nodeinbranch(web.repo, ctx),
302 302 branches=webutil.nodebranchdict(web.repo, ctx))
303 303
304 304 rev = changeset
305 305
306 306 def decodepath(path):
307 307 """Hook for mapping a path in the repository to a path in the
308 308 working copy.
309 309
310 310 Extensions (e.g., largefiles) can override this to remap files in
311 311 the virtual file system presented by the manifest command below."""
312 312 return path
313 313
314 314 def manifest(web, req, tmpl):
315 315 ctx = webutil.changectx(web.repo, req)
316 316 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
317 317 mf = ctx.manifest()
318 318 node = ctx.node()
319 319
320 320 files = {}
321 321 dirs = {}
322 322 parity = paritygen(web.stripecount)
323 323
324 324 if path and path[-1] != "/":
325 325 path += "/"
326 326 l = len(path)
327 327 abspath = "/" + path
328 328
329 329 for full, n in mf.iteritems():
330 330 # the virtual path (working copy path) used for the full
331 331 # (repository) path
332 332 f = decodepath(full)
333 333
334 334 if f[:l] != path:
335 335 continue
336 336 remain = f[l:]
337 337 elements = remain.split('/')
338 338 if len(elements) == 1:
339 339 files[remain] = full
340 340 else:
341 341 h = dirs # need to retain ref to dirs (root)
342 342 for elem in elements[0:-1]:
343 343 if elem not in h:
344 344 h[elem] = {}
345 345 h = h[elem]
346 346 if len(h) > 1:
347 347 break
348 348 h[None] = None # denotes files present
349 349
350 350 if mf and not files and not dirs:
351 351 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
352 352
353 353 def filelist(**map):
354 354 for f in sorted(files):
355 355 full = files[f]
356 356
357 357 fctx = ctx.filectx(full)
358 358 yield {"file": full,
359 359 "parity": parity.next(),
360 360 "basename": f,
361 361 "date": fctx.date(),
362 362 "size": fctx.size(),
363 363 "permissions": mf.flags(full)}
364 364
365 365 def dirlist(**map):
366 366 for d in sorted(dirs):
367 367
368 368 emptydirs = []
369 369 h = dirs[d]
370 370 while isinstance(h, dict) and len(h) == 1:
371 371 k, v = h.items()[0]
372 372 if v:
373 373 emptydirs.append(k)
374 374 h = v
375 375
376 376 path = "%s%s" % (abspath, d)
377 377 yield {"parity": parity.next(),
378 378 "path": path,
379 379 "emptydirs": "/".join(emptydirs),
380 380 "basename": d}
381 381
382 382 return tmpl("manifest",
383 383 rev=ctx.rev(),
384 384 node=hex(node),
385 385 path=abspath,
386 386 up=webutil.up(abspath),
387 387 upparity=parity.next(),
388 388 fentries=filelist,
389 389 dentries=dirlist,
390 390 archives=web.archivelist(hex(node)),
391 391 tags=webutil.nodetagsdict(web.repo, node),
392 392 bookmarks=webutil.nodebookmarksdict(web.repo, node),
393 393 inbranch=webutil.nodeinbranch(web.repo, ctx),
394 394 branches=webutil.nodebranchdict(web.repo, ctx))
395 395
396 396 def tags(web, req, tmpl):
397 397 i = reversed(web.repo.tagslist())
398 398 parity = paritygen(web.stripecount)
399 399
400 400 def entries(notip=False, limit=0, **map):
401 401 count = 0
402 402 for k, n in i:
403 403 if notip and k == "tip":
404 404 continue
405 405 if limit > 0 and count >= limit:
406 406 continue
407 407 count = count + 1
408 408 yield {"parity": parity.next(),
409 409 "tag": k,
410 410 "date": web.repo[n].date(),
411 411 "node": hex(n)}
412 412
413 413 return tmpl("tags",
414 414 node=hex(web.repo.changelog.tip()),
415 415 entries=lambda **x: entries(False, 0, **x),
416 416 entriesnotip=lambda **x: entries(True, 0, **x),
417 417 latestentry=lambda **x: entries(True, 1, **x))
418 418
419 419 def bookmarks(web, req, tmpl):
420 420 i = web.repo._bookmarks.items()
421 421 parity = paritygen(web.stripecount)
422 422
423 423 def entries(limit=0, **map):
424 424 count = 0
425 425 for k, n in sorted(i):
426 426 if limit > 0 and count >= limit:
427 427 continue
428 428 count = count + 1
429 429 yield {"parity": parity.next(),
430 430 "bookmark": k,
431 431 "date": web.repo[n].date(),
432 432 "node": hex(n)}
433 433
434 434 return tmpl("bookmarks",
435 435 node=hex(web.repo.changelog.tip()),
436 436 entries=lambda **x: entries(0, **x),
437 437 latestentry=lambda **x: entries(1, **x))
438 438
439 439 def branches(web, req, tmpl):
440 440 tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
441 441 heads = web.repo.heads()
442 442 parity = paritygen(web.stripecount)
443 443 sortkey = lambda ctx: (not ctx.closesbranch(), ctx.rev())
444 444
445 445 def entries(limit, **map):
446 446 count = 0
447 447 for ctx in sorted(tips, key=sortkey, reverse=True):
448 448 if limit > 0 and count >= limit:
449 449 return
450 450 count += 1
451 451 if not web.repo.branchheads(ctx.branch()):
452 452 status = 'closed'
453 453 elif ctx.node() not in heads:
454 454 status = 'inactive'
455 455 else:
456 456 status = 'open'
457 457 yield {'parity': parity.next(),
458 458 'branch': ctx.branch(),
459 459 'status': status,
460 460 'node': ctx.hex(),
461 461 'date': ctx.date()}
462 462
463 463 return tmpl('branches', node=hex(web.repo.changelog.tip()),
464 464 entries=lambda **x: entries(0, **x),
465 465 latestentry=lambda **x: entries(1, **x))
466 466
467 467 def summary(web, req, tmpl):
468 468 i = reversed(web.repo.tagslist())
469 469
470 470 def tagentries(**map):
471 471 parity = paritygen(web.stripecount)
472 472 count = 0
473 473 for k, n in i:
474 474 if k == "tip": # skip tip
475 475 continue
476 476
477 477 count += 1
478 478 if count > 10: # limit to 10 tags
479 479 break
480 480
481 481 yield tmpl("tagentry",
482 482 parity=parity.next(),
483 483 tag=k,
484 484 node=hex(n),
485 485 date=web.repo[n].date())
486 486
487 487 def bookmarks(**map):
488 488 parity = paritygen(web.stripecount)
489 489 b = web.repo._bookmarks.items()
490 490 for k, n in sorted(b)[:10]: # limit to 10 bookmarks
491 491 yield {'parity': parity.next(),
492 492 'bookmark': k,
493 493 'date': web.repo[n].date(),
494 494 'node': hex(n)}
495 495
496 496 def branches(**map):
497 497 parity = paritygen(web.stripecount)
498 498
499 499 b = web.repo.branchtags()
500 500 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
501 501 for r, n, t in sorted(l):
502 502 yield {'parity': parity.next(),
503 503 'branch': t,
504 504 'node': hex(n),
505 505 'date': web.repo[n].date()}
506 506
507 507 def changelist(**map):
508 508 parity = paritygen(web.stripecount, offset=start - end)
509 509 l = [] # build a list in forward order for efficiency
510 510 for i in xrange(start, end):
511 511 ctx = web.repo[i]
512 512 n = ctx.node()
513 513 hn = hex(n)
514 514
515 515 l.insert(0, tmpl(
516 516 'shortlogentry',
517 517 parity=parity.next(),
518 518 author=ctx.user(),
519 519 desc=ctx.description(),
520 520 date=ctx.date(),
521 521 rev=i,
522 522 node=hn,
523 523 tags=webutil.nodetagsdict(web.repo, n),
524 524 bookmarks=webutil.nodebookmarksdict(web.repo, n),
525 525 inbranch=webutil.nodeinbranch(web.repo, ctx),
526 526 branches=webutil.nodebranchdict(web.repo, ctx)))
527 527
528 528 yield l
529 529
530 530 tip = web.repo['tip']
531 531 count = len(web.repo)
532 532 start = max(0, count - web.maxchanges)
533 533 end = min(count, start + web.maxchanges)
534 534
535 535 return tmpl("summary",
536 536 desc=web.config("web", "description", "unknown"),
537 537 owner=get_contact(web.config) or "unknown",
538 538 lastchange=tip.date(),
539 539 tags=tagentries,
540 540 bookmarks=bookmarks,
541 541 branches=branches,
542 542 shortlog=changelist,
543 543 node=tip.hex(),
544 544 archives=web.archivelist("tip"))
545 545
546 546 def filediff(web, req, tmpl):
547 547 fctx, ctx = None, None
548 548 try:
549 549 fctx = webutil.filectx(web.repo, req)
550 550 except LookupError:
551 551 ctx = webutil.changectx(web.repo, req)
552 552 path = webutil.cleanpath(web.repo, req.form['file'][0])
553 553 if path not in ctx.files():
554 554 raise
555 555
556 556 if fctx is not None:
557 557 n = fctx.node()
558 558 path = fctx.path()
559 559 ctx = fctx.changectx()
560 560 else:
561 561 n = ctx.node()
562 562 # path already defined in except clause
563 563
564 564 parity = paritygen(web.stripecount)
565 565 style = web.config('web', 'style', 'paper')
566 566 if 'style' in req.form:
567 567 style = req.form['style'][0]
568 568
569 569 diffs = webutil.diffs(web.repo, tmpl, ctx, [path], parity, style)
570 570 rename = fctx and webutil.renamelink(fctx) or []
571 571 ctx = fctx and fctx or ctx
572 572 return tmpl("filediff",
573 573 file=path,
574 574 node=hex(n),
575 575 rev=ctx.rev(),
576 576 date=ctx.date(),
577 577 desc=ctx.description(),
578 578 author=ctx.user(),
579 579 rename=rename,
580 580 branch=webutil.nodebranchnodefault(ctx),
581 581 parent=webutil.parents(ctx),
582 582 child=webutil.children(ctx),
583 583 diff=diffs)
584 584
585 585 diff = filediff
586 586
587 587 def comparison(web, req, tmpl):
588 588 ctx = webutil.changectx(web.repo, req)
589 if 'file' not in req.form:
590 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
589 591 path = webutil.cleanpath(web.repo, req.form['file'][0])
590 592 rename = path in ctx and webutil.renamelink(ctx[path]) or []
591 593
592 594 parsecontext = lambda v: v == 'full' and -1 or int(v)
593 595 if 'context' in req.form:
594 596 context = parsecontext(req.form['context'][0])
595 597 else:
596 598 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
597 599
598 600 comparison = webutil.compare(tmpl, ctx, path, context)
599 601 return tmpl('filecomparison',
600 602 file=path,
601 603 node=hex(ctx.node()),
602 604 rev=ctx.rev(),
603 605 date=ctx.date(),
604 606 desc=ctx.description(),
605 607 author=ctx.user(),
606 608 rename=rename,
607 609 branch=webutil.nodebranchnodefault(ctx),
608 610 parent=webutil.parents(ctx),
609 611 child=webutil.children(ctx),
610 612 comparison=comparison)
611 613
612 614 def annotate(web, req, tmpl):
613 615 fctx = webutil.filectx(web.repo, req)
614 616 f = fctx.path()
615 617 parity = paritygen(web.stripecount)
616 618 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
617 619
618 620 def annotate(**map):
619 621 last = None
620 622 if binary(fctx.data()):
621 623 mt = (mimetypes.guess_type(fctx.path())[0]
622 624 or 'application/octet-stream')
623 625 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
624 626 '(binary:%s)' % mt)])
625 627 else:
626 628 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
627 629 diffopts=diffopts))
628 630 for lineno, ((f, targetline), l) in lines:
629 631 fnode = f.filenode()
630 632
631 633 if last != fnode:
632 634 last = fnode
633 635
634 636 yield {"parity": parity.next(),
635 637 "node": f.hex(),
636 638 "rev": f.rev(),
637 639 "author": f.user(),
638 640 "desc": f.description(),
639 641 "file": f.path(),
640 642 "targetline": targetline,
641 643 "line": l,
642 644 "lineid": "l%d" % (lineno + 1),
643 645 "linenumber": "% 6d" % (lineno + 1),
644 646 "revdate": f.date()}
645 647
646 648 return tmpl("fileannotate",
647 649 file=f,
648 650 annotate=annotate,
649 651 path=webutil.up(f),
650 652 rev=fctx.rev(),
651 653 node=fctx.hex(),
652 654 author=fctx.user(),
653 655 date=fctx.date(),
654 656 desc=fctx.description(),
655 657 rename=webutil.renamelink(fctx),
656 658 branch=webutil.nodebranchnodefault(fctx),
657 659 parent=webutil.parents(fctx),
658 660 child=webutil.children(fctx),
659 661 permissions=fctx.manifest().flags(f))
660 662
661 663 def filelog(web, req, tmpl):
662 664
663 665 try:
664 666 fctx = webutil.filectx(web.repo, req)
665 667 f = fctx.path()
666 668 fl = fctx.filelog()
667 669 except error.LookupError:
668 670 f = webutil.cleanpath(web.repo, req.form['file'][0])
669 671 fl = web.repo.file(f)
670 672 numrevs = len(fl)
671 673 if not numrevs: # file doesn't exist at all
672 674 raise
673 675 rev = webutil.changectx(web.repo, req).rev()
674 676 first = fl.linkrev(0)
675 677 if rev < first: # current rev is from before file existed
676 678 raise
677 679 frev = numrevs - 1
678 680 while fl.linkrev(frev) > rev:
679 681 frev -= 1
680 682 fctx = web.repo.filectx(f, fl.linkrev(frev))
681 683
682 684 revcount = web.maxshortchanges
683 685 if 'revcount' in req.form:
684 686 revcount = int(req.form.get('revcount', [revcount])[0])
685 687 revcount = max(revcount, 1)
686 688 tmpl.defaults['sessionvars']['revcount'] = revcount
687 689
688 690 lessvars = copy.copy(tmpl.defaults['sessionvars'])
689 691 lessvars['revcount'] = max(revcount / 2, 1)
690 692 morevars = copy.copy(tmpl.defaults['sessionvars'])
691 693 morevars['revcount'] = revcount * 2
692 694
693 695 count = fctx.filerev() + 1
694 696 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
695 697 end = min(count, start + revcount) # last rev on this page
696 698 parity = paritygen(web.stripecount, offset=start - end)
697 699
698 700 def entries(limit=0, **map):
699 701 l = []
700 702
701 703 repo = web.repo
702 704 for i in xrange(start, end):
703 705 iterfctx = fctx.filectx(i)
704 706
705 707 l.insert(0, {"parity": parity.next(),
706 708 "filerev": i,
707 709 "file": f,
708 710 "node": iterfctx.hex(),
709 711 "author": iterfctx.user(),
710 712 "date": iterfctx.date(),
711 713 "rename": webutil.renamelink(iterfctx),
712 714 "parent": webutil.parents(iterfctx),
713 715 "child": webutil.children(iterfctx),
714 716 "desc": iterfctx.description(),
715 717 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
716 718 "bookmarks": webutil.nodebookmarksdict(
717 719 repo, iterfctx.node()),
718 720 "branch": webutil.nodebranchnodefault(iterfctx),
719 721 "inbranch": webutil.nodeinbranch(repo, iterfctx),
720 722 "branches": webutil.nodebranchdict(repo, iterfctx)})
721 723
722 724 if limit > 0:
723 725 l = l[:limit]
724 726
725 727 for e in l:
726 728 yield e
727 729
728 730 nodefunc = lambda x: fctx.filectx(fileid=x)
729 731 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
730 732 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
731 733 entries=lambda **x: entries(limit=0, **x),
732 734 latestentry=lambda **x: entries(limit=1, **x),
733 735 revcount=revcount, morevars=morevars, lessvars=lessvars)
734 736
735 737 def archive(web, req, tmpl):
736 738 type_ = req.form.get('type', [None])[0]
737 739 allowed = web.configlist("web", "allow_archive")
738 740 key = req.form['node'][0]
739 741
740 742 if type_ not in web.archives:
741 743 msg = 'Unsupported archive type: %s' % type_
742 744 raise ErrorResponse(HTTP_NOT_FOUND, msg)
743 745
744 746 if not ((type_ in allowed or
745 747 web.configbool("web", "allow" + type_, False))):
746 748 msg = 'Archive type not allowed: %s' % type_
747 749 raise ErrorResponse(HTTP_FORBIDDEN, msg)
748 750
749 751 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
750 752 cnode = web.repo.lookup(key)
751 753 arch_version = key
752 754 if cnode == key or key == 'tip':
753 755 arch_version = short(cnode)
754 756 name = "%s-%s" % (reponame, arch_version)
755 757 mimetype, artype, extension, encoding = web.archive_specs[type_]
756 758 headers = [
757 759 ('Content-Type', mimetype),
758 760 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
759 761 ]
760 762 if encoding:
761 763 headers.append(('Content-Encoding', encoding))
762 764 req.header(headers)
763 765 req.respond(HTTP_OK)
764 766 archival.archive(web.repo, req, cnode, artype, prefix=name)
765 767 return []
766 768
767 769
768 770 def static(web, req, tmpl):
769 771 fname = req.form['file'][0]
770 772 # a repo owner may set web.static in .hg/hgrc to get any file
771 773 # readable by the user running the CGI script
772 774 static = web.config("web", "static", None, untrusted=False)
773 775 if not static:
774 776 tp = web.templatepath or templater.templatepath()
775 777 if isinstance(tp, str):
776 778 tp = [tp]
777 779 static = [os.path.join(p, 'static') for p in tp]
778 780 return [staticfile(static, fname, req)]
779 781
780 782 def graph(web, req, tmpl):
781 783
782 784 rev = webutil.changectx(web.repo, req).rev()
783 785 bg_height = 39
784 786 revcount = web.maxshortchanges
785 787 if 'revcount' in req.form:
786 788 revcount = int(req.form.get('revcount', [revcount])[0])
787 789 revcount = max(revcount, 1)
788 790 tmpl.defaults['sessionvars']['revcount'] = revcount
789 791
790 792 lessvars = copy.copy(tmpl.defaults['sessionvars'])
791 793 lessvars['revcount'] = max(revcount / 2, 1)
792 794 morevars = copy.copy(tmpl.defaults['sessionvars'])
793 795 morevars['revcount'] = revcount * 2
794 796
795 797 max_rev = len(web.repo) - 1
796 798 revcount = min(max_rev, revcount)
797 799 revnode = web.repo.changelog.node(rev)
798 800 revnode_hex = hex(revnode)
799 801 uprev = min(max_rev, rev + revcount)
800 802 downrev = max(0, rev - revcount)
801 803 count = len(web.repo)
802 804 changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
803 805 startrev = rev
804 806 # if starting revision is less than 60 set it to uprev
805 807 if rev < web.maxshortchanges:
806 808 startrev = uprev
807 809
808 810 dag = graphmod.dagwalker(web.repo, range(startrev, downrev - 1, -1))
809 811 tree = list(graphmod.colored(dag, web.repo))
810 812
811 813 def getcolumns(tree):
812 814 cols = 0
813 815 for (id, type, ctx, vtx, edges) in tree:
814 816 if type != graphmod.CHANGESET:
815 817 continue
816 818 cols = max(cols, max([edge[0] for edge in edges] or [0]),
817 819 max([edge[1] for edge in edges] or [0]))
818 820 return cols
819 821
820 822 def graphdata(usetuples, **map):
821 823 data = []
822 824
823 825 row = 0
824 826 for (id, type, ctx, vtx, edges) in tree:
825 827 if type != graphmod.CHANGESET:
826 828 continue
827 829 node = str(ctx)
828 830 age = templatefilters.age(ctx.date())
829 831 desc = templatefilters.firstline(ctx.description())
830 832 desc = cgi.escape(templatefilters.nonempty(desc))
831 833 user = cgi.escape(templatefilters.person(ctx.user()))
832 834 branch = ctx.branch()
833 835 try:
834 836 branchnode = web.repo.branchtip(branch)
835 837 except error.RepoLookupError:
836 838 branchnode = None
837 839 branch = branch, branchnode == ctx.node()
838 840
839 841 if usetuples:
840 842 data.append((node, vtx, edges, desc, user, age, branch,
841 843 ctx.tags(), ctx.bookmarks()))
842 844 else:
843 845 edgedata = [dict(col=edge[0], nextcol=edge[1],
844 846 color=(edge[2] - 1) % 6 + 1,
845 847 width=edge[3], bcolor=edge[4])
846 848 for edge in edges]
847 849
848 850 data.append(
849 851 dict(node=node,
850 852 col=vtx[0],
851 853 color=(vtx[1] - 1) % 6 + 1,
852 854 edges=edgedata,
853 855 row=row,
854 856 nextrow=row + 1,
855 857 desc=desc,
856 858 user=user,
857 859 age=age,
858 860 bookmarks=webutil.nodebookmarksdict(
859 861 web.repo, ctx.node()),
860 862 branches=webutil.nodebranchdict(web.repo, ctx),
861 863 inbranch=webutil.nodeinbranch(web.repo, ctx),
862 864 tags=webutil.nodetagsdict(web.repo, ctx.node())))
863 865
864 866 row += 1
865 867
866 868 return data
867 869
868 870 cols = getcolumns(tree)
869 871 rows = len(tree)
870 872 canvasheight = (rows + 1) * bg_height - 27
871 873
872 874 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
873 875 lessvars=lessvars, morevars=morevars, downrev=downrev,
874 876 cols=cols, rows=rows,
875 877 canvaswidth=(cols + 1) * bg_height,
876 878 truecanvasheight=rows * bg_height,
877 879 canvasheight=canvasheight, bg_height=bg_height,
878 880 jsdata=lambda **x: graphdata(True, **x),
879 881 nodes=lambda **x: graphdata(False, **x),
880 882 node=revnode_hex, changenav=changenav)
881 883
882 884 def _getdoc(e):
883 885 doc = e[0].__doc__
884 886 if doc:
885 887 doc = _(doc).split('\n')[0]
886 888 else:
887 889 doc = _('(no help text available)')
888 890 return doc
889 891
890 892 def help(web, req, tmpl):
891 893 from mercurial import commands # avoid cycle
892 894
893 895 topicname = req.form.get('node', [None])[0]
894 896 if not topicname:
895 897 def topics(**map):
896 898 for entries, summary, _ in helpmod.helptable:
897 899 entries = sorted(entries, key=len)
898 900 yield {'topic': entries[-1], 'summary': summary}
899 901
900 902 early, other = [], []
901 903 primary = lambda s: s.split('|')[0]
902 904 for c, e in commands.table.iteritems():
903 905 doc = _getdoc(e)
904 906 if 'DEPRECATED' in doc or c.startswith('debug'):
905 907 continue
906 908 cmd = primary(c)
907 909 if cmd.startswith('^'):
908 910 early.append((cmd[1:], doc))
909 911 else:
910 912 other.append((cmd, doc))
911 913
912 914 early.sort()
913 915 other.sort()
914 916
915 917 def earlycommands(**map):
916 918 for c, doc in early:
917 919 yield {'topic': c, 'summary': doc}
918 920
919 921 def othercommands(**map):
920 922 for c, doc in other:
921 923 yield {'topic': c, 'summary': doc}
922 924
923 925 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
924 926 othercommands=othercommands, title='Index')
925 927
926 928 u = webutil.wsgiui()
927 929 u.pushbuffer()
928 930 u.verbose = True
929 931 try:
930 932 commands.help_(u, topicname)
931 933 except error.UnknownCommand:
932 934 raise ErrorResponse(HTTP_NOT_FOUND)
933 935 doc = u.popbuffer()
934 936 return tmpl('help', topic=topicname, doc=doc)
@@ -1,364 +1,370
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, mimetypes, 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 from common import ErrorResponse
14 from common import HTTP_NOT_FOUND
13 15 import difflib
14 16
15 17 def up(p):
16 18 if p[0] != "/":
17 19 p = "/" + p
18 20 if p[-1] == "/":
19 21 p = p[:-1]
20 22 up = os.path.dirname(p)
21 23 if up == "/":
22 24 return "/"
23 25 return up + "/"
24 26
25 27 def revnavgen(pos, pagelen, limit, nodefunc):
26 28 def seq(factor, limit=None):
27 29 if limit:
28 30 yield limit
29 31 if limit >= 20 and limit <= 40:
30 32 yield 50
31 33 else:
32 34 yield 1 * factor
33 35 yield 3 * factor
34 36 for f in seq(factor * 10):
35 37 yield f
36 38
37 39 navbefore = []
38 40 navafter = []
39 41
40 42 last = 0
41 43 for f in seq(1, pagelen):
42 44 if f < pagelen or f <= last:
43 45 continue
44 46 if f > limit:
45 47 break
46 48 last = f
47 49 if pos + f < limit:
48 50 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
49 51 if pos - f >= 0:
50 52 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
51 53
52 54 navafter.append(("tip", "tip"))
53 55 try:
54 56 navbefore.insert(0, ("(0)", hex(nodefunc('0').node())))
55 57 except error.RepoError:
56 58 pass
57 59
58 60 def gen(l):
59 61 def f(**map):
60 62 for label, node in l:
61 63 yield {"label": label, "node": node}
62 64 return f
63 65
64 66 return (dict(before=gen(navbefore), after=gen(navafter)),)
65 67
66 68 def _siblings(siblings=[], hiderev=None):
67 69 siblings = [s for s in siblings if s.node() != nullid]
68 70 if len(siblings) == 1 and siblings[0].rev() == hiderev:
69 71 return
70 72 for s in siblings:
71 73 d = {'node': s.hex(), 'rev': s.rev()}
72 74 d['user'] = s.user()
73 75 d['date'] = s.date()
74 76 d['description'] = s.description()
75 77 d['branch'] = s.branch()
76 78 if util.safehasattr(s, 'path'):
77 79 d['file'] = s.path()
78 80 yield d
79 81
80 82 def parents(ctx, hide=None):
81 83 return _siblings(ctx.parents(), hide)
82 84
83 85 def children(ctx, hide=None):
84 86 return _siblings(ctx.children(), hide)
85 87
86 88 def renamelink(fctx):
87 89 r = fctx.renamed()
88 90 if r:
89 91 return [dict(file=r[0], node=hex(r[1]))]
90 92 return []
91 93
92 94 def nodetagsdict(repo, node):
93 95 return [{"name": i} for i in repo.nodetags(node)]
94 96
95 97 def nodebookmarksdict(repo, node):
96 98 return [{"name": i} for i in repo.nodebookmarks(node)]
97 99
98 100 def nodebranchdict(repo, ctx):
99 101 branches = []
100 102 branch = ctx.branch()
101 103 # If this is an empty repo, ctx.node() == nullid,
102 104 # ctx.branch() == 'default'.
103 105 try:
104 106 branchnode = repo.branchtip(branch)
105 107 except error.RepoLookupError:
106 108 branchnode = None
107 109 if branchnode == ctx.node():
108 110 branches.append({"name": branch})
109 111 return branches
110 112
111 113 def nodeinbranch(repo, ctx):
112 114 branches = []
113 115 branch = ctx.branch()
114 116 try:
115 117 branchnode = repo.branchtip(branch)
116 118 except error.RepoLookupError:
117 119 branchnode = None
118 120 if branch != 'default' and branchnode != ctx.node():
119 121 branches.append({"name": branch})
120 122 return branches
121 123
122 124 def nodebranchnodefault(ctx):
123 125 branches = []
124 126 branch = ctx.branch()
125 127 if branch != 'default':
126 128 branches.append({"name": branch})
127 129 return branches
128 130
129 131 def showtag(repo, tmpl, t1, node=nullid, **args):
130 132 for t in repo.nodetags(node):
131 133 yield tmpl(t1, tag=t, **args)
132 134
133 135 def showbookmark(repo, tmpl, t1, node=nullid, **args):
134 136 for t in repo.nodebookmarks(node):
135 137 yield tmpl(t1, bookmark=t, **args)
136 138
137 139 def cleanpath(repo, path):
138 140 path = path.lstrip('/')
139 141 return scmutil.canonpath(repo.root, '', path)
140 142
141 143 def changectx(repo, req):
142 144 changeid = "tip"
143 145 if 'node' in req.form:
144 146 changeid = req.form['node'][0]
145 147 elif 'manifest' in req.form:
146 148 changeid = req.form['manifest'][0]
147 149
148 150 try:
149 151 ctx = repo[changeid]
150 152 except error.RepoError:
151 153 man = repo.manifest
152 154 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
153 155
154 156 return ctx
155 157
156 158 def filectx(repo, req):
159 if 'file' not in req.form:
160 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
157 161 path = cleanpath(repo, req.form['file'][0])
158 162 if 'node' in req.form:
159 163 changeid = req.form['node'][0]
164 elif 'filenode' in req.form:
165 changeid = req.form['filenode'][0]
160 166 else:
161 changeid = req.form['filenode'][0]
167 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
162 168 try:
163 169 fctx = repo[changeid][path]
164 170 except error.RepoError:
165 171 fctx = repo.filectx(path, fileid=changeid)
166 172
167 173 return fctx
168 174
169 175 def listfilediffs(tmpl, files, node, max):
170 176 for f in files[:max]:
171 177 yield tmpl('filedifflink', node=hex(node), file=f)
172 178 if len(files) > max:
173 179 yield tmpl('fileellipses')
174 180
175 181 def diffs(repo, tmpl, ctx, files, parity, style):
176 182
177 183 def countgen():
178 184 start = 1
179 185 while True:
180 186 yield start
181 187 start += 1
182 188
183 189 blockcount = countgen()
184 190 def prettyprintlines(diff, blockno):
185 191 for lineno, l in enumerate(diff.splitlines(True)):
186 192 lineno = "%d.%d" % (blockno, lineno + 1)
187 193 if l.startswith('+'):
188 194 ltype = "difflineplus"
189 195 elif l.startswith('-'):
190 196 ltype = "difflineminus"
191 197 elif l.startswith('@'):
192 198 ltype = "difflineat"
193 199 else:
194 200 ltype = "diffline"
195 201 yield tmpl(ltype,
196 202 line=l,
197 203 lineid="l%s" % lineno,
198 204 linenumber="% 8s" % lineno)
199 205
200 206 if files:
201 207 m = match.exact(repo.root, repo.getcwd(), files)
202 208 else:
203 209 m = match.always(repo.root, repo.getcwd())
204 210
205 211 diffopts = patch.diffopts(repo.ui, untrusted=True)
206 212 parents = ctx.parents()
207 213 node1 = parents and parents[0].node() or nullid
208 214 node2 = ctx.node()
209 215
210 216 block = []
211 217 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
212 218 if chunk.startswith('diff') and block:
213 219 blockno = blockcount.next()
214 220 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
215 221 lines=prettyprintlines(''.join(block), blockno))
216 222 block = []
217 223 if chunk.startswith('diff') and style != 'raw':
218 224 chunk = ''.join(chunk.splitlines(True)[1:])
219 225 block.append(chunk)
220 226 blockno = blockcount.next()
221 227 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
222 228 lines=prettyprintlines(''.join(block), blockno))
223 229
224 230 def compare(tmpl, ctx, path, context):
225 231 '''Generator function that provides side-by-side comparison data.'''
226 232
227 233 def filelines(f):
228 234 if util.binary(f.data()):
229 235 mt = mimetypes.guess_type(f.path())[0]
230 236 if not mt:
231 237 mt = 'application/octet-stream'
232 238 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
233 239 return f.data().splitlines()
234 240
235 241 def compline(type, leftlineno, leftline, rightlineno, rightline):
236 242 lineid = leftlineno and ("l%s" % leftlineno) or ''
237 243 lineid += rightlineno and ("r%s" % rightlineno) or ''
238 244 return tmpl('comparisonline',
239 245 type=type,
240 246 lineid=lineid,
241 247 leftlinenumber="% 6s" % (leftlineno or ''),
242 248 leftline=leftline or '',
243 249 rightlinenumber="% 6s" % (rightlineno or ''),
244 250 rightline=rightline or '')
245 251
246 252 def getblock(opcodes):
247 253 for type, llo, lhi, rlo, rhi in opcodes:
248 254 len1 = lhi - llo
249 255 len2 = rhi - rlo
250 256 count = min(len1, len2)
251 257 for i in xrange(count):
252 258 yield compline(type=type,
253 259 leftlineno=llo + i + 1,
254 260 leftline=leftlines[llo + i],
255 261 rightlineno=rlo + i + 1,
256 262 rightline=rightlines[rlo + i])
257 263 if len1 > len2:
258 264 for i in xrange(llo + count, lhi):
259 265 yield compline(type=type,
260 266 leftlineno=i + 1,
261 267 leftline=leftlines[i],
262 268 rightlineno=None,
263 269 rightline=None)
264 270 elif len2 > len1:
265 271 for i in xrange(rlo + count, rhi):
266 272 yield compline(type=type,
267 273 leftlineno=None,
268 274 leftline=None,
269 275 rightlineno=i + 1,
270 276 rightline=rightlines[i])
271 277
272 278 if path in ctx:
273 279 fctx = ctx[path]
274 280 rightrev = fctx.filerev()
275 281 rightnode = fctx.filenode()
276 282 rightlines = filelines(fctx)
277 283 parents = fctx.parents()
278 284 if not parents:
279 285 leftrev = -1
280 286 leftnode = nullid
281 287 leftlines = ()
282 288 else:
283 289 pfctx = parents[0]
284 290 leftrev = pfctx.filerev()
285 291 leftnode = pfctx.filenode()
286 292 leftlines = filelines(pfctx)
287 293 else:
288 294 rightrev = -1
289 295 rightnode = nullid
290 296 rightlines = ()
291 297 fctx = ctx.parents()[0][path]
292 298 leftrev = fctx.filerev()
293 299 leftnode = fctx.filenode()
294 300 leftlines = filelines(fctx)
295 301
296 302 s = difflib.SequenceMatcher(None, leftlines, rightlines)
297 303 if context < 0:
298 304 blocks = [tmpl('comparisonblock', lines=getblock(s.get_opcodes()))]
299 305 else:
300 306 blocks = (tmpl('comparisonblock', lines=getblock(oc))
301 307 for oc in s.get_grouped_opcodes(n=context))
302 308
303 309 yield tmpl('comparison',
304 310 leftrev=leftrev,
305 311 leftnode=hex(leftnode),
306 312 rightrev=rightrev,
307 313 rightnode=hex(rightnode),
308 314 blocks=blocks)
309 315
310 316 def diffstatgen(ctx):
311 317 '''Generator function that provides the diffstat data.'''
312 318
313 319 stats = patch.diffstatdata(util.iterlines(ctx.diff()))
314 320 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
315 321 while True:
316 322 yield stats, maxname, maxtotal, addtotal, removetotal, binary
317 323
318 324 def diffsummary(statgen):
319 325 '''Return a short summary of the diff.'''
320 326
321 327 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
322 328 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
323 329 len(stats), addtotal, removetotal)
324 330
325 331 def diffstat(tmpl, ctx, statgen, parity):
326 332 '''Return a diffstat template for each file in the diff.'''
327 333
328 334 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
329 335 files = ctx.files()
330 336
331 337 def pct(i):
332 338 if maxtotal == 0:
333 339 return 0
334 340 return (float(i) / maxtotal) * 100
335 341
336 342 fileno = 0
337 343 for filename, adds, removes, isbinary in stats:
338 344 template = filename in files and 'diffstatlink' or 'diffstatnolink'
339 345 total = adds + removes
340 346 fileno += 1
341 347 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
342 348 total=total, addpct=pct(adds), removepct=pct(removes),
343 349 parity=parity.next())
344 350
345 351 class sessionvars(object):
346 352 def __init__(self, vars, start='?'):
347 353 self.start = start
348 354 self.vars = vars
349 355 def __getitem__(self, key):
350 356 return self.vars[key]
351 357 def __setitem__(self, key, value):
352 358 self.vars[key] = value
353 359 def __copy__(self):
354 360 return sessionvars(copy.copy(self.vars), self.start)
355 361 def __iter__(self):
356 362 separator = self.start
357 363 for key, value in self.vars.iteritems():
358 364 yield {'name': key, 'value': str(value), 'separator': separator}
359 365 separator = '&'
360 366
361 367 class wsgiui(ui.ui):
362 368 # default termwidth breaks under mod_wsgi
363 369 def termwidth(self):
364 370 return 80
General Comments 0
You need to be logged in to leave comments. Login now