PageRenderTime 47ms CodeModel.GetById 11ms RepoModel.GetById 1ms app.codeStats 0ms

/mercurial/hgweb/webcommands.py

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