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