The unified diff between revisions [14e5fc76..] and [52c07981..] is displayed below. It can also be downloaded as a raw diff.

#
#
# patch "ancestry.py"
#  from [6000ee066cd8eb2522563d195869b039e40fd70b]
#    to [6c95bd62605ad5bb4359dff0e4c8942b6faf9285]
#
# patch "handlers.py"
#  from [fa13ff0c027a94aeec104c8b07f63d605ad78db4]
#    to [e9af5798e92963ad207ab1c3a3646818ae952175]
#
# patch "links.py"
#  from [6ac854e0dfc2959803a89f2cb38c4fd7366eb070]
#    to [193e314b64731eee79799e24847557aa9cc7ee43]
#
# patch "render.py"
#  from [c1223ddbb5396f63101b981838cd8296d0cc82ba]
#    to [2a55c2e22ff97d808ca4fd06d39eb7bea8a101d3]
#
# patch "templates/base.html"
#  from [781439204c039e9426f067b004cbb9ba9f0b39ad]
#    to [8d1f9004c63ca950378f4dc1425deee063b0216d]
#
# patch "templates/branch.html"
#  from [7f162c333735ef7f64cd507d524653155972ca9f]
#    to [ed21182f8c1b680b5b25f3d59a4fc08c12a26a0a]
#
# patch "templates/branchchangesrss.html"
#  from [bfbbfead32d6e0aad40646932be79348f2aba0af]
#    to [b64e6216c319ef2a5a803b4e2483068c59a5f9d8]
#
# patch "templates/index.html"
#  from [c8cce216300ecbead5ba0ffad4c4cf26f43885d1]
#    to [dbcf4480fc966a46608f260adaf0689b0cf3c476]
#
# patch "templates/revision.html"
#  from [e9eeb6212f211ce522b1db16156e7129e00993d1]
#    to [aa7ca2c65f3076fdc9bdf2276aca414f99ca40d8]
#
# patch "templates/revisioninfo.html"
#  from [a6ef44ec8a9a16b735b7edea9467072e2c08ce12]
#    to [69fdcd40892b237c2cf85602a61e7f31ba112675]
#
# patch "urls.py"
#  from [16a510f175f983e3853bc0d61029a95e59ae0bbe]
#    to [74ec1a0af57afeb7d98bcf04248d799b8d9b2738]
#
# patch "viewmtn.py"
#  from [6629b45724cc639d2555c41248ccadf35838b83e]
#    to [ae38f0301e147803284029b0216e4c0c4e5fd6bb]
#
============================================================
--- ancestry.py	6000ee066cd8eb2522563d195869b039e40fd70b
+++ ancestry.py	6c95bd62605ad5bb4359dff0e4c8942b6faf9285
@@ -9,18 +9,17 @@ import mtn

 import rfc822, string, sha, os
 import mtn
-from links import link
 from colorsys import hls_to_rgb
 import config

-def ancestry_dot(ops, revision):
+def ancestry_dot(ctxt, revision):
     def dot_escape(s):
         # TODO: kind of paranoid, should probably revise
         permitted = string.digits + string.letters + ' -<>-:,*@!$%^&.+_~?/'
         return ''.join([t for t in s if t in permitted])
     revision = mtn.Revision(revision)
     original_branches = []
-    for cert in ops.certs(revision):
+    for cert in ctxt.ops.certs(revision):
         if cert[4] == 'name' and cert[5] == 'branch':
             original_branches.append(cert[7])

@@ -52,10 +51,10 @@ def ancestry_dot(ops, revision):
     visited = set()

     def visit_node(revision):
-        for node in ops.children(revision):
+        for node in ctxt.ops.children(revision):
             arcs.add((revision, node))
             nodes.add(node)
-        for node in ops.parents(revision):
+        for node in ctxt.ops.parents(revision):
             arcs.add((node, revision))
             nodes.add(node)
         visited.add(revision)
