changeset 6402:d1cf40b596f8

Merge with crew-stable
author Patrick Mezard <pmezard@gmail.com>
date Sat, 29 Mar 2008 17:27:35 +0100
parents 3f0294536b24 (diff) 635c57cf0de8 (current diff)
children f08662abdf3f
files
diffstat 10 files changed, 831 insertions(+), 705 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/mercurial.el	Sat Mar 29 17:16:39 2008 +0100
+++ b/contrib/mercurial.el	Sat Mar 29 17:27:35 2008 +0100
@@ -35,8 +35,10 @@
 ;; This code has been developed under XEmacs 21.5, and may not work as
 ;; well under GNU Emacs (albeit tested under 21.4).  Patches to
 ;; enhance the portability of this code, fix bugs, and add features
-;; are most welcome.  You can clone a Mercurial repository for this
-;; package from http://www.serpentine.com/hg/hg-emacs
+;; are most welcome.
+
+;; As of version 22.3, GNU Emacs's VC mode has direct support for
+;; Mercurial, so this package may not prove as useful there.
 
 ;; Please send problem reports and suggestions to bos@serpentine.com.
 
--- a/hgext/highlight.py	Sat Mar 29 17:16:39 2008 +0100
+++ b/hgext/highlight.py	Sat Mar 29 17:27:35 2008 +0100
@@ -20,18 +20,13 @@
 
 # pygmentize -f html -S <newstyle>
 
-
 -- Adam Hupp <adam@hupp.org>
