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