hgweb: refactor hgweb code
authorDirkjan Ochtman <dirkjan@ochtman.nl>
Fri, 28 Mar 2008 19:40:44 +0100
changeset 6393 894875eae49b
parent 6392 2540521dc7c1
child 6394 55bc0a035e1f
hgweb: refactor hgweb code
hgext/highlight.py
hgext/keyword.py
mercurial/hgweb/hgweb_mod.py
mercurial/hgweb/webcommands.py
mercurial/hgweb/webutil.py
--- a/hgext/highlight.py	Fri Mar 28 19:37:28 2008 +0100
+++ b/hgext/highlight.py	Fri Mar 28 19:40:44 2008 +0100
@@ -31,7 +31,7 @@
                             'pkg_resources',
                             '__main__',])
 
-from mercurial.hgweb.hgweb_mod import hgweb
+from mercurial.hgweb import webcommands, webutil
 from mercurial import util
 from mercurial.templatefilters import filters
 
@@ -79,20 +79,19 @@
     newl = oldl.replace('line|escape', 'line|colorize')
     tmpl.cache[field] = newl
 
-def filerevision_highlight(self, tmpl, fctx):
-    pygmentize(self, tmpl, fctx, 'fileline')
-
-    return realrevision(self, tmpl, fctx)
+web_filerevision = webcommands._filerevision
+web_annotate = webcommands.annotate
 
-def fileannotate_highlight(self, tmpl, fctx):
-    pygmentize(self, tmpl, fctx, 'annotateline')
+def filerevision_highlight(web, tmpl, fctx):
+    pygmentize(web, tmpl, fctx, 'fileline')
+    return web_filerevision(web, tmpl, fctx)
 
-    return realannotate(self, tmpl, fctx)
+def annotate_highlight(web, req, tmpl):
+    fctx = webutil.filectx(web.repo, req)
+    pygmentize(web, tmpl, fctx, 'annotateline')
+    return web_annotate(web, req, tmpl)
 
 # monkeypatch in the new version
-# should be safer than overriding the method in a derived class
-# and then patching the class
-realrevision = hgweb.filerevision
-hgweb.filerevision = filerevision_highlight
-realannotate = hgweb.fileannotate
-hgweb.fileannotate = fileannotate_highlight
+
+webcommands._filerevision = filerevision_highlight
+webcommands.annotate = annotate_highlight
--- a/hgext/keyword.py	Fri Mar 28 19:37:28 2008 +0100
+++ b/hgext/keyword.py	Fri Mar 28 19:40:44 2008 +0100
@@ -128,15 +128,21 @@
     _patch_diff(repo, node1=node1, node2=node2, files=files, match=match,
                 fp=fp, changes=changes, opts=opts)
 
+# monkeypatching hgweb functions changeset and filediff
+# actual monkeypatching is done at the bottom of reposetup()
+
+web_changeset = webcommands.changeset
+web_filediff = webcommands.filediff
+
 def _kwweb_changeset(web, req, tmpl):
     '''Wraps webcommands.changeset turning off keyword expansion.'''
     kwtools['templater'].matcher = util.never
-    return web.changeset(tmpl, web.changectx(req))
+    return web_changeset(web, req, tmpl)
 
 def _kwweb_filediff(web, req, tmpl):
     '''Wraps webcommands.filediff turning off keyword expansion.'''
     kwtools['templater'].matcher = util.never
-    return web.filediff(tmpl, web.filectx(req))
+    return web_filediff(web, req, tmpl)
 
 def _kwdispatch_parse(ui, args):
     '''Monkeypatch dispatch._parse to obtain running hg command.'''
--- a/mercurial/hgweb/hgweb_mod.py	Fri Mar 28 19:37:28 2008 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Fri Mar 28 19:40:44 2008 +0100
@@ -6,13 +6,12 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, mimetypes, re
-from mercurial.node import hex, nullid, short
+import os, mimetypes
+from mercurial.node import hex, nullid
 from mercurial.repo import RepoError
-from mercurial import mdiff, ui, hg, util, archival, patch, hook
+from mercurial import mdiff, ui, hg, util, patch, hook
 from mercurial import revlog, templater, templatefilters, changegroup
