eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/hgweb/webcommands.py
changeset 69 c6bca38c1cbf
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/hgweb/webcommands.py	Sat Jan 08 11:20:57 2011 +0530
@@ -0,0 +1,783 @@
+#
+# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
+# Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+import os, mimetypes, re, cgi, copy
+import webutil
+from mercurial import error, encoding, archival, templater, templatefilters
+from mercurial.node import short, hex
+from mercurial.util import binary
+from common import paritygen, staticfile, get_contact, ErrorResponse
+from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
+from mercurial import graphmod
+from mercurial import help as helpmod
+from mercurial.i18n import _
+
+# __all__ is populated with the allowed commands. Be sure to add to it if
+# you're adding a new command, or the new command won't work.
+
+__all__ = [
+   'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
+   'manifest', 'tags', 'branches', 'summary', 'filediff', 'diff', 'annotate',
+   'filelog', 'archive', 'static', 'graph', 'help',
+]
+
+def log(web, req, tmpl):
+    if 'file' in req.form and req.form['file'][0]:
+        return filelog(web, req, tmpl)
+    else:
+        return changelog(web, req, tmpl)
+
+def rawfile(web, req, tmpl):
+    path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
+    if not path:
+        content = manifest(web, req, tmpl)
+        req.respond(HTTP_OK, web.ctype)
+        return content
+
+    try:
+        fctx = webutil.filectx(web.repo, req)
+    except error.LookupError, inst:
+        try:
+            content = manifest(web, req, tmpl)
+            req.respond(HTTP_OK, web.ctype)
+            return content
+        except ErrorResponse:
+            raise inst
+
+    path = fctx.path()
+    text = fctx.data()
+    mt = mimetypes.guess_type(path)[0]
+    if mt is None:
+        mt = binary(text) and 'application/octet-stream' or 'text/plain'
+    if mt.startswith('text/'):
+        mt += '; charset="%s"' % encoding.encoding
+
+    req.respond(HTTP_OK, mt, path, len(text))
+    return [text]
+
+def _filerevision(web, tmpl, fctx):
+    f = fctx.path()
+    text = fctx.data()
+    parity = paritygen(web.stripecount)
+
+    if binary(text):
+        mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
+        text = '(binary:%s)' % mt
+
+    def lines():
+        for lineno, t in enumerate(text.splitlines(True)):
+            yield {"line": t,
+                   "lineid": "l%d" % (lineno + 1),
+                   "linenumber": "% 6d" % (lineno + 1),
+                   "parity": parity.next()}
+
+    return tmpl("filerevision",
+                file=f,
+                path=webutil.up(f),
+                text=lines(),
+                rev=fctx.rev(),
+                node=hex(fctx.node()),
+                author=fctx.user(),
+                date=fctx.date(),
+                desc=fctx.description(),
+                branch=webutil.nodebranchnodefault(fctx),
+                parent=webutil.parents(fctx),
+                child=webutil.children(fctx),
+                rename=webutil.renamelink(fctx),
+                permissions=fctx.manifest().flags(f))
+
+def file(web, req, tmpl):
+    path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
+    if not path:
+        return manifest(web, req, tmpl)
+    try:
+        return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
+    except error.LookupError, inst:
+        try:
+            return manifest(web, req, tmpl)
+        except ErrorResponse:
+            raise inst
+
+def _search(web, req, tmpl):
+
+    query = req.form['rev'][0]
+    revcount = web.maxchanges
+    if 'revcount' in req.form:
+        revcount = int(req.form.get('revcount', [revcount])[0])
+        tmpl.defaults['sessionvars']['revcount'] = revcount
+
+    lessvars = copy.copy(tmpl.defaults['sessionvars'])
+    lessvars['revcount'] = revcount / 2
+    lessvars['rev'] = query
+    morevars = copy.copy(tmpl.defaults['sessionvars'])
+    morevars['revcount'] = revcount * 2
+    morevars['rev'] = query
+
+    def changelist(**map):
+        count = 0
+        qw = query.lower().split()
+
+        def revgen():
+            for i in xrange(len(web.repo) - 1, 0, -100):
+                l = []
+                for j in xrange(max(0, i - 100), i + 1):
+                    ctx = web.repo[j]
+                    l.append(ctx)
+                l.reverse()
+                for e in l:
+                    yield e
+
+        for ctx in revgen():
+            miss = 0
+            for q in qw:
+                if not (q in ctx.user().lower() or
+                        q in ctx.description().lower() or
+                        q in " ".join(ctx.files()).lower()):
+                    miss = 1
+                    break
+            if miss:
+                continue
+
+            count += 1
+            n = ctx.node()
+            showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
+            files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
+
+            yield tmpl('searchentry',
+                       parity=parity.next(),
+                       author=ctx.user(),
+                       parent=webutil.parents(ctx),
+                       child=webutil.children(ctx),
+                       changelogtag=showtags,
+                       desc=ctx.description(),
+                       date=ctx.date(),
+                       files=files,
+                       rev=ctx.rev(),
+                       node=hex(n),
+                       tags=webutil.nodetagsdict(web.repo, n),
+                       inbranch=webutil.nodeinbranch(web.repo, ctx),
+                       branches=webutil.nodebranchdict(web.repo, ctx))
+
+            if count >= revcount:
+                break
+
+    tip = web.repo['tip']
+    parity = paritygen(web.stripecount)
+
+    return tmpl('search', query=query, node=tip.hex(),
+                entries=changelist, archives=web.archivelist("tip"),
+                morevars=morevars, lessvars=lessvars)
+
+def changelog(web, req, tmpl, shortlog=False):
+
+    if 'node' in req.form:
+        ctx = webutil.changectx(web.repo, req)
+    else:
+        if 'rev' in req.form:
+            hi = req.form['rev'][0]
+        else:
+            hi = len(web.repo) - 1
+        try:
+            ctx = web.repo[hi]
+        except error.RepoError:
+            return _search(web, req, tmpl) # XXX redirect to 404 page?
+
+    def changelist(limit=0, **map):
+        l = [] # build a list in forward order for efficiency
+        for i in xrange(start, end):
+            ctx = web.repo[i]
+            n = ctx.node()
+            showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
+            files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
+
+            l.insert(0, {"parity": parity.next(),
+                         "author": ctx.user(),
+                         "parent": webutil.parents(ctx, i - 1),
+                         "child": webutil.children(ctx, i + 1),
+                         "changelogtag": showtags,
+                         "desc": ctx.description(),
+                         "date": ctx.date(),
+                         "files": files,
+                         "rev": i,
+                         "node": hex(n),
+                         "tags": webutil.nodetagsdict(web.repo, n),
+                         "inbranch": webutil.nodeinbranch(web.repo, ctx),
+                         "branches": webutil.nodebranchdict(web.repo, ctx)
+                        })
+
+        if limit > 0:
+            l = l[:limit]
+
+        for e in l:
+            yield e
+
+    revcount = shortlog and web.maxshortchanges or web.maxchanges
+    if 'revcount' in req.form:
+        revcount = int(req.form.get('revcount', [revcount])[0])
+        tmpl.defaults['sessionvars']['revcount'] = revcount
+
+    lessvars = copy.copy(tmpl.defaults['sessionvars'])
+    lessvars['revcount'] = revcount / 2
+    morevars = copy.copy(tmpl.defaults['sessionvars'])
+    morevars['revcount'] = revcount * 2
+
+    count = len(web.repo)
+    pos = ctx.rev()
+    start = max(0, pos - revcount + 1)
+    end = min(count, start + revcount)
+    pos = end - 1
+    parity = paritygen(web.stripecount, offset=start - end)
+
+    changenav = webutil.revnavgen(pos, revcount, count, web.repo.changectx)
+
+    return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
+                node=hex(ctx.node()), rev=pos, changesets=count,
+                entries=lambda **x: changelist(limit=0,**x),
+                latestentry=lambda **x: changelist(limit=1,**x),
+                archives=web.archivelist("tip"), revcount=revcount,
+                morevars=morevars, lessvars=lessvars)
+
+def shortlog(web, req, tmpl):
+    return changelog(web, req, tmpl, shortlog = True)
+
+def changeset(web, req, tmpl):
+    ctx = webutil.changectx(web.repo, req)
+    showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
+    showbranch = webutil.nodebranchnodefault(ctx)
+
+    files = []
+    parity = paritygen(web.stripecount)
+    for f in ctx.files():
+        template = f in ctx and 'filenodelink' or 'filenolink'
+        files.append(tmpl(template,
+                          node=ctx.hex(), file=f,
+                          parity=parity.next()))
+
+    parity = paritygen(web.stripecount)
+    style = web.config('web', 'style', 'paper')
+    if 'style' in req.form:
+        style = req.form['style'][0]
+
+    diffs = webutil.diffs(web.repo, tmpl, ctx, None, parity, style)
+    return tmpl('changeset',
+                diff=diffs,
+                rev=ctx.rev(),
+                node=ctx.hex(),
+                parent=webutil.parents(ctx),
+                child=webutil.children(ctx),
+                changesettag=showtags,
+                changesetbranch=showbranch,
+                author=ctx.user(),
+                desc=ctx.description(),
+                date=ctx.date(),
+                files=files,
+                archives=web.archivelist(ctx.hex()),
+                tags=webutil.nodetagsdict(web.repo, ctx.node()),
+                branch=webutil.nodebranchnodefault(ctx),
+                inbranch=webutil.nodeinbranch(web.repo, ctx),
+                branches=webutil.nodebranchdict(web.repo, ctx))
+
+rev = changeset
+
+def manifest(web, req, tmpl):
+    ctx = webutil.changectx(web.repo, req)
+    path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
+    mf = ctx.manifest()
+    node = ctx.node()
+
+    files = {}
+    dirs = {}
+    parity = paritygen(web.stripecount)
+
+    if path and path[-1] != "/":
+        path += "/"
+    l = len(path)
+    abspath = "/" + path
+
+    for f, n in mf.iteritems():
+        if f[:l] != path:
+            continue
+        remain = f[l:]
+        elements = remain.split('/')
+        if len(elements) == 1:
+            files[remain] = f
+        else:
+            h = dirs # need to retain ref to dirs (root)
+            for elem in elements[0:-1]:
+                if elem not in h:
+                    h[elem] = {}
+                h = h[elem]
+                if len(h) > 1:
+                    break
+            h[None] = None # denotes files present
+
+    if mf and not files and not dirs:
+        raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
+
+    def filelist(**map):
+        for f in sorted(files):
+            full = files[f]
+
+            fctx = ctx.filectx(full)
+            yield {"file": full,
+                   "parity": parity.next(),
+                   "basename": f,
+                   "date": fctx.date(),
+                   "size": fctx.size(),
+                   "permissions": mf.flags(full)}
+
+    def dirlist(**map):
+        for d in sorted(dirs):
+
+            emptydirs = []
+            h = dirs[d]
+            while isinstance(h, dict) and len(h) == 1:
+                k, v = h.items()[0]
+                if v:
+                    emptydirs.append(k)
+                h = v
+
+            path = "%s%s" % (abspath, d)
+            yield {"parity": parity.next(),
+                   "path": path,
+                   "emptydirs": "/".join(emptydirs),
+                   "basename": d}
+
+    return tmpl("manifest",
+                rev=ctx.rev(),
+                node=hex(node),
+                path=abspath,
+                up=webutil.up(abspath),
+                upparity=parity.next(),
+                fentries=filelist,
+                dentries=dirlist,
+                archives=web.archivelist(hex(node)),
+                tags=webutil.nodetagsdict(web.repo, node),
+                inbranch=webutil.nodeinbranch(web.repo, ctx),
+                branches=webutil.nodebranchdict(web.repo, ctx))
+
+def tags(web, req, tmpl):
+    i = web.repo.tagslist()
+    i.reverse()
+    parity = paritygen(web.stripecount)
+
+    def entries(notip=False, limit=0, **map):
+        count = 0
+        for k, n in i:
+            if notip and k == "tip":
+                continue
+            if limit > 0 and count >= limit:
+                continue
+            count = count + 1
+            yield {"parity": parity.next(),
+                   "tag": k,
+                   "date": web.repo[n].date(),
+                   "node": hex(n)}
+
+    return tmpl("tags",
+                node=hex(web.repo.changelog.tip()),
+                entries=lambda **x: entries(False, 0, **x),
+                entriesnotip=lambda **x: entries(True, 0, **x),
+                latestentry=lambda **x: entries(True, 1, **x))
+
+def branches(web, req, tmpl):
+    tips = (web.repo[n] for t, n in web.repo.branchtags().iteritems())
+    heads = web.repo.heads()
+    parity = paritygen(web.stripecount)
+    sortkey = lambda ctx: ('close' not in ctx.extra(), ctx.rev())
+
+    def entries(limit, **map):
+        count = 0
+        for ctx in sorted(tips, key=sortkey, reverse=True):
+            if limit > 0 and count >= limit:
+                return
+            count += 1
+            if ctx.node() not in heads:
+                status = 'inactive'
+            elif not web.repo.branchheads(ctx.branch()):
+                status = 'closed'
+            else:
+                status = 'open'
+            yield {'parity': parity.next(),
+                   'branch': ctx.branch(),
+                   'status': status,
+                   'node': ctx.hex(),
+                   'date': ctx.date()}
+
+    return tmpl('branches', node=hex(web.repo.changelog.tip()),
+                entries=lambda **x: entries(0, **x),
+                latestentry=lambda **x: entries(1, **x))
+
+def summary(web, req, tmpl):
+    i = web.repo.tagslist()
+    i.reverse()
+
+    def tagentries(**map):
+        parity = paritygen(web.stripecount)
+        count = 0
+        for k, n in i:
+            if k == "tip": # skip tip
+                continue
+
+            count += 1
+            if count > 10: # limit to 10 tags
+                break
+
+            yield tmpl("tagentry",
+                       parity=parity.next(),
+                       tag=k,
+                       node=hex(n),
+                       date=web.repo[n].date())
+
+    def branches(**map):
+        parity = paritygen(web.stripecount)
+
+        b = web.repo.branchtags()
+        l = [(-web.repo.changelog.rev(n), n, t) for t, n in b.iteritems()]
+        for r, n, t in sorted(l):
+            yield {'parity': parity.next(),
+                   'branch': t,
+                   'node': hex(n),
+                   'date': web.repo[n].date()}
+
+    def changelist(**map):
+        parity = paritygen(web.stripecount, offset=start - end)
+        l = [] # build a list in forward order for efficiency
+        for i in xrange(start, end):
+            ctx = web.repo[i]
+            n = ctx.node()
+            hn = hex(n)
+
+            l.insert(0, tmpl(
+               'shortlogentry',
+                parity=parity.next(),
+                author=ctx.user(),
+                desc=ctx.description(),
+                date=ctx.date(),
+                rev=i,
+                node=hn,
+                tags=webutil.nodetagsdict(web.repo, n),
+                inbranch=webutil.nodeinbranch(web.repo, ctx),
+                branches=webutil.nodebranchdict(web.repo, ctx)))
+
+        yield l
+
+    tip = web.repo['tip']
+    count = len(web.repo)
+    start = max(0, count - web.maxchanges)
+    end = min(count, start + web.maxchanges)
+
+    return tmpl("summary",
+                desc=web.config("web", "description", "unknown"),
+                owner=get_contact(web.config) or "unknown",
+                lastchange=tip.date(),
+                tags=tagentries,
+                branches=branches,
+                shortlog=changelist,
+                node=tip.hex(),
+                archives=web.archivelist("tip"))
+
+def filediff(web, req, tmpl):
+    fctx, ctx = None, None
+    try:
+        fctx = webutil.filectx(web.repo, req)
+    except LookupError:
+        ctx = webutil.changectx(web.repo, req)
+        path = webutil.cleanpath(web.repo, req.form['file'][0])
+        if path not in ctx.files():
+            raise
+
+    if fctx is not None:
+        n = fctx.node()
+        path = fctx.path()
+    else:
+        n = ctx.node()
+        # path already defined in except clause
+
+    parity = paritygen(web.stripecount)
+    style = web.config('web', 'style', 'paper')
+    if 'style' in req.form:
+        style = req.form['style'][0]
+
+    diffs = webutil.diffs(web.repo, tmpl, fctx or ctx, [path], parity, style)
+    rename = fctx and webutil.renamelink(fctx) or []
+    ctx = fctx and fctx or ctx
+    return tmpl("filediff",
+                file=path,
+                node=hex(n),
+                rev=ctx.rev(),
+                date=ctx.date(),
+                desc=ctx.description(),
+                author=ctx.user(),
+                rename=rename,
+                branch=webutil.nodebranchnodefault(ctx),
+                parent=webutil.parents(ctx),
+                child=webutil.children(ctx),
+                diff=diffs)
+
+diff = filediff
+
+def annotate(web, req, tmpl):
+    fctx = webutil.filectx(web.repo, req)
+    f = fctx.path()
+    parity = paritygen(web.stripecount)
+
+    def annotate(**map):
+        last = None
+        if binary(fctx.data()):
+            mt = (mimetypes.guess_type(fctx.path())[0]
+                  or 'application/octet-stream')
+            lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
+                                '(binary:%s)' % mt)])
+        else:
+            lines = enumerate(fctx.annotate(follow=True, linenumber=True))
+        for lineno, ((f, targetline), l) in lines:
+            fnode = f.filenode()
+
+            if last != fnode:
+                last = fnode
+
+            yield {"parity": parity.next(),
+                   "node": hex(f.node()),
+                   "rev": f.rev(),
+                   "author": f.user(),
+                   "desc": f.description(),
+                   "file": f.path(),
+                   "targetline": targetline,
+                   "line": l,
+                   "lineid": "l%d" % (lineno + 1),
+                   "linenumber": "% 6d" % (lineno + 1)}
+
+    return tmpl("fileannotate",
+                file=f,
+                annotate=annotate,
+                path=webutil.up(f),
+                rev=fctx.rev(),
+                node=hex(fctx.node()),
+                author=fctx.user(),
+                date=fctx.date(),
+                desc=fctx.description(),
+                rename=webutil.renamelink(fctx),
+                branch=webutil.nodebranchnodefault(fctx),
+                parent=webutil.parents(fctx),
+                child=webutil.children(fctx),
+                permissions=fctx.manifest().flags(f))
+
+def filelog(web, req, tmpl):
+
+    try:
+        fctx = webutil.filectx(web.repo, req)
+        f = fctx.path()
+        fl = fctx.filelog()
+    except error.LookupError:
+        f = webutil.cleanpath(web.repo, req.form['file'][0])
+        fl = web.repo.file(f)
+        numrevs = len(fl)
+        if not numrevs: # file doesn't exist at all
+            raise
+        rev = webutil.changectx(web.repo, req).rev()
+        first = fl.linkrev(0)
+        if rev < first: # current rev is from before file existed
+            raise
+        frev = numrevs - 1
+        while fl.linkrev(frev) > rev:
+            frev -= 1
+        fctx = web.repo.filectx(f, fl.linkrev(frev))
+
+    revcount = web.maxshortchanges
+    if 'revcount' in req.form:
+        revcount = int(req.form.get('revcount', [revcount])[0])
+        tmpl.defaults['sessionvars']['revcount'] = revcount
+
+    lessvars = copy.copy(tmpl.defaults['sessionvars'])
+    lessvars['revcount'] = revcount / 2
+    morevars = copy.copy(tmpl.defaults['sessionvars'])
+    morevars['revcount'] = revcount * 2
+
+    count = fctx.filerev() + 1
+    start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
+    end = min(count, start + revcount) # last rev on this page
+    parity = paritygen(web.stripecount, offset=start - end)
+
+    def entries(limit=0, **map):
+        l = []
+
+        repo = web.repo
+        for i in xrange(start, end):
+            iterfctx = fctx.filectx(i)
+
+            l.insert(0, {"parity": parity.next(),
+                         "filerev": i,
+                         "file": f,
+                         "node": hex(iterfctx.node()),
+                         "author": iterfctx.user(),
+                         "date": iterfctx.date(),
+                         "rename": webutil.renamelink(iterfctx),
+                         "parent": webutil.parents(iterfctx),
+                         "child": webutil.children(iterfctx),
+                         "desc": iterfctx.description(),
+                         "tags": webutil.nodetagsdict(repo, iterfctx.node()),
+                         "branch": webutil.nodebranchnodefault(iterfctx),
+                         "inbranch": webutil.nodeinbranch(repo, iterfctx),
+                         "branches": webutil.nodebranchdict(repo, iterfctx)})
+
+        if limit > 0:
+            l = l[:limit]
+
+        for e in l:
+            yield e
+
+    nodefunc = lambda x: fctx.filectx(fileid=x)
+    nav = webutil.revnavgen(end - 1, revcount, count, nodefunc)
+    return tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
+                entries=lambda **x: entries(limit=0, **x),
+                latestentry=lambda **x: entries(limit=1, **x),
+                revcount=revcount, morevars=morevars, lessvars=lessvars)
+
+def archive(web, req, tmpl):
+    type_ = req.form.get('type', [None])[0]
+    allowed = web.configlist("web", "allow_archive")
+    key = req.form['node'][0]
+
+    if type_ not in web.archives:
+        msg = 'Unsupported archive type: %s' % type_
+        raise ErrorResponse(HTTP_NOT_FOUND, msg)
+
+    if not ((type_ in allowed or
+        web.configbool("web", "allow" + type_, False))):
+        msg = 'Archive type not allowed: %s' % type_
+        raise ErrorResponse(HTTP_FORBIDDEN, msg)
+
+    reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
+    cnode = web.repo.lookup(key)
+    arch_version = key
+    if cnode == key or key == 'tip':
+        arch_version = short(cnode)
+    name = "%s-%s" % (reponame, arch_version)
+    mimetype, artype, extension, encoding = web.archive_specs[type_]
+    headers = [
+        ('Content-Type', mimetype),
+        ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
+    ]
+    if encoding:
+        headers.append(('Content-Encoding', encoding))
+    req.header(headers)
+    req.respond(HTTP_OK)
+    archival.archive(web.repo, req, cnode, artype, prefix=name)
+    return []
+
+
+def static(web, req, tmpl):
+    fname = req.form['file'][0]
+    # a repo owner may set web.static in .hg/hgrc to get any file
+    # readable by the user running the CGI script
+    static = web.config("web", "static", None, untrusted=False)
+    if not static:
+        tp = web.templatepath or templater.templatepath()
+        if isinstance(tp, str):
+            tp = [tp]
+        static = [os.path.join(p, 'static') for p in tp]
+    return [staticfile(static, fname, req)]
+
+def graph(web, req, tmpl):
+
+    rev = webutil.changectx(web.repo, req).rev()
+    bg_height = 39
+    revcount = web.maxshortchanges
+    if 'revcount' in req.form:
+        revcount = int(req.form.get('revcount', [revcount])[0])
+        tmpl.defaults['sessionvars']['revcount'] = revcount
+
+    lessvars = copy.copy(tmpl.defaults['sessionvars'])
+    lessvars['revcount'] = revcount / 2
+    morevars = copy.copy(tmpl.defaults['sessionvars'])
+    morevars['revcount'] = revcount * 2
+
+    max_rev = len(web.repo) - 1
+    revcount = min(max_rev, revcount)
+    revnode = web.repo.changelog.node(rev)
+    revnode_hex = hex(revnode)
+    uprev = min(max_rev, rev + revcount)
+    downrev = max(0, rev - revcount)
+    count = len(web.repo)
+    changenav = webutil.revnavgen(rev, revcount, count, web.repo.changectx)
+
+    dag = graphmod.revisions(web.repo, rev, downrev)
+    tree = list(graphmod.colored(dag))
+    canvasheight = (len(tree) + 1) * bg_height - 27
+    data = []
+    for (id, type, ctx, vtx, edges) in tree:
+        if type != graphmod.CHANGESET:
+            continue
+        node = short(ctx.node())
+        age = templatefilters.age(ctx.date())
+        desc = templatefilters.firstline(ctx.description())
+        desc = cgi.escape(templatefilters.nonempty(desc))
+        user = cgi.escape(templatefilters.person(ctx.user()))
+        branch = ctx.branch()
+        branch = branch, web.repo.branchtags().get(branch) == ctx.node()
+        data.append((node, vtx, edges, desc, user, age, branch, ctx.tags()))
+
+    return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
+                lessvars=lessvars, morevars=morevars, downrev=downrev,
+                canvasheight=canvasheight, jsdata=data, bg_height=bg_height,
+                node=revnode_hex, changenav=changenav)
+
+def _getdoc(e):
+    doc = e[0].__doc__
+    if doc:
+        doc = doc.split('\n')[0]
+    else:
+        doc = _('(no help text available)')
+    return doc
+
+def help(web, req, tmpl):
+    from mercurial import commands # avoid cycle
+
+    topicname = req.form.get('node', [None])[0]
+    if not topicname:
+        topic = []
+
+        def topics(**map):
+            for entries, summary, _ in helpmod.helptable:
+                entries = sorted(entries, key=len)
+                yield {'topic': entries[-1], 'summary': summary}
+
+        early, other = [], []
+        primary = lambda s: s.split('|')[0]
+        for c, e in commands.table.iteritems():
+            doc = _getdoc(e)
+            if 'DEPRECATED' in doc or c.startswith('debug'):
+                continue
+            cmd = primary(c)
+            if cmd.startswith('^'):
+                early.append((cmd[1:], doc))
+            else:
+                other.append((cmd, doc))
+
+        early.sort()
+        other.sort()
+
+        def earlycommands(**map):
+            for c, doc in early:
+                yield {'topic': c, 'summary': doc}
+
+        def othercommands(**map):
+            for c, doc in other:
+                yield {'topic': c, 'summary': doc}
+
+        return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
+                    othercommands=othercommands, title='Index')
+
+    u = webutil.wsgiui()
+    u.pushbuffer()
+    try:
+        commands.help_(u, topicname)
+    except error.UnknownCommand:
+        raise ErrorResponse(HTTP_NOT_FOUND)
+    doc = u.popbuffer()
+    return tmpl('help', topic=topicname, doc=doc)