##// END OF EJS Templates
graph: limit the number of displayed changest to a reasonable value
Benoit Allard -
r6704:8251ffb3 default
parent child Browse files
Show More
@@ -1,611 +1,611
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
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, mimetypes, re, cgi
9 9 import webutil
10 10 from mercurial import revlog, archival, templatefilters
11 11 from mercurial.node import short, hex, nullid
12 12 from mercurial.util import binary, datestr
13 13 from mercurial.repo import RepoError
14 14 from common import paritygen, staticfile, get_contact, ErrorResponse
15 15 from common import HTTP_OK, HTTP_NOT_FOUND
16 16 from mercurial import graphmod
17 17
18 18 # __all__ is populated with the allowed commands. Be sure to add to it if
19 19 # you're adding a new command, or the new command won't work.
20 20
21 21 __all__ = [
22 22 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
23 23 'manifest', 'tags', 'summary', 'filediff', 'diff', 'annotate', 'filelog',
24 24 'archive', 'static', 'graph',
25 25 ]
26 26
27 27 def log(web, req, tmpl):
28 28 if 'file' in req.form and req.form['file'][0]:
29 29 return filelog(web, req, tmpl)
30 30 else:
31 31 return changelog(web, req, tmpl)
32 32
33 33 def rawfile(web, req, tmpl):
34 34 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
35 35 if not path:
36 36 content = manifest(web, req, tmpl)
37 37 req.respond(HTTP_OK, web.ctype)
38 38 return content
39 39
40 40 try:
41 41 fctx = webutil.filectx(web.repo, req)
42 42 except revlog.LookupError, inst:
43 43 try:
44 44 content = manifest(web, req, tmpl)
45 45 req.respond(HTTP_OK, web.ctype)
46 46 return content
47 47 except ErrorResponse:
48 48 raise inst
49 49
50 50 path = fctx.path()
51 51 text = fctx.data()
52 52 mt = mimetypes.guess_type(path)[0]
53 53 if mt is None or binary(text):
54 54 mt = mt or 'application/octet-stream'
55 55
56 56 req.respond(HTTP_OK, mt, path, len(text))
57 57 return [text]
58 58
59 59 def _filerevision(web, tmpl, fctx):
60 60 f = fctx.path()
61 61 text = fctx.data()
62 62 fl = fctx.filelog()
63 63 n = fctx.filenode()
64 64 parity = paritygen(web.stripecount)
65 65
66 66 if binary(text):
67 67 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
68 68 text = '(binary:%s)' % mt
69 69
70 70 def lines():
71 71 for lineno, t in enumerate(text.splitlines(1)):
72 72 yield {"line": t,
73 73 "lineid": "l%d" % (lineno + 1),
74 74 "linenumber": "% 6d" % (lineno + 1),
75 75 "parity": parity.next()}
76 76
77 77 return tmpl("filerevision",
78 78 file=f,
79 79 path=webutil.up(f),
80 80 text=lines(),
81 81 rev=fctx.rev(),
82 82 node=hex(fctx.node()),
83 83 author=fctx.user(),
84 84 date=fctx.date(),
85 85 desc=fctx.description(),
86 86 branch=webutil.nodebranchnodefault(fctx),
87 87 parent=webutil.siblings(fctx.parents()),
88 88 child=webutil.siblings(fctx.children()),
89 89 rename=webutil.renamelink(fctx),
90 90 permissions=fctx.manifest().flags(f))
91 91
92 92 def file(web, req, tmpl):
93 93 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
94 94 if path:
95 95 try:
96 96 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
97 97 except revlog.LookupError, inst:
98 98 pass
99 99
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, tmpl, query):
106 106
107 107 def changelist(**map):
108 108 cl = web.repo.changelog
109 109 count = 0
110 110 qw = query.lower().split()
111 111
112 112 def revgen():
113 113 for i in xrange(cl.count() - 1, 0, -100):
114 114 l = []
115 115 for j in xrange(max(0, i - 100), i + 1):
116 116 ctx = web.repo.changectx(j)
117 117 l.append(ctx)
118 118 l.reverse()
119 119 for e in l:
120 120 yield e
121 121
122 122 for ctx in revgen():
123 123 miss = 0
124 124 for q in qw:
125 125 if not (q in ctx.user().lower() or
126 126 q in ctx.description().lower() or
127 127 q in " ".join(ctx.files()).lower()):
128 128 miss = 1
129 129 break
130 130 if miss:
131 131 continue
132 132
133 133 count += 1
134 134 n = ctx.node()
135 135 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
136 136
137 137 yield tmpl('searchentry',
138 138 parity=parity.next(),
139 139 author=ctx.user(),
140 140 parent=webutil.siblings(ctx.parents()),
141 141 child=webutil.siblings(ctx.children()),
142 142 changelogtag=showtags,
143 143 desc=ctx.description(),
144 144 date=ctx.date(),
145 145 files=web.listfilediffs(tmpl, ctx.files(), n),
146 146 rev=ctx.rev(),
147 147 node=hex(n),
148 148 tags=webutil.nodetagsdict(web.repo, n),
149 149 inbranch=webutil.nodeinbranch(web.repo, ctx),
150 150 branches=webutil.nodebranchdict(web.repo, ctx))
151 151
152 152 if count >= web.maxchanges:
153 153 break
154 154
155 155 cl = web.repo.changelog
156 156 parity = paritygen(web.stripecount)
157 157
158 158 return tmpl('search',
159 159 query=query,
160 160 node=hex(cl.tip()),
161 161 entries=changelist,
162 162 archives=web.archivelist("tip"))
163 163
164 164 def changelog(web, req, tmpl, shortlog = False):
165 165 if 'node' in req.form:
166 166 ctx = webutil.changectx(web.repo, req)
167 167 else:
168 168 if 'rev' in req.form:
169 169 hi = req.form['rev'][0]
170 170 else:
171 171 hi = web.repo.changelog.count() - 1
172 172 try:
173 173 ctx = web.repo.changectx(hi)
174 174 except RepoError:
175 175 return _search(web, tmpl, hi) # XXX redirect to 404 page?
176 176
177 177 def changelist(limit=0, **map):
178 178 cl = web.repo.changelog
179 179 l = [] # build a list in forward order for efficiency
180 180 for i in xrange(start, end):
181 181 ctx = web.repo.changectx(i)
182 182 n = ctx.node()
183 183 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
184 184
185 185 l.insert(0, {"parity": parity.next(),
186 186 "author": ctx.user(),
187 187 "parent": webutil.siblings(ctx.parents(), i - 1),
188 188 "child": webutil.siblings(ctx.children(), i + 1),
189 189 "changelogtag": showtags,
190 190 "desc": ctx.description(),
191 191 "date": ctx.date(),
192 192 "files": web.listfilediffs(tmpl, ctx.files(), n),
193 193 "rev": i,
194 194 "node": hex(n),
195 195 "tags": webutil.nodetagsdict(web.repo, n),
196 196 "inbranch": webutil.nodeinbranch(web.repo, ctx),
197 197 "branches": webutil.nodebranchdict(web.repo, ctx)
198 198 })
199 199
200 200 if limit > 0:
201 201 l = l[:limit]
202 202
203 203 for e in l:
204 204 yield e
205 205
206 206 maxchanges = shortlog and web.maxshortchanges or web.maxchanges
207 207 cl = web.repo.changelog
208 208 count = cl.count()
209 209 pos = ctx.rev()
210 210 start = max(0, pos - maxchanges + 1)
211 211 end = min(count, start + maxchanges)
212 212 pos = end - 1
213 213 parity = paritygen(web.stripecount, offset=start-end)
214 214
215 215 changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx)
216 216
217 217 return tmpl(shortlog and 'shortlog' or 'changelog',
218 218 changenav=changenav,
219 219 node=hex(ctx.node()),
220 220 rev=pos, changesets=count,
221 221 entries=lambda **x: changelist(limit=0,**x),
222 222 latestentry=lambda **x: changelist(limit=1,**x),
223 223 archives=web.archivelist("tip"))
224 224
225 225 def shortlog(web, req, tmpl):
226 226 return changelog(web, req, tmpl, shortlog = True)
227 227
228 228 def changeset(web, req, tmpl):
229 229 ctx = webutil.changectx(web.repo, req)
230 230 n = ctx.node()
231 231 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', n)
232 232 parents = ctx.parents()
233 233 p1 = parents[0].node()
234 234
235 235 files = []
236 236 parity = paritygen(web.stripecount)
237 237 for f in ctx.files():
238 238 files.append(tmpl("filenodelink",
239 239 node=hex(n), file=f,
240 240 parity=parity.next()))
241 241
242 242 diffs = web.diff(tmpl, p1, n, None)
243 243 return tmpl('changeset',
244 244 diff=diffs,
245 245 rev=ctx.rev(),
246 246 node=hex(n),
247 247 parent=webutil.siblings(parents),
248 248 child=webutil.siblings(ctx.children()),
249 249 changesettag=showtags,
250 250 author=ctx.user(),
251 251 desc=ctx.description(),
252 252 date=ctx.date(),
253 253 files=files,
254 254 archives=web.archivelist(hex(n)),
255 255 tags=webutil.nodetagsdict(web.repo, n),
256 256 branch=webutil.nodebranchnodefault(ctx),
257 257 inbranch=webutil.nodeinbranch(web.repo, ctx),
258 258 branches=webutil.nodebranchdict(web.repo, ctx))
259 259
260 260 rev = changeset
261 261
262 262 def manifest(web, req, tmpl):
263 263 ctx = webutil.changectx(web.repo, req)
264 264 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
265 265 mf = ctx.manifest()
266 266 node = ctx.node()
267 267
268 268 files = {}
269 269 parity = paritygen(web.stripecount)
270 270
271 271 if path and path[-1] != "/":
272 272 path += "/"
273 273 l = len(path)
274 274 abspath = "/" + path
275 275
276 276 for f, n in mf.items():
277 277 if f[:l] != path:
278 278 continue
279 279 remain = f[l:]
280 280 if "/" in remain:
281 281 short = remain[:remain.index("/") + 1] # bleah
282 282 files[short] = (f, None)
283 283 else:
284 284 short = os.path.basename(remain)
285 285 files[short] = (f, n)
286 286
287 287 if not files:
288 288 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
289 289
290 290 def filelist(**map):
291 291 fl = files.keys()
292 292 fl.sort()
293 293 for f in fl:
294 294 full, fnode = files[f]
295 295 if not fnode:
296 296 continue
297 297
298 298 fctx = ctx.filectx(full)
299 299 yield {"file": full,
300 300 "parity": parity.next(),
301 301 "basename": f,
302 302 "date": fctx.changectx().date(),
303 303 "size": fctx.size(),
304 304 "permissions": mf.flags(full)}
305 305
306 306 def dirlist(**map):
307 307 fl = files.keys()
308 308 fl.sort()
309 309 for f in fl:
310 310 full, fnode = files[f]
311 311 if fnode:
312 312 continue
313 313
314 314 yield {"parity": parity.next(),
315 315 "path": "%s%s" % (abspath, f),
316 316 "basename": f[:-1]}
317 317
318 318 return tmpl("manifest",
319 319 rev=ctx.rev(),
320 320 node=hex(node),
321 321 path=abspath,
322 322 up=webutil.up(abspath),
323 323 upparity=parity.next(),
324 324 fentries=filelist,
325 325 dentries=dirlist,
326 326 archives=web.archivelist(hex(node)),
327 327 tags=webutil.nodetagsdict(web.repo, node),
328 328 inbranch=webutil.nodeinbranch(web.repo, ctx),
329 329 branches=webutil.nodebranchdict(web.repo, ctx))
330 330
331 331 def tags(web, req, tmpl):
332 332 i = web.repo.tagslist()
333 333 i.reverse()
334 334 parity = paritygen(web.stripecount)
335 335
336 336 def entries(notip=False,limit=0, **map):
337 337 count = 0
338 338 for k, n in i:
339 339 if notip and k == "tip":
340 340 continue
341 341 if limit > 0 and count >= limit:
342 342 continue
343 343 count = count + 1
344 344 yield {"parity": parity.next(),
345 345 "tag": k,
346 346 "date": web.repo.changectx(n).date(),
347 347 "node": hex(n)}
348 348
349 349 return tmpl("tags",
350 350 node=hex(web.repo.changelog.tip()),
351 351 entries=lambda **x: entries(False,0, **x),
352 352 entriesnotip=lambda **x: entries(True,0, **x),
353 353 latestentry=lambda **x: entries(True,1, **x))
354 354
355 355 def summary(web, req, tmpl):
356 356 i = web.repo.tagslist()
357 357 i.reverse()
358 358
359 359 def tagentries(**map):
360 360 parity = paritygen(web.stripecount)
361 361 count = 0
362 362 for k, n in i:
363 363 if k == "tip": # skip tip
364 364 continue
365 365
366 366 count += 1
367 367 if count > 10: # limit to 10 tags
368 368 break
369 369
370 370 yield tmpl("tagentry",
371 371 parity=parity.next(),
372 372 tag=k,
373 373 node=hex(n),
374 374 date=web.repo.changectx(n).date())
375 375
376 376 def branches(**map):
377 377 parity = paritygen(web.stripecount)
378 378
379 379 b = web.repo.branchtags()
380 380 l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.items()]
381 381 l.sort()
382 382
383 383 for r,n,t in l:
384 384 ctx = web.repo.changectx(n)
385 385 yield {'parity': parity.next(),
386 386 'branch': t,
387 387 'node': hex(n),
388 388 'date': ctx.date()}
389 389
390 390 def changelist(**map):
391 391 parity = paritygen(web.stripecount, offset=start-end)
392 392 l = [] # build a list in forward order for efficiency
393 393 for i in xrange(start, end):
394 394 ctx = web.repo.changectx(i)
395 395 n = ctx.node()
396 396 hn = hex(n)
397 397
398 398 l.insert(0, tmpl(
399 399 'shortlogentry',
400 400 parity=parity.next(),
401 401 author=ctx.user(),
402 402 desc=ctx.description(),
403 403 date=ctx.date(),
404 404 rev=i,
405 405 node=hn,
406 406 tags=webutil.nodetagsdict(web.repo, n),
407 407 inbranch=webutil.nodeinbranch(web.repo, ctx),
408 408 branches=webutil.nodebranchdict(web.repo, ctx)))
409 409
410 410 yield l
411 411
412 412 cl = web.repo.changelog
413 413 count = cl.count()
414 414 start = max(0, count - web.maxchanges)
415 415 end = min(count, start + web.maxchanges)
416 416
417 417 return tmpl("summary",
418 418 desc=web.config("web", "description", "unknown"),
419 419 owner=get_contact(web.config) or "unknown",
420 420 lastchange=cl.read(cl.tip())[2],
421 421 tags=tagentries,
422 422 branches=branches,
423 423 shortlog=changelist,
424 424 node=hex(cl.tip()),
425 425 archives=web.archivelist("tip"))
426 426
427 427 def filediff(web, req, tmpl):
428 428 fctx = webutil.filectx(web.repo, req)
429 429 n = fctx.node()
430 430 path = fctx.path()
431 431 parents = fctx.parents()
432 432 p1 = parents and parents[0].node() or nullid
433 433
434 434 diffs = web.diff(tmpl, p1, n, [path])
435 435 return tmpl("filediff",
436 436 file=path,
437 437 node=hex(n),
438 438 rev=fctx.rev(),
439 439 date=fctx.date(),
440 440 desc=fctx.description(),
441 441 author=fctx.user(),
442 442 rename=webutil.renamelink(fctx),
443 443 branch=webutil.nodebranchnodefault(fctx),
444 444 parent=webutil.siblings(parents),
445 445 child=webutil.siblings(fctx.children()),
446 446 diff=diffs)
447 447
448 448 diff = filediff
449 449
450 450 def annotate(web, req, tmpl):
451 451 fctx = webutil.filectx(web.repo, req)
452 452 f = fctx.path()
453 453 n = fctx.filenode()
454 454 fl = fctx.filelog()
455 455 parity = paritygen(web.stripecount)
456 456
457 457 def annotate(**map):
458 458 last = None
459 459 if binary(fctx.data()):
460 460 mt = (mimetypes.guess_type(fctx.path())[0]
461 461 or 'application/octet-stream')
462 462 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
463 463 '(binary:%s)' % mt)])
464 464 else:
465 465 lines = enumerate(fctx.annotate(follow=True, linenumber=True))
466 466 for lineno, ((f, targetline), l) in lines:
467 467 fnode = f.filenode()
468 468
469 469 if last != fnode:
470 470 last = fnode
471 471
472 472 yield {"parity": parity.next(),
473 473 "node": hex(f.node()),
474 474 "rev": f.rev(),
475 475 "author": f.user(),
476 476 "desc": f.description(),
477 477 "file": f.path(),
478 478 "targetline": targetline,
479 479 "line": l,
480 480 "lineid": "l%d" % (lineno + 1),
481 481 "linenumber": "% 6d" % (lineno + 1)}
482 482
483 483 return tmpl("fileannotate",
484 484 file=f,
485 485 annotate=annotate,
486 486 path=webutil.up(f),
487 487 rev=fctx.rev(),
488 488 node=hex(fctx.node()),
489 489 author=fctx.user(),
490 490 date=fctx.date(),
491 491 desc=fctx.description(),
492 492 rename=webutil.renamelink(fctx),
493 493 branch=webutil.nodebranchnodefault(fctx),
494 494 parent=webutil.siblings(fctx.parents()),
495 495 child=webutil.siblings(fctx.children()),
496 496 permissions=fctx.manifest().flags(f))
497 497
498 498 def filelog(web, req, tmpl):
499 499 fctx = webutil.filectx(web.repo, req)
500 500 f = fctx.path()
501 501 fl = fctx.filelog()
502 502 count = fl.count()
503 503 pagelen = web.maxshortchanges
504 504 pos = fctx.filerev()
505 505 start = max(0, pos - pagelen + 1)
506 506 end = min(count, start + pagelen)
507 507 pos = end - 1
508 508 parity = paritygen(web.stripecount, offset=start-end)
509 509
510 510 def entries(limit=0, **map):
511 511 l = []
512 512
513 513 for i in xrange(start, end):
514 514 ctx = fctx.filectx(i)
515 515 n = fl.node(i)
516 516
517 517 l.insert(0, {"parity": parity.next(),
518 518 "filerev": i,
519 519 "file": f,
520 520 "node": hex(ctx.node()),
521 521 "author": ctx.user(),
522 522 "date": ctx.date(),
523 523 "rename": webutil.renamelink(fctx),
524 524 "parent": webutil.siblings(fctx.parents()),
525 525 "child": webutil.siblings(fctx.children()),
526 526 "desc": ctx.description()})
527 527
528 528 if limit > 0:
529 529 l = l[:limit]
530 530
531 531 for e in l:
532 532 yield e
533 533
534 534 nodefunc = lambda x: fctx.filectx(fileid=x)
535 535 nav = webutil.revnavgen(pos, pagelen, count, nodefunc)
536 536 return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
537 537 entries=lambda **x: entries(limit=0, **x),
538 538 latestentry=lambda **x: entries(limit=1, **x))
539 539
540 540
541 541 def archive(web, req, tmpl):
542 542 type_ = req.form.get('type', [None])[0]
543 543 allowed = web.configlist("web", "allow_archive")
544 544 key = req.form['node'][0]
545 545
546 546 if not (type_ in web.archives and (type_ in allowed or
547 547 web.configbool("web", "allow" + type_, False))):
548 548 msg = 'Unsupported archive type: %s' % type_
549 549 raise ErrorResponse(HTTP_NOT_FOUND, msg)
550 550
551 551 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
552 552 cnode = web.repo.lookup(key)
553 553 arch_version = key
554 554 if cnode == key or key == 'tip':
555 555 arch_version = short(cnode)
556 556 name = "%s-%s" % (reponame, arch_version)
557 557 mimetype, artype, extension, encoding = web.archive_specs[type_]
558 558 headers = [
559 559 ('Content-Type', mimetype),
560 560 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
561 561 ]
562 562 if encoding:
563 563 headers.append(('Content-Encoding', encoding))
564 564 req.header(headers)
565 565 req.respond(HTTP_OK)
566 566 archival.archive(web.repo, req, cnode, artype, prefix=name)
567 567 return []
568 568
569 569
570 570 def static(web, req, tmpl):
571 571 fname = req.form['file'][0]
572 572 # a repo owner may set web.static in .hg/hgrc to get any file
573 573 # readable by the user running the CGI script
574 574 static = web.config("web", "static",
575 575 os.path.join(web.templatepath, "static"),
576 576 untrusted=False)
577 577 return [staticfile(static, fname, req)]
578 578
579 579 def graph(web, req, tmpl):
580 580 rev = webutil.changectx(web.repo, req).rev()
581 revcount = int(req.form.get('revcount', [25])[0])
582 581 bg_height = 39
583 582
584 583 max_rev = web.repo.changelog.count() - 1
584 revcount = min(max_rev, int(req.form.get('revcount', [25])[0]))
585 585 revnode = web.repo.changelog.node(rev)
586 586 revnode_hex = hex(revnode)
587 587 uprev = min(max_rev, rev + revcount)
588 588 downrev = max(0, rev - revcount)
589 589 lessrev = max(0, rev - revcount / 2)
590 590
591 591 maxchanges = web.maxshortchanges or web.maxchanges
592 592 count = web.repo.changelog.count()
593 593 changenav = webutil.revnavgen(rev, maxchanges, count, web.repo.changectx)
594 594
595 595 tree = list(graphmod.graph(web.repo, rev, rev - revcount))
596 596 canvasheight = (len(tree) + 1) * bg_height - 27;
597 597
598 598 data = []
599 599 for i, (ctx, vtx, edges) in enumerate(tree):
600 600 node = short(ctx.node())
601 601 age = templatefilters.age(ctx.date())
602 602 desc = templatefilters.firstline(ctx.description())
603 603 desc = cgi.escape(desc)
604 604 user = cgi.escape(templatefilters.person(ctx.user()))
605 605 data.append((node, vtx, edges, desc, user, age, ctx.tags()))
606 606
607 607 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
608 608 lessrev=lessrev, revcountmore=revcount and 2 * revcount or 1,
609 609 revcountless=revcount / 2, downrev=downrev,
610 610 canvasheight=canvasheight, bg_height=bg_height,
611 611 jsdata=data, node=revnode_hex, changenav=changenav)
General Comments 0
You need to be logged in to leave comments. Login now