view mercurial/hgweb.py @ 1076:01db658cc78a

tarball support v0.3 Hello, I'm slowly improving support for tarballs in Mercurial. Attached patch is made against current tip in Matt's repository - f859e9cba1b9, and contains everything done so far. Changes: - gzip and bzip2 tarballs are sent immediately without writing to temporary files (I was wrong Matt, it can be done very easy) - hgrc customization, you can choose which type (if any) you will support There's no easy way to support compression levels, since TarFile open() assume that it is 9. I tried to use gzopen(), and bz2open() methods instead, but it seems that headers of generated archives, are missing or wrong. We could eventually try to rewrite tarfile.py and include our own version into Mercurial, but I don't know if it's good idea... Wojtek
author Wojciech Milkowski <wmilkowski@interia.pl>
date Fri, 26 Aug 2005 20:51:34 -0700
parents 7b35a980b982
children b87aeccf73d9
line wrap: on
line source

# hgweb.py - web interface to a mercurial repository
#
# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
# Copyright 2005 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, cgi, time, re, difflib, socket, sys, zlib
from mercurial.hg import *
from mercurial.ui import *

def templatepath():
    for f in "templates", "../templates":
        p = os.path.join(os.path.dirname(__file__), f)
        if os.path.isdir(p):
            return p

def age(t):
    def plural(t, c):
        if c == 1:
            return t
        return t + "s"
    def fmt(t, c):
        return "%d %s" % (c, plural(t, c))

    now = time.time()
    delta = max(1, int(now - t))

    scales = [["second", 1],
              ["minute", 60],
              ["hour", 3600],
              ["day", 3600 * 24],
              ["week", 3600 * 24 * 7],
              ["month", 3600 * 24 * 30],
              ["year", 3600 * 24 * 365]]

    scales.reverse()

    for t, s in scales:
        n = delta / s
        if n >= 2 or s == 1:
            return fmt(t, n)

def nl2br(text):
    return text.replace('\n', '<br/>\n')

def obfuscate(text):
    return ''.join(['&#%d;' % ord(c) for c in text])

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 httphdr(type, file="", size=0):
    sys.stdout.write('Content-type: %s\n' % type)
    if file:
        sys.stdout.write('Content-disposition: attachment; filename=%s\n'
            % file)
    if size > 0:
        sys.stdout.write('Content-length: %d\n' % size)
    sys.stdout.write('\n')

def write(*things):
    for thing in things:
        if hasattr(thing, "__iter__"):
            for part in thing:
                write(part)
        else:
            sys.stdout.write(str(thing))

class templater:
    def __init__(self, mapfile, filters={}, defaults={}):
        self.cache = {}
        self.map = {}
        self.base = os.path.dirname(mapfile)
        self.filters = filters
        self.defaults = defaults

        for l in file(mapfile):
            m = re.match(r'(\S+)\s*=\s*"(.*)"$', l)
            if m:
                self.cache[m.group(1)] = m.group(2)
            else:
                m = re.match(r'(\S+)\s*=\s*(\S+)', l)
                if m:
                    self.map[m.group(1)] = os.path.join(self.base, m.group(2))
                else:
                    raise LookupError("unknown map entry '%s'" % l)

    def __call__(self, t, **map):
        m = self.defaults.copy()
        m.update(map)
        try:
            tmpl = self.cache[t]
        except KeyError:
            tmpl = self.cache[t] = file(self.map[t]).read()
        return self.template(tmpl, self.filters, **m)

    def template(self, tmpl, filters={}, **map):
        while tmpl:
            m = re.search(r"#([a-zA-Z0-9]+)((%[a-zA-Z0-9]+)*)((\|[a-zA-Z0-9]+)*)#", tmpl)
            if m:
                yield tmpl[:m.start(0)]
                v = map.get(m.group(1), "")
                v = callable(v) and v(**map) or v

                format = m.group(2)
                fl = m.group(4)

                if format:
                    q = v.__iter__
                    for i in q():
                        lm = map.copy()
                        lm.update(i)
                        yield self(format[1:], **lm)

                    v = ""

                elif fl:
                    for f in fl.split("|")[1:]:
                        v = filters[f](v)

                yield v
                tmpl = tmpl[m.end(0):]
            else:
                yield tmpl
                return

def rfc822date(x):
    return time.strftime("%a, %d %b %Y %H:%M:%S +0000", time.gmtime(x))

common_filters = {
    "escape": cgi.escape,
    "age": age,
    "date": (lambda x: time.asctime(time.gmtime(x))),
    "addbreaks": nl2br,
    "obfuscate": obfuscate,
    "short": (lambda x: x[:12]),
    "firstline": (lambda x: x.splitlines(1)[0]),
    "permissions": (lambda x: x and "-rwxr-xr-x" or "-rw-r--r--"),
    "rfc822date": rfc822date,
    }

class hgweb:
    def __init__(self, repo, name=None):
        if type(repo) == type(""):
            self.repo = repository(ui(), repo)
        else:
            self.repo = repo

        self.mtime = -1
        self.reponame = name or self.repo.ui.config("web", "name",
                                                    self.repo.root)

    def refresh(self):
        s = os.stat(os.path.join(self.repo.root, ".hg", "00changelog.i"))
        if s.st_mtime != self.mtime:
            self.mtime = s.st_mtime
            self.repo = repository(self.repo.ui, self.repo.root)
            self.maxchanges = self.repo.ui.config("web", "maxchanges", 10)
            self.maxfiles = self.repo.ui.config("web", "maxchanges", 10)
            self.allowpull = self.repo.ui.configbool("web", "allowpull", True)
            self.allowzip = self.repo.ui.configbool("web", "zip", True)
            self.allowgz = self.repo.ui.configbool("web", "gz", True)
            self.allowbz2 = self.repo.ui.configbool("web", "bz2", True)

    def date(self, cs):
        return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))

    def listfiles(self, files, mf):
        for f in files[:self.maxfiles]:
            yield self.t("filenodelink", node=hex(mf[f]), file=f)
        if len(files) > self.maxfiles:
            yield self.t("fileellipses")

    def listfilediffs(self, files, changeset):
        for f in files[:self.maxfiles]:
            yield self.t("filedifflink", node=hex(changeset), file=f)
        if len(files) > self.maxfiles:
            yield self.t("fileellipses")

    def parents(self, t1, nodes=[], rev=None,**args):
        if not rev:
            rev = lambda x: ""
        for node in nodes:
            if node != nullid:
                yield self.t(t1, node=hex(node), rev=rev(node), **args)

    def showtag(self, t1, node=nullid, **args):
        for t in self.repo.nodetags(node):
             yield self.t(t1, tag=t, **args)

    def tarballbuttons(self, m):
        s = ''
        if self.allowzip:
            s += '<a href="?cmd=tarball;manifest=%s;type=zip">zip</a>\n' % m
        if self.allowgz:
            s += '<a href="?cmd=tarball;manifest=%s;type=gz">gz</a>\n' % m
        if self.allowbz2:
            s += '<a href="?cmd=tarball;manifest=%s;type=bz2">bz2</a>\n' % m
        return s

    def diff(self, node1, node2, files):
        def filterfiles(list, files):
            l = [x for x in list if x in files]

            for f in files:
                if f[-1] != os.sep:
                    f += os.sep
                l += [x for x in list if x.startswith(f)]
            return l

        parity = [0]
        def diffblock(diff, f, fn):
            yield self.t("diffblock",
                         lines=prettyprintlines(diff),
                         parity=parity[0],
                         file=f,
                         filenode=hex(fn or nullid))
            parity[0] = 1 - parity[0]

        def prettyprintlines(diff):
            for l in diff.splitlines(1):
                if l.startswith('+'):
                    yield self.t("difflineplus", line=l)
                elif l.startswith('-'):
                    yield self.t("difflineminus", line=l)
                elif l.startswith('@'):
                    yield self.t("difflineat", line=l)
                else:
                    yield self.t("diffline", line=l)

        r = self.repo
        cl = r.changelog
        mf = r.manifest
        change1 = cl.read(node1)
        change2 = cl.read(node2)
        mmap1 = mf.read(change1[0])
        mmap2 = mf.read(change2[0])
        date1 = self.date(change1)
        date2 = self.date(change2)

        c, a, d, u = r.changes(node1, node2)
        if files:
            c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))

        for f in c:
            to = r.file(f).read(mmap1[f])
            tn = r.file(f).read(mmap2[f])
            yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
        for f in a:
            to = None
            tn = r.file(f).read(mmap2[f])
            yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)
        for f in d:
            to = r.file(f).read(mmap1[f])
            tn = None
            yield diffblock(mdiff.unidiff(to, date1, tn, date2, f), f, tn)

    def changelog(self, pos):
        def changenav(**map):
            def seq(factor=1):
                yield 1 * factor
                yield 3 * factor
                #yield 5 * factor
                for f in seq(factor * 10):
                    yield f

            l = []
            for f in seq():
                if f < self.maxchanges / 2:
                    continue
                if f > count:
                    break
                r = "%d" % f
                if pos + f < count:
                    l.append(("+" + r, pos + f))
                if pos - f >= 0:
                    l.insert(0, ("-" + r, pos - f))

            yield {"rev": 0, "label": "(0)"}

            for label, rev in l:
                yield {"label": label, "rev": rev}

            yield {"label": "tip", "rev": ""}

        def changelist(**map):
            parity = (start - end) & 1
            cl = self.repo.changelog
            l = [] # build a list in forward order for efficiency
            for i in range(start, end):
                n = cl.node(i)
                changes = cl.read(n)
                hn = hex(n)
                t = float(changes[2].split(' ')[0])

                l.insert(0, {"parity": parity,
                             "author": changes[1],
                             "parent": self.parents("changelogparent",
                                                    cl.parents(n), cl.rev),
                             "changelogtag": self.showtag("changelogtag",n),
                             "manifest": hex(changes[0]),
                             "desc": changes[4],
                             "date": t,
                             "files": self.listfilediffs(changes[3], n),
                             "rev": i,
                             "node": hn})
                parity = 1 - parity

            for e in l:
                yield e

        cl = self.repo.changelog
        mf = cl.read(cl.tip())[0]
        count = cl.count()
        start = max(0, pos - self.maxchanges + 1)
        end = min(count, start + self.maxchanges)
        pos = end - 1

        yield self.t('changelog',
                     changenav=changenav,
                     manifest=hex(mf),
                     rev=pos, changesets=count, entries=changelist)

    def search(self, query):

        def changelist(**map):
            cl = self.repo.changelog
            count = 0
            qw = query.lower().split()

            def revgen():
                for i in range(cl.count() - 1, 0, -100):
                    l = []
                    for j in range(max(0, i - 100), i):
                        n = cl.node(j)
                        changes = cl.read(n)
                        l.append((n, j, changes))
                    l.reverse()
                    for e in l:
                        yield e

            for n, i, changes in revgen():
                miss = 0
                for q in qw:
                    if not (q in changes[1].lower() or
                            q in changes[4].lower() or
                            q in " ".join(changes[3][:20]).lower()):
                        miss = 1
                        break
                if miss:
                    continue

                count += 1
                hn = hex(n)
                t = float(changes[2].split(' ')[0])

                yield self.t('searchentry',
                             parity=count & 1,
                             author=changes[1],
                             parent=self.parents("changelogparent",
                                                 cl.parents(n), cl.rev),
                             changelogtag=self.showtag("changelogtag",n),
                             manifest=hex(changes[0]),
                             desc=changes[4],
                             date=t,
                             files=self.listfilediffs(changes[3], n),
                             rev=i,
                             node=hn)

                if count >= self.maxchanges:
                    break

        cl = self.repo.changelog
        mf = cl.read(cl.tip())[0]

        yield self.t('search',
                     query=query,
                     manifest=hex(mf),
                     entries=changelist)

    def changeset(self, nodeid):
        n = bin(nodeid)
        cl = self.repo.changelog
        changes = cl.read(n)
        p1 = cl.parents(n)[0]
        t = float(changes[2].split(' ')[0])

        files = []
        mf = self.repo.manifest.read(changes[0])
        for f in changes[3]:
            files.append(self.t("filenodelink",
                                filenode=hex(mf.get(f, nullid)), file=f))

        def diff(**map):
            yield self.diff(p1, n, None)

        yield self.t('changeset',
                     diff=diff,
                     rev=cl.rev(n),
                     node=nodeid,
                     parent=self.parents("changesetparent",
                                         cl.parents(n), cl.rev),
                     changesettag=self.showtag("changesettag",n),
                     manifest=hex(changes[0]),
                     author=changes[1],
                     desc=changes[4],
                     date=t,
                     files=files,
                     tarballbuttons=self.tarballbuttons(hex(changes[0])))

    def filelog(self, f, filenode):
        cl = self.repo.changelog
        fl = self.repo.file(f)
        count = fl.count()

        def entries(**map):
            l = []
            parity = (count - 1) & 1

            for i in range(count):
                n = fl.node(i)
                lr = fl.linkrev(n)
                cn = cl.node(lr)
                cs = cl.read(cl.node(lr))
                t = float(cs[2].split(' ')[0])

                l.insert(0, {"parity": parity,
                             "filenode": hex(n),
                             "filerev": i,
                             "file": f,
                             "node": hex(cn),
                             "author": cs[1],
                             "date": t,
                             "parent": self.parents("filelogparent",
                                                    fl.parents(n),
                                                    fl.rev, file=f),
                             "desc": cs[4]})
                parity = 1 - parity

            for e in l:
                yield e

        yield self.t("filelog", file=f, filenode=filenode, entries=entries)

    def filerevision(self, f, node):
        fl = self.repo.file(f)
        n = bin(node)
        text = fl.read(n)
        changerev = fl.linkrev(n)
        cl = self.repo.changelog
        cn = cl.node(changerev)
        cs = cl.read(cn)
        t = float(cs[2].split(' ')[0])
        mfn = cs[0]

        def lines():
            for l, t in enumerate(text.splitlines(1)):
                yield {"line": t,
                       "linenumber": "% 6d" % (l + 1),
                       "parity": l & 1}

        yield self.t("filerevision",
                     file=f,
                     filenode=node,
                     path=up(f),
                     text=lines(),
                     rev=changerev,
                     node=hex(cn),
                     manifest=hex(mfn),
                     author=cs[1],
                     date=t,
                     parent=self.parents("filerevparent",
                                         fl.parents(n), fl.rev, file=f),
                     permissions=self.repo.manifest.readflags(mfn)[f])

    def fileannotate(self, f, node):
        bcache = {}
        ncache = {}
        fl = self.repo.file(f)
        n = bin(node)
        changerev = fl.linkrev(n)

        cl = self.repo.changelog
        cn = cl.node(changerev)
        cs = cl.read(cn)
        t = float(cs[2].split(' ')[0])
        mfn = cs[0]

        def annotate(**map):
            parity = 1
            last = None
            for r, l in fl.annotate(n):
                try:
                    cnode = ncache[r]
                except KeyError:
                    cnode = ncache[r] = self.repo.changelog.node(r)

                try:
                    name = bcache[r]
                except KeyError:
                    cl = self.repo.changelog.read(cnode)
                    name = cl[1]
                    f = name.find('@')
                    if f >= 0:
                        name = name[:f]
                    f = name.find('<')
                    if f >= 0:
                        name = name[f+1:]
                    bcache[r] = name

                if last != cnode:
                    parity = 1 - parity
                    last = cnode

                yield {"parity": parity,
                       "node": hex(cnode),
                       "rev": r,
                       "author": name,
                       "file": f,
                       "line": l}

        yield self.t("fileannotate",
                     file=f,
                     filenode=node,
                     annotate=annotate,
                     path=up(f),
                     rev=changerev,
                     node=hex(cn),
                     manifest=hex(mfn),
                     author=cs[1],
                     date=t,
                     parent=self.parents("fileannotateparent",
                                         fl.parents(n), fl.rev, file=f),
                     permissions=self.repo.manifest.readflags(mfn)[f])

    def manifest(self, mnode, path):
        mf = self.repo.manifest.read(bin(mnode))
        rev = self.repo.manifest.rev(bin(mnode))
        node = self.repo.changelog.node(rev)
        mff=self.repo.manifest.readflags(bin(mnode))

        files = {}

        p = path[1:]
        l = len(p)

        for f,n in mf.items():
            if f[:l] != p:
                continue
            remain = f[l:]
            if "/" in remain:
                short = remain[:remain.find("/") + 1] # bleah
                files[short] = (f, None)
            else:
                short = os.path.basename(remain)
                files[short] = (f, n)

        def filelist(**map):
            parity = 0
            fl = files.keys()
            fl.sort()
            for f in fl:
                full, fnode = files[f]
                if not fnode:
                    continue

                yield {"file": full,
                       "manifest": mnode,
                       "filenode": hex(fnode),
                       "parity": parity,
                       "basename": f,
                       "permissions": mff[full]}
                parity = 1 - parity

        def dirlist(**map):
            parity = 0
            fl = files.keys()
            fl.sort()
            for f in fl:
                full, fnode = files[f]
                if fnode:
                    continue

                yield {"parity": parity,
                       "path": os.path.join(path, f),
                       "manifest": mnode,
                       "basename": f[:-1]}
                parity = 1 - parity

        yield self.t("manifest",
                     manifest=mnode,
                     rev=rev,
                     node=hex(node),
                     path=path,
                     up=up(path),
                     fentries=filelist,
                     dentries=dirlist)

    def tags(self):
        cl = self.repo.changelog
        mf = cl.read(cl.tip())[0]

        i = self.repo.tagslist()
        i.reverse()

        def entries(**map):
            parity = 0
            for k,n in i:
                yield {"parity": parity,
                       "tag": k,
                       "node": hex(n)}
                parity = 1 - parity

        yield self.t("tags",
                     manifest=hex(mf),
                     entries=entries)

    def filediff(self, file, changeset):
        n = bin(changeset)
        cl = self.repo.changelog
        p1 = cl.parents(n)[0]
        cs = cl.read(n)
        mf = self.repo.manifest.read(cs[0])

        def diff(**map):
            yield self.diff(p1, n, file)

        yield self.t("filediff",
                     file=file,
                     filenode=hex(mf.get(file, nullid)),
                     node=changeset,
                     rev=self.repo.changelog.rev(n),
                     parent=self.parents("filediffparent",
                                         cl.parents(n), cl.rev),
                     diff=diff)

    def ziparchive(self, mnode):
        import zipfile

        tmp = tempfile.mkstemp()[1]
        zf = zipfile.ZipFile(tmp, "w", zipfile.ZIP_DEFLATED)
        mf = self.repo.manifest.read(bin(mnode))
        rev = self.repo.manifest.rev(bin(mnode))
        cnode = short(self.repo.changelog.node(rev))
        name = os.path.basename(self.repo.path[:-4]) # without '/.hg' suffix
        name += '-' + str(rev) + '-' + cnode + '/'

        for fname in mf.keys():
            r = self.repo.file(fname)
            zf.writestr(name + fname, r.read(mf[fname]))
        zf.close()

        f = open(tmp, 'r')
        httphdr('application/zip', name[:-1] + '.zip', os.path.getsize(tmp))
        sys.stdout.write(f.read())
        f.close()
        os.unlink(tmp)

    def tararchive(self, mnode, type):
        import StringIO
        import time
        import tarfile

        #if type == "gz":
        #    tf = tarfile.TarFile.gzopen('', 'w', sys.stdout, compressionlevel)
        #else:
        #    tf = tarfile.TarFile.bz2open('', 'w', sys.stdout, compressionlevel)
        tf = tarfile.TarFile.open(mode='w|' + type, fileobj=sys.stdout)

        mf = self.repo.manifest.read(bin(mnode))
        rev = self.repo.manifest.rev(bin(mnode))
        cnode = short(self.repo.changelog.node(rev))
        mff = self.repo.manifest.readflags(bin(mnode))
        mtime = int(time.time())
        name = os.path.basename(self.repo.path[:-4]) # without '/.hg' suffix
        name += '-' + str(rev) + '-' + cnode  + '/'

        httphdr('application/octet-stream', name[:-1] + '.tar.' + type)
        for fname in mf.keys():
            r = self.repo.file(fname)
            rcont = r.read(mf[fname])
            finfo = tarfile.TarInfo(name + fname)
            finfo.mtime = mtime
            finfo.size = len(rcont)
            finfo.mode = mff[fname] and 0755 or 0644
            tf.addfile(finfo, StringIO.StringIO(rcont))
        tf.close()

    # add tags to things
    # tags -> list of changesets corresponding to tags
    # find tag, changeset, file

    def run(self):
        def header(**map):
            yield self.t("header", **map)

        def footer(**map):
            yield self.t("footer", **map)

        self.refresh()
        args = cgi.parse()

        t = self.repo.ui.config("web", "templates", templatepath())
        m = os.path.join(t, "map")
        style = self.repo.ui.config("web", "style", "")
        if args.has_key('style'):
            style = args['style'][0]
        if style:
            b = os.path.basename("map-" + style)
            p = os.path.join(t, b)
            if os.path.isfile(p):
                m = p

        port = os.environ["SERVER_PORT"]
        port = port != "80" and (":" + port) or ""
        uri = os.environ["REQUEST_URI"]
        if "?" in uri:
            uri = uri.split("?")[0]
        url = "http://%s%s%s" % (os.environ["SERVER_NAME"], port, uri)

        self.t = templater(m, common_filters,
                           {"url": url,
                            "repo": self.reponame,
                            "header": header,
                            "footer": footer,
                           })

        if not args.has_key('cmd'):
            args['cmd'] = [self.t.cache['default'],]

        if args['cmd'][0] == 'changelog':
            c = self.repo.changelog.count() - 1
            hi = c
            if args.has_key('rev'):
                hi = args['rev'][0]
                try:
                    hi = self.repo.changelog.rev(self.repo.lookup(hi))
                except RepoError:
                    write(self.search(hi))
                    return

            write(self.changelog(hi))

        elif args['cmd'][0] == 'changeset':
            write(self.changeset(args['node'][0]))

        elif args['cmd'][0] == 'manifest':
            write(self.manifest(args['manifest'][0], args['path'][0]))

        elif args['cmd'][0] == 'tags':
            write(self.tags())

        elif args['cmd'][0] == 'filediff':
            write(self.filediff(args['file'][0], args['node'][0]))

        elif args['cmd'][0] == 'file':
            write(self.filerevision(args['file'][0], args['filenode'][0]))

        elif args['cmd'][0] == 'annotate':
            write(self.fileannotate(args['file'][0], args['filenode'][0]))

        elif args['cmd'][0] == 'filelog':
            write(self.filelog(args['file'][0], args['filenode'][0]))

        elif args['cmd'][0] == 'heads':
            httphdr("application/mercurial-0.1")
            h = self.repo.heads()
            sys.stdout.write(" ".join(map(hex, h)) + "\n")

        elif args['cmd'][0] == 'branches':
            httphdr("application/mercurial-0.1")
            nodes = []
            if args.has_key('nodes'):
                nodes = map(bin, args['nodes'][0].split(" "))
            for b in self.repo.branches(nodes):
                sys.stdout.write(" ".join(map(hex, b)) + "\n")

        elif args['cmd'][0] == 'between':
            httphdr("application/mercurial-0.1")
            nodes = []
            if args.has_key('pairs'):
                pairs = [map(bin, p.split("-"))
                         for p in args['pairs'][0].split(" ")]
            for b in self.repo.between(pairs):
                sys.stdout.write(" ".join(map(hex, b)) + "\n")

        elif args['cmd'][0] == 'changegroup':
            httphdr("application/mercurial-0.1")
            nodes = []
            if not self.allowpull:
                return

            if args.has_key('roots'):
                nodes = map(bin, args['roots'][0].split(" "))

            z = zlib.compressobj()
            f = self.repo.changegroup(nodes)
            while 1:
                chunk = f.read(4096)
                if not chunk:
                    break
                sys.stdout.write(z.compress(chunk))

            sys.stdout.write(z.flush())

        elif args['cmd'][0] == 'tarball':
            manifest = args['manifest'][0]
            type = args['type'][0]
            if type == 'zip' and self.allowzip:
                self.ziparchive(manifest)
            elif type == 'gz' and self.allowgz:
                self.tararchive(manifest, 'gz')
            elif type == 'bz2' and self.allowbz2:
                self.tararchive(manifest, 'bz2')
            else:
                write(self.t("error"))

        else:
            write(self.t("error"))

