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