[PATCH 1/1]: New bzr webserve new version

Goffredo Baroncelli kreijack at inwind.it
Wed Oct 12 21:44:25 BST 2005





 bzrlib/plugins/webserve/__init__.py                       |   37
 bzrlib/plugins/webserve/hgweb.py                          |  747 ++++++++++++++
 bzrlib/plugins/webserve/templates/changelog-rss.tmpl      |    7
 bzrlib/plugins/webserve/templates/changelog.tmpl          |   41
 bzrlib/plugins/webserve/templates/changelogentry-rss.tmpl |    8
 bzrlib/plugins/webserve/templates/changelogentry.tmpl     |   20
 bzrlib/plugins/webserve/templates/filelog.tmpl            |   60 +
 bzrlib/plugins/webserve/templates/footer.tmpl             |    2
 bzrlib/plugins/webserve/templates/header-rss.tmpl         |    7
 bzrlib/plugins/webserve/templates/header.tmpl             |   63 +
 bzrlib/plugins/webserve/templates/index.tmpl              |   18
 bzrlib/plugins/webserve/templates/inventory.tmpl          |   30
 bzrlib/plugins/webserve/templates/map                     |   48
 bzrlib/plugins/webserve/templates/map-rss                 |    3
 bzrlib/plugins/webserve/templates/revision.tmpl           |   62 +
 hgweb.cgi                                                 |   13
 hgweb.config.examples                                     |    3
 hgwebdir.cgi                                              |   18
 18 files changed, 1187 insertions(+)




=== added directory 'bzrlib/plugins/webserve'
=== added file 'bzrlib/plugins/webserve/__init__.py'
--- /dev/null
+++ bzrlib/plugins/webserve/__init__.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+"""\
+web interface
+"""
+
+import bzrlib, bzrlib.commands
+import os
+import hgweb
+
+class cmd_webserve(bzrlib.commands.Command):
+   """Start the webserver"""
+
+   takes_options = ['templates', 'address', 'port', 'ipv6', 'acceslog',
+        'errorlog']
+   takes_args = ['name?', 'rootrepository?']
+   def run(self, name = None, rootrepository = None,
+            templates = None, address = "", port = 8088, ipv6 = None,
+            accesslog = '', errorlog = '' ):
+        #import hgweb
+
+        if rootrepository == None: rootrepository = os.getcwd( )
+        if name == None: name = os.path.basename( rootrepository )
+        #if templates == None:
+        #    templates = os.path.join( rootrepository, "templates")
+
+        httpd = hgweb.create_server(rootrepository, name, templates,
+                 address, port, ipv6, accesslog, errorlog)
+        httpd.serve_forever()
+
+bzrlib.commands.OPTIONS['templates'] = str
+bzrlib.commands.OPTIONS['addresses'] = str
+bzrlib.commands.OPTIONS['port'] = int
+bzrlib.commands.OPTIONS['ipv6'] = None
+bzrlib.commands.OPTIONS['acceslog'] = str
+bzrlib.commands.OPTIONS['errorlog'] = str
+
+bzrlib.commands.register_command(cmd_webserve)

