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