diff --git a/sample-plugins/commit_ticket_update.py b/sample-plugins/commit_ticket_update.py
--- a/sample-plugins/commit_ticket_update.py
+++ b/sample-plugins/commit_ticket_update.py
@@ -275,7 +275,8 @@
                              "ticket)", class_='hint')
         if ChangesetModule(self.env).wiki_format_messages:
             return tag.div(format_to_html(self.env,
-                formatter.context('changeset', (reponame, changeset.rev)),
+                formatter.context('changeset', changeset.rev,
+                                  parent=Resource('repository', reponame)),
                 changeset.message, escape_newlines=True), class_='message')
         else:
             return tag.pre(changeset.message, class_='message')
diff --git a/trac/mimeview/api.py b/trac/mimeview/api.py
--- a/trac/mimeview/api.py
+++ b/trac/mimeview/api.py
@@ -128,7 +128,7 @@
 
     @classmethod
     def from_request(cls, req, resource=None, id=False, version=False,
-                     absurls=False):
+                     parent=False, absurls=False):
         """Create a rendering context from a request.
 
         The `perm` and `href` properties of the context will be initialized
@@ -158,8 +158,8 @@
         else:
             href = None
             perm = None
-        self = cls(Resource(resource, id=id, version=version), href=href,
-                   perm=perm)
+        self = cls(Resource(resource, id=id, version=version, parent=parent),
+                   href=href, perm=perm)
         self.req = req
         return self
 
@@ -172,7 +172,7 @@
             context = context.parent
         return '<%s %s>' % (type(self).__name__, ' - '.join(reversed(path)))
 
-    def __call__(self, resource=None, id=False, version=False):
+    def __call__(self, resource=None, id=False, version=False, parent=False):
         """Create a nested rendering context.
 
         `self` will be the parent for the new nested context.
@@ -196,7 +196,8 @@
         True
         """
         if resource:
-            resource = Resource(resource, id=id, version=version)
+            resource = Resource(resource, id=id, version=version,
+                                parent=parent)
         else:
             resource = self.resource
         context = Context(resource, href=self.href, perm=self.perm)
diff --git a/trac/resource.py b/trac/resource.py
--- a/trac/resource.py
+++ b/trac/resource.py
@@ -187,7 +187,6 @@
         resource.parent = parent
         return resource
 
-
     def __call__(self, realm=False, id=False, version=False, parent=False):
         """Create a new Resource using the current resource as a template.
 
@@ -206,8 +205,7 @@
         >>> repr(Resource(None).child('attachment', 'file.txt'))
         "<Resource u', attachment:file.txt'>"
         """
-        return self.__call__(realm, id, version, self)
-    
+        return Resource(realm, id, version, self)
 
 
 class ResourceSystem(Component):
diff --git a/trac/versioncontrol/api.py b/trac/versioncontrol/api.py
--- a/trac/versioncontrol/api.py
+++ b/trac/versioncontrol/api.py
@@ -33,6 +33,12 @@
 from trac.web.api import IRequestFilter
 
 
+def split_vc_resource(resource):
+    """Split a versioncontrol resource into reponame and path."""
+    assert resource.parent.realm == 'repository'
+    return (resource.parent.id, resource.id)
+
+
 class IRepositoryConnector(Interface):
     """Provide support for a specific version control system."""
 
@@ -343,21 +349,20 @@
 
     # IResourceManager methods
 
-    # Note: with multiple repository support, the repository name becomes
-    #       part of the 'id', which becomes a `(reponame, rev or path)` pair.
-
     def get_resource_realms(self):
         yield 'changeset'
         yield 'source'
+        yield 'repository'
 
     def get_resource_description(self, resource, format=None, **kwargs):
-        reponame, id = resource.id
         if resource.realm == 'changeset':
+            (reponame, id) = split_vc_resource(resource)
             if reponame:
                 return _("Changeset %(rev)s in %(repo)s", rev=id, repo=reponame)
             else:
                 return _("Changeset %(rev)s", rev=id)
         elif resource.realm == 'source':
+            (reponame, id) = split_vc_resource(resource)
             version = in_repo = ''
             if format == 'summary':
                 repos = resource.env.get_repository(reponame)
@@ -375,14 +380,18 @@
             if reponame:
                 in_repo = _(" in %(repo)s", repo=reponame)
             return ''.join([kind, ' ', id, version, in_repo])
+        elif resource.realm == 'repository':
+            return _("Repository %(repo)s", repo=resource.id)
 
     def get_resource_url(self, resource, href, **kwargs):
-        if resource and resource.realm in ('source', 'changeset'):
-            repos, id = resource.id
-            if resource.realm == 'source':
-                return href.source(repos, id)
-            else:
-                return href.changeset(id, repos)
+        if resource.realm == 'changeset':
+            (reponame, id) = split_vc_resource(resource)
+            return href.changeset(id, reponame)
+        elif resource.realm == 'source':
+            (reponame, id) = split_vc_resource(resource)
+            return href.source(reponame, id)
+        elif resource.realm == 'repository':
+            return href.source(resource.id)
 
     # IRepositoryProvider methods
 
@@ -524,7 +533,7 @@
         """
         while context:
             if context.resource.realm in ('source', 'changeset'):
