The unified diff between revisions [ace1480d..] and [e573b84f..] is displayed below. It can also be downloaded as a raw diff.

#
#
# patch "viewmtn.py"
#  from [78b92d8c6980db2077c8fb48277b64f5088c74ee]
#    to [c0e46420ea8ad46d0a3a4c1b80d981e4fd4970c0]
#
============================================================
--- viewmtn.py	78b92d8c6980db2077c8fb48277b64f5088c74ee
+++ viewmtn.py	c0e46420ea8ad46d0a3a4c1b80d981e4fd4970c0
@@ -367,10 +367,6 @@ class Renderer(object):
         self._templates_loaded = True

     def render(self, template, **kwargs):
-        # technically it'd be better to do this before serving the
-        # request, however this is about the only per-request
-        # spot that runs for every handler..
-        ops.per_request()
         self.load_templates()
         terms = self.terms.copy()
         terms.update(kwargs)
@@ -382,8 +378,8 @@ class OperationsFactory(object):
         self.ops_instances = {}
         self.default = None
         if hasattr (config, "dbfiles"):
-            for name, dbfile in config.dbfiles:
-                self.ops_instances[name] = mtn.Operations([config.monotone, dbfile])
+            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:
@@ -391,17 +387,25 @@ class OperationsFactory(object):
             self.default = "legacy"

     def get_ops(self, name):
-        if not (name is None):
-            return self.ops.get (name, None)
+        ops = None
+        if name is None:
+            ops = self.ops_instances.get (self.default, None)
         else:
-            return self.ops.get (self.default, None)
+            name = name.rstrip('/')
+            ops = self.ops_instances.get (name, None)
+        # technically it'd be better to do this before serving the
+        # request, however this is about the only per-request
+        # spot that runs for every handler..
+        if not ops is None:
+            ops.per_request()
+        return ops

 #
 # TODO: try and wrap these globals up in a single global object
 #
 renderer = Renderer()
 op_fact = OperationsFactory()
-ops = mtn.Operations([config.monotone, config.dbfile])
+##ops = mtn.Operations([config.monotone, config.dbfile])
 mimehelp = sharedmimeinfo.LookupHelper(getattr(config, "mime_map", None))
 if config.icon_theme:
     try:
@@ -437,7 +441,7 @@ class Index(object):
 divisions = BranchDivisions ()

 class Index(object):
-    def GET(self, *args):
+    def GET(self, ops):
         branches = list(ops.branches ())
         divisions.calculate_divisions (branches)
         def division_iter():
@@ -476,7 +480,7 @@ class Tags(object):
         renderer.render('about.html', page_title="About")

 class Tags(object):
-    def GET(self, restrict_branch=None):
+    def GET(self, ops, restrict_branch=None):
         # otherwise we couldn't use automate again..
         tags = list(ops.tags())
         if restrict_branch != None:
@@ -505,7 +509,7 @@ class Changes(object):
         renderer.render('help.html', page_title="Help")

 class Changes(object):
-    def __get_last_changes(self, start_from, parent_func, selection_func, n):
+    def __get_last_changes(self, ops, start_from, parent_func, selection_func, n):
         """returns at least n revisions that are parents of the revisions in start_from,
         ordered by time (descending). selection_func is called for each revision, and
         that revision is only included in the result if the function returns True."""
@@ -550,7 +554,7 @@ class Changes(object):
         rv = map (lambda x: (x.revision, x.certs), result), revq
         return rv

-    def on_our_branch(self, branch, revision):
+    def on_our_branch(self, ops, branch, revision):
         rv = False
         for cert in ops.certs(revision):
             if cert[4] == 'name' and cert[5] == 'branch':
@@ -558,7 +562,7 @@ class Changes(object):
                     rv = True
         return rv

-    def for_template(self, revs, pathinfo=None, constrain_diff_to=None):
+    def for_template(self, ops, revs, pathinfo=None, constrain_diff_to=None):
         rv = []
         for rev, certs in revs:
             rev_branch = ""
@@ -611,20 +615,21 @@ class Changes(object):
         previous_to = previous_from + per_page
         return (from_change, to_change, next_from, next_to, previous_from, previous_to)

-    def branch_get_last_changes(self, branch, from_change, to_change):
+    def branch_get_last_changes(self, ops, branch, from_change, to_change):
         heads = [t for t in ops.heads(branch.name)]
         if not heads:
             return web.notfound()
