view mercurial/hgweb/hgwebdir_mod.py @ 2434:a2df85adface

http server: support persistent connections. only "hg serve" affected yet. http server running cgi script will not use persistent connections. support for fastcgi will help that. clients that support keepalive can use one tcp connection for all commands during clone and pull. this makes latency of binary search during pull much lower over wan. if server does not know content-length, it will force connection to close at end. right fix is to use chunked transfer-encoding but this is easier and does not hurt performance. only command that is affected is "changegroup" which is always last command during a pull.
author Vadim Gelfer <vadim.gelfer@gmail.com>
date Thu, 15 Jun 2006 12:55:58 -0700
parents d351a3be3371
children 76ff5efe8181 d0db3462d568
line wrap: on
line source

# hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
#
# 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
from mercurial.demandload import demandload
demandload(globals(), "ConfigParser")
demandload(globals(), "mercurial:ui,hg,util,templater")
demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb")
demandload(globals(), "mercurial.hgweb.request:hgrequest")
demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
from mercurial.i18n import gettext as _

# This is a stopgap
class hgwebdir(object):
    def __init__(self, config):
        def cleannames(items):
            return [(name.strip(os.sep), path) for name, path in items]

        self.motd = ""
        self.repos_sorted = ('name', False)
        if isinstance(config, (list, tuple)):
            self.repos = cleannames(config)
            self.repos_sorted = ('', False)
        elif isinstance(config, dict):
            self.repos = cleannames(config.items())
            self.repos.sort()
        else:
            cp = ConfigParser.SafeConfigParser()
            cp.read(config)
            self.repos = []
            if cp.has_section('web') and cp.has_option('web', 'motd'):
                self.motd = cp.get('web', 'motd')
            if cp.has_section('paths'):
                self.repos.extend(cleannames(cp.items('paths')))
            if cp.has_section('collections'):
                for prefix, root in cp.items('collections'):
                    for path in util.walkrepos(root):
                        repo = os.path.normpath(path)
                        name = repo
                        if name.startswith(prefix):
                            name = name[len(prefix):]
                        self.repos.append((name.lstrip(os.sep), repo))
            self.repos.sort()

    def run(self, req=hgrequest()):
        def header(**map):
            yield tmpl("header", **map)

        def footer(**map):
            yield tmpl("footer", motd=self.motd, **map)

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

        def archivelist(ui, nodeid, url):
            allowed = (ui.config("web", "allow_archive", "")
                       .replace(",", " ").split())
            for i in ['zip', 'gz', 'bz2']:
                if i in allowed or ui.configbool("web", "allow" + i):
                    yield {"type" : i, "node": nodeid, "url": url}

        def entries(sortcolumn="", descending=False, **map):
            rows = []
            parity = 0
            for name, path in self.repos:
                u = ui.ui()
                try:
                    u.readconfig(os.path.join(path, '.hg', 'hgrc'))
                except IOError:
                    pass
                get = u.config

                url = ('/'.join([req.env["REQUEST_URI"].split('?')[0], name])
                       .replace("//", "/"))

                # update time with local timezone
                try:
                    d = (get_mtime(path), util.makedate()[1])
                except OSError:
                    continue

                contact = (get("ui", "username") or # preferred
                           get("web", "contact") or # deprecated
                           get("web", "author", "")) # also
                description = get("web", "description", "")
                name = get("web", "name", name)
                row = dict(contact=contact or "unknown",
                           contact_sort=contact.upper() or "unknown",
                           name=name,
                           name_sort=name,
                           url=url,
                           description=description or "unknown",
                           description_sort=description.upper() or "unknown",
                           lastchange=d,
                           lastchange_sort=d[1]-d[0],
                           archives=archivelist(u, "tip", url))
                if (not sortcolumn
                    or (sortcolumn, descending) == self.repos_sorted):
                    # fast path for unsorted output
                    row['parity'] = parity
                    parity = 1 - parity
                    yield row
                else:
                    rows.append((row["%s_sort" % sortcolumn], row))
            if rows:
                rows.sort()
                if descending:
                    rows.reverse()
                for key, row in rows:
                    row['parity'] = parity
                    parity = 1 - parity
                    yield row

        virtual = req.env.get("PATH_INFO", "").strip('/')
        if virtual:
            real = dict(self.repos).get(virtual)
            if real:
                try:
                    hgweb(real).run(req)
                except IOError, inst:
                    req.write(tmpl("error", error=inst.strerror))
                except hg.RepoError, inst:
                    req.write(tmpl("error", error=str(inst)))
            else:
                req.write(tmpl("notfound", repo=virtual))
        else:
            if req.form.has_key('static'):
                static = os.path.join(templater.templatepath(), "static")
                fname = req.form['static'][0]
                req.write(staticfile(static, fname)
                          or tmpl("error", error="%r not found" % fname))
            else:
                sortable = ["name", "description", "contact", "lastchange"]
                sortcolumn, descending = self.repos_sorted
                if req.form.has_key('sort'):
                    sortcolumn = req.form['sort'][0]
                    descending = sortcolumn.startswith('-')
                    if descending:
                        sortcolumn = sortcolumn[1:]
                    if sortcolumn not in sortable:
                        sortcolumn = ""

                sort = [("sort_%s" % column,
                         "%s%s" % ((not descending and column == sortcolumn)
                                   and "-" or "", column))
                        for column in sortable]
                req.write(tmpl("index", entries=entries,
                               sortcolumn=sortcolumn, descending=descending,
                               **dict(sort)))