-from common import get_mtime, style_map, paritygen, countgen, get_contact
-from common import ErrorResponse
+from common import get_mtime, style_map, paritygen, countgen, ErrorResponse
 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
 from request import wsgirequest
 import webcommands, protocol, webutil
@@ -32,54 +31,6 @@
     'static': [('cmd', ['static']), ('file', None)]
 }
 
-def _up(p):
-    if p[0] != "/":
-        p = "/" + p
-    if p[-1] == "/":
-        p = p[:-1]
-    up = os.path.dirname(p)
-    if up == "/":
-        return "/"
-    return up + "/"
-
-def revnavgen(pos, pagelen, limit, nodefunc):
-    def seq(factor, limit=None):
-        if limit:
-            yield limit
-            if limit >= 20 and limit <= 40:
-                yield 50
-        else:
-            yield 1 * factor
-            yield 3 * factor
-        for f in seq(factor * 10):
-            yield f
-
-    def nav(**map):
-        l = []
-        last = 0
-        for f in seq(1, pagelen):
-            if f < pagelen or f <= last:
-                continue
-            if f > limit:
-                break
-            last = f
-            if pos + f < limit:
-                l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
-            if pos - f >= 0:
-                l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
-
-        try:
-            yield {"label": "(0)", "node": hex(nodefunc('0').node())}
-
-            for label, node in l:
-                yield {"label": label, "node": node}
-
-            yield {"label": "tip", "node": "tip"}
-        except RepoError:
-            pass
-
-    return nav
-
 class hgweb(object):
     def __init__(self, repo, name=None):
         if isinstance(repo, str):
@@ -407,476 +358,12 @@
             yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
                                           opts=diffopts), f, tn)
 