-                return context.resource.id[0]
+                return context.resource.parent.id
             context = context.parent
 
     def get_all_repositories(self):
diff --git a/trac/versioncontrol/svn_prop.py b/trac/versioncontrol/svn_prop.py
--- a/trac/versioncontrol/svn_prop.py
+++ b/trac/versioncontrol/svn_prop.py
@@ -21,7 +21,7 @@
 from genshi.builder import tag
 
 from trac.core import *
-from trac.versioncontrol import NoSuchNode
+from trac.versioncontrol import NoSuchNode, split_vc_resource
 from trac.versioncontrol.svn_fs import _path_within_scope
 from trac.versioncontrol.web_ui.browser import IPropertyRenderer
 from trac.versioncontrol.web_ui.changeset import IPropertyDiffRenderer
@@ -147,7 +147,7 @@
         has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo')
         revs_label = (_('merged'), _('blocked'))[name.endswith('blocked')]
         revs_cols = has_eligible and 2 or None
-        (reponame, target_path) = context.resource.id
+        (reponame, target_path) = split_vc_resource(context.resource)
         repos = self.env.get_repository(reponame)
         target_rev = context.resource.version
         if has_eligible:
@@ -170,7 +170,8 @@
             deleted = False
             try:
                 node = repos.get_node(spath, target_rev)
-                if 'LOG_VIEW' in context.perm('source', spath):
+                resource = context.resource.parent.child('source', spath)
+                if 'LOG_VIEW' in context.perm(resource):
                     row = [_get_source_link(spath, context),
                            _get_revs_link(revs_label, context, spath, revs)]
                     if has_eligible:
@@ -228,7 +229,7 @@
 
 def _get_source_link(spath, context):
     """Return a link to a merge source."""
-    reponame = context.resource.id[0]
+    reponame = context.resource.parent.id
     return tag.a('/' + spath, title=_('View merge source'),
                  href=context.href.browser(reponame, spath,
                                            rev=context.resource.version))