-        changed, new_starting_point = self.__get_last_changes(heads,
+        changed, new_starting_point = self.__get_last_changes(ops,
+                                                              heads,
                                                               lambda r: ops.parents(r),
-                                                              lambda r: self.on_our_branch(branch, r),
+                                                              lambda r: self.on_our_branch(ops, branch, r),
                                                               to_change)
         return changed, new_starting_point

-    def Branch_GET(self, branch, from_change, to_change, template_name):
+    def Branch_GET(self, ops, 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(branch, from_change, to_change)
+        changed, new_starting_point = self.branch_get_last_changes(ops, branch, from_change, to_change)
         changed = changed[from_change:to_change]
         if len(changed) != to_change - from_change:
             next_from, next_to = None, None
@@ -639,9 +644,9 @@ class Changes(object):
                         previous_to=previous_to,
                         next_from=next_from,
                         next_to=next_to,
-                        display_revs=self.for_template(changed))
+                        display_revs=self.for_template(ops, changed))

-    def file_get_last_changes(self, from_change, to_change, revision, path):
+    def file_get_last_changes(self, ops, from_change, to_change, revision, path):
         def content_changed_fn(start_revision, start_path, in_revision, pathinfo):
             uniq = set()
             parents = list(ops.parents(in_revision))
@@ -660,15 +665,16 @@ class Changes(object):
         pathinfo = {}
         # not just the starting revision! we might not have changed 'path' in the starting rev..
         start_at = content_changed_fn(revision, path, revision, pathinfo)
-        changed, new_starting_point = self.__get_last_changes(start_at,
+        changed, new_starting_point = self.__get_last_changes(ops,
+                                                              start_at,
                                                               lambda r: content_changed_fn(revision, path, r, pathinfo),
                                                               lambda r: True,
                                                               to_change)
         return changed, new_starting_point, pathinfo

-    def File_GET(self, from_change, to_change, revision, path, template_name):
+    def File_GET(self, ops, from_change, to_change, revision, path, template_name):
         from_change, to_change, next_from, next_to, previous_from, previous_to = self.determine_bounds(from_change, to_change)
-        changed, new_starting_point, pathinfo = self.file_get_last_changes(from_change, to_change, revision, path)
+        changed, new_starting_point, pathinfo = self.file_get_last_changes(ops, from_change, to_change, revision, path)
         changed = changed[from_change:to_change]
         if len(changed) != to_change - from_change:
             next_from, next_to = None, None
@@ -684,18 +690,18 @@ class Changes(object):
                         previous_to=previous_to,
                         next_from=next_from,
                         next_to=next_to,
-                        display_revs=self.for_template(changed, pathinfo=pathinfo, constrain_diff_to=revision))
+                        display_revs=self.for_template(ops, changed, pathinfo=pathinfo, constrain_diff_to=revision))

 class HTMLBranchChanges(Changes):
-    def GET(self, branch, from_change, to_change):
-        Changes.Branch_GET(self, branch, from_change, to_change, "branchchanges.html")
+    def GET(self, ops, branch, from_change, to_change):
+        Changes.Branch_GET(self, ops, branch, from_change, to_change, "branchchanges.html")

 class RSSBranchChanges(Changes):
-    def GET(self, branch, from_change, to_change):
-        Changes.Branch_GET(self, branch, from_change, to_change, "branchchangesrss.html")
+    def GET(self, ops, branch, from_change, to_change):
+        Changes.Branch_GET(self, ops, branch, from_change, to_change, "branchchangesrss.html")

 class RevisionPage(object):
-    def get_fileid(self, revision, filename):
+    def get_fileid(self, ops, revision, filename):
         rv = None
         for stanza in ops.get_manifest_of(revision):
             if stanza[0] != 'file':
@@ -703,13 +709,15 @@ class RevisionPage(object):
             if stanza[1] == filename:
                 rv = stanza[3]
         return rv
-    def exists(self, revision):
+
+    def exists(self, ops, revision):
         try:
             certs = [t for t in ops.certs(revision)]
             return True
         except mtn.MonotoneException:
             return False
-    def branches_for_rev(self, revisions_val):
+
+    def branches_for_rev(self, ops, revisions_val):
         rv = []
         for stanza in ops.certs(revisions_val):
             if stanza[4] == 'name' and stanza[5] == 'branch':
@@ -717,27 +725,27 @@ class RevisionFileChanges(Changes, Revis
         return rv

 class RevisionFileChanges(Changes, RevisionPage):
-    def GET(self, from_change, to_change, revision, path):
+    def GET(self, ops, from_change, to_change, revision, path):
         revision = mtn.Revision(revision)
-        if not self.exists(revision):
+        if not self.exists(ops, revision):
             return web.notfound()
-        Changes.File_GET(self, from_change, to_change, revision, path, "revisionfilechanges.html")
+        Changes.File_GET(self, ops, from_change, to_change, revision, path, "revisionfilechanges.html")

 class RevisionFileChangesRSS(Changes, RevisionPage):
-    def GET(self, from_change, to_change, revision, path):
+    def GET(self, ops, from_change, to_change, revision, path):
         revision = mtn.Revision(revision)
-        if not self.exists(revision):
+        if not self.exists(ops, revision):
             return web.notfound()
-        Changes.File_GET(self, from_change, to_change, revision, path, "revisionfilechangesrss.html")
+        Changes.File_GET(self, ops, from_change, to_change, revision, path, "revisionfilechangesrss.html")

 class RevisionInfo(RevisionPage):
-    def GET(self, revision):
+    def GET(self, ops, revision):
         revision = mtn.Revision(revision)
-        if not self.exists(revision):
+        if not self.exists(ops, revision):
             return web.notfound()
         certs = ops.certs(revision)
         revisions = ops.get_revision(revision)
-        output_png, output_imagemap = ancestry_graph(revision)
+        output_png, output_imagemap = ancestry_graph(ops, revision)
         if os.access(output_imagemap, os.R_OK):
             imagemap = open(output_imagemap).read().replace('\\n', ' by ')
             imageuri = dynamic_join('revision/graph/' + revision)
@@ -752,12 +760,12 @@ class RevisionDiff(RevisionPage):
                         revisions=revisions_for_template(revision, revisions))

 class RevisionDiff(RevisionPage):
-    def GET(self, revision_from, revision_to, filename=None):
+    def GET(self, ops, revision_from, revision_to, filename=None):
         revision_from = mtn.Revision(revision_from)
         revision_to = mtn.Revision(revision_to)
-        if not self.exists(revision_from):
+        if not self.exists(ops, revision_from):
             return web.notfound()
-        if not self.exists(revision_to):
+        if not self.exists(ops, revision_to):
             return web.notfound()
         if filename != None:
             files = [filename]
@@ -775,12 +783,12 @@ class RevisionRawDiff(RevisionPage):
                         files=files)

 class RevisionRawDiff(RevisionPage):
-    def GET(self, revision_from, revision_to, filename=None):
+    def GET(self, ops, revision_from, revision_to, filename=None):
         revision_from = mtn.Revision(revision_from)
         revision_to = mtn.Revision(revision_to)
-        if not self.exists(revision_from):
+        if not self.exists(ops, revision_from):
             return web.notfound()
-        if not self.exists(revision_to):
+        if not self.exists(ops, revision_to):
             return web.notfound()
         if filename != None:
             files = [filename]
@@ -793,12 +801,12 @@ class RevisionFile(RevisionPage):
         sys.stdout.flush()

 class RevisionFile(RevisionPage):
-    def GET(self, revision, filename):
+    def GET(self, ops, revision, filename):
         revision = mtn.Revision(revision)
-        if not self.exists(revision):
+        if not self.exists(ops, revision):
             return web.notfound()
         language = filename.rsplit('.', 1)[-1]
-        fileid = RevisionPage.get_fileid(self, revision, filename)
+        fileid = RevisionPage.get_fileid(self, ops, revision, filename)
         if not fileid:
             return web.notfound()
         contents = ops.get_file(fileid)
@@ -826,12 +834,12 @@ class RevisionDownloadFile(RevisionPage)
                         contents=syntax.highlight(contents, language))

 class RevisionDownloadFile(RevisionPage):
-    def GET(self, revision, filename):
+    def GET(self, ops, revision, filename):
         web.header('Content-Disposition', 'attachment; filename=%s' % filename)
         revision = mtn.Revision(revision)
-        if not self.exists(revision):
+        if not self.exists(ops, revision):
             return web.notfound()
-        fileid = RevisionPage.get_fileid(self, revision, filename)
+        fileid = RevisionPage.get_fileid(self, ops, revision, filename)
         if not fileid:
             return web.notfound()
         for idx, data in enumerate(ops.get_file(fileid)):
@@ -842,11 +850,11 @@ class RevisionTar(RevisionPage):
         sys.stdout.flush()

 class RevisionTar(RevisionPage):
-    def GET(self, revision):
+    def GET(self, ops, revision):
         # we'll output in the USTAR tar format; documentation taken from:
         # http://en.wikipedia.org/wiki/Tar_%28file_format%29
         revision = mtn.Revision(revision)
-        if not self.exists(revision):
+        if not self.exists(ops, revision):
             return web.notfound()
         filename = "%s.tar" % revision
         web.header('Content-Disposition', 'attachment; filename=%s' % filename)
