##// END OF EJS Templates
hgweb: `limit` argument is actually `latestonly` renames and enforce...
Pierre-Yves David -
r18402:bfba6d95 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 def changelist(limit=0, **map):
197 def changelist(latestonly, **map):
198 198 l = [] # build a list in forward order for efficiency
199 for i in xrange(start, end):
199 if latestonly:
200 revs = (end - 1,)
201 else:
202 revs = xrange(start, end)
203 for i in revs:
200 204 ctx = web.repo[i]
201 205 n = ctx.node()
202 206 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
203 207 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
204 208
205 209 l.append({"parity": parity.next(),
206 210 "author": ctx.user(),
207 211 "parent": webutil.parents(ctx, i - 1),
208 212 "child": webutil.children(ctx, i + 1),
209 213 "changelogtag": showtags,
210 214 "desc": ctx.description(),
211 215 "date": ctx.date(),
212 216 "files": files,
213 217 "rev": i,
214 218 "node": hex(n),
215 219 "tags": webutil.nodetagsdict(web.repo, n),
216 220 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
217 221 "inbranch": webutil.nodeinbranch(web.repo, ctx),
218 222 "branches": webutil.nodebranchdict(web.repo, ctx)
219 223 })
220 if limit > 0:
221 l = l[-limit:]
222
223 224 for e in reversed(l):
224 225 yield e
225 226
226 227 revcount = shortlog and web.maxshortchanges or web.maxchanges
227 228 if 'revcount' in req.form:
228 229 revcount = int(req.form.get('revcount', [revcount])[0])
229 230 revcount = max(revcount, 1)
230 231 tmpl.defaults['sessionvars']['revcount'] = revcount
231 232
232 233 lessvars = copy.copy(tmpl.defaults['sessionvars'])
233 234 lessvars['revcount'] = max(revcount / 2, 1)
234 235 morevars = copy.copy(tmpl.defaults['sessionvars'])
235 236 morevars['revcount'] = revcount * 2
236 237
237 238 count = len(web.repo)
238 239 pos = ctx.rev()
239 240 start = max(0, pos - revcount + 1)
240 241 end = min(count, start + revcount)
241 242 pos = end - 1
242 243 parity = paritygen(web.stripecount, offset=start - end)
243 244
244 245 changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
245 246
246 247 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
247 248 node=ctx.hex(), rev=pos, changesets=count,
248 entries=lambda **x: changelist(limit=0,**x),
249 latestentry=lambda **x: changelist(limit=1,**x),
249 entries=lambda **x: changelist(latestonly=False, **x),
250 latestentry=lambda **x: changelist(latestonly=True, **x),
250 251 archives=web.archivelist("tip"), revcount=revcount,
251 252 morevars=morevars, lessvars=lessvars)
252 253
253 254 def shortlog(web, req, tmpl):
254 255 return changelog(web, req, tmpl, shortlog = True)
255 256
256 257 def changeset(web, req, tmpl):
257 258 ctx = webutil.changectx(web.repo, req)
258 259 basectx = webutil.basechangectx(web.repo, req)
259 260 if basectx is None:
260 261 basectx = ctx.p1()
261 262 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
262 263 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
263 264 ctx.node())
264 265 showbranch = webutil.nodebranchnodefault(ctx)
265 266
266 267 files = []
267 268 parity = paritygen(web.stripecount)
268 269 for blockno, f in enumerate(ctx.files()):
269 270 template = f in ctx and 'filenodelink' or 'filenolink'
270 271 files.append(tmpl(template,
271 272 node=ctx.hex(), file=f, blockno=blockno + 1,
272 273 parity=parity.next()))
273 274
274 275 style = web.config('web', 'style', 'paper')
275 276 if 'style' in req.form:
276 277 style = req.form['style'][0]
277 278
278 279 parity = paritygen(web.stripecount)
279 280 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
280 281
281 282 parity = paritygen(web.stripecount)
282 283 diffstatgen = webutil.diffstatgen(ctx, basectx)
283 284 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
284 285
285 286 return tmpl('changeset',
286 287 diff=diffs,
287 288 rev=ctx.rev(),
288 289 node=ctx.hex(),
289 290 parent=webutil.parents(ctx),
290 291 child=webutil.children(ctx),
291 292 currentbaseline=basectx.hex(),
292 293 changesettag=showtags,
293 294 changesetbookmark=showbookmarks,
294 295 changesetbranch=showbranch,
295 296 author=ctx.user(),
296 297 desc=ctx.description(),
297 298 date=ctx.date(),
298 299 files=files,
299 300 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
300 301 diffstat=diffstat,
301 302 archives=web.archivelist(ctx.hex()),
302 303 tags=webutil.nodetagsdict(web.repo, ctx.node()),
303 304 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
304 305 branch=webutil.nodebranchnodefault(ctx),
305 306 inbranch=webutil.nodeinbranch(web.repo, ctx),
306 307 branches=webutil.nodebranchdict(web.repo, ctx))
307 308
308 309 rev = changeset
309 310
310 311 def decodepath(path):
311 312 """Hook for mapping a path in the repository to a path in the
312 313 working copy.
313 314
314 315 Extensions (e.g., largefiles) can override this to remap files in
315 316 the virtual file system presented by the manifest command below."""
316 317 return path
317 318
318 319 def manifest(web, req, tmpl):
319 320 ctx = webutil.changectx(web.repo, req)
320 321 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
321 322 mf = ctx.manifest()
322 323 node = ctx.node()
323 324
324 325 files = {}
325 326 dirs = {}
326 327 parity = paritygen(web.stripecount)
327 328
328 329 if path and path[-1] != "/":
329 330 path += "/"
330 331 l = len(path)
331 332 abspath = "/" + path
332 333
333 334 for full, n in mf.iteritems():
334 335 # the virtual path (working copy path) used for the full
335 336 # (repository) path
336 337 f = decodepath(full)
337 338
338 339 if f[:l] != path:
339 340 continue
340 341 remain = f[l:]
341 342 elements = remain.split('/')
342 343 if len(elements) == 1:
343 344 files[remain] = full
344 345 else:
345 346 h = dirs # need to retain ref to dirs (root)
346 347 for elem in elements[0:-1]:
347 348 if elem not in h:
348 349 h[elem] = {}
349 350 h = h[elem]
350 351 if len(h) > 1:
351 352 break
352 353 h[None] = None # denotes files present
353 354
354 355 if mf and not files and not dirs:
355 356 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
356 357
357 358 def filelist(**map):
358 359 for f in sorted(files):
359 360 full = files[f]
360 361
361 362 fctx = ctx.filectx(full)
362 363 yield {"file": full,
363 364 "parity": parity.next(),
364 365 "basename": f,
365 366 "date": fctx.date(),
366 367 "size": fctx.size(),
367 368 "permissions": mf.flags(full)}
368 369
369 370 def dirlist(**map):
370 371 for d in sorted(dirs):
371 372
372 373 emptydirs = []
373 374 h = dirs[d]
374 375 while isinstance(h, dict) and len(h) == 1:
375 376 k, v = h.items()[0]
376 377 if v:
377 378 emptydirs.append(k)
378 379 h = v
379 380
380 381 path = "%s%s" % (abspath, d)
381 382 yield {"parity": parity.next(),
382 383 "path": path,
383 384 "emptydirs": "/".join(emptydirs),
384 385 "basename": d}
385 386
386 387 return tmpl("manifest",
387 388 rev=ctx.rev(),
388 389 node=hex(node),
389 390 path=abspath,
390 391 up=webutil.up(abspath),
391 392 upparity=parity.next(),
392 393 fentries=filelist,
393 394 dentries=dirlist,
394 395 archives=web.archivelist(hex(node)),
395 396 tags=webutil.nodetagsdict(web.repo, node),
396 397 bookmarks=webutil.nodebookmarksdict(web.repo, node),
397 398 inbranch=webutil.nodeinbranch(web.repo, ctx),
398 399 branches=webutil.nodebranchdict(web.repo, ctx))
399 400
400 401 def tags(web, req, tmpl):
401 402 i = list(reversed(web.repo.tagslist()))
402 403 parity = paritygen(web.stripecount)
403 404
404 def entries(notip=False, limit=0, **map):
405 count = 0
406 for k, n in i:
407 if notip and k == "tip":
408 continue
409 if limit > 0 and count >= limit:
410 continue
411 count = count + 1
405 def entries(notip, latestonly, **map):
406 t = i
407 if notip:
408 t = [(k, n) for k, n in i if k != "tip"]
409 if latestonly:
410 t = t[:1]
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 entries=lambda **x: entries(False, 0, **x),
420 entriesnotip=lambda **x: entries(True, 0, **x),
421 latestentry=lambda **x: entries(True, 1, **x))
419 entries=lambda **x: entries(False, False, **x),
420 entriesnotip=lambda **x: entries(True, False, **x),
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 def entries(limit=0, **map):
428 count = 0
429 for k, n in sorted(i):
430 if limit > 0 and count >= limit:
431 continue
432 count = count + 1
427 def entries(latestonly, **map):
428 if latestonly:
429 t = [min(i)]
430 else:
431 t = sorted(i)
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 entries=lambda **x: entries(0, **x),
441 latestentry=lambda **x: entries(1, **x))
440 entries=lambda **x: entries(latestonly=False, **x),
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 def entries(limit=0, **map):
744 def entries(latestonly, **map):
745 745 l = []
746 746
747 747 repo = web.repo
748 for i in xrange(start, end):
748 if latestonly:
749 revs = (end - 1,)
750 else:
751 revs = xrange(start, end)
752 for i in revs:
749 753 iterfctx = fctx.filectx(i)
750 754
751 755 l.append({"parity": parity.next(),
752 756 "filerev": i,
753 757 "file": f,
754 758 "node": iterfctx.hex(),
755 759 "author": iterfctx.user(),
756 760 "date": iterfctx.date(),
757 761 "rename": webutil.renamelink(iterfctx),
758 762 "parent": webutil.parents(iterfctx),
759 763 "child": webutil.children(iterfctx),
760 764 "desc": iterfctx.description(),
761 765 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
762 766 "bookmarks": webutil.nodebookmarksdict(
763 767 repo, iterfctx.node()),
764 768 "branch": webutil.nodebranchnodefault(iterfctx),
765 769 "inbranch": webutil.nodeinbranch(repo, iterfctx),
766 770 "branches": webutil.nodebranchdict(repo, iterfctx)})
767
768 if limit > 0:
769 l = l[-limit:]
770
771 771 for e in reversed(l):
772 772 yield e
773 773
774 774 nodefunc = lambda x: fctx.filectx(fileid=x)
775 775 nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
776 776 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
777 entries=lambda **x: entries(limit=0, **x),
778 latestentry=lambda **x: entries(limit=1, **x),
777 entries=lambda **x: entries(latestonly=False, **x),
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.revnavgen(pos, revcount, count, web.repo.changectx)
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)
General Comments 0
You need to be logged in to leave comments. Login now