@@ -238,7 +239,7 @@
     given, to the revision itself for a single revision, or a `<span>`
     with "no revision" for none.
     """
-    reponame = context.resource.id[0]
+    reponame = context.resource.parent.id
     if not revs:
         return tag.span(label, title=_('No revisions'))
     elif ',' in revs or '-' in revs:
@@ -262,7 +263,7 @@
         # Build 3 columns table showing modifications on merge sources
         # || source || added revs || removed revs ||
         # || source || removed                    ||
-        repos = self.env.get_repository(old_context.resource.id[0])
+        repos = self.env.get_repository(old_context.resource.parent.id)
         def parse_sources(props):
             sources = {}
             for line in props[name].splitlines():
diff --git a/trac/versioncontrol/templates/browser.html b/trac/versioncontrol/templates/browser.html
--- a/trac/versioncontrol/templates/browser.html
+++ b/trac/versioncontrol/templates/browser.html
@@ -111,7 +111,8 @@
         <tr py:if="file">
           <td class="message searchable" py:choose="">
             <py:when test="wiki_format_messages" xml:space="preserve">
-              ${wiki_to_html(context('changeset', (reponame, file.changeset.rev)), file.changeset.message, escape_newlines=True)}
+              ${wiki_to_html(context('changeset', file.changeset.rev, parent=repos_resource),
+                             file.changeset.message, escape_newlines=True)}
             </py:when>
             <py:otherwise>${file.changeset.message}</py:otherwise>
           </td>
diff --git a/trac/versioncontrol/templates/changeset.html b/trac/versioncontrol/templates/changeset.html
--- a/trac/versioncontrol/templates/changeset.html
+++ b/trac/versioncontrol/templates/changeset.html
@@ -122,8 +122,8 @@
             &nbsp;
             </py:when>
             <py:when test="wiki_format_messages">
-              ${wiki_to_html(context('changeset', (reponame, changeset.rev)), changeset.message, 
-                             escape_newlines=True)}
+              ${wiki_to_html(context('changeset', changeset.rev, parent=repos_resource),
+                             changeset.message, escape_newlines=True)}
             </py:when>
             <py:otherwise><pre>${changeset.message}</pre></py:otherwise>
           </dd>
diff --git a/trac/versioncontrol/templates/dir_entries.html b/trac/versioncontrol/templates/dir_entries.html
--- a/trac/versioncontrol/templates/dir_entries.html
+++ b/trac/versioncontrol/templates/dir_entries.html
@@ -24,7 +24,8 @@
         </td>
         <td class="change">
           <span class="author" py:if="change">${authorinfo(change.author)}:</span>
-          <span class="change" py:choose="" py:with="chgset_context = context('changeset', (reponame, change.rev))">
+          <span class="change" py:choose=""
+                py:with="chgset_context = context('changeset', change.rev, parent=repos_resource)">
             <py:when test="not change or 'CHANGESET_VIEW' not in perm(chgset_context.resource)">-</py:when>
             <py:when test="wiki_format_messages">
               ${change and wiki_to_oneliner(chgset_context, change.message, shorten=True)}
diff --git a/trac/versioncontrol/templates/repository_index.html b/trac/versioncontrol/templates/repository_index.html
--- a/trac/versioncontrol/templates/repository_index.html
+++ b/trac/versioncontrol/templates/repository_index.html
@@ -28,7 +28,8 @@
           </td>
           <td class="change">
             <span class="author" py:if="change">${authorinfo(change.author)}:</span>
-            <span class="change" py:choose="" py:with="chgset_context = context('changeset', (reponame, change.rev))">
+            <span class="change" py:choose=""
+                  py:with="chgset_context = context('changeset', change.rev, parent=repos_resource)">
               <em py:when="err" py:content="err" />
               <py:when test="not change or 'CHANGESET_VIEW' not in perm(chgset_context.resource)">-</py:when>
               <py:when test="wiki_format_messages">
diff --git a/trac/versioncontrol/templates/revisionlog.html b/trac/versioncontrol/templates/revisionlog.html
--- a/trac/versioncontrol/templates/revisionlog.html
+++ b/trac/versioncontrol/templates/revisionlog.html
@@ -110,7 +110,7 @@
             <py:for each="idx, item in enumerate(items)">
               <py:with vars="change = changes[item.rev];
                              is_separator = item.change is None;
-                             chgset_context = context('changeset', (reponame, change.rev));
+                             chgset_context = context('changeset', change.rev, parent=repos_resource);
                              chgset_view = 'CHANGESET_VIEW' in perm(chgset_context.resource);
                              odd_even = idx % 2 and 'odd' or 'even'">
                 <!--! highlight copy or rename operations -->
diff --git a/trac/versioncontrol/templates/revisionlog.rss b/trac/versioncontrol/templates/revisionlog.rss
--- a/trac/versioncontrol/templates/revisionlog.rss
+++ b/trac/versioncontrol/templates/revisionlog.rss
@@ -17,7 +17,7 @@
 
     <item py:for="item in items" 
           py:with="change = changes[item.rev]; 
-                   item_context = context('changeset', (reponame, change.rev))">
+                   item_context = context('changeset', change.rev, parent=repos_resource))">
       ${author_or_creator(change.author, email_map)}
       <pubDate>${http_date(change.date)}</pubDate>
       <title>Revision $item.rev: ${shorten_line(change.message)}</title>
diff --git a/trac/versioncontrol/web_ui/browser.py b/trac/versioncontrol/web_ui/browser.py
--- a/trac/versioncontrol/web_ui/browser.py
+++ b/trac/versioncontrol/web_ui/browser.py
@@ -28,7 +28,7 @@
 from trac.mimeview.api import Mimeview, is_binary, get_mimetype, \
                               IHTMLPreviewAnnotator, Context
 from trac.perm import IPermissionRequestor
-from trac.resource import ResourceNotFound, Resource
+from trac.resource import Resource, ResourceNotFound
 from trac.util import embedded_numbers
 from trac.util.compat import all
 from trac.util.datefmt import http_date, utc
@@ -41,7 +41,7 @@
 from trac.wiki.api import IWikiSyntaxProvider, IWikiMacroProvider, parse_args
 from trac.wiki.formatter import format_to_html, format_to_oneliner
 from trac.versioncontrol.api import RepositoryManager, NoSuchChangeset, \
-                                    NoSuchNode
+                                    NoSuchNode, split_vc_resource
 from trac.versioncontrol.web_ui.util import *
 
 
@@ -361,9 +361,12 @@
                 rev_or_latest = rev or repos.youngest_rev
                 node = get_existing_node(req, repos, path, rev_or_latest)
             except NoSuchChangeset, e:
-                raise ResourceNotFound(e.message, _('Invalid Changeset Number'))
+                raise ResourceNotFound(e.message,
+                                       _('Invalid changeset number'))
 
-            context = context('source', (reponame, path), node.created_rev)
+            repos_resource = Resource('repository', reponame)
+            context = context(repos_resource.child('source', path,
+                                                   version=node.created_rev))
 
         # Prepare template data
         path_links = get_path_links(req.href, reponame, path, rev, order, desc)
@@ -390,6 +393,7 @@
 
         data = {
             'context': context, 'reponame': reponame or None,
+            'repos_resource': repos and repos_resource,
             'path': path, 'rev': node and node.rev, 'stickyrev': rev,
             'created_path': node and node.created_path,
             'created_rev': node and node.created_rev,
@@ -789,11 +793,9 @@
     def __init__(self, env, context):
         self.env = env
         self.context = context
-        # `context`'s resource is ('source', (reponame, path), version=rev)
-        r = context.resource
-        self.reponame, self.path = r.id
+        (self.reponame, self.path) = split_vc_resource(context.resource)
         self.repos = env.get_repository(self.reponame)
-        self.rev = r.version
+        self.rev = context.resource.version
         # maintain state
         self.prev_chgset = None
         self.chgset_data = {}
diff --git a/trac/versioncontrol/web_ui/changeset.py b/trac/versioncontrol/web_ui/changeset.py
--- a/trac/versioncontrol/web_ui/changeset.py
+++ b/trac/versioncontrol/web_ui/changeset.py
@@ -307,7 +307,8 @@
         data['wiki_format_messages'] = self.wiki_format_messages
 
         if chgset:
-            req.perm('changeset', new).require('CHANGESET_VIEW')
+            resource = Resource('repository', reponame).child('changeset', new)
+            req.perm(resource).require('CHANGESET_VIEW')
             chgset = repos.get_changeset(new)
 
             # TODO: find a cheaper way to reimplement r2636
@@ -408,8 +409,10 @@
             title = _changeset_title(rev)
 
             # Support for revision properties (#2545)
-            context = Context.from_request(req, 'changeset', 
-                                           (reponame, chgset.rev))
+            repos_resource = Resource('repository', reponame)
+            data['repos_resource'] = repos_resource
+            context = Context.from_request(req, 'changeset', chgset.rev,
+                                           parent=repos_resource)
             revprops = chgset.get_properties()
             data['properties'] = browser.render_properties('revprop', context,
                                                            revprops)
@@ -485,12 +488,15 @@
         #           with _that_ node specific history...
 
         options = data['diff']['options']
+        repos_resource = Resource('repository', reponame)
 
         def _prop_changes(old_node, new_node):
-            old_source = Resource('source', (reponame, old_node.created_path),
-                                  version=old_node.created_rev)
-            new_source = Resource('source', (reponame, new_node.created_path),
-                                  version=new_node.created_rev)
+            old_source = Resource('source', old_node.created_path,
+                                  version=old_node.created_rev,
+                                  parent=repos_resource)
+            new_source = Resource('source', new_node.created_path,
+                                  version=new_node.created_rev,
+                                  parent=repos_resource)
             old_props = new_props = []
             if 'FILE_VIEW' in req.perm(old_source):
                 old_props = old_node.get_properties()
@@ -854,12 +860,13 @@
                 
             uids_seen = {}
             def generate_changesets(reponame, repos):
+                repos_resource = Resource('repository', reponame)
                 for _, changesets in groupby(repos.get_changesets(start, stop),
                                              key=collapse_changesets):
                     viewable_changesets = []
                     for cset in changesets:
-                        cset_resource = Resource('changeset', 
-                                                 (reponame, cset.rev))
+                        cset_resource = Resource('changeset', cset.rev,
+                                                 parent=repos_resource)
                         if 'CHANGESET_VIEW' in req.perm(cset_resource):
                             repos_for_uid = [reponame]
                             uid = repos.get_changeset_uid(cset.rev)
@@ -885,7 +892,7 @@
         changesets, show_location, show_files = event[3]
         cset, cset_resource, repos_for_uid = changesets[0]
         message = cset.message or ''
-        reponame = cset_resource.id[0] # first repo
+        reponame = cset_resource.parent.id
         rev_b, rev_a = cset.rev, cset.rev
 
         if field == 'url':
@@ -997,8 +1004,8 @@
             repos = rm.get_repository(reponame, authname)
 
         # rendering changeset link
-        if repos and 'CHANGESET_VIEW' in formatter.perm('changeset', 
-                                                        (reponame, rev)):
+        resource = Resource('repository', reponame).child('changeset', rev)
+        if repos and 'CHANGESET_VIEW' in formatter.perm(resource):
             try:
                 changeset = repos.get_changeset(rev)
                 href = formatter.href.changeset(rev, reponame or None, path)
@@ -1069,7 +1076,7 @@
             #cset = Resource('repository', reponame).child('changeset' , rev)
             #cset = repos.resource.child('changeset' , rev)
             #cset = repos.changeset_resource(rev)
-            cset = Resource('changeset', (reponame, rev))
+            cset = Resource('repository', reponame).child('changeset', rev)
             if 'CHANGESET_VIEW' in req.perm(cset):
                 yield (req.href.changeset(rev, reponame),
                        '[%s]: %s' % (rev, shorten_line(log)),
diff --git a/trac/versioncontrol/web_ui/log.py b/trac/versioncontrol/web_ui/log.py
--- a/trac/versioncontrol/web_ui/log.py
+++ b/trac/versioncontrol/web_ui/log.py
@@ -26,6 +26,7 @@
 from trac.core import *
 from trac.mimeview import Context
 from trac.perm import IPermissionRequestor
+from trac.resource import Resource
 from trac.util import Ranges
 from trac.util.compat import any
 from trac.util.datefmt import http_date
@@ -85,6 +86,7 @@
 
         reponame, repos, path = RepositoryManager(self.env).\
                 get_repository_by_path(path, req.authname)
+        repos_resource = Resource('repository', reponame)
 
         normpath = repos.normalize_path(path)
         # if `revs` parameter is given, then we're restricted to the 
@@ -253,8 +255,9 @@
         if range:
             item_ranges.append(range)
         data = {
-            'context': Context.from_request(req, 'source', (reponame, path)),
-            'reponame': reponame, 
+            'context': Context.from_request(req, 'source', path,
+                                            parent=repos_resource),
+            'reponame': reponame, 'repos_resource': repos_resource,
             'path': path, 'rev': rev, 'stop_rev': stop_rev,
             'path': path, 'rev': rev, 'stop_rev': stop_rev, 
             'revranges': revranges,
@@ -269,7 +272,7 @@
             return 'revisionlog.txt', data, 'text/plain'
         elif req.args.get('format') == 'rss':
             data['context'] = Context.from_request(req, 'source', 
-                                                   (reponame, path),
+                                                   path, parent=repos_resource,
                                                    absurls=True)
             return 'revisionlog.rss', data, 'application/rss+xml'
 