=== added file 'bzrlib/plugins/webserve/hgweb.py'
--- /dev/null
+++ bzrlib/plugins/webserve/hgweb.py
@@ -0,0 +1,747 @@
+# hgweb.py - web interface to a mercurial repository
+#
+# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake at edge2.net>
+# Copyright 2005 Matt Mackall <mpm at 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, ConfigParser
+
+from bzrlib.log import _enumerate_history
+from bzrlib.osutils import format_date
+from bzrlib.diff import show_diff_trees
+from bzrlib.errors import *
+from bzrlib.revisionspec import RevisionSpec
+import bzrlib
+import bzrlib.branch
+
+def templatepath():
+    p = os.path.join(os.getcwd( ),"templtes")
+    if os.path.isdir(p): return p
+
+    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):
+    sys.stdout.write('Content-type: %s\n\n' % type)
+
+def write(*things):
+    for thing in things:
+        if hasattr(thing, "__iter__"):
+            for part in thing:
+                write(part)
+        else:
+            sys.stdout.write(str(thing))
+
+def template(tmpl, filters = {}, **map):
+    while tmpl:
+        m = re.search(r"#([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
+
+            fl = m.group(2)
+            if fl:
+                for f in fl.split("|")[1:]:
+                    v = filters[f](v)
+
+            yield v
+            tmpl = tmpl[m.end(0):]
+        else:
+            yield tmpl
+            return
+
+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 "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 template(tmpl, self.filters, **m)
+
+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:
+    maxchanges = 10
+    maxfiles = 10
+
+    def __init__(self, path, name=None, templates=""):
+        self.templates = templates
+        self.reponame = name
+        self.path = path
+        self.mtime = -1
+        self.viewonly = 0
+
+    def refresh(self):
+        s = os.stat(os.path.join(self.path, ".bzr", "revision-history"))
+        if s.st_mtime != self.mtime:
+            self.mtime = s.st_mtime
+            self.branch = bzrlib.branch.Branch.open(self.path)
+            self.history = _enumerate_history(self.branch)
+
+    def date(self, cs):
+        return time.asctime(time.gmtime(float(cs[2].split(' ')[0])))
+
+    def changelog(self, pos, search=None):
+
+        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 self.t("naventry", rev = 1, label="(1)")
+
+            for label, rev in l:
+                yield self.t("naventry", label = label, rev = rev)
+
+            yield self.t("naventry", label="tip")
+
+        def changelist(**map):
+            parity = (start - end) & 1
+
+            l = [] # build a list in forward order for efficiency
+            for revno, rev_id in cut_revs:
+                rev = self.branch.get_revision(rev_id)
+                n = revno
+                date_str = format_date(rev.timestamp,
+                               rev.timezone or 0,
+                               'original')
+
+                l.insert(0, self.t(
+                    'changelogentry',
+                    parity = parity,
+                    rev = revno,
+                    node = rev.revision_id,
+                    author = rev.committer,
+                    desc = rev.message,
+                    date = rev.timestamp,
+                ))
+                parity = 1 - parity
+
+            yield l
+
+
+        file_id = None
+
+        count = len(self.history)
+        if pos == -1 : pos = count
+
+        if search != None:
+            lsearch = search.lower( )
+            while pos >= 1 :
+                revno,rev_id = self.history[pos-1]
+                if rev_id.lower( ) == lsearch: break
+                rev = self.branch.get_revision(rev_id)
+                if rev.message.lower( ).find(lsearch) >= 0 : break
+                pos = pos - 1
+
+        start = max(1, pos - self.maxchanges + 1)
+        end = min(count, start + self.maxchanges -1)
+        pos = end 
+
+        searchfrom = pos
+        if search != None: searchfrom -= 1
+
+        cut_revs = self.history[(start-1):(end)]
+
+        yield self.t('changelog',
+                     changenav = changenav,
+                     rev = pos, 
+                     searchfrom = searchfrom,
+                     changesets = 10, 
+                     entries = changelist)
+
+    def diff(self, b, r1, r2):
+        class stream:
+            buf = []
+            def write( self, x ):
+                self.buf.append(x)
+
+        s = stream( )
+        old_tree = b.revision_tree(RevisionSpec(r1).in_history(b).rev_id)
+        new_tree = b.revision_tree(RevisionSpec(r2).in_history(b).rev_id)
+        show_diff_trees(old_tree, new_tree, s )
+
+        return s.buf
+
+
+    def revision(self, revno):
+
+        revn,revid = self.history[int(revno)-1]
+        rev = self.branch.get_revision(revid)
+        delta = self.branch.get_revision_delta(revn)
+        count = len(self.history)
+        pos = revn
+
+        def changenav( ):
+            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 > 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 self.t("revnaventry", rev = 1, label="(1)")
+
+            for label, rev in l:
+                yield self.t("revnaventry", label = label, rev = rev)
+
+            yield self.t("revnaventry", label="tip")
+
+
+        def diff(**map):
+            def prettyprintlines( diff ):
+                for l in diff:
+                    if l.startswith('*'):
+                        yield self.t("difflineinfo", line = l)
+                    elif 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)
+ 
+            yield self.t("diffblock",
+                lines = prettyprintlines(self.diff(self.branch, revn-1, revn)),
+                parity = 0 )
+
+        def files(files):
+            for path, fid, kind in files:
+                if kind == 'directory':
+                    path += '/'
+                elif kind == 'symlink':
+                    path += '@'
+                yield path+'<br>\n'
+
+        def files_renamed(files):
+            for oldpath, newpath, fid, kind, text_modified, meta_modified in files:
+                if kind == 'directory':
+                    yield self.t("revrename", oldpath = oldpath, 
+                        newpath = self.t("revdirentry",fullname = newpath, 
+                        rev=revn))
+                elif kind == 'symlink':
+                    yield self.t("revrename", oldpath = oldpath, 
+                        newpath = self.t("revlinkentry",fullname = newpath, 
+                        rev=revn))
+                else:
+                    yield self.t("revrename", oldpath = oldpath, 
+                        newpath = self.t("revfileentry",fullname = newpath, 
+                        rev=revn))
+
+        def fileentry(files):
+            for path, fid, kind in files:
+                if kind == 'directory':
+                    yield self.t("revdirentry",fullname = path, rev=revn)
+                elif kind == 'symlink':
+                    yield self.t("revlinkentry",fullname = path, rev=revn)
+                else:
+                    yield self.t("revfileentry",fullname = path, rev=revn)
+
+        def getparents( ):
+            if revn<1: return
+
+            for pid in rev.parent_ids:
+
+                try:
+                    n = self.branch.revision_id_to_revno(pid)
+                except bzrlib.errors.NoSuchRevision:
+                    n = "[Missing]"
+                    tmpl = "revparentmissing"
+                else:
+                    if n == revn -1:
+                        tmpl = "revparentbold"
+                    else:
+                        tmpl = "revparent"
+
+                yield self.t( tmpl, node = pid, rev = n )
+
+        yield self.t('revision',
+            diff = diff,
+            rev = revn,
+            revisionnav = changenav( ),
+            parents = getparents( ),
+            node = rev.revision_id,
+            author = rev.committer,
+            desc = rev.message,
+            date = rev.timestamp,
+            filesadded = fileentry( delta.added),
+            filesmodified = fileentry( [ (path,id,kind) 
+                        for path, id, kind, text_modified, meta_modified in 
+                            delta.modified]),
+            filesrenamed = files_renamed(delta.renamed),
+            filesremoved = fileentry(delta.removed),
+        )
+ 
+    def content(self, revno, path ):
+        if revno == None:
+            revno = b.revno()
+        revn,revid = self.history[int(revno)-1]
+        rev = self.branch.get_revision(revid)
+
+        count = len(self.history)
+        pos = revno
+
+        def changenav( ):
+            def seq(factor = 1):
+                yield 1 * factor
+                yield 3 * factor
+
+                for f in seq(factor * 10):
+                    yield f
+
+            l = []
+            for f in seq():
+                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 self.t("cntnaventry", rev = 1, label="(1)",
+                path = path)
+
+            for label, rev in l:
+                yield self.t("cntnaventry", label = label, 
+                    path = path,rev = rev)
+
+            yield self.t("cntnaventry", label="tip", path = path)
+
+
+        def file_contents( rev, path ):
+            count = 0
+            parity = 1
+
+            self.branch.lock_read()
+            try:
+                tree = self.branch.revision_tree(RevisionSpec(revno).in_history(self.branch).rev_id)
+                # use inventory as it was in that revision
+                file_id = tree.inventory.path2id(path)
+                if not file_id:
+                    fc= None
+                else:
+                    fc = tree.get_file(file_id).read()
+            finally:
+                self.branch.unlock()
+
+            if fc is None:
+                yield self.t("filedoesntexist", path = path, revno = revno)
+            else:
+                for i in fc.split('\n' ):
+                    parity = 1- parity
+                    count += 1
+                    yield self.t("fileline", parity=parity, 
+                        linenumber = count, line = i )
+
+        yield self.t('filelog',
+                     entries = file_contents( revno, path ),
+                     rev = revn,
+                     cntnav = changenav( ),
+                     node = rev.revision_id,
+                     author = rev.committer,
+                     desc = rev.message,
+                     path = path,
+                     date = rev.timestamp,
+        )
+
+    def inventory(self, rev, path = None):
+
+        count = len(self.history)
+        pos = rev
+
+        def changenav( ):
+            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 > 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 self.t("invnaventry", rev = 1, label="(1)",
+                path = path)
+
+            for label, rev in l:
+                yield self.t("invnaventry", label = label, 
+                    path = path,rev = rev)
+
+            yield self.t("invnaventry", label="tip", path = path)
+
+
+        def dname( path ):
+            d = path.rfind('/')
+            if d>= 0:
+                return path[:d]
+            else:
+                return ""
+
+        def fname( path ):
+            d = path.rfind('/')
+            return path[d+1:]
+
+        b = self.branch
+        if rev == None:
+            inv = b.get_revision_inventory(b.revno())
+        else:
+            inv = b.get_revision_inventory(RevisionSpec(int(rev)).in_history(b).rev_id)
+
+        if not path : path = "/"
+        def filelist(**map):
+            parity=0
+            for fpath, entry in inv.entries():
+                if dname(fpath)+'/' != path: 
+                    continue
+                bname = fname(fpath)
+                if entry.kind == "file":
+                    yield self.t("inventoryfileentry",
+                                    parity = parity,
+                                    name = bname,
+                                    fullname = fpath,
+                                    id = entry.file_id,
+                                    rev = rev,
+                    )
+                elif entry.kind == "directory":
+                    yield self.t("inventorydirentry",
+                                    parity = parity,
+                                    name = bname + '/',
+                                    path = fpath,
+                                    id = entry.file_id,
+                                    rev = rev,
+                    )
+                parity = 1 - parity
+
+
+        yield self.t("inventory",
+                     rev = rev,
+                     invnav = changenav( ),
+                     path = path,
+                     parent = dname(path[:-1]),
+                     entries = filelist)
+
+    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.templates or templatepath( )
+        #or self.repo.ui.config("web", "templates",
+        #                                          templatepath())
+        m = os.path.join(t, "map")
+        if args.has_key('style'):
+            b = os.path.basename("map-" + args['style'][0])
+            p = os.path.join(self.templates, 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)
+
+        name = self.reponame 
+
+        self.t = templater(m, common_filters,
+                           {"url":url,
+                            "repo":name,
+                            "header":header,
+                            "footer":footer,
+                            })
+
+        if not args.has_key('cmd'):
+            args['cmd'] = [self.t.cache['default'],]
+
+        if args.has_key('rev'):
+            revno = int(args['rev'][0])
+            if revno == -1:
+                revno = self.branch.revno( )
+        else:
+            revno = self.branch.revno( )
+
+        if args['cmd'][0] == 'changelog':
+             write(self.changelog(revno))
+
+        elif args['cmd'][0] == 'search':
+            w = args['w'][0]
+            write(self.changelog(revno,w))
+
+        elif args['cmd'][0] == 'revision':
+            write(self.revision(revno))
+
+        elif args['cmd'][0] == 'inventory':
+            path = None
+            if args.has_key("path"): path = args["path"][0]
+            write(self.inventory(revno, path))
+
+        elif args['cmd'][0] == 'content':
+            write(self.content(revno, path = args["path"][0]))
+
+        else:
+            write(self.t("error"))
+
+def create_server(path, name, templates, address, port, use_ipv6,
+                  accesslog, errorlog):
+
+    def openlog(opt, default):
+        if opt and opt != '-':
+            return open(opt, 'w')
+        return default
+
+    accesslog = sys.stdout
+    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(path, name, templates)
+    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()
+# 
+# def serve( ):
+# 
+#     httpd = hgweb.create_server(repo.root, opts["name"], opts["templates"],
+#                                 opts["address"], opts["port"], opts["ipv6"],
+#                                 opts['accesslog'], opts['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:
+            real = self.cp.get("paths", virtual[1:])
+            h = hgweb(real, virtual[1:])
+            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
+
+                yield tmpl("indexentry",
+                           author = get("web", "author", "unknown"),
+                           name = get("web", "name", v),
+                           url = os.environ["REQUEST_URI"] + "/" + v,
+                           parity = parity,
+                           shortdesc = get("web", "description", "unknown"),
+                           lastupdate = os.stat(os.path.join(r, ".bzr",
+                                                "revision-history")).st_mtime)
+
+                parity = 1 - parity
+
+        write(tmpl("index", entries = entries))

