##// END OF EJS Templates
webcommands: document "branches" web command
Gregory Szorc -
r24082:32dabf81 default
parent child Browse files
Show More
@@ -1,1138 +1,1150 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 523 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
524 524 parity = paritygen(web.stripecount)
525 525
526 526 def entries(latestonly, **map):
527 527 if latestonly:
528 528 t = [min(i)]
529 529 else:
530 530 t = sorted(i)
531 531 for k, n in t:
532 532 yield {"parity": parity.next(),
533 533 "bookmark": k,
534 534 "date": web.repo[n].date(),
535 535 "node": hex(n)}
536 536
537 537 return tmpl("bookmarks",
538 538 node=hex(web.repo.changelog.tip()),
539 539 entries=lambda **x: entries(latestonly=False, **x),
540 540 latestentry=lambda **x: entries(latestonly=True, **x))
541 541
542 542 @webcommand('branches')
543 543 def branches(web, req, tmpl):
544 """
545 /branches
546 ---------
547
548 Show information about branches.
549
550 All known branches are contained in the output, even closed branches.
551
552 No arguments are accepted.
553
554 The ``branches`` template is rendered.
555 """
544 556 tips = []
545 557 heads = web.repo.heads()
546 558 parity = paritygen(web.stripecount)
547 559 sortkey = lambda item: (not item[1], item[0].rev())
548 560
549 561 def entries(limit, **map):
550 562 count = 0
551 563 if not tips:
552 564 for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
553 565 tips.append((web.repo[tip], closed))
554 566 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
555 567 if limit > 0 and count >= limit:
556 568 return
557 569 count += 1
558 570 if closed:
559 571 status = 'closed'
560 572 elif ctx.node() not in heads:
561 573 status = 'inactive'
562 574 else:
563 575 status = 'open'
564 576 yield {'parity': parity.next(),
565 577 'branch': ctx.branch(),
566 578 'status': status,
567 579 'node': ctx.hex(),
568 580 'date': ctx.date()}
569 581
570 582 return tmpl('branches', node=hex(web.repo.changelog.tip()),
571 583 entries=lambda **x: entries(0, **x),
572 584 latestentry=lambda **x: entries(1, **x))
573 585
574 586 @webcommand('summary')
575 587 def summary(web, req, tmpl):
576 588 i = reversed(web.repo.tagslist())
577 589
578 590 def tagentries(**map):
579 591 parity = paritygen(web.stripecount)
580 592 count = 0
581 593 for k, n in i:
582 594 if k == "tip": # skip tip
583 595 continue
584 596
585 597 count += 1
586 598 if count > 10: # limit to 10 tags
587 599 break
588 600
589 601 yield tmpl("tagentry",
590 602 parity=parity.next(),
591 603 tag=k,
592 604 node=hex(n),
593 605 date=web.repo[n].date())
594 606
595 607 def bookmarks(**map):
596 608 parity = paritygen(web.stripecount)
597 609 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
598 610 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
599 611 yield {'parity': parity.next(),
600 612 'bookmark': k,
601 613 'date': web.repo[n].date(),
602 614 'node': hex(n)}
603 615
604 616 def branches(**map):
605 617 parity = paritygen(web.stripecount)
606 618
607 619 b = web.repo.branchmap()
608 620 l = [(-web.repo.changelog.rev(tip), tip, tag)
609 621 for tag, heads, tip, closed in b.iterbranches()]
610 622 for r, n, t in sorted(l):
611 623 yield {'parity': parity.next(),
612 624 'branch': t,
613 625 'node': hex(n),
614 626 'date': web.repo[n].date()}
615 627
616 628 def changelist(**map):
617 629 parity = paritygen(web.stripecount, offset=start - end)
618 630 l = [] # build a list in forward order for efficiency
619 631 revs = []
620 632 if start < end:
621 633 revs = web.repo.changelog.revs(start, end - 1)
622 634 for i in revs:
623 635 ctx = web.repo[i]
624 636 n = ctx.node()
625 637 hn = hex(n)
626 638
627 639 l.append(tmpl(
628 640 'shortlogentry',
629 641 parity=parity.next(),
630 642 author=ctx.user(),
631 643 desc=ctx.description(),
632 644 extra=ctx.extra(),
633 645 date=ctx.date(),
634 646 rev=i,
635 647 node=hn,
636 648 tags=webutil.nodetagsdict(web.repo, n),
637 649 bookmarks=webutil.nodebookmarksdict(web.repo, n),
638 650 inbranch=webutil.nodeinbranch(web.repo, ctx),
639 651 branches=webutil.nodebranchdict(web.repo, ctx)))
640 652
641 653 l.reverse()
642 654 yield l
643 655
644 656 tip = web.repo['tip']
645 657 count = len(web.repo)
646 658 start = max(0, count - web.maxchanges)
647 659 end = min(count, start + web.maxchanges)
648 660
649 661 return tmpl("summary",
650 662 desc=web.config("web", "description", "unknown"),
651 663 owner=get_contact(web.config) or "unknown",
652 664 lastchange=tip.date(),
653 665 tags=tagentries,
654 666 bookmarks=bookmarks,
655 667 branches=branches,
656 668 shortlog=changelist,
657 669 node=tip.hex(),
658 670 archives=web.archivelist("tip"))
659 671
660 672 @webcommand('filediff')
661 673 def filediff(web, req, tmpl):
662 674 fctx, ctx = None, None
663 675 try:
664 676 fctx = webutil.filectx(web.repo, req)
665 677 except LookupError:
666 678 ctx = webutil.changectx(web.repo, req)
667 679 path = webutil.cleanpath(web.repo, req.form['file'][0])
668 680 if path not in ctx.files():
669 681 raise
670 682
671 683 if fctx is not None:
672 684 n = fctx.node()
673 685 path = fctx.path()
674 686 ctx = fctx.changectx()
675 687 else:
676 688 n = ctx.node()
677 689 # path already defined in except clause
678 690
679 691 parity = paritygen(web.stripecount)
680 692 style = web.config('web', 'style', 'paper')
681 693 if 'style' in req.form:
682 694 style = req.form['style'][0]
683 695
684 696 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
685 697 rename = fctx and webutil.renamelink(fctx) or []
686 698 ctx = fctx and fctx or ctx
687 699 return tmpl("filediff",
688 700 file=path,
689 701 node=hex(n),
690 702 rev=ctx.rev(),
691 703 date=ctx.date(),
692 704 desc=ctx.description(),
693 705 extra=ctx.extra(),
694 706 author=ctx.user(),
695 707 rename=rename,
696 708 branch=webutil.nodebranchnodefault(ctx),
697 709 parent=webutil.parents(ctx),
698 710 child=webutil.children(ctx),
699 711 diff=diffs)
700 712
701 713 diff = webcommand('diff')(filediff)
702 714
703 715 @webcommand('comparison')
704 716 def comparison(web, req, tmpl):
705 717 ctx = webutil.changectx(web.repo, req)
706 718 if 'file' not in req.form:
707 719 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
708 720 path = webutil.cleanpath(web.repo, req.form['file'][0])
709 721 rename = path in ctx and webutil.renamelink(ctx[path]) or []
710 722
711 723 parsecontext = lambda v: v == 'full' and -1 or int(v)
712 724 if 'context' in req.form:
713 725 context = parsecontext(req.form['context'][0])
714 726 else:
715 727 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
716 728
717 729 def filelines(f):
718 730 if util.binary(f.data()):
719 731 mt = mimetypes.guess_type(f.path())[0]
720 732 if not mt:
721 733 mt = 'application/octet-stream'
722 734 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
723 735 return f.data().splitlines()
724 736
725 737 parent = ctx.p1()
726 738 leftrev = parent.rev()
727 739 leftnode = parent.node()
728 740 rightrev = ctx.rev()
729 741 rightnode = ctx.node()
730 742 if path in ctx:
731 743 fctx = ctx[path]
732 744 rightlines = filelines(fctx)
733 745 if path not in parent:
734 746 leftlines = ()
735 747 else:
736 748 pfctx = parent[path]
737 749 leftlines = filelines(pfctx)
738 750 else:
739 751 rightlines = ()
740 752 fctx = ctx.parents()[0][path]
741 753 leftlines = filelines(fctx)
742 754
743 755 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
744 756 return tmpl('filecomparison',
745 757 file=path,
746 758 node=hex(ctx.node()),
747 759 rev=ctx.rev(),
748 760 date=ctx.date(),
749 761 desc=ctx.description(),
750 762 extra=ctx.extra(),
751 763 author=ctx.user(),
752 764 rename=rename,
753 765 branch=webutil.nodebranchnodefault(ctx),
754 766 parent=webutil.parents(fctx),
755 767 child=webutil.children(fctx),
756 768 leftrev=leftrev,
757 769 leftnode=hex(leftnode),
758 770 rightrev=rightrev,
759 771 rightnode=hex(rightnode),
760 772 comparison=comparison)
761 773
762 774 @webcommand('annotate')
763 775 def annotate(web, req, tmpl):
764 776 fctx = webutil.filectx(web.repo, req)
765 777 f = fctx.path()
766 778 parity = paritygen(web.stripecount)
767 779 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
768 780 section='annotate', whitespace=True)
769 781
770 782 def annotate(**map):
771 783 last = None
772 784 if util.binary(fctx.data()):
773 785 mt = (mimetypes.guess_type(fctx.path())[0]
774 786 or 'application/octet-stream')
775 787 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
776 788 '(binary:%s)' % mt)])
777 789 else:
778 790 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
779 791 diffopts=diffopts))
780 792 for lineno, ((f, targetline), l) in lines:
781 793 fnode = f.filenode()
782 794
783 795 if last != fnode:
784 796 last = fnode
785 797
786 798 yield {"parity": parity.next(),
787 799 "node": f.hex(),
788 800 "rev": f.rev(),
789 801 "author": f.user(),
790 802 "desc": f.description(),
791 803 "extra": f.extra(),
792 804 "file": f.path(),
793 805 "targetline": targetline,
794 806 "line": l,
795 807 "lineid": "l%d" % (lineno + 1),
796 808 "linenumber": "% 6d" % (lineno + 1),
797 809 "revdate": f.date()}
798 810
799 811 return tmpl("fileannotate",
800 812 file=f,
801 813 annotate=annotate,
802 814 path=webutil.up(f),
803 815 rev=fctx.rev(),
804 816 node=fctx.hex(),
805 817 author=fctx.user(),
806 818 date=fctx.date(),
807 819 desc=fctx.description(),
808 820 extra=fctx.extra(),
809 821 rename=webutil.renamelink(fctx),
810 822 branch=webutil.nodebranchnodefault(fctx),
811 823 parent=webutil.parents(fctx),
812 824 child=webutil.children(fctx),
813 825 permissions=fctx.manifest().flags(f))
814 826
815 827 @webcommand('filelog')
816 828 def filelog(web, req, tmpl):
817 829
818 830 try:
819 831 fctx = webutil.filectx(web.repo, req)
820 832 f = fctx.path()
821 833 fl = fctx.filelog()
822 834 except error.LookupError:
823 835 f = webutil.cleanpath(web.repo, req.form['file'][0])
824 836 fl = web.repo.file(f)
825 837 numrevs = len(fl)
826 838 if not numrevs: # file doesn't exist at all
827 839 raise
828 840 rev = webutil.changectx(web.repo, req).rev()
829 841 first = fl.linkrev(0)
830 842 if rev < first: # current rev is from before file existed
831 843 raise
832 844 frev = numrevs - 1
833 845 while fl.linkrev(frev) > rev:
834 846 frev -= 1
835 847 fctx = web.repo.filectx(f, fl.linkrev(frev))
836 848
837 849 revcount = web.maxshortchanges
838 850 if 'revcount' in req.form:
839 851 try:
840 852 revcount = int(req.form.get('revcount', [revcount])[0])
841 853 revcount = max(revcount, 1)
842 854 tmpl.defaults['sessionvars']['revcount'] = revcount
843 855 except ValueError:
844 856 pass
845 857
846 858 lessvars = copy.copy(tmpl.defaults['sessionvars'])
847 859 lessvars['revcount'] = max(revcount / 2, 1)
848 860 morevars = copy.copy(tmpl.defaults['sessionvars'])
849 861 morevars['revcount'] = revcount * 2
850 862
851 863 count = fctx.filerev() + 1
852 864 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
853 865 end = min(count, start + revcount) # last rev on this page
854 866 parity = paritygen(web.stripecount, offset=start - end)
855 867
856 868 def entries():
857 869 l = []
858 870
859 871 repo = web.repo
860 872 revs = fctx.filelog().revs(start, end - 1)
861 873 for i in revs:
862 874 iterfctx = fctx.filectx(i)
863 875
864 876 l.append({"parity": parity.next(),
865 877 "filerev": i,
866 878 "file": f,
867 879 "node": iterfctx.hex(),
868 880 "author": iterfctx.user(),
869 881 "date": iterfctx.date(),
870 882 "rename": webutil.renamelink(iterfctx),
871 883 "parent": webutil.parents(iterfctx),
872 884 "child": webutil.children(iterfctx),
873 885 "desc": iterfctx.description(),
874 886 "extra": iterfctx.extra(),
875 887 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
876 888 "bookmarks": webutil.nodebookmarksdict(
877 889 repo, iterfctx.node()),
878 890 "branch": webutil.nodebranchnodefault(iterfctx),
879 891 "inbranch": webutil.nodeinbranch(repo, iterfctx),
880 892 "branches": webutil.nodebranchdict(repo, iterfctx)})
881 893 for e in reversed(l):
882 894 yield e
883 895
884 896 entries = list(entries())
885 897 latestentry = entries[:1]
886 898
887 899 revnav = webutil.filerevnav(web.repo, fctx.path())
888 900 nav = revnav.gen(end - 1, revcount, count)
889 901 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
890 902 entries=entries,
891 903 latestentry=latestentry,
892 904 revcount=revcount, morevars=morevars, lessvars=lessvars)
893 905
894 906 @webcommand('archive')
895 907 def archive(web, req, tmpl):
896 908 type_ = req.form.get('type', [None])[0]
897 909 allowed = web.configlist("web", "allow_archive")
898 910 key = req.form['node'][0]
899 911
900 912 if type_ not in web.archives:
901 913 msg = 'Unsupported archive type: %s' % type_
902 914 raise ErrorResponse(HTTP_NOT_FOUND, msg)
903 915
904 916 if not ((type_ in allowed or
905 917 web.configbool("web", "allow" + type_, False))):
906 918 msg = 'Archive type not allowed: %s' % type_
907 919 raise ErrorResponse(HTTP_FORBIDDEN, msg)
908 920
909 921 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
910 922 cnode = web.repo.lookup(key)
911 923 arch_version = key
912 924 if cnode == key or key == 'tip':
913 925 arch_version = short(cnode)
914 926 name = "%s-%s" % (reponame, arch_version)
915 927
916 928 ctx = webutil.changectx(web.repo, req)
917 929 pats = []
918 930 matchfn = scmutil.match(ctx, [])
919 931 file = req.form.get('file', None)
920 932 if file:
921 933 pats = ['path:' + file[0]]
922 934 matchfn = scmutil.match(ctx, pats, default='path')
923 935 if pats:
924 936 files = [f for f in ctx.manifest().keys() if matchfn(f)]
925 937 if not files:
926 938 raise ErrorResponse(HTTP_NOT_FOUND,
927 939 'file(s) not found: %s' % file[0])
928 940
929 941 mimetype, artype, extension, encoding = web.archive_specs[type_]
930 942 headers = [
931 943 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
932 944 ]
933 945 if encoding:
934 946 headers.append(('Content-Encoding', encoding))
935 947 req.headers.extend(headers)
936 948 req.respond(HTTP_OK, mimetype)
937 949
938 950 archival.archive(web.repo, req, cnode, artype, prefix=name,
939 951 matchfn=matchfn,
940 952 subrepos=web.configbool("web", "archivesubrepos"))
941 953 return []
942 954
943 955
944 956 @webcommand('static')
945 957 def static(web, req, tmpl):
946 958 fname = req.form['file'][0]
947 959 # a repo owner may set web.static in .hg/hgrc to get any file
948 960 # readable by the user running the CGI script
949 961 static = web.config("web", "static", None, untrusted=False)
950 962 if not static:
951 963 tp = web.templatepath or templater.templatepaths()
952 964 if isinstance(tp, str):
953 965 tp = [tp]
954 966 static = [os.path.join(p, 'static') for p in tp]
955 967 staticfile(static, fname, req)
956 968 return []
957 969
958 970 @webcommand('graph')
959 971 def graph(web, req, tmpl):
960 972
961 973 ctx = webutil.changectx(web.repo, req)
962 974 rev = ctx.rev()
963 975
964 976 bg_height = 39
965 977 revcount = web.maxshortchanges
966 978 if 'revcount' in req.form:
967 979 try:
968 980 revcount = int(req.form.get('revcount', [revcount])[0])
969 981 revcount = max(revcount, 1)
970 982 tmpl.defaults['sessionvars']['revcount'] = revcount
971 983 except ValueError:
972 984 pass
973 985
974 986 lessvars = copy.copy(tmpl.defaults['sessionvars'])
975 987 lessvars['revcount'] = max(revcount / 2, 1)
976 988 morevars = copy.copy(tmpl.defaults['sessionvars'])
977 989 morevars['revcount'] = revcount * 2
978 990
979 991 count = len(web.repo)
980 992 pos = rev
981 993
982 994 uprev = min(max(0, count - 1), rev + revcount)
983 995 downrev = max(0, rev - revcount)
984 996 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
985 997
986 998 tree = []
987 999 if pos != -1:
988 1000 allrevs = web.repo.changelog.revs(pos, 0)
989 1001 revs = []
990 1002 for i in allrevs:
991 1003 revs.append(i)
992 1004 if len(revs) >= revcount:
993 1005 break
994 1006
995 1007 # We have to feed a baseset to dagwalker as it is expecting smartset
996 1008 # object. This does not have a big impact on hgweb performance itself
997 1009 # since hgweb graphing code is not itself lazy yet.
998 1010 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
999 1011 # As we said one line above... not lazy.
1000 1012 tree = list(graphmod.colored(dag, web.repo))
1001 1013
1002 1014 def getcolumns(tree):
1003 1015 cols = 0
1004 1016 for (id, type, ctx, vtx, edges) in tree:
1005 1017 if type != graphmod.CHANGESET:
1006 1018 continue
1007 1019 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1008 1020 max([edge[1] for edge in edges] or [0]))
1009 1021 return cols
1010 1022
1011 1023 def graphdata(usetuples, **map):
1012 1024 data = []
1013 1025
1014 1026 row = 0
1015 1027 for (id, type, ctx, vtx, edges) in tree:
1016 1028 if type != graphmod.CHANGESET:
1017 1029 continue
1018 1030 node = str(ctx)
1019 1031 age = templatefilters.age(ctx.date())
1020 1032 desc = templatefilters.firstline(ctx.description())
1021 1033 desc = cgi.escape(templatefilters.nonempty(desc))
1022 1034 user = cgi.escape(templatefilters.person(ctx.user()))
1023 1035 branch = cgi.escape(ctx.branch())
1024 1036 try:
1025 1037 branchnode = web.repo.branchtip(branch)
1026 1038 except error.RepoLookupError:
1027 1039 branchnode = None
1028 1040 branch = branch, branchnode == ctx.node()
1029 1041
1030 1042 if usetuples:
1031 1043 data.append((node, vtx, edges, desc, user, age, branch,
1032 1044 [cgi.escape(x) for x in ctx.tags()],
1033 1045 [cgi.escape(x) for x in ctx.bookmarks()]))
1034 1046 else:
1035 1047 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1036 1048 'color': (edge[2] - 1) % 6 + 1,
1037 1049 'width': edge[3], 'bcolor': edge[4]}
1038 1050 for edge in edges]
1039 1051
1040 1052 data.append(
1041 1053 {'node': node,
1042 1054 'col': vtx[0],
1043 1055 'color': (vtx[1] - 1) % 6 + 1,
1044 1056 'edges': edgedata,
1045 1057 'row': row,
1046 1058 'nextrow': row + 1,
1047 1059 'desc': desc,
1048 1060 'user': user,
1049 1061 'age': age,
1050 1062 'bookmarks': webutil.nodebookmarksdict(
1051 1063 web.repo, ctx.node()),
1052 1064 'branches': webutil.nodebranchdict(web.repo, ctx),
1053 1065 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1054 1066 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1055 1067
1056 1068 row += 1
1057 1069
1058 1070 return data
1059 1071
1060 1072 cols = getcolumns(tree)
1061 1073 rows = len(tree)
1062 1074 canvasheight = (rows + 1) * bg_height - 27
1063 1075
1064 1076 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1065 1077 lessvars=lessvars, morevars=morevars, downrev=downrev,
1066 1078 cols=cols, rows=rows,
1067 1079 canvaswidth=(cols + 1) * bg_height,
1068 1080 truecanvasheight=rows * bg_height,
1069 1081 canvasheight=canvasheight, bg_height=bg_height,
1070 1082 jsdata=lambda **x: graphdata(True, **x),
1071 1083 nodes=lambda **x: graphdata(False, **x),
1072 1084 node=ctx.hex(), changenav=changenav)
1073 1085
1074 1086 def _getdoc(e):
1075 1087 doc = e[0].__doc__
1076 1088 if doc:
1077 1089 doc = _(doc).split('\n')[0]
1078 1090 else:
1079 1091 doc = _('(no help text available)')
1080 1092 return doc
1081 1093
1082 1094 @webcommand('help')
1083 1095 def help(web, req, tmpl):
1084 1096 """
1085 1097 /help[/{topic}]
1086 1098 ---------------
1087 1099
1088 1100 Render help documentation.
1089 1101
1090 1102 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1091 1103 is defined, that help topic will be rendered. If not, an index of
1092 1104 available help topics will be rendered.
1093 1105
1094 1106 The ``help`` template will be rendered when requesting help for a topic.
1095 1107 ``helptopics`` will be rendered for the index of help topics.
1096 1108 """
1097 1109 from mercurial import commands # avoid cycle
1098 1110 from mercurial import help as helpmod # avoid cycle
1099 1111
1100 1112 topicname = req.form.get('node', [None])[0]
1101 1113 if not topicname:
1102 1114 def topics(**map):
1103 1115 for entries, summary, _doc in helpmod.helptable:
1104 1116 yield {'topic': entries[0], 'summary': summary}
1105 1117
1106 1118 early, other = [], []
1107 1119 primary = lambda s: s.split('|')[0]
1108 1120 for c, e in commands.table.iteritems():
1109 1121 doc = _getdoc(e)
1110 1122 if 'DEPRECATED' in doc or c.startswith('debug'):
1111 1123 continue
1112 1124 cmd = primary(c)
1113 1125 if cmd.startswith('^'):
1114 1126 early.append((cmd[1:], doc))
1115 1127 else:
1116 1128 other.append((cmd, doc))
1117 1129
1118 1130 early.sort()
1119 1131 other.sort()
1120 1132
1121 1133 def earlycommands(**map):
1122 1134 for c, doc in early:
1123 1135 yield {'topic': c, 'summary': doc}
1124 1136
1125 1137 def othercommands(**map):
1126 1138 for c, doc in other:
1127 1139 yield {'topic': c, 'summary': doc}
1128 1140
1129 1141 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1130 1142 othercommands=othercommands, title='Index')
1131 1143
1132 1144 u = webutil.wsgiui()
1133 1145 u.verbose = True
1134 1146 try:
1135 1147 doc = helpmod.help_(u, topicname)
1136 1148 except error.UnknownCommand:
1137 1149 raise ErrorResponse(HTTP_NOT_FOUND)
1138 1150 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now