-
-
 """
 
 from mercurial import demandimport
-demandimport.ignore.extend(['pkgutil',
-                            'pkg_resources',
-                            '__main__',])
+demandimport.ignore.extend(['pkgutil', '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
 
@@ -43,7 +38,8 @@
 SYNTAX_CSS = ('\n<link rel="stylesheet" href="#staticurl#highlight.css" '
               'type="text/css" />')
 
-def pygmentize(self, tmpl, fctx, field):
+def pygmentize(field, fctx, style, tmpl):
+
     # append a <link ...> to the syntax highlighting css
     old_header = ''.join(tmpl('header'))
     if SYNTAX_CSS not in old_header:
@@ -54,7 +50,6 @@
     if util.binary(text):
         return
 
-    style = self.config("web", "pygments_style", "colorful")
     # To get multi-line strings right, we can't format line-by-line
     try:
         lexer = guess_lexer_for_filename(fctx.path(), text,
@@ -79,20 +74,21 @@
     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):
+    style = web.config('web', 'pygments_style', 'colorful')
+    pygmentize('fileline', fctx, style, tmpl)
+    return web_filerevision(web, tmpl, fctx)
 
-    return realannotate(self, tmpl, fctx)
+def annotate_highlight(web, req, tmpl):
+    fctx = webutil.filectx(web.repo, req)
+    style = web.config('web', 'pygments_style', 'colorful')
+    pygmentize('annotateline', fctx, style, tmpl)
+    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	Sat Mar 29 17:16:39 2008 +0100
+++ b/hgext/keyword.py	Sat Mar 29 17:27:35 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	Sat Mar 29 17:16:39 2008 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Sat Mar 29 17:27:35 2008 +0100
@@ -6,16 +6,15 @@
 # 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, mimetools, cStringIO
-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
+import webcommands, protocol, webutil
 
 shortcuts = {
     'cl': [('cmd', ['changelog']), ('rev', None)],
@@ -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):
@@ -226,17 +177,8 @@
         try:
 
             tmpl = self.templater(req)
-            try:
-                ctype = tmpl('mimetype', encoding=self.encoding)
-                ctype = templater.stringify(ctype)
-            except KeyError:
-                # old templates with inline HTTP headers?
-                if 'mimetype' in tmpl:
-                    raise
-                header = tmpl('header', encoding=self.encoding)
-                header_file = cStringIO.StringIO(templater.stringify(header))
-                msg = mimetools.Message(header_file, 0)
-                ctype = msg['content-type']
+            ctype = tmpl('mimetype', encoding=self.encoding)
+            ctype = templater.stringify(ctype)
 
             if cmd == '':
                 req.form['cmd'] = [tmpl.cache['default']]
@@ -291,13 +233,7 @@
         # some functions for the templater
 
         def header(**map):
-            header = tmpl('header', encoding=self.encoding, **map)
-            if 'mimetype' not in tmpl:
-                # old template with inline HTTP headers
-                header_file = cStringIO.StringIO(templater.stringify(header))
-                msg = mimetools.Message(header_file, 0)
-                header = header_file.read()
-            yield header
+            yield tmpl('header', encoding=self.encoding, **map)
 
         def footer(**map):
             yield tmpl("footer", **map)
@@ -355,54 +291,6 @@
         if len(files) > self.maxfiles:
             yield tmpl("fileellipses")
 
-    def siblings(self, siblings=[], hiderev=None, **args):
-        siblings = [s for s in siblings if s.node() != nullid]
-        if len(siblings) == 1 and siblings[0].rev() == hiderev:
-            return
-        for s in siblings:
-            d = {'node': hex(s.node()), 'rev': s.rev()}
-            if hasattr(s, 'path'):
-                d['file'] = s.path()
-            d.update(args)
-            yield d
-
-    def renamelink(self, fl, node):
-        r = fl.renamed(node)
-        if r:
-            return [dict(file=r[0], node=hex(r[1]))]
-        return []
-
-    def nodetagsdict(self, node):
-        return [{"name": i} for i in self.repo.nodetags(node)]
-
-    def nodebranchdict(self, ctx):
-        branches = []
-        branch = ctx.branch()
-        # If this is an empty repo, ctx.node() == nullid,
-        # ctx.branch() == 'default', but branchtags() is
-        # an empty dict. Using dict.get avoids a traceback.
-        if self.repo.branchtags().get(branch) == ctx.node():
-            branches.append({"name": branch})
-        return branches
-
-    def nodeinbranch(self, ctx):
-        branches = []
-        branch = ctx.branch()
-        if branch != 'default' and self.repo.branchtags().get(branch) != ctx.node():
-            branches.append({"name": branch})
-        return branches
-
-    def nodebranchnodefault(self, ctx):
-        branches = []
-        branch = ctx.branch()
-        if branch != 'default':
-            branches.append({"name": branch})
-        return branches
-
-    def showtag(self, tmpl, t1, node=nullid, **args):
-        for t in self.repo.nodetags(node):
-            yield tmpl(t1, tag=t, **args)
-
     def diff(self, tmpl, node1, node2, files):
         def filterfiles(filters, files):
             l = [x for x in files if x in filters]
@@ -470,514 +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 = self.showtag(tmpl, 'changelogtag', n)
-
-                l.insert(0, {"parity": parity.next(),
-                             "author": ctx.user(),
-                             "parent": self.siblings(ctx.parents(), i - 1),
-                             "child": self.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": self.nodetagsdict(n),
-                             "inbranch": self.nodeinbranch(ctx),
-                             "branches": self.nodebranchdict(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 = self.showtag(tmpl, 'changelogtag', n)
-
-                yield tmpl('searchentry',
-                           parity=parity.next(),
-                           author=ctx.user(),
-                           parent=self.siblings(ctx.parents()),
-                           child=self.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=self.nodetagsdict(n),
-                           inbranch=self.nodeinbranch(ctx),
-                           branches=self.nodebranchdict(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 = self.showtag(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=self.siblings(parents),
-                    child=self.siblings(ctx.children()),
-                    changesettag=showtags,
-                    author=ctx.user(),
-                    desc=ctx.description(),
-                    date=ctx.date(),
-                    files=files,
-                    archives=self.archivelist(hex(n)),
-                    tags=self.nodetagsdict(n),
-                    branch=self.nodebranchnodefault(ctx),
-                    inbranch=self.nodeinbranch(ctx),
-                    branches=self.nodebranchdict(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": self.renamelink(fl, n),
-                             "parent": self.siblings(fctx.parents()),
-                             "child": self.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=self.nodebranchnodefault(fctx),
-                    parent=self.siblings(fctx.parents()),
-                    child=self.siblings(fctx.children()),
-                    rename=self.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=self.renamelink(fl, n),
-                    branch=self.nodebranchnodefault(fctx),
-                    parent=self.siblings(fctx.parents()),
-                    child=self.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=self.nodetagsdict(node),
-                    inbranch=self.nodeinbranch(ctx),
-                    branches=self.nodebranchdict(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=self.nodetagsdict(n),
-                    inbranch=self.nodeinbranch(ctx),
-                    branches=self.nodebranchdict(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=self.nodebranchnodefault(fctx),
-                    parent=self.siblings(parents),
-                    child=self.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)
-
-    # add tags to things
-    # tags -> list of changesets corresponding to tags
-    # find tag, changeset, file
-
-    def cleanpath(self, path):
-        path = path.lstrip('/')
-        return util.canonpath(self.repo.root, '', path)
-
-    def changectx(self, req):
-        if 'node' in req.form:
-            changeid = req.form['node'][0]
-        elif 'manifest' in req.form:
-            changeid = req.form['manifest'][0]
-        else:
-            changeid = self.repo.changelog.count() - 1
-
-        try:
-            ctx = self.repo.changectx(changeid)
-        except RepoError:
-            man = self.repo.manifest
-            mn = man.lookup(changeid)
-            ctx = self.repo.changectx(man.linkrev(mn))
-
-        return ctx
-
-    def filectx(self, req):
-        path = self.cleanpath(req.form['file'][0])
-        if 'node' in req.form:
-            changeid = req.form['node'][0]
-        else:
-            changeid = req.form['filenode'][0]
-        try:
-            ctx = self.repo.changectx(changeid)
-            fctx = ctx.filectx(path)
-        except RepoError:
-            fctx = self.repo.filectx(path, fileid=changeid)
-
-        return fctx
-
     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/hgwebdir_mod.py	Sat Mar 29 17:16:39 2008 +0100
+++ b/mercurial/hgweb/hgwebdir_mod.py	Sat Mar 29 17:27:35 2008 +0100
@@ -6,7 +6,7 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import os, mimetools, cStringIO
+import os
 from mercurial.i18n import gettext as _
 from mercurial.repo import RepoError
 from mercurial import ui, hg, util, templater, templatefilters
@@ -81,17 +81,8 @@
 
                 virtual = req.env.get("PATH_INFO", "").strip('/')
                 tmpl = self.templater(req)
-                try:
-                    ctype = tmpl('mimetype', encoding=util._encoding)
-                    ctype = templater.stringify(ctype)
-                except KeyError:
-                    # old templates with inline HTTP headers?
-                    if 'mimetype' in tmpl:
-                        raise
-                    header = tmpl('header', encoding=util._encoding)
-                    header_file = cStringIO.StringIO(templater.stringify(header))
-                    msg = mimetools.Message(header_file, 0)
-                    ctype = msg['content-type']
+                ctype = tmpl('mimetype', encoding=util._encoding)
+                ctype = templater.stringify(ctype)
 
                 # a static file
                 if virtual.startswith('static/') or 'static' in req.form:
@@ -255,13 +246,7 @@
     def templater(self, req):
 
         def header(**map):
-            header = tmpl('header', encoding=util._encoding, **map)
-            if 'mimetype' not in tmpl:
-                # old template with inline HTTP headers
-                header_file = cStringIO.StringIO(templater.stringify(header))
-                msg = mimetools.Message(header_file, 0)
-                header = header_file.read()
-            yield header
+            yield tmpl('header', encoding=util._encoding, **map)
 
         def footer(**map):
             yield tmpl("footer", **map)
--- a/mercurial/hgweb/webcommands.py	Sat Mar 29 17:16:39 2008 +0100
+++ b/mercurial/hgweb/webcommands.py	Sat Mar 29 17:27:35 2008 +0100
@@ -5,10 +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
-from mercurial import revlog, util
+import os, mimetypes, re
+import webutil
+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.
@@ -26,17 +30,17 @@
         return changelog(web, req, tmpl)
 
 def rawfile(web, req, tmpl):
-    path = web.cleanpath(req.form.get('file', [''])[0])
+    path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     if not path:
-        content = web.manifest(tmpl, web.changectx(req), path)
+        content = manifest(web, req, tmpl)
         req.respond(HTTP_OK, web.ctype)
         return content
 
     try:
-        fctx = web.filectx(req)
+        fctx = webutil.filectx(web.repo, req)
     except revlog.LookupError, inst:
         try:
-            content = web.manifest(tmpl, web.changectx(req), path)
+            content = manifest(web, req, tmpl)
             req.respond(HTTP_OK, web.ctype)
             return content
         except ErrorResponse:
@@ -45,28 +49,120 @@
     path = fctx.path()
     text = fctx.data()
     mt = mimetypes.guess_type(path)[0]
-    if mt is None or util.binary(text):
+    if mt is None or binary(text):
         mt = mt or 'application/octet-stream'
 
     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 = web.cleanpath(req.form.get('file', [''])[0])
+    path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     if path:
         try:
-            return web.filerevision(tmpl, web.filectx(req))
+            return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
         except revlog.LookupError, inst:
             pass
 
     try:
-        return web.manifest(tmpl, web.changectx(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 = web.changectx(req)
+        ctx = webutil.changectx(web.repo, req)
     else:
         if 'rev' in req.form:
             hi = req.form['rev'][0]
@@ -75,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, web.changectx(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, web.changectx(req),
-                        web.cleanpath(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, web.filectx(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, web.filectx(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, web.filectx(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]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/hgweb/webutil.py	Sat Mar 29 17:27:35 2008 +0100
@@ -0,0 +1,143 @@
+# hgweb/webutil.py - utility library for the web interface.
+#
+# 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, 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:
+        return
+    for s in siblings:
+        d = {'node': hex(s.node()), 'rev': s.rev()}
+        if hasattr(s, 'path'):
+            d['file'] = s.path()
+        d.update(args)
+        yield d
+
+def renamelink(fl, node):
+    r = fl.renamed(node)
+    if r:
+        return [dict(file=r[0], node=hex(r[1]))]
+    return []
+
+def nodetagsdict(repo, node):
+    return [{"name": i} for i in repo.nodetags(node)]
+
+def nodebranchdict(repo, ctx):
+    branches = []
+    branch = ctx.branch()
+    # If this is an empty repo, ctx.node() == nullid,
+    # ctx.branch() == 'default', but branchtags() is
+    # an empty dict. Using dict.get avoids a traceback.
+    if repo.branchtags().get(branch) == ctx.node():
+        branches.append({"name": branch})
+    return branches
+
+def nodeinbranch(repo, ctx):
+    branches = []
+    branch = ctx.branch()
+    if branch != 'default' and repo.branchtags().get(branch) != ctx.node():
+        branches.append({"name": branch})
+    return branches
+
+def nodebranchnodefault(ctx):
+    branches = []
+    branch = ctx.branch()
+    if branch != 'default':
+        branches.append({"name": branch})
+    return branches
+
+def showtag(repo, tmpl, t1, node=nullid, **args):
+    for t in repo.nodetags(node):
+        yield tmpl(t1, tag=t, **args)
+
+def cleanpath(repo, path):
+    path = path.lstrip('/')
+    return util.canonpath(repo.root, '', path)
+
+def changectx(repo, req):
+    if 'node' in req.form:
+        changeid = req.form['node'][0]
+    elif 'manifest' in req.form:
+        changeid = req.form['manifest'][0]
+    else:
+        changeid = self.repo.changelog.count() - 1
+
+    try:
+        ctx = repo.changectx(changeid)
+    except RepoError:
+        man = repo.manifest
+        mn = man.lookup(changeid)
+        ctx = repo.changectx(man.linkrev(mn))
+
+    return ctx
+
+def filectx(repo, req):
+    path = cleanpath(repo, req.form['file'][0])
+    if 'node' in req.form:
+        changeid = req.form['node'][0]
+    else:
+        changeid = req.form['filenode'][0]
+    try:
+        ctx = repo.changectx(changeid)
+        fctx = ctx.filectx(path)
+    except RepoError:
+        fctx = repo.filectx(path, fileid=changeid)
+
+    return fctx
--- a/mercurial/manifest.py	Sat Mar 29 17:16:39 2008 +0100
+++ b/mercurial/manifest.py	Sat Mar 29 17:27:35 2008 +0100
@@ -8,7 +8,7 @@
 from node import bin, hex, nullid
 from revlog import revlog, RevlogError
 from i18n import _
-import array, struct, mdiff
+import array, struct, mdiff, parsers
 
 class manifestdict(dict):
     def __init__(self, mapping=None, flags=None):
@@ -39,14 +39,7 @@
 
     def parse(self, lines):
         mfdict = manifestdict()
-        fdict = mfdict._flags
-        for l in lines.splitlines():
-            f, n = l.split('\0')
-            if len(n) > 40:
-                fdict[f] = n[40:]
-                mfdict[f] = bin(n[:40])
-            else:
-                mfdict[f] = bin(n)
+        parsers.parse_manifest(mfdict, mfdict._flags, lines)
         return mfdict
 
     def readdelta(self, node):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/parsers.c	Sat Mar 29 17:27:35 2008 +0100
@@ -0,0 +1,169 @@
+/*
+ parsers.c - efficient content parsing
+
+ Copyright 2008 Matt Mackall <mpm@selenic.com> and others
+
+ This software may be used and distributed according to the terms of
+ the GNU General Public License, incorporated herein by reference.
+*/
+
+#include <Python.h>
+#include <ctype.h>
+#include <string.h>
+
+static int hexdigit(char c)
+{
+	if (c >= '0' && c <= '9')
+		return c - '0';
+
+	if (c >= 'A' && c <= 'F')
+		return c - 'A' + 10;
+
+	if (c >= 'a' && c <= 'f')
+		return c - 'a' + 10;
+	
+	return -1;
+}
+
+/*
+ * Turn a hex-encoded string into binary.
+ */
+static PyObject *unhexlify(const char *str, int len)
+{
+	PyObject *ret = NULL;
+	const char *c;
+	char *d;
+
+	if (len % 2) {
+		PyErr_SetString(PyExc_ValueError,
+				"input is not even in length");
+		goto bail;
+	}
+
+	ret = PyString_FromStringAndSize(NULL, len / 2);
+	if (!ret)
+		goto bail;
+
+	d = PyString_AsString(ret);
+	if (!d)
+		goto bail;
+
+	for (c = str; c < str + len;) {
+		int hi = hexdigit(*c++);
+		int lo = hexdigit(*c++);
+
+		if (hi == -1 || lo == -1) {
+			PyErr_SetString(PyExc_ValueError,
+					"input contains non-hex character");
+			goto bail;
+		}
+
+		*d++ = (hi << 4) | lo;
+	}
+	
+	goto done;
+	
+bail:
+	Py_XDECREF(ret);
+	ret = NULL;
+done:
+	return ret;
+}
+
+/*
+ * This code assumes that a manifest is stitched together with newline
+ * ('\n') characters.
+ */
+static PyObject *parse_manifest(PyObject *self, PyObject *args)
+{
+	PyObject *mfdict, *fdict;
+	char *str, *cur, *start, *zero;
+	int len;
+
+	if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
+			      &PyDict_Type, &mfdict,
+			      &PyDict_Type, &fdict,
+			      &str, &len))
+		goto quit;
+
+	for (start = cur = str, zero = NULL; cur < str + len; cur++) {
+		PyObject *file = NULL, *node = NULL;
+		PyObject *flags = NULL;
+		int nlen;
+
+		if (!*cur) {
+			zero = cur;
+			continue;
+		}
+		else if (*cur != '\n')
+			continue;
+
+		if (!zero) {
+			PyErr_SetString(PyExc_ValueError,
+					"manifest entry has no separator");
+			goto quit;
+		}
+
+		file = PyString_FromStringAndSize(start, zero - start);
+		if (!file)
+			goto bail;
+
+		nlen = cur - zero - 1;
+
+		node = unhexlify(zero + 1, nlen > 40 ? 40 : nlen);
+		if (!node)
+			goto bail;
+
+		if (nlen > 40) {
+			PyObject *flags;
+
+			flags = PyString_FromStringAndSize(zero + 41,
+							   nlen - 40);
+			if (!flags)
+				goto bail;
+
+			if (PyDict_SetItem(fdict, file, flags) == -1)
+				goto bail;
+		}
+
+		if (PyDict_SetItem(mfdict, file, node) == -1)
+			goto bail;
+
+		start = cur + 1;
+		zero = NULL;
+
+		Py_XDECREF(flags);
+		Py_XDECREF(node);
+		Py_XDECREF(file);
+		continue;
+	bail:
+		Py_XDECREF(flags);
+		Py_XDECREF(node);
+		Py_XDECREF(file);
+		goto quit;
+	}
+
+	if (len > 0 && *(cur - 1) != '\n') {
+		PyErr_SetString(PyExc_ValueError,
+				"manifest contains trailing garbage");
+		goto quit;
+	}
+
+	Py_INCREF(Py_None);
+	return Py_None;
+
+quit:
+	return NULL;
+}
+
+static char parsers_doc[] = "Efficient content parsing.";
+
+static PyMethodDef methods[] = {
+	{"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
+	{NULL, NULL}
+};
+
+PyMODINIT_FUNC initparsers(void)
+{
+	Py_InitModule3("parsers", methods, parsers_doc);
+}
--- a/setup.py	Sat Mar 29 17:16:39 2008 +0100
+++ b/setup.py	Sat Mar 29 17:27:35 2008 +0100
@@ -88,10 +88,11 @@
 cmdclass = {'install_data': install_package_data}
 
 ext_modules=[
-    Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
+    Extension('mercurial.base85', ['mercurial/base85.c']),
     Extension('mercurial.bdiff', ['mercurial/bdiff.c']),
-    Extension('mercurial.base85', ['mercurial/base85.c']),
-    Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c'])
+    Extension('mercurial.diffhelpers', ['mercurial/diffhelpers.c']),
+    Extension('mercurial.mpatch', ['mercurial/mpatch.c']),
+    Extension('mercurial.parsers', ['mercurial/parsers.c']),
     ]
 
 packages = ['mercurial', 'mercurial.hgweb', 'hgext', 'hgext.convert']