=== added directory 'bzrlib/plugins/webserve/templates'
=== added file 'bzrlib/plugins/webserve/templates/changelog-rss.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/changelog-rss.tmpl
@@ -0,0 +1,7 @@
+#header#
+    <!-- derived from mercurial project -->
+    <title>#repo|escape# Changelog</title>
+    <description>#repo|escape# Changelog</description>
+    #entries#
+  </channel>
+</rss>
\ No newline at end of file

=== added file 'bzrlib/plugins/webserve/templates/changelog.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/changelog.tmpl
@@ -0,0 +1,41 @@
+#header#
+<title>#repo|escape#: log</title>
+<link rel="alternate" type="application/rss+xml"
+   href="?cmd=changelog;style=rss" title="RSS feed for #repo|escape#">
+</head>
+<body>
+
+<table width=100%>
+  <td align=left>
+    <div class="buttons">
+    <!--<a href="?cmd=tags">tags</a>-->
+    <a href="?cmd=inventory;rev=#rev#;path=">inventory</a>
+    <a type="application/rss+xml" href="?cmd=changelog;style=rss">rss</a>
+    </div>
+  <td align=right>
+    navigate: <small>#changenav#</small>
+</table>
+
+<h2>log for repository '#repo|escape#'</h2>
+
+<form action="#">
+<p>
+<label for="search2">search:</label>
+<input type="hidden" name="rev" value="#searchfrom#">
+<input type="hidden" name="cmd" value="search">
+<input name="w" id="search2" type="text" size="30">
+</p>
+</form>
+
+#entries#
+
+<form action="#">
+<p>
+<label for="search2">search:</label>
+<input type="hidden" name="rev" value="#searchfrom#">
+<input type="hidden" name="cmd" value="search">
+<input name="w" id="search2" type="text" size="30">
+</p>
+</form>
+
+#footer#