@@ -886,11 +894,11 @@ class RevisionBrowse(RevisionPage):
             tarobj.addfile(ti, filecontents)

 class RevisionBrowse(RevisionPage):
-    def GET(self, revision, path):
+    def GET(self, ops, revision, path):
         revision = mtn.Revision(revision)
-        if not self.exists(revision):
+        if not self.exists(ops, revision):
             return web.notfound()
-        branches = RevisionPage.branches_for_rev(self, revision)
+        branches = RevisionPage.branches_for_rev(self, ops, revision)
         revisions = ops.get_revision(revision)

         def components(path):
@@ -1036,7 +1044,7 @@ class RevisionBrowse(RevisionPage):
                         mime_icon=mime_icon,
                         entries=info_for_manifest(cut_manifest_to_subdir()))

-def ancestry_dot(revision):
+def ancestry_dot(ops, revision):
     def dot_escape(s):
         # kinda paranoid, should probably revise later
         permitted=string.digits + string.letters + ' -<>-:,*@!$%^&.+_~?/'
@@ -1172,8 +1180,8 @@ digraph ancestry {
     graph += '}'
     return graph

-def ancestry_graph(revision):
-    dot_data = ancestry_dot(revision)
+def ancestry_graph(ops, revision):
+    dot_data = ancestry_dot(ops, revision)
     # okay, let's output the graph
     graph_sha = sha.new(dot_data).hexdigest()
     if not os.access(config.graphopts['directory'], os.R_OK):
@@ -1195,8 +1203,8 @@ class RevisionGraph(object):
     return output_png, output_imagemap

 class RevisionGraph(object):
-    def GET(self, revision):
-        output_png, output_imagemap = ancestry_graph(revision)
+    def GET(self, ops, revision):
+        output_png, output_imagemap = ancestry_graph(ops, revision)
         if os.access(output_png, os.R_OK):
             web.header('Content-Type', 'image/png')
             sys.stdout.write(open(output_png).read())
@@ -1214,13 +1222,13 @@ class Json(object):
                 revdate = common.parse_timecert(cert[7])
                 rv['ago'] = common.ago(revdate)

-    def BranchLink(self, for_branch):
+    def BranchLink(self, ops, for_branch):
         rv = {
             'type' : 'branch',
             'branch' : for_branch,
         }
         branch = mtn.Branch(for_branch)
-        changes, new_starting_point = Changes().branch_get_last_changes(branch, 0, 1)
+        changes, new_starting_point = Changes().branch_get_last_changes(ops, branch, 0, 1)
         if len(changes) < 1:
             return web.notfound()
         if not changes:
@@ -1230,7 +1238,7 @@ class Json(object):
             self.fill_from_certs(rv, certs)
         return rv

-    def RevisionLink(self, revision_id):
+    def RevisionLink(self, ops, revision_id):
         rv = {
             'type' : 'revision',
             'revision_id' : revision_id,
@@ -1240,19 +1248,19 @@ class Json(object):
         self.fill_from_certs(rv, certs)
         return rv

-    def GET(self, method, encoded_args):
+    def GET(self, ops, method, encoded_args):
         writer = json.JsonWriter()
         if not encoded_args.startswith('js_'):
             return web.notfound()
         args = json.read(binascii.unhexlify((encoded_args[3:])))
         if hasattr(self, method):
-            rv = getattr(self, method)(*args)
+            rv = getattr(self, method)(ops, *args)
         else:
             return web.notfound()
         print writer.write(rv)

 class BranchHead(object):
-    def GET(self, head_method, proxy_to, branch, extra_path):
+    def GET(self, ops, head_method, proxy_to, branch, extra_path):
         branch = mtn.Branch(branch)
         valid = ('browse', 'file', 'downloadfile', 'info', 'tar', 'graph')
         if not proxy_to in valid:
@@ -1355,7 +1363,6 @@ def per_request_wrapper(func, *args, **k
     web.wsgi.runfcgi(func, None)

 def per_request_wrapper(func, *args, **kwargs):
-    ops.per_request()
     return func(*args, **kwargs)

 if __name__ == '__main__':
@@ -1377,10 +1384,11 @@ if __name__ == '__main__':
             class PerDBClosure(object):
                 def GET (self, *args, **kwargs):
                     db, other_args = args[0], args[1:]
-                    print >>sys.stderr, "-> selected db", db
-                    the_cls.GET (*other_args, **kwargs)
+                    ops = op_fact.get_ops (db)
+                    if ops is None:
+                        return web.notfound()
+                    return the_cls.GET (ops, *other_args, **kwargs)
             rv = PerDBClosure
-            print >>sys.stderr, rv
             return rv

         for url, fn in grouper (2, perdb_urls):