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