=== added file 'bzrlib/plugins/webserve/templates/changelogentry-rss.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/changelogentry-rss.tmpl
@@ -0,0 +1,8 @@
+<item>
+    <!-- derived from mercurial project -->
+    <title>#desc|firstline|escape#</title>
+    <link>#url#?cmd=revision;rev=#rev#</link>
+    <description><![CDATA[#desc|escape|addbreaks#]]></description>
+    <author>#author|obfuscate#</author>
+    <pubDate>#date|rfc822date#</pubDate>
+</item>

=== added file 'bzrlib/plugins/webserve/templates/changelogentry.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/changelogentry.tmpl
@@ -0,0 +1,20 @@
+<table class="changelogEntry parity#parity#">
+ <tr>
+  <th class="age">#date|age# ago:</th>
+  <th class="firstline">#desc|firstline|escape#</th>
+ </tr>
+ <tr>
+  <th class="changesetRev">revision #rev#:</th>
+  <td class="changesetNode"><a href="?cmd=revision;rev=#rev#">#node#</a></td>
+ </tr>
+ #parent#
+ #changelogtag#
+ <tr>
+  <th class="author">author:</th>
+  <td class="author">#author|obfuscate#</td>
+ </tr>
+ <tr>
+  <th class="date">date:</th>
+  <td class="date">#date|date#</td>
+ </tr>
+</table>
\ No newline at end of file

=== added file 'bzrlib/plugins/webserve/templates/filelog.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/filelog.tmpl
@@ -0,0 +1,60 @@
+#header#
+<title>#repo|escape#: filelog #rev#</title>
+</head>
+<body>
+
+<table width=100%>
+  <tr>
+     <td align=left>
+        <div class="buttons">
+        <a href="?cmd=changelog;rev=#rev#">log</a>
+        <a href="?cmd=inventory;rev=#rev#;path=">inventory</a>
+        </div>
+     <td align=right>
+        navigate: <small>#cntnav#</small>
+</table>
+
+<h2>filelog: #path#</h2>
+
+<table id="changesetEntry">
+<tr>
+ <th class="changeset">revision #rev#:</th>
+ <td class="changeset"><a href="?cmd=revision;rev=#rev#">#node#</a></td>
+</tr>
+
+<tr>
+ <th class="author">author:</th>
+ <td class="author">#author|obfuscate#</td>
+</tr>
+<tr>
+ <th class="date">date:</th>
+ <td class="date">#date|date# (#date|age# ago)</td></tr>
+<!-- <tr>
+    <th class="files">files added:</th>
+    <td class="files">#filesadded#</td></tr>
+    <tr>
+    <th class="files">files removed:</th>
+    <td class="files">#filesremoved#</td></tr>
+    <tr>
+    <th class="files">files modified:</th>
+    <td class="files">#filesmodified#</td></tr>
+    <tr>
+    <th class="files">files renamed:</th>
+    <td class="files">#filesrenamed#</td></tr>
+-->
+<tr>
+ <th class="description">description:</th>
+ <td class="description">#desc|escape|addbreaks#</td>
+</tr>
+</table>
+
+<div id="filelog">
+<pre>
+#entries#
+</pre>
+</div>
+
+</body>
+</html>
+
+

=== added file 'bzrlib/plugins/webserve/templates/footer.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/footer.tmpl
@@ -0,0 +1,2 @@
+</body>
+</html>

=== added file 'bzrlib/plugins/webserve/templates/header-rss.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/header-rss.tmpl
@@ -0,0 +1,7 @@
+Content-type: text/xml
+
+<!-- derived from mercurial project -->
+<rss version="2.0">
+  <channel>
+    <link>#url#</link>
+    <language>en-us</language>

=== added file 'bzrlib/plugins/webserve/templates/header.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/header.tmpl
@@ -0,0 +1,63 @@
+Content-type: text/html
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+<style type="text/css">
+<!--
+a { text-decoration:none; }
+.parity0 { background-color: #aabbcc; }
+.parity1 { background-color: #bbccdd; }
+.lineno { width: 60px; color: #ffffff; font-size: smaller; }
+.plusline { color: green; }
+.minusline { color: red; }
+.atline { color: purple; }
+.infoline { color: blue; }
+.annotate { font-size: smaller; text-align: right; padding-right: 1em; }
+.buttons a {
+  background-color: #446677;
+  padding: 2pt;
+  color: white;
+  font-family: sans;
+  font-weight: bold;
+}
+
+.buttons a:hover {
+  background-color: #bbddee;
+  padding: 2pt;
+  color: black;
+  font-family: sans;
+  font-weight: bold;
+}
+.metatag {
+  background-color: #888888;
+  color: white;
+  text-align: right; 
+}
+
+/* Common */
+pre { margin: 0; }
+
+
+/* Changelog entries */
+.changelogEntry { width: 100%; }
+.changelogEntry th { font-weight: normal; text-align: right; vertical-align: top; width: 15%;}
+.changelogEntry th.age, .changelogEntry th.firstline { font-weight: bold; }
+.changelogEntry th.firstline { text-align: left; width: inherit; }
+
+/* Tag entries */
+#tagEntries { list-style: none; margin: 0; padding: 0; }
+#tagEntries .tagEntry { list-style: none; margin: 0; padding: 0; }
+#tagEntries .tagEntry span.node { font-family: monospace; }
+
+/* Changeset entry */
+#changesetEntry { }
+#changesetEntry th { font-weight: normal; background-color: #446677; color: white; text-align: right; }
+#changesetEntry th.files, #changesetEntry th.description { vertical-align: top; }
+
+/* File diff view */
+#filediffEntry { }
+#filediffEntry th { font-weight: normal; background-color: #446677; color: white; text-align: right; }
+
+-->
+</style>

=== added file 'bzrlib/plugins/webserve/templates/index.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/index.tmpl
@@ -0,0 +1,18 @@
+#header#
+<title>Mercurial repositories index</title>
+</head>
+<body>
+
+<h2>Mercurial Repositories</h2>
+
+<table>
+    <tr>
+        <td>Name</td>
+        <td>Description</td>
+        <td>Author</td>
+        <td>Last change</td>
+    <tr>
+    #entries#
+</table>
+
+#footer#

=== added file 'bzrlib/plugins/webserve/templates/inventory.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/inventory.tmpl
@@ -0,0 +1,30 @@
+#header#
+<title>#repo|escape#: inventory #rev#: #path#</title>
+</head>
+<body>
+
+
+
+<table width=100%>
+  <tr>
+     <td align=left>
+        <div class="buttons">
+        <a href="?cmd=changelog;rev=#rev#">log</a>
+        <!--<a href="?cmd=tags">tags</a>-->
+        <a href="?cmd=revision;rev=#rev#">revision</a>
+        </div>
+     <td align=right>
+        navigate: <small>#invnav#</small>
+</table>
+
+
+<h2>inventory #rev#: #path#</h2>
+
+<table cellpadding="0" cellspacing="0" width="100%">
+<tr class="parity1">
+  <td><tt>drwxr-xr-x</tt>&nbsp;
+  <td><a href="?cmd=inventory;rev=#rev#;path=#parent#/">[up]</a>
+  <td align=right>
+#entries#
+</table>
+#footer#

=== added file 'bzrlib/plugins/webserve/templates/map'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/map
@@ -0,0 +1,48 @@
+default = "changelog"
+header = header.tmpl
+footer = footer.tmpl
+search = search.tmpl
+changelog = changelog.tmpl
+naventry = "<a href="?cmd=changelog;rev=#rev#">#label#</a> "
+filedifflink = "<a href="?cmd=filediff;node=#node#;file=#file#">#file#</a> "
+filenodelink = "<a href="?cmd=file;filenode=#filenode#;file=#file#">#file#</a> "
+fileellipses = "..."
+changelogentry = changelogentry.tmpl
+searchentry = changelogentry.tmpl
+revision = revision.tmpl
+inventory = inventory.tmpl
+inventoryfileentry = "<tr class="parity#parity#"><td><tt>-rwxr-xr-x</tt>&nbsp;<td><a href='?cmd=content;rev=#rev#;path=#fullname#'>#name#</a><td align=right>#id#"
+inventorydirentry = "<tr class="parity#parity#"><td><tt>drwxr-xr-x</tt>&nbsp;<td><a href="?cmd=inventory;rev=#rev#;path=#path#/">#name#</a><td align=right>#id#"
+filerevision = filerevision.tmpl
+fileannotate = fileannotate.tmpl
+filediff = filediff.tmpl
+filelog = filelog.tmpl
+fileline = "<div class="parity#parity#"><span class="lineno">#linenumber# </span>#line|escape#</div>"
+filelogentry = filelogentry.tmpl
+annotateline = "<tr class="parity#parity#"><td class="annotate"><a href="?cmd=revision;node=#node#">#author|obfuscate#@#rev#</a></td><td><pre>#line|escape#</pre></td></tr>"
+difflineplus = "<span class="plusline">#line|escape#</span>"
+difflineminus = "<span class="minusline">#line|escape#</span>"
+difflineat = "<span class="atline">#line|escape#</span>"
+difflineinfo = "<span class="infoline">#line|escape#</span>"
+diffline = "#line|escape#"
+changelogparent = "<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=revision;node=#node#">#node|short#</a></td></tr>"
+revisionparent = "<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=revision;node=#node#">#node|short#</a></td></tr>"
+filerevparent = "<tr><td class="metatag">parent:</td><td><a href="?cmd=file;file=#file#;filenode=#node#">#node|short#</a></td></tr>"
+fileannotateparent = "<tr><td class="metatag">parent:</td><td><a href="?cmd=annotate;file=#file#;filenode=#node#">#node|short#</a></td></tr>"
+tagentry = "<li class="tagEntry parity#parity#"><span class="node">#node#</span> <a href="?cmd=revision;node=#node#">#tag#</a></li>"
+diffblock = "<pre class="parity#parity#">#lines#</pre>"
+filediffparent = "<tr><th class="parent">parent #rev#:</th><td class="parent"><a href="?cmd=revision;node=#node#">#node|short#</a></td></tr>"
+filelogparent = "<tr><td align="right">parent #rev#:&nbsp;</td><td><a href="?cmd=file;file=#file#;filenode=#node#">#node|short#</a></td></tr>"
+indexentry = "<tr class="parity#parity#"><td><a  href="#url#">#name#</a></td><td>#shortdesc#</td><td>#author# <i>#email|obfuscate#</i></td><td>#lastupdate|age# ago</td></tr>"
+index = index.tmpl
+revfileentry = "<a href='?cmd=content;rev=#rev#;path=#fullname#'>#fullname#</a><br>"
+revdirentry = "<a href='?cmd=inventory;rev=#rev#;path=#fullname#/'>#fullname#/</a><br>"
+revlinkentry = "@#fullname#<br>"
+revparent = "<a href="?cmd=revision;rev=#rev#">#rev#:#node#</a><br>"
+revparentbold = "<a href="?cmd=revision;rev=#rev#"><b>#rev#:#node#</b></a><br>"
+revparentmissing = "<font color=red>[#node#]</font><br>"
+revrename = "#oldpath# => #newpath#"
+revnaventry = "<a href="?cmd=revision;rev=#rev#">#label#</a> "
+invnaventry = "<a href="?cmd=inventory;path=#path#;rev=#rev#">#label#</a> "
+cntnaventry = "<a href="?cmd=content;path=#path#;rev=#rev#">#label#</a> "
+filedoesntexist = "<H1><font color=red>WARNING the file<font color=blue><div align=center>#path#</div></font>doesn't exist at revision '#revno#'</font></h1>"
\ No newline at end of file

=== added file 'bzrlib/plugins/webserve/templates/map-rss'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/map-rss
@@ -0,0 +1,3 @@
+changelog = changelog-rss.tmpl
+changelogentry = changelogentry-rss.tmpl
+header = header-rss.tmpl
\ No newline at end of file

=== added file 'bzrlib/plugins/webserve/templates/revision.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/revision.tmpl
@@ -0,0 +1,62 @@
+#header#
+<title>#repo|escape#: revision #rev#</title>
+</head>
+<body>
+
+<table width=100%>
+  <tr>
+     <td align=left>
+        <div class="buttons">
+        <a href="?cmd=changelog;rev=#rev#">log</a>
+        <a href="?cmd=inventory;rev=#rev#;path=">inventory</a>
+        </div>
+     <td align=right>
+        navigate: <small>#revisionnav#</small>
+</table>
+
+
+<h2>revision: #desc|escape|firstline#</h2>
+
+<table id="changesetEntry">
+<tr>
+ <th class="changeset">revision #rev#:</th>
+ <td class="changeset"><a href="?cmd=revision;rev=#rev#">#node#</a></td>
+</tr>
+<tr>
+<tr>
+ <th class="changeset">parents:</th>
+ <td class="changeset">#parents#</td>
+</tr>
+
+ <th class="author">author:</th>
+ <td class="author">#author|obfuscate#</td>
+</tr>
+<tr>
+ <th class="date">date:</th>
+ <td class="date">#date|date# (#date|age# ago)</td></tr>
+<tr>
+ <th class="files">files added:</th>
+ <td class="files">#filesadded#</td></tr>
+<tr>
+ <th class="files">files removed:</th>
+ <td class="files">#filesremoved#</td></tr>
+<tr>
+ <th class="files">files modified:</th>
+ <td class="files">#filesmodified#</td></tr>
+<tr>
+ <th class="files">files renamed:</th>
+ <td class="files">#filesrenamed#</td></tr>
+<tr>
+ <th class="description">description:</th>
+ <td class="description">#desc|escape|addbreaks#</td>
+</tr>
+</table>
+
+<div id="changesetDiff">
+#diff#
+</div>
+
+</body>
+</html>
+
+

=== added file 'hgweb.cgi'
--- /dev/null
+++ hgweb.cgi
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+#
+# An example CGI script to use hgweb, edit as necessary
+
+import cgitb, os, sys
+cgitb.enable()
+
+# sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
+from bzrlib.plugins.webserve import hgweb
+
+h = hgweb.hgweb(os.getcwd( ), "Experimental bazaar-ng web interface", 
+	"bzrlib/plugins/webserve/templates" )
+h.run()

=== added file 'hgweb.config.examples'
--- /dev/null
+++ hgweb.config.examples
@@ -0,0 +1,3 @@
+[paths]
+bazaar-NG_stable_branch = bzr.dev
+hgweb_devel = bzr-hgweb

=== added file 'hgwebdir.cgi'
--- /dev/null
+++ hgwebdir.cgi
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+#
+# An example CGI script to export multiple hgweb repos, edit as necessary
+
+import cgitb, sys
+cgitb.enable()
+
+# sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
+sys.path.insert(0, "bzr-hgweb") # if not a system-wide install
+from bzrlib import hgweb
+
+# The config file looks like this:
+# [paths]
+# virtual/path = /real/path
+# virtual/path = /real/path
+
+h = hgweb.hgwebdir("hgweb.config")
+h.run()




-- 
gpg key@ keyserver.linux.it: Goffredo Baroncelli (ghigo) <kreijack AT inwind.it>
Key fingerprint = CE3C 7E01 6782 30A3 5B87  87C0 BB86 505C 6B2A CFF9





More information about the bazaar mailing list