def create_server(repo):

    def openlog(opt, default):
        if opt and opt != '-':
            return open(opt, 'w')
        return default

    address = repo.ui.config("web", "address", "")
    port = int(repo.ui.config("web", "port", 8000))
    use_ipv6 = repo.ui.configbool("web", "ipv6")
    accesslog = openlog(repo.ui.config("web", "accesslog", "-"), sys.stdout)
    errorlog = openlog(repo.ui.config("web", "errorlog", "-"), sys.stderr)

    import BaseHTTPServer

    class IPv6HTTPServer(BaseHTTPServer.HTTPServer):
        address_family = getattr(socket, 'AF_INET6', None)

        def __init__(self, *args, **kwargs):
            if self.address_family is None:
                raise RepoError('IPv6 not available on this system')
            BaseHTTPServer.HTTPServer.__init__(self, *args, **kwargs)

    class hgwebhandler(BaseHTTPServer.BaseHTTPRequestHandler):
        def log_error(self, format, *args):
            errorlog.write("%s - - [%s] %s\n" % (self.address_string(),
                                                 self.log_date_time_string(),
                                                 format % args))

        def log_message(self, format, *args):
            accesslog.write("%s - - [%s] %s\n" % (self.address_string(),
                                                  self.log_date_time_string(),
                                                  format % args))

        def do_POST(self):
            try:
                self.do_hgweb()
            except socket.error, inst:
                if inst.args[0] != 32:
                    raise

        def do_GET(self):
            self.do_POST()

        def do_hgweb(self):
            query = ""
            p = self.path.find("?")
            if p:
                query = self.path[p + 1:]
                query = query.replace('+', ' ')

            env = {}
            env['GATEWAY_INTERFACE'] = 'CGI/1.1'
            env['REQUEST_METHOD'] = self.command
            env['SERVER_NAME'] = self.server.server_name
            env['SERVER_PORT'] = str(self.server.server_port)
            env['REQUEST_URI'] = "/"
            if query:
                env['QUERY_STRING'] = query
            host = self.address_string()
            if host != self.client_address[0]:
                env['REMOTE_HOST'] = host
                env['REMOTE_ADDR'] = self.client_address[0]

            if self.headers.typeheader is None:
                env['CONTENT_TYPE'] = self.headers.type
            else:
                env['CONTENT_TYPE'] = self.headers.typeheader
            length = self.headers.getheader('content-length')
            if length:
                env['CONTENT_LENGTH'] = length
            accept = []
            for line in self.headers.getallmatchingheaders('accept'):
                if line[:1] in "\t\n\r ":
                    accept.append(line.strip())
                else:
                    accept = accept + line[7:].split(',')
            env['HTTP_ACCEPT'] = ','.join(accept)

            os.environ.update(env)

            save = sys.argv, sys.stdin, sys.stdout, sys.stderr
            try:
                sys.stdin = self.rfile
                sys.stdout = self.wfile
                sys.argv = ["hgweb.py"]
                if '=' not in query:
                    sys.argv.append(query)
                self.send_response(200, "Script output follows")
                hg.run()
            finally:
                sys.argv, sys.stdin, sys.stdout, sys.stderr = save

    hg = hgweb(repo)
    if use_ipv6:
        return IPv6HTTPServer((address, port), hgwebhandler)
    else:
        return BaseHTTPServer.HTTPServer((address, port), hgwebhandler)

