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