-    def changelog(self, tmpl, ctx, shortlog=False):
-        def changelist(limit=0,**map):
-            cl = self.repo.changelog
-            l = [] # build a list in forward order for efficiency
-            for i in xrange(start, end):
-                ctx = self.repo.changectx(i)
-                n = ctx.node()
-                showtags = webutil.showtag(self.repo, tmpl, 'changelogtag', n)
-
-                l.insert(0, {"parity": parity.next(),
-                             "author": ctx.user(),
-                             "parent": webutil.siblings(ctx.parents(), i - 1),
-                             "child": webutil.siblings(ctx.children(), i + 1),
-                             "changelogtag": showtags,
-                             "desc": ctx.description(),
-                             "date": ctx.date(),
-                             "files": self.listfilediffs(tmpl, ctx.files(), n),
-                             "rev": i,
-                             "node": hex(n),
-                             "tags": webutil.nodetagsdict(self.repo, n),
-                             "inbranch": webutil.nodeinbranch(self.repo, ctx),
-                             "branches": webutil.nodebranchdict(self.repo, ctx)
-                            })
-
-            if limit > 0:
-                l = l[:limit]
-
-            for e in l:
-                yield e
-
-        maxchanges = shortlog and self.maxshortchanges or self.maxchanges
-        cl = self.repo.changelog
-        count = cl.count()
-        pos = ctx.rev()
-        start = max(0, pos - maxchanges + 1)
-        end = min(count, start + maxchanges)
-        pos = end - 1
-        parity = paritygen(self.stripecount, offset=start-end)
-
-        changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
-
-        return tmpl(shortlog and 'shortlog' or 'changelog',
-                    changenav=changenav,
-                    node=hex(cl.tip()),
-                    rev=pos, changesets=count,
-                    entries=lambda **x: changelist(limit=0,**x),
-                    latestentry=lambda **x: changelist(limit=1,**x),
-                    archives=self.archivelist("tip"))
-
-    def search(self, tmpl, query):
-
-        def changelist(**map):
-            cl = self.repo.changelog
-            count = 0
-            qw = query.lower().split()
-
-            def revgen():
-                for i in xrange(cl.count() - 1, 0, -100):
-                    l = []
-                    for j in xrange(max(0, i - 100), i + 1):
-                        ctx = self.repo.changectx(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(self.repo, tmpl, 'changelogtag', n)
-
-                yield tmpl('searchentry',
-                           parity=parity.next(),
-                           author=ctx.user(),
-                           parent=webutil.siblings(ctx.parents()),
-                           child=webutil.siblings(ctx.children()),
-                           changelogtag=showtags,
-                           desc=ctx.description(),
-                           date=ctx.date(),
-                           files=self.listfilediffs(tmpl, ctx.files(), n),
-                           rev=ctx.rev(),
-                           node=hex(n),
-                           tags=webutil.nodetagsdict(self.repo, n),
-                           inbranch=webutil.nodeinbranch(self.repo, ctx),
-                           branches=webutil.nodebranchdict(self.repo, ctx))
-
-                if count >= self.maxchanges:
-                    break
-
-        cl = self.repo.changelog
-        parity = paritygen(self.stripecount)
-
-        return tmpl('search',
-                    query=query,
-                    node=hex(cl.tip()),
-                    entries=changelist,
-                    archives=self.archivelist("tip"))
-
-    def changeset(self, tmpl, ctx):
-        n = ctx.node()
-        showtags = webutil.showtag(self.repo, tmpl, 'changesettag', n)
-        parents = ctx.parents()
-        p1 = parents[0].node()
-
-        files = []
-        parity = paritygen(self.stripecount)
-        for f in ctx.files():
-            files.append(tmpl("filenodelink",
-                              node=hex(n), file=f,
-                              parity=parity.next()))
-
-        def diff(**map):
-            yield self.diff(tmpl, p1, n, None)
-
-        return tmpl('changeset',
-                    diff=diff,
-                    rev=ctx.rev(),
-                    node=hex(n),
-                    parent=webutil.siblings(parents),
-                    child=webutil.siblings(ctx.children()),
-                    changesettag=showtags,
-                    author=ctx.user(),
-                    desc=ctx.description(),
-                    date=ctx.date(),
-                    files=files,
-                    archives=self.archivelist(hex(n)),
-                    tags=webutil.nodetagsdict(self.repo, n),
-                    branch=webutil.nodebranchnodefault(ctx),
-                    inbranch=webutil.nodeinbranch(self.repo, ctx),
-                    branches=webutil.nodebranchdict(self.repo, ctx))
-
-    def filelog(self, tmpl, fctx):
-        f = fctx.path()
-        fl = fctx.filelog()
-        count = fl.count()
-        pagelen = self.maxshortchanges
-        pos = fctx.filerev()
-        start = max(0, pos - pagelen + 1)
-        end = min(count, start + pagelen)
-        pos = end - 1
-        parity = paritygen(self.stripecount, offset=start-end)
-
-        def entries(limit=0, **map):
-            l = []
-
-            for i in xrange(start, end):
-                ctx = fctx.filectx(i)
-                n = fl.node(i)
-
-                l.insert(0, {"parity": parity.next(),
-                             "filerev": i,
-                             "file": f,
-                             "node": hex(ctx.node()),
-                             "author": ctx.user(),
-                             "date": ctx.date(),
-                             "rename": webutil.renamelink(fl, n),
-                             "parent": webutil.siblings(fctx.parents()),
-                             "child": webutil.siblings(fctx.children()),
-                             "desc": ctx.description()})
-
-            if limit > 0:
-                l = l[:limit]
-
-            for e in l:
-                yield e
-
-        nodefunc = lambda x: fctx.filectx(fileid=x)
-        nav = revnavgen(pos, pagelen, 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))
-
-    def filerevision(self, tmpl, fctx):
-        f = fctx.path()
-        text = fctx.data()
-        fl = fctx.filelog()
-        n = fctx.filenode()
-        parity = paritygen(self.stripecount)
-
-        if util.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(1)):
-                yield {"line": t,
-                       "lineid": "l%d" % (lineno + 1),
-                       "linenumber": "% 6d" % (lineno + 1),
-                       "parity": parity.next()}
-
-        return tmpl("filerevision",
-                    file=f,
-                    path=_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.siblings(fctx.parents()),
-                    child=webutil.siblings(fctx.children()),
-                    rename=webutil.renamelink(fl, n),
-                    permissions=fctx.manifest().flags(f))
-
-    def fileannotate(self, tmpl, fctx):
-        f = fctx.path()
-        n = fctx.filenode()
-        fl = fctx.filelog()
-        parity = paritygen(self.stripecount)
-
-        def annotate(**map):
-            last = None
-            if util.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()
-                name = self.repo.ui.shortuser(f.user())
-
-                if last != fnode:
-                    last = fnode
-
-                yield {"parity": parity.next(),
-                       "node": hex(f.node()),
-                       "rev": f.rev(),
-                       "author": name,
-                       "file": f.path(),
-                       "targetline": targetline,
-                       "line": l,
-                       "lineid": "l%d" % (lineno + 1),
-                       "linenumber": "% 6d" % (lineno + 1)}
-
-        return tmpl("fileannotate",
-                    file=f,
-                    annotate=annotate,
-                    path=_up(f),
-                    rev=fctx.rev(),
-                    node=hex(fctx.node()),
-                    author=fctx.user(),
-                    date=fctx.date(),
-                    desc=fctx.description(),
-                    rename=webutil.renamelink(fl, n),
-                    branch=webutil.nodebranchnodefault(fctx),
-                    parent=webutil.siblings(fctx.parents()),
-                    child=webutil.siblings(fctx.children()),
-                    permissions=fctx.manifest().flags(f))
-
-    def manifest(self, tmpl, ctx, path):
-        mf = ctx.manifest()
-        node = ctx.node()
-
-        files = {}
-        parity = paritygen(self.stripecount)
-
-        if path and path[-1] != "/":
-            path += "/"
-        l = len(path)
-        abspath = "/" + path
-
-        for f, n in mf.items():
-            if f[:l] != path:
-                continue
-            remain = f[l:]
-            if "/" in remain:
-                short = remain[:remain.index("/") + 1] # bleah
-                files[short] = (f, None)
-            else:
-                short = os.path.basename(remain)
-                files[short] = (f, n)
-
-        if not files:
-            raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
-
-        def filelist(**map):
-            fl = files.keys()
-            fl.sort()
-            for f in fl:
-                full, fnode = files[f]
-                if not fnode:
-                    continue
-
-                fctx = ctx.filectx(full)
-                yield {"file": full,
-                       "parity": parity.next(),
-                       "basename": f,
-                       "date": fctx.changectx().date(),
-                       "size": fctx.size(),
-                       "permissions": mf.flags(full)}
-
-        def dirlist(**map):
-            fl = files.keys()
-            fl.sort()
-            for f in fl:
-                full, fnode = files[f]
-                if fnode:
-                    continue
-
-                yield {"parity": parity.next(),
-                       "path": "%s%s" % (abspath, f),
-                       "basename": f[:-1]}
-
-        return tmpl("manifest",
-                    rev=ctx.rev(),
-                    node=hex(node),
-                    path=abspath,
-                    up=_up(abspath),
-                    upparity=parity.next(),
-                    fentries=filelist,
-                    dentries=dirlist,
-                    archives=self.archivelist(hex(node)),
-                    tags=webutil.nodetagsdict(self.repo, node),
-                    inbranch=webutil.nodeinbranch(self.repo, ctx),
-                    branches=webutil.nodebranchdict(self.repo, ctx))
-
-    def tags(self, tmpl):
-        i = self.repo.tagslist()
-        i.reverse()
-        parity = paritygen(self.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": self.repo.changectx(n).date(),
-                       "node": hex(n)}
-
-        return tmpl("tags",
-                    node=hex(self.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 summary(self, tmpl):
-        i = self.repo.tagslist()
-        i.reverse()
-
-        def tagentries(**map):
-            parity = paritygen(self.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=self.repo.changectx(n).date())
-
-
-        def branches(**map):
-            parity = paritygen(self.stripecount)
-
-            b = self.repo.branchtags()
-            l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
-            l.sort()
-
-            for r,n,t in l:
-                ctx = self.repo.changectx(n)
-
-                yield {'parity': parity.next(),
-                       'branch': t,
-                       'node': hex(n),
-                       'date': ctx.date()}
-
-        def changelist(**map):
-            parity = paritygen(self.stripecount, offset=start-end)
-            l = [] # build a list in forward order for efficiency
-            for i in xrange(start, end):
-                ctx = self.repo.changectx(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(self.repo, n),
-                    inbranch=webutil.nodeinbranch(self.repo, ctx),
-                    branches=webutil.nodebranchdict(self.repo, ctx)))
-
-            yield l
-
-        cl = self.repo.changelog
-        count = cl.count()
-        start = max(0, count - self.maxchanges)
-        end = min(count, start + self.maxchanges)
-
-        return tmpl("summary",
-                    desc=self.config("web", "description", "unknown"),
-                    owner=get_contact(self.config) or "unknown",
-                    lastchange=cl.read(cl.tip())[2],
-                    tags=tagentries,
-                    branches=branches,
-                    shortlog=changelist,
-                    node=hex(cl.tip()),
-                    archives=self.archivelist("tip"))
-
-    def filediff(self, tmpl, fctx):
-        n = fctx.node()
-        path = fctx.path()
-        parents = fctx.parents()
-        p1 = parents and parents[0].node() or nullid
-
-        def diff(**map):
-            yield self.diff(tmpl, p1, n, [path])
-
-        return tmpl("filediff",
-                    file=path,
-                    node=hex(n),
-                    rev=fctx.rev(),
-                    branch=webutil.nodebranchnodefault(fctx),
-                    parent=webutil.siblings(parents),
-                    child=webutil.siblings(fctx.children()),
-                    diff=diff)
-
     archive_specs = {
         'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
         'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
         'zip': ('application/zip', 'zip', '.zip', None),
         }
 
-    def archive(self, tmpl, req, key, type_):
-        reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
-        cnode = self.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 = self.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(self.repo, req, cnode, artype, prefix=name)
-
     def check_perm(self, req, op, default):
         '''check permission for operation based on user auth.
         return true if op allowed, else false.
--- a/mercurial/hgweb/webcommands.py	Fri Mar 28 19:37:28 2008 +0100
+++ b/mercurial/hgweb/webcommands.py	Fri Mar 28 19:40:44 2008 +0100
@@ -5,12 +5,14 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, mimetypes
+import os, mimetypes, re
 import webutil
-from mercurial import revlog
+from mercurial import revlog, archival
+from mercurial.node import hex, nullid
 from mercurial.util import binary
 from mercurial.repo import RepoError
-from common import staticfile, ErrorResponse, HTTP_OK, HTTP_NOT_FOUND
+from common import paritygen, staticfile, get_contact, ErrorResponse
+from common import HTTP_OK, HTTP_NOT_FOUND
 
 # __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.
@@ -30,7 +32,7 @@
 def rawfile(web, req, tmpl):
     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     if not path:
-        content = web.manifest(tmpl, webutil.changectx(web.repo, req), path)
+        content = manifest(web, req, tmpl)
         req.respond(HTTP_OK, web.ctype)
         return content
 
@@ -38,7 +40,7 @@
         fctx = webutil.filectx(web.repo, req)
     except revlog.LookupError, inst:
         try:
-            content = web.manifest(tmpl, webutil.changectx(web.repo, req), path)
+            content = manifest(web, req, tmpl)
             req.respond(HTTP_OK, web.ctype)
             return content
         except ErrorResponse:
@@ -53,19 +55,111 @@
     req.respond(HTTP_OK, mt, path, len(text))
     return [text]
 
+def _filerevision(web, tmpl, fctx):
+    f = fctx.path()
+    text = fctx.data()
+    fl = fctx.filelog()
+    n = fctx.filenode()
+    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(1)):
+            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.siblings(fctx.parents()),
+                child=webutil.siblings(fctx.children()),
+                rename=webutil.renamelink(fl, n),
+                permissions=fctx.manifest().flags(f))
+
 def file(web, req, tmpl):
     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     if path:
         try:
-            return web.filerevision(tmpl, webutil.filectx(web.repo, req))
+            return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
         except revlog.LookupError, inst:
             pass
 
     try:
-        return web.manifest(tmpl, webutil.changectx(web.repo, req), path)
+        return manifest(web, req, tmpl)
     except ErrorResponse:
         raise inst
 
+def _search(web, tmpl, query):
+
+    def changelist(**map):
+        cl = web.repo.changelog
+        count = 0
+        qw = query.lower().split()
+
+        def revgen():
+            for i in xrange(cl.count() - 1, 0, -100):
+                l = []
+                for j in xrange(max(0, i - 100), i + 1):
+                    ctx = web.repo.changectx(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)
+
+            yield tmpl('searchentry',
+                       parity=parity.next(),
+                       author=ctx.user(),
+                       parent=webutil.siblings(ctx.parents()),
+                       child=webutil.siblings(ctx.children()),
+                       changelogtag=showtags,
+                       desc=ctx.description(),
+                       date=ctx.date(),
+                       files=web.listfilediffs(tmpl, ctx.files(), n),
+                       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 >= web.maxchanges:
+                break
+
+    cl = web.repo.changelog
+    parity = paritygen(web.stripecount)
+
+    return tmpl('search',
+                query=query,
+                node=hex(cl.tip()),
+                entries=changelist,
+                archives=web.archivelist("tip"))
+
 def changelog(web, req, tmpl, shortlog = False):
     if 'node' in req.form:
         ctx = webutil.changectx(web.repo, req)
@@ -77,47 +171,396 @@
         try:
             ctx = web.repo.changectx(hi)
         except RepoError:
-            return web.search(tmpl, hi) # XXX redirect to 404 page?
+            return _search(web, tmpl, hi) # XXX redirect to 404 page?
+
+    def changelist(limit=0, **map):
+        cl = web.repo.changelog
+        l = [] # build a list in forward order for efficiency
+        for i in xrange(start, end):
+            ctx = web.repo.changectx(i)
+            n = ctx.node()
+            showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
+
+            l.insert(0, {"parity": parity.next(),
+                         "author": ctx.user(),
+                         "parent": webutil.siblings(ctx.parents(), i - 1),
+                         "child": webutil.siblings(ctx.children(), i + 1),
+                         "changelogtag": showtags,
+                         "desc": ctx.description(),
+                         "date": ctx.date(),
+                         "files": web.listfilediffs(tmpl, ctx.files(), n),
+                         "rev": i,
+                         "node": hex(n),
+                         "tags": webutil.nodetagsdict(web.repo, n),
+                         "inbranch": webutil.nodeinbranch(web.repo, ctx),
+                         "branches": webutil.nodebranchdict(web.repo, ctx)
+                        })
 
-    return web.changelog(tmpl, ctx, shortlog = shortlog)
+        if limit > 0:
+            l = l[:limit]
+
+        for e in l:
+            yield e
+
+    maxchanges = shortlog and web.maxshortchanges or web.maxchanges
+    cl = web.repo.changelog
+    count = cl.count()
+    pos = ctx.rev()
+    start = max(0, pos - maxchanges + 1)
+    end = min(count, start + maxchanges)
+    pos = end - 1
+    parity = paritygen(web.stripecount, offset=start-end)
+
+    changenav = webutil.revnavgen(pos, maxchanges, count, web.repo.changectx)
+
+    return tmpl(shortlog and 'shortlog' or 'changelog',
+                changenav=changenav,
+                node=hex(cl.tip()),
+                rev=pos, changesets=count,
+                entries=lambda **x: changelist(limit=0,**x),
+                latestentry=lambda **x: changelist(limit=1,**x),
+                archives=web.archivelist("tip"))
 
 def shortlog(web, req, tmpl):
     return changelog(web, req, tmpl, shortlog = True)
 
 def changeset(web, req, tmpl):
-    return web.changeset(tmpl, webutil.changectx(web.repo, req))
+    ctx = webutil.changectx(web.repo, req)
+    n = ctx.node()
+    showtags = webutil.showtag(web.repo, tmpl, 'changesettag', n)
+    parents = ctx.parents()
+    p1 = parents[0].node()
+
+    files = []
+    parity = paritygen(web.stripecount)
+    for f in ctx.files():
+        files.append(tmpl("filenodelink",
+                          node=hex(n), file=f,
+                          parity=parity.next()))
+
+    diffs = web.diff(tmpl, p1, n, None)
+    return tmpl('changeset',
+                diff=diffs,
+                rev=ctx.rev(),
+                node=hex(n),
+                parent=webutil.siblings(parents),
+                child=webutil.siblings(ctx.children()),
+                changesettag=showtags,
+                author=ctx.user(),
+                desc=ctx.description(),
+                date=ctx.date(),
+                files=files,
+                archives=web.archivelist(hex(n)),
+                tags=webutil.nodetagsdict(web.repo, n),
+                branch=webutil.nodebranchnodefault(ctx),
+                inbranch=webutil.nodeinbranch(web.repo, ctx),
+                branches=webutil.nodebranchdict(web.repo, ctx))
 
 rev = changeset
 
 def manifest(web, req, tmpl):
-    return web.manifest(tmpl, webutil.changectx(web.repo, req),
-                        webutil.cleanpath(web.repo, req.form['path'][0]))
+    ctx = webutil.changectx(web.repo, req)
+    path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
+    mf = ctx.manifest()
+    node = ctx.node()
+
+    files = {}
+    parity = paritygen(web.stripecount)
+
+    if path and path[-1] != "/":
+        path += "/"
+    l = len(path)
+    abspath = "/" + path
+
+    for f, n in mf.items():
+        if f[:l] != path:
+            continue
+        remain = f[l:]
+        if "/" in remain:
+            short = remain[:remain.index("/") + 1] # bleah
+            files[short] = (f, None)
+        else:
+            short = os.path.basename(remain)
+            files[short] = (f, n)
+
+    if not files:
+        raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
+
+    def filelist(**map):
+        fl = files.keys()
+        fl.sort()
+        for f in fl:
+            full, fnode = files[f]
+            if not fnode:
+                continue
+
+            fctx = ctx.filectx(full)
+            yield {"file": full,
+                   "parity": parity.next(),
+                   "basename": f,
+                   "date": fctx.changectx().date(),
+                   "size": fctx.size(),
+                   "permissions": mf.flags(full)}
+
+    def dirlist(**map):
+        fl = files.keys()
+        fl.sort()
+        for f in fl:
+            full, fnode = files[f]
+            if fnode:
+                continue
+
+            yield {"parity": parity.next(),
+                   "path": "%s%s" % (abspath, f),
+                   "basename": f[:-1]}
+
+    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):
-    return web.tags(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.changectx(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 summary(web, req, tmpl):
-    return web.summary(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.changectx(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.items()]
+        l.sort()
+
+        for r,n,t in l:
+            ctx = web.repo.changectx(n)
+            yield {'parity': parity.next(),
+                   'branch': t,
+                   'node': hex(n),
+                   'date': ctx.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.changectx(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
+
+    cl = web.repo.changelog
+    count = cl.count()
+    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=cl.read(cl.tip())[2],
+                tags=tagentries,
+                branches=branches,
+                shortlog=changelist,
+                node=hex(cl.tip()),
+                archives=web.archivelist("tip"))
 
 def filediff(web, req, tmpl):
-    return web.filediff(tmpl, webutil.filectx(web.repo, req))
+    fctx = webutil.filectx(web.repo, req)
+    n = fctx.node()
+    path = fctx.path()
+    parents = fctx.parents()
+    p1 = parents and parents[0].node() or nullid
+
+    diffs = web.diff(tmpl, p1, n, [path])
+    return tmpl("filediff",
+                file=path,
+                node=hex(n),
+                rev=fctx.rev(),
+                branch=webutil.nodebranchnodefault(fctx),
+                parent=webutil.siblings(parents),
+                child=webutil.siblings(fctx.children()),
+                diff=diffs)
 
 diff = filediff
 
 def annotate(web, req, tmpl):
-    return web.fileannotate(tmpl, webutil.filectx(web.repo, req))
+    fctx = webutil.filectx(web.repo, req)
+    f = fctx.path()
+    n = fctx.filenode()
+    fl = fctx.filelog()
+    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()
+            name = web.repo.ui.shortuser(f.user())
+
+            if last != fnode:
+                last = fnode
+
+            yield {"parity": parity.next(),
+                   "node": hex(f.node()),
+                   "rev": f.rev(),
+                   "author": name,
+                   "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(fl, n),
+                branch=webutil.nodebranchnodefault(fctx),
+                parent=webutil.siblings(fctx.parents()),
+                child=webutil.siblings(fctx.children()),
+                permissions=fctx.manifest().flags(f))
 
 def filelog(web, req, tmpl):
-    return web.filelog(tmpl, webutil.filectx(web.repo, req))
+    fctx = webutil.filectx(web.repo, req)
+    f = fctx.path()
+    fl = fctx.filelog()
+    count = fl.count()
+    pagelen = web.maxshortchanges
+    pos = fctx.filerev()
+    start = max(0, pos - pagelen + 1)
+    end = min(count, start + pagelen)
+    pos = end - 1
+    parity = paritygen(web.stripecount, offset=start-end)
+
+    def entries(limit=0, **map):
+        l = []
+
+        for i in xrange(start, end):
+            ctx = fctx.filectx(i)
+            n = fl.node(i)
+
+            l.insert(0, {"parity": parity.next(),
+                         "filerev": i,
+                         "file": f,
+                         "node": hex(ctx.node()),
+                         "author": ctx.user(),
+                         "date": ctx.date(),
+                         "rename": webutil.renamelink(fl, n),
+                         "parent": webutil.siblings(fctx.parents()),
+                         "child": webutil.siblings(fctx.children()),
+                         "desc": ctx.description()})
+
+        if limit > 0:
+            l = l[:limit]
+
+        for e in l:
+            yield e
+
+    nodefunc = lambda x: fctx.filectx(fileid=x)
+    nav = webutil.revnavgen(pos, pagelen, 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))
+
 
 def archive(web, req, tmpl):
     type_ = req.form['type'][0]
     allowed = web.configlist("web", "allow_archive")
-    if (type_ in web.archives and (type_ in allowed or
+    key = req.form['node'][0]
+
+    if not (type_ in web.archives and (type_ in allowed or
         web.configbool("web", "allow" + type_, False))):
-        web.archive(tmpl, req, req.form['node'][0], type_)
-        return []
-    raise ErrorResponse(HTTP_NOT_FOUND, 'unsupported archive type: %s' % type_)
+        msg = 'Unsupported archive type: %s' % type_
+        raise ErrorResponse(HTTP_NOT_FOUND, 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/mercurial/hgweb/webutil.py	Fri Mar 28 19:37:28 2008 +0100
+++ b/mercurial/hgweb/webutil.py	Fri Mar 28 19:40:44 2008 +0100
@@ -6,10 +6,59 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
+import os
 from mercurial.node import hex, nullid
 from mercurial.repo import RepoError
 from mercurial import util
 
+def up(p):
+    if p[0] != "/":
+        p = "/" + p
+    if p[-1] == "/":
+        p = p[:-1]
+    up = os.path.dirname(p)
+    if up == "/":
+        return "/"
+    return up + "/"
+
+def revnavgen(pos, pagelen, limit, nodefunc):
+    def seq(factor, limit=None):
+        if limit:
+            yield limit
+            if limit >= 20 and limit <= 40:
+                yield 50
+        else:
+            yield 1 * factor
+            yield 3 * factor
+        for f in seq(factor * 10):
+            yield f
+
+    def nav(**map):
+        l = []
+        last = 0
+        for f in seq(1, pagelen):
+            if f < pagelen or f <= last:
+                continue
+            if f > limit:
+                break
+            last = f
+            if pos + f < limit:
+                l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
+            if pos - f >= 0:
+                l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
+
+        try:
+            yield {"label": "(0)", "node": hex(nodefunc('0').node())}
+
+            for label, node in l:
+                yield {"label": label, "node": node}
+
+            yield {"label": "tip", "node": "tip"}
+        except RepoError:
+            pass
+
+    return nav
+
 def siblings(siblings=[], hiderev=None, **args):
     siblings = [s for s in siblings if s.node() != nullid]
     if len(siblings) == 1 and siblings[0].rev() == hiderev: