[PATH 1/1] Bzr web interfaces
Goffredo Baroncelli
kreijack at alice.it
Thu Oct 20 19:36:06 BST 2005
README | 108 ++++
__init__.py | 37 +
hgweb.cgi | 13
hgweb.py | 935 ++++++++++++++++++++++++++++++++++++++
templates/changefilelog.tmpl | 42 +
templates/changefilelogentry.tmpl | 20
templates/changelog-rss.tmpl | 7
templates/changelog.tmpl | 41 +
templates/changelogentry-rss.tmpl | 8
templates/changelogentry.tmpl | 20
templates/filelog.tmpl | 61 ++
templates/footer.tmpl | 2
templates/header-rss.tmpl | 7
templates/header.tmpl | 63 ++
templates/index.tmpl | 18
templates/inventory.tmpl | 30 +
templates/map | 52 ++
templates/map-rss | 3
templates/revision.tmpl | 62 ++
=== added directory 'bzrlib/plugins/webserve'
=== added file 'bzrlib/plugins/webserve/README'
--- /dev/null
+++ bzrlib/plugins/webserve/README
@@ -0,0 +1,108 @@
+
+
+This, is the porting of the mercurial[*] web interfaces to bazaar-ng; todate
+the porting is implemented as plugin. The web interface can be started in
+two ways
+
+1) standalone server, via the 'bzr serve' command
+
+ $ bzr serve --help
+ usage: serve [NAME] [ROOTREPOSITORY]
+
+ Start the webserver
+
+ options:
+ --templates
+ --address
+ --port
+ --ipv6
+ --acceslog
+ --errorlog
+
+
+ where
+ NAME -> name of the repository ( default the current dir )
+ ROOTREPOSITORY -> repository directory ( default the current dir )
+ --templates -> set the template directory ( default the
+ ROOTREPOSITORY )
+ --address -> interface to listen
+ --port -> port to listen ( default 8088 )
+ --ipv6 -> ipv6 enable/disable ( untested )
+ --accesslog -> accesslog file ( default stderr )
+ --errorlog -> errorlog file ( default stderr )
+
+ After the start of the server with the default parameters ( bzr serve ),
+ you can browse the repository at the address: 'http://127.0.0.1:8088/'
+
+2) cgi script via apache, configuring apache to use the 'hgweb.cgi' python
+script as the default index script. Below is an example of the httpd.conf file
+
+ <Directory BlaBlaBla/BlaBlaBla/bzr>
+ DirectoryIndex index.html hgweb.cgi
+ AllowOverride None
+ Options ExecCGI -MultiViews +SymLinksIfOwnerMatch Indexes
+ Order allow,deny
+ Allow from all
+ AddHandler cgi-script .cgi
+ </Directory>
+
+
+
+The code of the web interface is based on expansion of a template files. Under
+the directory templates/ there are files which are the skeleton of the html
+pages. These files have field like '#<field name>#' which the code expand
+with a value or with another template expansion....
+
+In code:
+
+ $ cat templatefile
+ <html>
+ <body>
+ <table>
+ #rows#
+ </table>
+ </body>
+ </html>
+
+ $ cat templaterows
+ <tr>
+ <td>#tag#
+
+ $
+
+
+ [...]
+ t = templater("mapfile")
+
+ def func( ):
+ for i in range(1,10):
+ yield t("templaterows", tag = str(i) )
+
+
+ write(t("templatefile", rows = func( ) ))
+ [...]
+
+
+the code above, take the contents of the file "templatefile" and
+replace every string '#tag#' with the value(s) returned by
+the function func( ).
+The function func( ) return 9 times the content of the file
+"templatefile2", replacing the field '#anothertag#' with a value
+between 1 and 9.
+Because the function 'func( )' return 9 different values,
+the unique field '#tag#' is expanded 9 times.
+The 'mapfile' file contains the mapping between the name of the
+template name and the template filename...
+
+The goal is to separate the html code to the python code. In fact the
+template contains the major part of the html code, only the
+dynamic part are generated by the python code, tipically on the basis of another
+template; an example is the changelog web page: the main page is
+a template, where the table rows are an another template expansion...
+
+
+TODO:
+ BugFix, BugFIx, BugFix
+ Manage the revisions which don't exist
+ Manage the tar archive
+ Implement a diff view between two revisions
\ No newline at end of file
=== 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, bzrlib.option
+
+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.option._global_option('templates', type=str)
+bzrlib.option._global_option('address', type=str)
+bzrlib.option._global_option('port', type=int)
+bzrlib.option._global_option('ipv6')
+bzrlib.option._global_option('acceslog', type=str)
+bzrlib.option._global_option('errorlog', type=str)
+
+bzrlib.commands.register_command(cmd_webserve)
=== added file 'bzrlib/plugins/webserve/hgweb.cgi'
--- /dev/null
+++ bzrlib/plugins/webserve/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 'bzrlib/plugins/webserve/hgweb.py'
--- /dev/null
+++ bzrlib/plugins/webserve/hgweb.py
@@ -0,0 +1,935 @@
+# 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
+from bzrlib.annotate import _annotate_file
+
+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 changefilelog(self, pos, revno, path, 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("cflnaventry", pos=1, rev=revno, label="(1)",
+ path=path)
+
+ for label, rev in l:
+ yield self.t("cflnaventry", revno=revno, label=label, pos=rev,
+ path=path)
+
+ yield self.t("cflnaventry", label="tip", path=path)
+
+ def changelist(cut_revs):
+ parity = (start - end) & 1
+
+ l = cut_revs
+ l.reverse( )
+ revno_base = len(self.history)
+ for filerevno, rev_id in l:
+ rev = self.branch.get_revision(rev_id)
+ date_str = format_date(rev.timestamp,
+ rev.timezone or 0,
+ 'original')
+
+ # find the revno of rev_id
+ i = revno_base
+ while self.history[i-1][1] != rev_id and i >0 :
+ #sys.stderr.write("%5d %s %s\n"%(i, self.history[i-1][1], rev_id))
+ i-= 1
+
+ if i == 0 :
+ revno = "[merge]"
+ else:
+ revno = str(i)
+ revno_base = i-1
+
+ yield self.t(
+ 'changefilelogentry',
+ parity = parity,
+ rev = revno,
+ node = rev.revision_id,
+ author = rev.committer,
+ desc = rev.message,
+ date = rev.timestamp,
+ )
+ parity = 1 - parity
+
+ def get_history_file(revno, path):
+
+ rev_id = RevisionSpec(revno).in_history(self.branch).rev_id
+
+ self.branch.lock_read()
+ try:
+ tree = self.branch.revision_tree(rev_id)
+ # use inventory as it was in that revision
+ file_id = tree.inventory.path2id(path)
+
+ if file_id:
+ file_version = tree.inventory[file_id].revision
+
+ finally:
+ self.branch.unlock()
+
+ #def _annotate_file(branch, rev_id, file_id ):
+ w = self.branch.weave_store.get_weave(file_id,
+ self.branch.get_transaction())
+ count = 1
+ hist = []
+ for ids in w.iter_names( ):
+ hist.append( (count, ids) )
+ count += 1
+ #sys.stderr.write("hist=%s\n"%str(hist))
+ return hist
+
+ history = get_history_file(revno, path)
+
+ count = len(history)
+ if pos == -1 : pos = count
+
+ if search != None:
+ lsearch = search.lower( )
+ while pos >= 1 :
+ revno,rev_id = 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 = history[(start-1):(end)]
+
+ yield self.t('changefilelog',
+ changenav = changenav,
+ rev = revno,
+ path = path,
+ searchfrom = searchfrom,
+ changesets = 10,
+ entries = changelist(cut_revs))
+
+
+ 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(cut_revs):
+ 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
+
+ history = self.history
+
+ count = len(history)
+ if pos == -1 : pos = count
+
+ if search != None:
+ lsearch = search.lower( )
+ while pos >= 1 :
+ revno,rev_id = 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 = history[(start-1):(end)]
+
+ yield self.t('changelog',
+ changenav = changenav,
+ rev = pos,
+ searchfrom = searchfrom,
+ changesets = 10,
+ entries = changelist(cut_revs))
+
+ 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)
+
+ # TODO move this functioninto the bzrlib.annotate
+# def _annotate_file(branch, rev_id, file_id ):
+#
+# rh = branch.revision_history()
+# w = branch.weave_store.get_weave(file_id, branch.get_transaction())
+# last_origin = None
+# for origin, text in w.annotate_iter(rev_id):
+# text = text.rstrip('\r\n')
+# if origin == last_origin:
+# (revno_str, author, date_str) = ('','','')
+# else:
+# last_origin = origin
+# line_rev_id = w.idx_to_name(origin)
+# if not branch.has_revision(line_rev_id):
+# (revno_str, author, date_str) = ('?','?','?')
+# else:
+# if line_rev_id in rh:
+# revno_str = str(rh.index(line_rev_id) + 1)
+# else:
+# revno_str = 'merge'
+# rev = branch.get_revision(line_rev_id)
+# tz = rev.timezone or 0
+# date_str = time.strftime('%Y%m%d',
+# time.gmtime(rev.timestamp + tz))
+# # a lazy way to get something like the email address
+# # TODO: Get real email address
+# author = line_rev_id
+# m = author.rfind('-')
+# if m >= 0: m = author.rfind('-',0,m-1)
+# if m >= 0: author = author[:m]
+#
+# yield (revno_str, author, date_str, line_rev_id, text)
+
+ def file_contents_annotate( rev, path ):
+ count = 0
+ parity = 1
+ fc = None
+
+ 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 file_id:
+ fc = tree.get_file(file_id).read()
+ file_version = tree.inventory[file_id].revision
+
+ finally:
+ self.branch.unlock()
+
+ # TODO: it should exists a simpler method to detect if a file
+ # doesn't exist
+ if fc is None:
+ yield self.t("filedoesntexist", path = path, revno = revno)
+
+ else:
+ prevanno=''
+ for (revno_str, author, date_str, line_rev_id, text ) in \
+ _annotate_file(self.branch, file_version, file_id ):
+
+ anno = "%5s %-7s " % ( revno_str, author[:7] )
+
+ if anno.lstrip() == "" :
+ anno = prevanno
+ else:
+ parity = 1- parity
+
+ count = count + 1
+ prevanno = anno
+
+ if revno_str == "":
+ tmpl = "annotateline"
+ (revno_str,author ) = oldvalues
+ else:
+ tmpl = "annotateline"
+ oldvalues = (revno_str,author )
+
+ yield self.t( tmpl,
+ parity = parity,
+ linenumber = count,
+ author = author[:7],
+ rev = revno_str,
+ line = text )
+
+ yield self.t('filelog',
+ entries = file_contents_annotate( 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] == 'changefilelog':
+ path = args["path"][0]
+ pos = -1
+ if args.has_key("pos"): pos = args["pos"][0]
+ write(self.changefilelog(int(pos), int(revno), path))
+
+ 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/changefilelog.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/changefilelog.tmpl
@@ -0,0 +1,42 @@
+#header#
+<title>#repo|escape#: filelog</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 href="?cmd=content;rev=#rev#;path=#path#">content</a>
+ <a type="application/rss+xml" href="?cmd=changefilelog;style=rss">rss</a>
+ </div>
+ <td align=right>
+ navigate: <small>#changenav#</small>
+</table>
+
+<h2>log for file #path# 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/changefilelogentry.tmpl'
--- /dev/null
+++ bzrlib/plugins/webserve/templates/changefilelogentry.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">commiter:</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/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">commiter:</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,61 @@
+#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>
+ <a href="?cmd=changefilelog;pos=#pos#;rev=#rev#;path=#path#">filelog</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">
+<table cellspacing="0" cellpadding="0">
+#entries#
+<table>
+</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>
+ <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,52 @@
+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> <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> <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
+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#: </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>"
+annotateline = "<tr class="parity#parity#"><td class="annotate"><a href="?cmd=revision;rev=#rev#">#rev#:#author|obfuscate#</a></td><td><pre>#line|escape#</pre></td></tr>"
+annotatefile = "<table>#annotateentries#</table>"
+changefilelog = changefilelog.tmpl
+changefilelogentry = changefilelogentry.tmpl
+cflnaventry = "<a href="?cmd=changefilelog;revno=#revno#;pos=#pos#;path=#path#">#label#</a> "
\ 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>
+
+
--
gpg key@ keyserver.linux.it: Goffredo Baroncelli (ghigo) <kreijack AT inwind.it>
Key fingerprint = CE3C 7E01 6782 30A3 5B87 87C0 BB86 505C 6B2A CFF9
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
Url : https://lists.ubuntu.com/archives/bazaar/attachments/20051020/4db9bebe/attachment.pgp
More information about the bazaar
mailing list