PageRenderTime 57ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/mercurial/hgweb/webutil.py

https://bitbucket.org/mirror/mercurial/
Python | 411 lines | 393 code | 6 blank | 12 comment | 8 complexity | 0e35bdcd19bf41255cd032d0507ee3f8 MD5 | raw file
Possible License(s): GPL-2.0
  1. # hgweb/webutil.py - utility library for the web interface.
  2. #
  3. # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
  4. # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
  5. #
  6. # This software may be used and distributed according to the terms of the
  7. # GNU General Public License version 2 or any later version.
  8. import os, copy
  9. from mercurial import match, patch, error, ui, util, pathutil, context
  10. from mercurial.i18n import _
  11. from mercurial.node import hex, nullid
  12. from common import ErrorResponse
  13. from common import HTTP_NOT_FOUND
  14. import difflib
  15. def up(p):
  16. if p[0] != "/":
  17. p = "/" + p
  18. if p[-1] == "/":
  19. p = p[:-1]
  20. up = os.path.dirname(p)
  21. if up == "/":
  22. return "/"
  23. return up + "/"
  24. def _navseq(step, firststep=None):
  25. if firststep:
  26. yield firststep
  27. if firststep >= 20 and firststep <= 40:
  28. firststep = 50
  29. yield firststep
  30. assert step > 0
  31. assert firststep > 0
  32. while step <= firststep:
  33. step *= 10
  34. while True:
  35. yield 1 * step
  36. yield 3 * step
  37. step *= 10
  38. class revnav(object):
  39. def __init__(self, repo):
  40. """Navigation generation object
  41. :repo: repo object we generate nav for
  42. """
  43. # used for hex generation
  44. self._revlog = repo.changelog
  45. def __nonzero__(self):
  46. """return True if any revision to navigate over"""
  47. return self._first() is not None
  48. def _first(self):
  49. """return the minimum non-filtered changeset or None"""
  50. try:
  51. return iter(self._revlog).next()
  52. except StopIteration:
  53. return None
  54. def hex(self, rev):
  55. return hex(self._revlog.node(rev))
  56. def gen(self, pos, pagelen, limit):
  57. """computes label and revision id for navigation link
  58. :pos: is the revision relative to which we generate navigation.
  59. :pagelen: the size of each navigation page
  60. :limit: how far shall we link
  61. The return is:
  62. - a single element tuple
  63. - containing a dictionary with a `before` and `after` key
  64. - values are generator functions taking arbitrary number of kwargs
  65. - yield items are dictionaries with `label` and `node` keys
  66. """
  67. if not self:
  68. # empty repo
  69. return ({'before': (), 'after': ()},)
  70. targets = []
  71. for f in _navseq(1, pagelen):
  72. if f > limit:
  73. break
  74. targets.append(pos + f)
  75. targets.append(pos - f)
  76. targets.sort()
  77. first = self._first()
  78. navbefore = [("(%i)" % first, self.hex(first))]
  79. navafter = []
  80. for rev in targets:
  81. if rev not in self._revlog:
  82. continue
  83. if pos < rev < limit:
  84. navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
  85. if 0 < rev < pos:
  86. navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
  87. navafter.append(("tip", "tip"))
  88. data = lambda i: {"label": i[0], "node": i[1]}
  89. return ({'before': lambda **map: (data(i) for i in navbefore),
  90. 'after': lambda **map: (data(i) for i in navafter)},)
  91. class filerevnav(revnav):
  92. def __init__(self, repo, path):
  93. """Navigation generation object
  94. :repo: repo object we generate nav for
  95. :path: path of the file we generate nav for
  96. """
  97. # used for iteration
  98. self._changelog = repo.unfiltered().changelog
  99. # used for hex generation
  100. self._revlog = repo.file(path)
  101. def hex(self, rev):
  102. return hex(self._changelog.node(self._revlog.linkrev(rev)))
  103. def _siblings(siblings=[], hiderev=None):
  104. siblings = [s for s in siblings if s.node() != nullid]
  105. if len(siblings) == 1 and siblings[0].rev() == hiderev:
  106. return
  107. for s in siblings:
  108. d = {'node': s.hex(), 'rev': s.rev()}
  109. d['user'] = s.user()
  110. d['date'] = s.date()
  111. d['description'] = s.description()
  112. d['branch'] = s.branch()
  113. if util.safehasattr(s, 'path'):
  114. d['file'] = s.path()
  115. yield d
  116. def parents(ctx, hide=None):
  117. if (isinstance(ctx, context.basefilectx) and
  118. ctx.changectx().rev() != ctx.linkrev()):
  119. return _siblings([ctx._repo[ctx.linkrev()]], hide)
  120. return _siblings(ctx.parents(), hide)
  121. def children(ctx, hide=None):
  122. return _siblings(ctx.children(), hide)
  123. def renamelink(fctx):
  124. r = fctx.renamed()
  125. if r:
  126. return [{'file': r[0], 'node': hex(r[1])}]
  127. return []
  128. def nodetagsdict(repo, node):
  129. return [{"name": i} for i in repo.nodetags(node)]
  130. def nodebookmarksdict(repo, node):
  131. return [{"name": i} for i in repo.nodebookmarks(node)]
  132. def nodebranchdict(repo, ctx):
  133. branches = []
  134. branch = ctx.branch()
  135. # If this is an empty repo, ctx.node() == nullid,
  136. # ctx.branch() == 'default'.
  137. try:
  138. branchnode = repo.branchtip(branch)
  139. except error.RepoLookupError:
  140. branchnode = None
  141. if branchnode == ctx.node():
  142. branches.append({"name": branch})
  143. return branches
  144. def nodeinbranch(repo, ctx):
  145. branches = []
  146. branch = ctx.branch()
  147. try:
  148. branchnode = repo.branchtip(branch)
  149. except error.RepoLookupError:
  150. branchnode = None
  151. if branch != 'default' and branchnode != ctx.node():
  152. branches.append({"name": branch})
  153. return branches
  154. def nodebranchnodefault(ctx):
  155. branches = []
  156. branch = ctx.branch()
  157. if branch != 'default':
  158. branches.append({"name": branch})
  159. return branches
  160. def showtag(repo, tmpl, t1, node=nullid, **args):
  161. for t in repo.nodetags(node):
  162. yield tmpl(t1, tag=t, **args)
  163. def showbookmark(repo, tmpl, t1, node=nullid, **args):
  164. for t in repo.nodebookmarks(node):
  165. yield tmpl(t1, bookmark=t, **args)
  166. def cleanpath(repo, path):
  167. path = path.lstrip('/')
  168. return pathutil.canonpath(repo.root, '', path)
  169. def changeidctx (repo, changeid):
  170. try:
  171. ctx = repo[changeid]
  172. except error.RepoError:
  173. man = repo.manifest
  174. ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
  175. return ctx
  176. def changectx (repo, req):
  177. changeid = "tip"
  178. if 'node' in req.form:
  179. changeid = req.form['node'][0]
  180. ipos=changeid.find(':')
  181. if ipos != -1:
  182. changeid = changeid[(ipos + 1):]
  183. elif 'manifest' in req.form:
  184. changeid = req.form['manifest'][0]
  185. return changeidctx(repo, changeid)
  186. def basechangectx(repo, req):
  187. if 'node' in req.form:
  188. changeid = req.form['node'][0]
  189. ipos=changeid.find(':')
  190. if ipos != -1:
  191. changeid = changeid[:ipos]
  192. return changeidctx(repo, changeid)
  193. return None
  194. def filectx(repo, req):
  195. if 'file' not in req.form:
  196. raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
  197. path = cleanpath(repo, req.form['file'][0])
  198. if 'node' in req.form:
  199. changeid = req.form['node'][0]
  200. elif 'filenode' in req.form:
  201. changeid = req.form['filenode'][0]
  202. else:
  203. raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
  204. try:
  205. fctx = repo[changeid][path]
  206. except error.RepoError:
  207. fctx = repo.filectx(path, fileid=changeid)
  208. return fctx
  209. def listfilediffs(tmpl, files, node, max):
  210. for f in files[:max]:
  211. yield tmpl('filedifflink', node=hex(node), file=f)
  212. if len(files) > max:
  213. yield tmpl('fileellipses')
  214. def diffs(repo, tmpl, ctx, basectx, files, parity, style):
  215. def countgen():
  216. start = 1
  217. while True:
  218. yield start
  219. start += 1
  220. blockcount = countgen()
  221. def prettyprintlines(diff, blockno):
  222. for lineno, l in enumerate(diff.splitlines(True)):
  223. lineno = "%d.%d" % (blockno, lineno + 1)
  224. if l.startswith('+'):
  225. ltype = "difflineplus"
  226. elif l.startswith('-'):
  227. ltype = "difflineminus"
  228. elif l.startswith('@'):
  229. ltype = "difflineat"
  230. else:
  231. ltype = "diffline"
  232. yield tmpl(ltype,
  233. line=l,
  234. lineid="l%s" % lineno,
  235. linenumber="% 8s" % lineno)
  236. if files:
  237. m = match.exact(repo.root, repo.getcwd(), files)
  238. else:
  239. m = match.always(repo.root, repo.getcwd())
  240. diffopts = patch.diffopts(repo.ui, untrusted=True)
  241. if basectx is None:
  242. parents = ctx.parents()
  243. node1 = parents and parents[0].node() or nullid
  244. else:
  245. node1 = basectx.node()
  246. node2 = ctx.node()
  247. block = []
  248. for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
  249. if chunk.startswith('diff') and block:
  250. blockno = blockcount.next()
  251. yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
  252. lines=prettyprintlines(''.join(block), blockno))
  253. block = []
  254. if chunk.startswith('diff') and style != 'raw':
  255. chunk = ''.join(chunk.splitlines(True)[1:])
  256. block.append(chunk)
  257. blockno = blockcount.next()
  258. yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
  259. lines=prettyprintlines(''.join(block), blockno))
  260. def compare(tmpl, context, leftlines, rightlines):
  261. '''Generator function that provides side-by-side comparison data.'''
  262. def compline(type, leftlineno, leftline, rightlineno, rightline):
  263. lineid = leftlineno and ("l%s" % leftlineno) or ''
  264. lineid += rightlineno and ("r%s" % rightlineno) or ''
  265. return tmpl('comparisonline',
  266. type=type,
  267. lineid=lineid,
  268. leftlinenumber="% 6s" % (leftlineno or ''),
  269. leftline=leftline or '',
  270. rightlinenumber="% 6s" % (rightlineno or ''),
  271. rightline=rightline or '')
  272. def getblock(opcodes):
  273. for type, llo, lhi, rlo, rhi in opcodes:
  274. len1 = lhi - llo
  275. len2 = rhi - rlo
  276. count = min(len1, len2)
  277. for i in xrange(count):
  278. yield compline(type=type,
  279. leftlineno=llo + i + 1,
  280. leftline=leftlines[llo + i],
  281. rightlineno=rlo + i + 1,
  282. rightline=rightlines[rlo + i])
  283. if len1 > len2:
  284. for i in xrange(llo + count, lhi):
  285. yield compline(type=type,
  286. leftlineno=i + 1,
  287. leftline=leftlines[i],
  288. rightlineno=None,
  289. rightline=None)
  290. elif len2 > len1:
  291. for i in xrange(rlo + count, rhi):
  292. yield compline(type=type,
  293. leftlineno=None,
  294. leftline=None,
  295. rightlineno=i + 1,
  296. rightline=rightlines[i])
  297. s = difflib.SequenceMatcher(None, leftlines, rightlines)
  298. if context < 0:
  299. yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
  300. else:
  301. for oc in s.get_grouped_opcodes(n=context):
  302. yield tmpl('comparisonblock', lines=getblock(oc))
  303. def diffstatgen(ctx, basectx):
  304. '''Generator function that provides the diffstat data.'''
  305. stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
  306. maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
  307. while True:
  308. yield stats, maxname, maxtotal, addtotal, removetotal, binary
  309. def diffsummary(statgen):
  310. '''Return a short summary of the diff.'''
  311. stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
  312. return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
  313. len(stats), addtotal, removetotal)
  314. def diffstat(tmpl, ctx, statgen, parity):
  315. '''Return a diffstat template for each file in the diff.'''
  316. stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
  317. files = ctx.files()
  318. def pct(i):
  319. if maxtotal == 0:
  320. return 0
  321. return (float(i) / maxtotal) * 100
  322. fileno = 0
  323. for filename, adds, removes, isbinary in stats:
  324. template = filename in files and 'diffstatlink' or 'diffstatnolink'
  325. total = adds + removes
  326. fileno += 1
  327. yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
  328. total=total, addpct=pct(adds), removepct=pct(removes),
  329. parity=parity.next())
  330. class sessionvars(object):
  331. def __init__(self, vars, start='?'):
  332. self.start = start
  333. self.vars = vars
  334. def __getitem__(self, key):
  335. return self.vars[key]
  336. def __setitem__(self, key, value):
  337. self.vars[key] = value
  338. def __copy__(self):
  339. return sessionvars(copy.copy(self.vars), self.start)
  340. def __iter__(self):
  341. separator = self.start
  342. for key, value in sorted(self.vars.iteritems()):
  343. yield {'name': key, 'value': str(value), 'separator': separator}
  344. separator = '&'
  345. class wsgiui(ui.ui):
  346. # default termwidth breaks under mod_wsgi
  347. def termwidth(self):
  348. return 80