@@ -98,7 +97,7 @@ digraph ancestry {
     for node in nodes:
         author, date = '', ''
         branches = []
-        for cert in ops.certs(node):
+        for cert in ctxt.ops.certs(node):
             if cert[4] == 'name' and cert[5] == 'date':
                 date = cert[7]
             elif cert[4] == 'name' and cert[5] == 'author':
@@ -133,7 +132,7 @@ digraph ancestry {
                 value = nodeopts[option]
             options.append('%s="%s"' % (option, value))
         options.append('label="%s"' % (node_label[node]))
-        options.append('href="%s"' % link(node).uri())
+        options.append('href="%s"' % ctxt.link(node).uri())
         line += '[' + ','.join(options) + ']'
         graph += line + '\n'

@@ -149,8 +148,8 @@ digraph ancestry {
     graph += '}'
     return graph

-def ancestry_graph(ops, revision):
-    dot_data = ancestry_dot(ops, revision)
+def ancestry_graph(ctxt, revision):
+    dot_data = ancestry_dot(ctxt, revision)
     # okay, let's output the graph
     graph_sha = sha.new(dot_data).hexdigest()
     if not os.access(config.graphopts['directory'], os.R_OK):
============================================================
--- handlers.py	fa13ff0c027a94aeec104c8b07f63d605ad78db4
+++ handlers.py	e9af5798e92963ad207ab1c3a3646818ae952175
@@ -18,7 +18,6 @@ import mtn, common, syntax, release, bra
 from fdo import sharedmimeinfo, icontheme
 # Other bits of ViewMTN
 import mtn, common, syntax, release, branchdiv
-from links import link, dynamic_join, static_join
 from ancestry import ancestry_graph
 from render import *
 hq = cgi.escape
@@ -38,8 +37,6 @@ else:
 else:
     mimeicon = None

-# Renderer, sort out template inheritance, etc, etc.
-renderer = Renderer()
 # Figure out branch divisions
 divisions = branchdiv.BranchDivisions ()

@@ -76,11 +73,11 @@ class Index(object):
             # any stragglers need to be closed
             for div in in_divs.keys():
                 yield end_div (div)
-        renderer.render('index.html', page_title="Branches", branches=division_iter())
+        ctxt.render('index.html', page_title="Branches", branches=division_iter())

 class About(object):
-    def GET(self):
-        renderer.render('about.html', page_title="About")
+    def GET(self, ctxt):
+        ctxt.render('about.html', page_title="About")

 class Tags(object):
     def GET(self, ctxt, restrict_branch=None):
@@ -105,11 +102,11 @@ class Tags(object):
                     revdate = common.parse_timecert(cert[7])
                     rv = common.ago(revdate)
             return rv
-        renderer.render(template_file, page_title="Tags", tags=tags, revision_ago=revision_ago, branch=restrict_branch)
+        ctxt.render(template_file, page_title="Tags", tags=tags, revision_ago=revision_ago, branch=restrict_branch)

 class Help(object):
-    def GET(self):
-        renderer.render('help.html', page_title="Help")
+    def GET(self, ctxt):
+        ctxt.render('help.html', page_title="Help")

 class Changes(object):
     class ComparisonRev:
@@ -183,7 +180,7 @@ class Changes(object):
                     rv = True
         return rv

-    def for_template(self, ops, revs, pathinfo=None, constrain_diff_to=None):
+    def for_template(self, ctxt, revs, pathinfo=None, constrain_diff_to=None):
         rv = []
         for rev, certs in revs:
             rev_branch = ""
@@ -210,12 +207,12 @@ class Changes(object):
                 diff_to_revision = mtn.Revision(constrain_diff_to)
             else:
                 diff_to_revision = revision
-            for stanza in ops.get_revision(rev):
+            for stanza in ctxt.ops.get_revision(rev):
                 if stanza and stanza[0] == "old_revision":
                     diffs.append(Diff(mtn.Revision(stanza[1]), diff_to_revision, filename))
             if diffs:
                 if constrain_diff_to: diffs = [ diffs[0] ]
-                diffs = '| ' + ', '.join([link(d).html('diff') for d in diffs])
+                diffs = '| ' + ', '.join([ctxt.link(d).html('diff') for d in diffs])
             else:
                 diffs = ''
             rv.append((revision, diffs, ago, mtn.Author(author), '<br />\n'.join(changelog), shortlog, when, mtn.File(filename, rev)))
@@ -250,22 +247,25 @@ class Changes(object):
     def Branch_GET(self, ctxt, branch, from_change, to_change, template_name):
         branch = mtn.Branch(branch)
         from_change, to_change, next_from, next_to, previous_from, previous_to = self.determine_bounds(from_change, to_change)
-        changed, new_starting_point = self.branch_get_last_changes(ctxt.ops, branch, from_change, to_change)
+        last_changes = self.branch_get_last_changes(ctxt.ops, branch, from_change, to_change)
+        if last_changes is None:
+            return web.notfound()
+        changed, new_starting_point = last_changes
         changed = changed[from_change:to_change]
         if len(changed) != to_change - from_change:
             next_from, next_to = None, None
         if from_change <= 0:
             previous_from, previous_to = None, None
-        renderer.render(template_name,
-                        page_title="Branch %s" % branch.name,
-                        branch=branch,
-                        from_change=from_change,
-                        to_change=to_change,
-                        previous_from=previous_from,
-                        previous_to=previous_to,
-                        next_from=next_from,
-                        next_to=next_to,
-                        display_revs=self.for_template(ctxt.ops, changed))
+        ctxt.render(template_name,
+                    page_title="Branch %s" % branch.name,
+                    branch=branch,
+                    from_change=from_change,
+                    to_change=to_change,
+                    previous_from=previous_from,
+                    previous_to=previous_to,
+                    next_from=next_from,
+                    next_to=next_to,
+                    display_revs=self.for_template(ctxt, changed))

     def file_get_last_changes(self, ops, from_change, to_change, revision, path):
         def content_changed_fn(start_revision, start_path, in_revision, pathinfo):
@@ -301,17 +301,17 @@ class Changes(object):
             next_from, next_to = None, None
         if from_change <= 0:
             previous_from, previous_to = None, None
-        renderer.render(template_name,
-                        page_title="Changes to '%s' (from %s)" % (cgi.escape(path), revision.abbrev()),
-                        filename=mtn.File(path, revision),
-                        revision=revision,
-                        from_change=from_change,
-                        to_change=to_change,
-                        previous_from=previous_from,
-                        previous_to=previous_to,
-                        next_from=next_from,
-                        next_to=next_to,
-                        display_revs=self.for_template(ctxt.ops, changed, pathinfo=pathinfo, constrain_diff_to=revision))
+        ctxt.render(template_name,
+                    page_title="Changes to '%s' (from %s)" % (cgi.escape(path), revision.abbrev()),
+                    filename=mtn.File(path, revision),
+                    revision=revision,
+                    from_change=from_change,
+                    to_change=to_change,
+                    previous_from=previous_from,
+                    previous_to=previous_to,
+                    next_from=next_from,
+                    next_to=next_to,
+                    display_revs=self.for_template(ctxt, changed, pathinfo=pathinfo, constrain_diff_to=revision))

 class HTMLBranchChanges(Changes):
     def GET(self, ctxt, branch, from_change, to_change):
@@ -366,19 +366,19 @@ class RevisionInfo(RevisionPage):
             return web.notfound()
         certs = ctxt.ops.certs(revision)
         revisions = ctxt.ops.get_revision(revision)
-        output_png, output_imagemap = ancestry_graph(ctxt.ops, revision)
+        output_png, output_imagemap = ancestry_graph(ctxt, revision)
         if os.access(output_imagemap, os.R_OK):
             imagemap = open(output_imagemap).read().replace('\\n', ' by ')
-            imageuri = dynamic_join('revision/graph/' + revision)
+            imageuri = ctxt.perdb_join('revision/graph/' + revision)
         else:
             imagemap = imageuri = None
-        renderer.render('revisioninfo.html',
-                        page_title="Revision %s" % revision.abbrev(),
-                        revision=revision,
-                        certs=certs_for_template(certs),
-                        imagemap=imagemap,
-                        imageuri=imageuri,
-                        revisions=revisions_for_template(revision, revisions))
+        ctxt.render('revisioninfo.html',
+                    page_title="Revision %s" % revision.abbrev(),
+                    revision=revision,
+                    certs=certs_for_template(ctxt, certs),
+                    imagemap=imagemap,
+                    imageuri=imageuri,
+                    revisions=revisions_for_template(ctxt, revision, revisions))

 class RevisionDiff(RevisionPage):
     def GET(self, ctxt, revision_from, revision_to, filename=None):
@@ -394,14 +394,14 @@ class RevisionDiff(RevisionPage):
             files = []
         diff = ctxt.ops.diff(revision_from, revision_to, files)
         diff_obj = Diff(revision_from, revision_to, files)
-        renderer.render('revisiondiff.html',
-                        page_title="Diff from %s to %s" % (revision_from.abbrev(), revision_to.abbrev()),
-                        revision=revision_from,
-                        revision_from=revision_from,
-                        revision_to=revision_to,
-                        diff=syntax.highlight(diff, 'diff'),
-                        diff_obj=diff_obj,
-                        files=files)
+        ctxt.render('revisiondiff.html',
+                    page_title="Diff from %s to %s" % (revision_from.abbrev(), revision_to.abbrev()),
+                    revision=revision_from,
+                    revision_from=revision_from,
+                    revision_to=revision_to,
+                    diff=syntax.highlight(diff, 'diff'),
+                    diff_obj=diff_obj,
+                    files=files)

 class RevisionRawDiff(RevisionPage):
     def GET(self, ctxt, revision_from, revision_to, filename=None):
@@ -447,12 +447,12 @@ class RevisionFile(RevisionPage):
                 template = 'revisionfiletxt.html'
             else:
                 template = 'revisionfilebin.html'
-        renderer.render(template,
-                        filename=mtn.File(filename, revision),
-                        page_title="File %s in revision %s" % (filename, revision.abbrev()),
-                        revision=revision,
-                        mimetype=mimetype,
-                        contents=syntax.highlight(contents, language))
+        ctxt.render(template,
+                    filename=mtn.File(filename, revision),
+                    page_title="File %s in revision %s" % (filename, revision.abbrev()),
+                    revision=revision,
+                    mimetype=mimetype,
+                    contents=syntax.highlight(contents, language))

 class RevisionDownloadFile(RevisionPage):
     def GET(self, ctxt, revision, filename):
@@ -652,22 +652,22 @@ class RevisionBrowse(RevisionPage):
                 yield "even"

         def mime_icon(mime_type):
-            return dynamic_join('mimeicon/' + mime_type)
+            return ctxt.nodb_join('mimeicon/' + mime_type)

-        renderer.render('revisionbrowse.html',
-                        branches=branches,
-                        branch_links=', '.join([link(mtn.Branch(b)).html() for b in branches]),
-                        path=path,
-                        page_title=page_title,
-                        revision=revision,
-                        path_links=path_links(path_components),
-                        row_class=row_class(),
-                        mime_icon=mime_icon,
-                        entries=info_for_manifest(cut_manifest_to_subdir()))
+        ctxt.render('revisionbrowse.html',
+                    branches=branches,
+                    branch_links=', '.join([ctxt.link(mtn.Branch(b)).html() for b in branches]),
+                    path=path,
+                    page_title=page_title,
+                    revision=revision,
+                    path_links=path_links(path_components),
+                    row_class=row_class(),
+                    mime_icon=mime_icon,
+                    entries=info_for_manifest(cut_manifest_to_subdir()))

 class RevisionGraph(object):
     def GET(self, ctxt, revision):
-        output_png, output_imagemap = ancestry_graph(ctxt.ops, revision)
+        output_png, output_imagemap = ancestry_graph(ctxt, revision)
         if os.access(output_png, os.R_OK):
             web.header('Content-Type', 'image/png')
             sys.stdout.write(open(output_png).read())
@@ -732,12 +732,12 @@ class BranchHead(object):
         if len(heads) == 0:
             return web.notfound()
         def proxyurl(revision):
-            return dynamic_join('revision/' + proxy_to + '/' + revision + urllib.quote(extra_path))
+            return ctxt.perdb_join('revision/' + proxy_to + '/' + revision + urllib.quote(extra_path))
         if len(heads) == 1 or head_method == 'anyhead':
             web.redirect(proxyurl(heads[0]))
         else:
             # present an option to the user to choose the head
-            anyhead = '<a href="%s">link</a>' % (dynamic_join('branch/anyhead/' + \
+            anyhead = '<a href="%s">link</a>' % (ctxt.perdb_join('branch/anyhead/' + \
                       proxy_to + '/' + urllib.quote(branch.name, safe = '')))
             head_links = []
             for revision in heads:
@@ -749,14 +749,14 @@ class BranchHead(object):
                         author = mtn.Author(cert[7])
                 head_links.append('<a href="%s">%s</a> %s at %s' % (proxyurl(revision),
                                                                     revision.abbrev(),
-                                                                    link(author).html(),
+                                                                    ctxt.link(author).html(),
                                                                     hq(date)))
-            renderer.render('branchchoosehead.html',
-                            page_title="Branch %s" % branch.name,
-                            branch=branch,
-                            proxy_to=proxy_to,
-                            anyhead=anyhead,
-                            head_links=head_links)
+            ctxt.render('branchchoosehead.html',
+                        page_title="Branch %s" % branch.name,
+                        branch=branch,
+                        proxy_to=proxy_to,
+                        anyhead=anyhead,
+                        head_links=head_links)

 class MimeIcon(object):
     def GET(self, type, sub_type):
============================================================
--- links.py	6ac854e0dfc2959803a89f2cb38c4fd7366eb070
+++ links.py	193e314b64731eee79799e24847557aa9cc7ee43
@@ -16,9 +16,6 @@ import config
 import json
 import config

-dynamic_join = lambda path: urlparse.urljoin(config.dynamic_uri_path, path)
-static_join = lambda path: urlparse.urljoin(config.static_uri_path, path)
-
 hq = cgi.escape

 class Link(object):
@@ -27,10 +24,19 @@ class Link(object):
         self.relative_uri = None
         self.description = description
         self.json_args = None
+        self.ctxt = kwargs['ctxt']
+
     def nbhq(self, s):
         return '&nbsp;'.join([hq(t) for t in s.split(' ')])
+
     def uri(self):
-        return dynamic_join(self.relative_uri)
+        if not self.absolute_uri is None:
+            return self.absolute_uri
+        elif not self.relative_uri is None:
+            return self.ctxt.perdb_join(self.relative_uri)
+        else:
+            return None
+
     def html(self, override_description=None, force_nbsp=False):
         if override_description:
             if force_nbsp:
@@ -39,12 +45,9 @@ class Link(object):
                 d = hq(override_description)
         else:
             d = self.description
-        if self.relative_uri:
-            uri = dynamic_join(self.relative_uri)
-        elif self.absolute_uri:
-            uri = self.absolute_uri
-        else:
-            return self.description
+        uri = self.uri()
+        if uri is None:
+            return hq(self.description)
         rv = '<a href="%s">%s</a>' % (uri, d)
         if self.json_args != None:
             enc_args = binascii.hexlify(json.write(self.json_args))
@@ -150,11 +153,9 @@ def link(obj, link_type=None, **kwargs):
     link_class = type_to_link_class.get(obj.obj_type)
     if not link_class:
         raise LinkException("Unable to link to objects of type: '%s'" % (obj.obj_type))
-    # ugh
-    if link_type:
+    # bodgy, but can't give named arguments from the templates so we need to
+    # have the second argument go into link_type
+    if not link_type is None:
         kwargs['link_type'] = link_type
+    rv = link_class(obj, **kwargs)
+    return rv
-    return link_class(obj, **kwargs)
-
-
-
-
============================================================
--- render.py	c1223ddbb5396f63101b981838cd8296d0cc82ba
+++ render.py	2a55c2e22ff97d808ca4fd06d39eb7bea8a101d3
@@ -9,7 +9,6 @@ import mtn, release, config, common

 import cgi, urllib, web
 import mtn, release, config, common
-from links import link, dynamic_join, static_join

 hq = cgi.escape

@@ -55,7 +54,7 @@ def prettify(s):
 def prettify(s):
     return '&nbsp;'.join([hq(x[0].upper() + x[1:]) for x in s.replace("_", "").split(" ")])

-def certs_for_template(cert_gen):
+def certs_for_template(ctxt, cert_gen):
     for cert in cert_gen:
         if cert[0] == 'key' and len(cert) != 10:
             raise Exception("Not a correctly formatted certificate: %s" % cert)
@@ -66,7 +65,7 @@ def certs_for_template(cert_gen):
         name = cert[5]
         value = cert[7]
         if name == "branch":
-            value = link(mtn.Branch(value)).html()
+            value = ctxt.link(mtn.Branch(value)).html()
         else:
             value = '<br />'.join(map(hq, value.split('\n')))

@@ -74,7 +73,7 @@ def certs_for_template(cert_gen):
                 'name' : prettify(name),
                 'value' : value }

-def revisions_for_template(revision, rev_gen):
+def revisions_for_template(ctxt, revision, rev_gen):
     old_revisions = []
     stanzas = []
     grouping = None
@@ -99,18 +98,18 @@ def revisions_for_template(revision, rev
             # skip it here
             if not from_id:
                 continue
-            diff_links = ','.join([link(Diff(old_revision, revision, fname)).html() for old_revision in old_revisions])
-            value = "Patch file %s (%s)" % (link(mtn.File(fname, revision)).html(), diff_links)
+            diff_links = ','.join([ctxt.link(Diff(old_revision, revision, fname)).html() for old_revision in old_revisions])
+            value = "Patch file %s (%s)" % (ctxt.link(mtn.File(fname, revision)).html(), diff_links)
         elif stanza_type == "old_revision":
             old_revision = mtn.Revision(stanza[1])
             if old_revision.is_empty:
                 value = "This revision is has no ancestor."
             else:
                 old_revisions.append(old_revision)
-                value = "Old revision is: %s (%s)" % (link(old_revision).html(), link(Diff(old_revision, revision)).html())
+                value = "Old revision is: %s (%s)" % (ctxt.link(old_revision).html(), ctxt.link(Diff(old_revision, revision)).html())
         elif stanza_type == "add_file":
             fname = stanza[1]
-            value = "Add file: %s" % (link(mtn.File(fname, revision)).html())
+            value = "Add file: %s" % (ctxt.link(mtn.File(fname, revision)).html())
         elif stanza_type == "add_dir":
             dname = stanza[1]
             value = "Add directory: %s" % (hq(dname))
@@ -119,10 +118,10 @@ def revisions_for_template(revision, rev
             value = "Delete: %s" % (hq(fname))
         elif stanza_type == "set":
             fname, attr, value = stanza[1], stanza[3], stanza[5]
-            value = "Set attribute '%s' to '%s' upon %s" % (hq(attr), hq(value), link(mtn.File(fname, revision)).html())
+            value = "Set attribute '%s' to '%s' upon %s" % (hq(attr), hq(value), ctxt.link(mtn.File(fname, revision)).html())
         elif stanza_type == "rename":
             oldname, newname = stanza[1], stanza[3]
-            value = "Rename %s to %s" % (hq(oldname), link(mtn.File(newname, revision)).html())
+            value = "Rename %s to %s" % (hq(oldname), ctxt.link(mtn.File(newname, revision)).html())
         else:
             value = "(this stanza type is not explicitly rendered; please report this.)\n%s" % hq(str(stanza))

@@ -146,11 +145,8 @@ class Renderer(object):
         # these variables will be available to any template
         self.terms = {
             'dynamic_uri_path' : config.dynamic_uri_path,
-            'dynamic_join' : dynamic_join,
             'urllib_quote' : urllib.quote,
             'static_uri_path' : config.static_uri_path,
-            'static_join' : static_join,
-            'link' : link,
             'version' : release.version,
             }

@@ -160,8 +156,12 @@ class Renderer(object):
             web.render(template, None, True, mod_name)
         self._templates_loaded = True

-    def render(self, template, **kwargs):
+    def render(self, ctxt, template, **kwargs):
         self.load_templates()
         terms = self.terms.copy()
         terms.update(kwargs)
+        terms['link'] = ctxt.link
+        terms['perdb_join'] = ctxt.perdb_join
+        terms['nodb_join'] = ctxt.nodb_join
+        terms['static_join'] = ctxt.static_join
         web.render(template, terms)
============================================================
--- templates/base.html	781439204c039e9426f067b004cbb9ba9f0b39ad
+++ templates/base.html	8d1f9004c63ca950378f4dc1425deee063b0216d
@@ -17,10 +17,10 @@
 <div id="popupBox" class="invisible"></div>
 <div id="menuBar">
   <strong>ViewMTN</strong>:
-  <a href="$dynamic_join('')">Branches</a> |
-  <a href="$dynamic_join('tags')">Tags</a> |
-  <a href="$dynamic_join('help')">Help</a> |
-  <a href="$dynamic_join('about')">About</a><br />
+  <a href="$perdb_join('')">Branches</a> |
+  <a href="$perdb_join('tags')">Tags</a> |
+  <a href="$perdb_join('help')">Help</a> |
+  <a href="$perdb_join('about')">About</a><br />

 #block extramenu
 #end block
============================================================
--- templates/branch.html	7f162c333735ef7f64cd507d524653155972ca9f
+++ templates/branch.html	ed21182f8c1b680b5b25f3d59a4fc08c12a26a0a
@@ -2,10 +2,10 @@

 #def extramenu
 <strong>Branch $branch.name</strong>:
-<a href="$dynamic_join('branch/changes/%s' % $urllib_quote($branch.name, safe=''))">Changes</a> |
-<a href="$dynamic_join('branch/head/info/%s' % $urllib_quote($branch.name, safe=''))">Head revision</a> |
-<a href="$dynamic_join('branch/tags/%s' % $urllib_quote($branch.name, safe=''))">Tags</a> |
-<a href="$dynamic_join('branch/changes/%s/rss' % $urllib_quote($branch.name, safe=''))">RSS</a>
+<a href="$perdb_join('branch/changes/%s' % $urllib_quote($branch.name, safe=''))">Changes</a> |
+<a href="$perdb_join('branch/head/info/%s' % $urllib_quote($branch.name, safe=''))">Head revision</a> |
+<a href="$perdb_join('branch/tags/%s' % $urllib_quote($branch.name, safe=''))">Tags</a> |
+<a href="$perdb_join('branch/changes/%s/rss' % $urllib_quote($branch.name, safe=''))">RSS</a>
 #end def

 #def rssheaders
============================================================
--- templates/branchchangesrss.html	bfbbfead32d6e0aad40646932be79348f2aba0af
+++ templates/branchchangesrss.html	b64e6216c319ef2a5a803b4e2483068c59a5f9d8
@@ -3,7 +3,7 @@
       <language>en-us</language>

       <title>Changes to branch $branch.name</title>
-      <link>$dynamic_join('branch/changes/%s' % $urllib_quote($branch.name, safe=''))</link>
+      <link>$perdb_join('branch/changes/%s' % $urllib_quote($branch.name, safe=''))</link>
       <description>Changes to branch $branch.name</description>


============================================================
--- templates/index.html	c8cce216300ecbead5ba0ffad4c4cf26f43885d1
+++ templates/index.html	dbcf4480fc966a46608f260adaf0689b0cf3c476
@@ -23,7 +23,7 @@ Select one of the branches and you will

 <p>
 Select one of the branches and you will be shown a list of recent changes that have occurred within it.
-If you are looking for a particular revision (for example, a release) the <a href="$dynamic_join('tags')">list of tags</a>
+If you are looking for a particular revision (for example, a release) the <a href="$perdb_join('tags')">list of tags</a>
 might be useful.
 </p>

============================================================
--- templates/revision.html	e9eeb6212f211ce522b1db16156e7129e00993d1
+++ templates/revision.html	aa7ca2c65f3076fdc9bdf2276aca414f99ca40d8
@@ -2,7 +2,7 @@

 #def extramenu
 <strong>Revision $revision.abbrev()</strong>:
-<a href="$dynamic_join('revision/info/%s' % $revision)">Info</a> |
-<a href="$dynamic_join('revision/browse/%s' % $revision)">Browse Files</a> |
-<a href="$dynamic_join('revision/tar/%s' % $revision)">Download (tar)</a><br />
+<a href="$perdb_join('revision/info/%s' % $revision)">Info</a> |
+<a href="$perdb_join('revision/browse/%s' % $revision)">Browse Files</a> |
+<a href="$perdb_join('revision/tar/%s' % $revision)">Download (tar)</a><br />
 #end def
============================================================
--- templates/revisioninfo.html	a6ef44ec8a9a16b735b7edea9467072e2c08ce12
+++ templates/revisioninfo.html	69fdcd40892b237c2cf85602a61e7f31ba112675
@@ -50,7 +50,7 @@ $imagemap
 <div style="float: left; margin: 0; margin-bottom: 1em; text-align: center;">
 #if $imageuri
 <img style="border-width: 1px; border-color: black; border-style: solid;" usemap="#ancestry" src="$imageuri" alt="Ancestry of $revision" />
-<br /><small><a href="$dynamic_join('help#readinggraph')">reading this graph</a></small>
+<br /><small><a href="$perdb_join('help#readinggraph')">reading this graph</a></small>
 #end if
 </div>

============================================================
--- urls.py	16a510f175f983e3853bc0d61029a95e59ae0bbe
+++ urls.py	74ec1a0af57afeb7d98bcf04248d799b8d9b2738
@@ -15,8 +15,6 @@ common_urls = (
 # as their first argument.
 #
 common_urls = (
-    r'about', 'About',
-    r'help', 'Help',
     r'robots.txt', 'RobotsTxt',  ## FIXME needs o exclude per-db paths
     r'mimeicon/([A-Za-z0-9][a-z0-9\-\+\.]*)/([A-Za-z0-9][a-z0-9\-\+\.]*)', 'MimeIcon',
 )
@@ -27,10 +25,12 @@ perdb_urls = (
 #
 perdb_urls = (
     r'', 'Index',
+    r'about', 'About',
+    r'help', 'Help',
     r'tags', 'Tags',
     r'json/([A-Za-z]+)/(.*)', 'Json',

-    r'([a-zA-Z]/)?revision/browse/('+revision_re+')/(.*)', 'RevisionBrowse',
+    r'revision/browse/('+revision_re+')/(.*)', 'RevisionBrowse',
     r'revision/browse/('+revision_re+')()', 'RevisionBrowse',
     r'revision/diff/('+revision_re+')/with/('+revision_re+')', 'RevisionDiff',
     r'revision/diff/('+revision_re+')/with/('+revision_re+')'+'/(.*)', 'RevisionDiff',
============================================================
--- viewmtn.py	6629b45724cc639d2555c41248ccadf35838b83e
+++ viewmtn.py	ae38f0301e147803284029b0216e4c0c4e5fd6bb
@@ -9,11 +9,13 @@
 # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 # PURPOSE.

-import os, sys
+import os, sys, urllib
 from itertools import izip, chain, repeat
+from urlparse import urljoin
 import web
-import mtn, handlers
+import mtn, handlers, links
 from urls import common_urls, perdb_urls
+from render import Renderer
 import config

 # purloined from: http://docs.python.org/lib/itertools-recipes.html
@@ -22,33 +24,49 @@ class RequestContext(object):
     return izip(*[chain(iterable, repeat(padvalue, n-1))]*n)

 class RequestContext(object):
-    def __init__ (self, dbname, ops):
-        self.dbname, self.ops = dbname, ops
+    def __init__ (self, dbname, ops, renderer):
+        self.dbname, self.ops, self.renderer = dbname, ops, renderer
         # make sure that any unread automate output is flushed away
         if not ops is None:
             ops.per_request()
+        self.nodb_join = lambda path: urljoin(config.dynamic_uri_path, path)
+        if self.dbname is None:
+            self.perdb_join = self.nodb_join
+        else:
+            self.perdb_join = lambda path: urljoin(config.dynamic_uri_path, urllib.quote(self.dbname) + '/' + path)
+        self.static_join = lambda path: urljoin(config.static_uri_path, path)

+    def link (self, *args, **kwargs):
+        kwargs['ctxt'] = self
+        return links.link (*args, **kwargs)
+
+    def render(self, *args, **kwargs):
+        self.renderer.render (self, *args, **kwargs)
+
 class RequestContextFactory(object):
     def __init__ (self):
         # has the user specified a dbfiles hash? if so, use it
         self.ops_instances = {}
         self.default = None
+        self.renderer = Renderer()
         if hasattr (config, "dbfiles"):
             for name in config.dbfiles:
                 self.ops_instances[name] = mtn.Operations([config.monotone, config.dbfiles[name]])
             if hasattr (config, "defaultdb"):
                 self.default = config.defaultdb
         else:
-            self.ops_instances["legacy"] = mtn.Operations([config.monotone, config.dbfile])
-            self.default = "legacy"
+            self.ops_instances[None] = mtn.Operations([config.monotone, config.dbfile])
+            self.default = None

     def __getitem__(self, name):
         if name is None:
             ops = self.ops_instances.get (self.default, None)
         else:
-            name = name.rstrip('/')
             ops = self.ops_instances.get (name, None)
-        return RequestContext(name, ops)
+        if ops is None:
+            return None
+        else:
+            return RequestContext(name, ops, self.renderer)

 def runfcgi_apache(func):
     web.wsgi.runfcgi(func, None)
@@ -67,6 +85,19 @@ if __name__ == '__main__':
         urls = ()

         factory = RequestContextFactory()
+
+        def per_db_closure (handler):
+            class PerDBClosure(object):
+                def GET (self, *args, **kwargs):
+                    db, other_args = args[0], args[1:]
+                    # due to regexp we use, db passed in will always have a trailing slash
+                    if not db is None:
+                        db = db[:-1]
+                    ctxt = factory[db]
+                    if ctxt is None:
+                        return web.notfound()
+                    return handler.GET (ctxt, *other_args, **kwargs)
+            return PerDBClosure

         for url, fn in grouper (2, common_urls):
             url = r'^/' + url
@@ -75,22 +106,12 @@ if __name__ == '__main__':
                 urls += (url, fn)
             else:
                 print >>sys.stderr, "*** URL defined for non-existant handler %s: %s" % (fn, url)
-
-        def get_db_closure (handler):
-            class PerDBClosure(object):
-                def GET (self, *args, **kwargs):
-                    db, other_args = args[0], args[1:]
-                    ops = factory[db]
-                    if ops is None:
-                        return web.notfound()
-                    return handler.GET (ops, *other_args, **kwargs)
-            return PerDBClosure

         for url, fn in grouper (2, perdb_urls):
             if hasattr(handlers, fn):
                 url = r'^/([A-Za-z]+/)?' + url
                 urls += (url, fn)
-                fvars[fn] = get_db_closure (getattr(handlers, fn)())
+                fvars[fn] = per_db_closure (getattr(handlers, fn)())
             else:
                 print >>sys.stderr, "*** URL defined for non-existant handler %s: %s" % (fn, url)

@@ -98,7 +119,7 @@ if __name__ == '__main__':

     urls, fvars = assemble_urls()
     func = lambda : per_request_wrapper(web.webpyfunc(urls, fvars=fvars))
-    web.run(func, globals(), web.reloader)
+    web.run(func, globals())

 ###
 ### vi:expandtab:sw=4:ts=4