def server(path, name, templates, address, port, use_ipv6=False,
           accesslog=sys.stdout, errorlog=sys.stderr):
    httpd = create_server(path, name, templates, address, port, use_ipv6,
                          accesslog, errorlog)
    httpd.serve_forever()

# This is a stopgap
class hgwebdir:
    def __init__(self, config):
        self.cp = ConfigParser.SafeConfigParser()
        self.cp.read(config)

    def run(self):
        try:
            virtual = os.environ["PATH_INFO"]
        except:
            virtual = ""

        if virtual[1:]:
            real = self.cp.get("paths", virtual[1:])
            h = hgweb(real)
            h.run()
            return

        def header(**map):
            yield tmpl("header", **map)

        def footer(**map):
            yield tmpl("footer", **map)

        templates = templatepath()
        m = os.path.join(templates, "map")
        tmpl = templater(m, common_filters,
                         {"header": header, "footer": footer})

        def entries(**map):
            parity = 0
            l = self.cp.items("paths")
            l.sort()
            for v,r in l:
                cp2 = ConfigParser.SafeConfigParser()
                cp2.read(os.path.join(r, ".hg", "hgrc"))

                def get(sec, val, default):
                    try:
                        return cp2.get(sec, val)
                    except:
                        return default

                url = os.environ["REQUEST_URI"] + "/" + v
                url = url.replace("//", "/")

                yield dict(author=get("web", "author", "unknown"),
                           name=get("web", "name", v),
                           url=url,
                           parity=parity,
                           shortdesc=get("web", "description", "unknown"),
                           lastupdate=os.stat(os.path.join(r, ".hg",
                                                "00changelog.d")).st_mtime)

                parity = 1 - parity

        write(tmpl("index", entries=entries))