diff --git a/trac/util/__init__.py b/trac/util/__init__.py
--- a/trac/util/__init__.py
+++ b/trac/util/__init__.py
@@ -870,3 +870,7 @@
     if max is not None and value > max:
         value = max
     return value
+
+def pathjoin(*args):
+    """Strip `/` from the arguments and join them with a single `/`."""
+    return '/'.join(filter(None, (each.strip('/') for each in args if each)))
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,7 @@
         <tr py:if="file">
           <td class="message searchable" py:choose="">
             <py:when test="wiki_format_messages" xml:space="preserve">
-              ${wiki_to_html(context('changeset', file.changeset.rev, parent=repos_resource),
+              ${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>
@@ -159,8 +159,8 @@
       <div id="anydiff">
         <form action="${href.diff()}" method="get">
           <div class="buttons">
-            <input type="hidden" name="new_path" value="$reponame/$path" />
-            <input type="hidden" name="old_path" value="$reponame/$path" />
+            <input type="hidden" name="new_path" value="${'/' + pathjoin(reponame, path)}" />
+            <input type="hidden" name="old_path" value="${'/' + pathjoin(reponame, path)}" />
             <input type="hidden" name="new_rev" value="$stickyrev" />
             <input type="hidden" name="old_rev" value="$stickyrev" />
             <input type="submit" value="${_('View changes...')}" title="${_('Select paths and revs for Diff')}" />
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
@@ -51,8 +51,8 @@
         id="prefs" action="">
         <div>
           <py:if test="not changeset">
-            <input type="hidden" name="old_path" value="$reponame/$old_path" />
-            <input type="hidden" name="new_path" value="$reponame/$new_path" />
+            <input type="hidden" name="old_path" value="${'/' + pathjoin(reponame, old_path)}" />
+            <input type="hidden" name="new_path" value="${'/' + pathjoin(reponame, new_path)}" />
             <input type="hidden" name="old" value="$old_rev" />
             <input type="hidden" name="new" value="$new_rev" />
           </py:if>
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
@@ -7,7 +7,7 @@
   </py:if>
   <py:for each="idx, entry in enumerate(dir.entries)">
     <py:with vars="change = dir.changes[entry.rev];
-                   chgset_context = change and context('changeset', change.rev, parent=repos_resource);
+                   chgset_context = change and context('changeset', change.rev, parent=repos.resource);
                    chgset_view = change and change.can_view(perm)">
       <tr class="${idx % 2 and 'even' or 'odd'}">
         <td class="name">
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
@@ -5,23 +5,23 @@
   <table class="listing dirlist" id="${repoindex or None}">
     <xi:include href="dirlist_thead.html" />
     <tbody>
-      <py:for each="idx, (reponame, repoinfo, change, err) in enumerate(repo.repositories)"
-              py:with="chgset_context = change and context('changeset', change.rev, parent=Resource('repository', reponame));
+      <py:for each="idx, (reponame, repoinfo, repos, change, err) in enumerate(repo.repositories)"
+              py:with="chgset_context = change and context('changeset', change.rev, parent=repos.resource);
                        chgset_view = change and change.can_view(perm)">
         <tr class="${idx % 2 and 'even' or 'odd'}">
           <td class="name">
             <em py:strip="not err">
               <b py:strip="repoinfo.alias != ''">
                 <a class="dir" title="View Root Directory"
-                  href="${href.browser(reponame, order=(order != 'name' and order or None), desc=desc)}">$reponame</a>
+                  href="${href.browser(repos.reponame, order=(order != 'name' and order or None), desc=desc)}">$reponame</a>
               </b>
             </em>
           </td>
           <td class="size" />
           <td class="rev">
             <py:if test="not err">
-              <a title="View Revision Log" href="${href.log(reponame)}">$change.rev</a>
-              <a title="View Changeset" class="chgset" href="${href.changeset(change.rev, reponame)}">&nbsp;</a>
+              <a title="View Revision Log" href="${href.log(repos.reponame)}">$change.rev</a>
+              <a title="View Changeset" class="chgset" href="${href.changeset(change.rev, repos.reponame)}">&nbsp;</a>
             </py:if>
           </td>
           <td class="age" style="${chgset_view and change and repo.timerange and 'border-color: rgb(%s,%s,%s)' %
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', change.rev, parent=context.resource.parent);
+                             chgset_context = context('changeset', change.rev, parent=repos.resource);
                              odd_even = idx % 2 and 'odd' or 'even'">
                 <!--! highlight copy or rename operations -->
                 <tr py:if="not is_separator and item.get('copyfrom_path')" class="$odd_even">
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', change.rev, parent=context.resource.parent)">
+                   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/templates/revisionlog.txt b/trac/versioncontrol/templates/revisionlog.txt
--- a/trac/versioncontrol/templates/revisionlog.txt
+++ b/trac/versioncontrol/templates/revisionlog.txt
@@ -1,5 +1,5 @@
 #
-# ChangeLog for $path${reponame and _(" in $(reponame)s", reponame=reponame) or ''}
+# ChangeLog for $path${reponame and _(" in $(reponame)s", reponame=reponame) or None}
 # 
 # Generated by Trac $trac.version
 # ${format_datetime()}
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
@@ -26,7 +26,7 @@
 from trac.mimeview.api import Mimeview, is_binary, \
                               IHTMLPreviewAnnotator, Context
 from trac.perm import IPermissionRequestor
-from trac.resource import Resource, ResourceNotFound
+from trac.resource import ResourceNotFound
 from trac.util import embedded_numbers
 from trac.util.compat import any
 from trac.util.datefmt import http_date, utc
@@ -291,10 +291,9 @@
 
     def get_navigation_items(self, req):
         rm = RepositoryManager(self.env)
-        all_repos = rm.get_all_repositories()
-        if any(info.get('hidden') not in _TRUE_VALUES
-               and rm.get_repository(reponame).can_view(req.perm)
-               for reponame, info in all_repos.iteritems()):
+        if any(repos and repos.params.get('hidden') not in _TRUE_VALUES
+               and repos.can_view(req.perm)
+               for repos in rm.get_real_repositories()):
             yield ('mainnav', 'browser',
                    tag.a(_('Browse Source'), href=req.href.browser()))
 
@@ -347,6 +346,11 @@
             raise ResourceNotFound(_("No repository '%(repo)s' found",
                                    repo=reponame))
 
+        if reponame != repos.reponame:  # Redirect alias
+            qs = req.query_string
+            req.redirect(req.href.browser(repos.reponame or None, path)
+                         + (qs and '?' + qs or ''))
+        
         # Find node for the requested path/rev
         context = Context.from_request(req)
         node = None
@@ -362,12 +366,12 @@
                 raise ResourceNotFound(e.message,
                                        _('Invalid changeset number'))
 
-            repos_resource = Resource('repository', reponame)
-            context = context(repos_resource.child('source', path,
+            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)
+        path_links = get_path_links(req.href, repos.reponame, path, rev,
+                                    order, desc)
 
         repo_data = dir_data = file_data = None
         if all_repositories:
@@ -375,11 +379,9 @@
                     context, all_repositories, order, desc)
         if node:
             if node.isdir:
-                dir_data = self._render_dir(
-                        req, reponame, repos, node, rev, order, desc)
+                dir_data = self._render_dir(req, repos, node, rev, order, desc)
             elif node.isfile:
-                file_data = self._render_file(
-                        req, context, reponame, repos, node, rev)
+                file_data = self._render_file(req, context, repos, node, rev)
 
         quickjump_data = properties_data = None
         if node and not xhr:
@@ -387,9 +389,10 @@
                     'browser', context, node.get_properties())
             quickjump_data = list(repos.get_quickjump_entries(rev))
 
+        reponame = repos.reponame or None
         data = {
-            'context': context, 'reponame': reponame or None,
-            'repos_resource': repos and repos_resource,
+            'context': context, 'reponame': reponame,
+            'repos': repos,
             'path': path, 'rev': node and node.rev, 'stickyrev': rev,
             'created_path': node and node.created_path,
             'created_rev': node and node.created_rev,
@@ -415,18 +418,18 @@
                 prev_rev = repos.previous_rev(rev=node.rev,
                                               path=node.created_path)
                 if prev_rev:
-                    href = req.href.browser(reponame or None,
+                    href = req.href.browser(reponame,
                                             node.created_path, rev=prev_rev)
                     add_link(req, 'prev', href,
                              _('Revision %(num)s', num=prev_rev))
                 if rev is not None:
-                    add_link(req, 'up', req.href.browser(reponame or None,
+                    add_link(req, 'up', req.href.browser(reponame,
                                                          node.created_path))
                 next_rev = repos.next_rev(rev=node.rev,
                                           path=node.created_path)
                 if next_rev:
-                    href = req.href.browser(reponame or None,
-                                            node.created_path, rev=next_rev)
+                    href = req.href.browser(reponame, node.created_path,
+                                            rev=next_rev)
                     add_link(req, 'next', href,
                              _('Revision %(num)s', num=next_rev))
                 prevnext_nav(req, _('Previous Revision'), _('Next Revision'),
@@ -436,13 +439,13 @@
                     add_link(req, 'up', path_links[-2]['href'],
                              _('Parent directory'))
             add_ctxtnav(req, tag.a(_('Last Change'), 
-                        href=req.href.changeset(node.rev, reponame or None,
+                        href=req.href.changeset(node.rev, reponame,
                                                 node.created_path)))
             if node.isfile:
                 if data['file']['annotate']:
                     add_ctxtnav(req, _('Normal'), 
                                 title=_('View file without annotations'), 
-                                href=req.href.browser(reponame or None,
+                                href=req.href.browser(reponame,
                                                       node.created_path, 
                                                       rev=node.rev))
                 else:
@@ -450,12 +453,12 @@
                                 title=_('Annotate each line with the last '
                                         'changed revision '
                                         '(this can be time consuming...)'), 
-                                href=req.href.browser(reponame or None,
+                                href=req.href.browser(reponame,
                                                       node.created_path, 
                                                       rev=node.rev,
                                                       annotate='blame'))
             add_ctxtnav(req, _('Revision Log'), 
-                        href=req.href.log(reponame or None, path, rev=rev))
+                        href=req.href.log(reponame, path, rev=rev))
             path_url = repos.get_path_url(path, rev)
             if path_url:
                 if path_url.startswith('//'):
@@ -489,19 +492,20 @@
                             timerange = TimeRange(youngest.date)
                         else:
                             timerange.insert(youngest.date)
-                    entry = (reponame, repoinfo, youngest, None)
+                    entry = (reponame, repoinfo, repos, youngest, None)
                 else:
-                    entry = (reponame, repoinfo, None, "XXX")
+                    entry = (reponame, repoinfo, None, None, "XXX")
             except TracError, err:
-                entry = (reponame, repoinfo, None, exception_to_unicode(err))
+                entry = (reponame, repoinfo, None, None,
+                         exception_to_unicode(err))
             repositories.append(entry)
 
         # Ordering of repositories
         if order == 'date':
-            def repo_order((reponame, repoinfo, repos, youngest)):
+            def repo_order((reponame, repoinfo, repos, youngest, err)):
                 return youngest and youngest.date
         else:
-            def repo_order((reponame, repoinfo, repos, youngest)):
+            def repo_order((reponame, repoinfo, repos, youngest, err)):
                 return embedded_numbers(reponame.lower())
 
         repositories = sorted(repositories, key=repo_order, reverse=desc)
@@ -509,7 +513,7 @@
         return {'repositories' : repositories,
                 'timerange': timerange, 'colorize_age': custom_colorizer}
 
-    def _render_dir(self, req, reponame, repos, node, rev, order, desc):
+    def _render_dir(self, req, repos, node, rev, order, desc):
         req.perm(node.resource).require('BROWSER_VIEW')
 
         # Entries metadata
@@ -564,8 +568,9 @@
         if node.path and patterns and \
                filter(None, [fnmatchcase(node.path, p) for p in patterns]):
             zip_href = req.href.changeset(rev or repos.youngest_rev, 
-                                          reponame, node.path, old=rev, 
-                                          old_path=reponame or '/', 
+                                          repos.reponame or None, node.path,
+                                          old=rev,
+                                          old_path=repos.reponame or '/',
                                           format='zip')
             add_link(req, 'alternate', zip_href, _('Zip Archive'),
                      'application/zip', 'zip')
@@ -578,7 +583,7 @@
                                    timerange.to_seconds(timerange.oldest)),
                 }
 
-    def _render_file(self, req, context, reponame, repos, node, rev=None):
+    def _render_file(self, req, context, repos, node, rev=None):
         req.perm(node.resource).require('FILE_VIEW')
 
         mimeview = Mimeview(self.env)
@@ -618,14 +623,14 @@
 
             # add ''Plain Text'' alternate link if needed
             if not is_binary(chunk) and mime_type != 'text/plain':
-                plain_href = req.href.browser(reponame or None, node.path,
-                                              rev=rev, format='txt')
+                plain_href = req.href.browser(repos.reponame or None,
+                                              node.path, rev=rev, format='txt')
                 add_link(req, 'alternate', plain_href, _('Plain Text'),
                          'text/plain')
 
             # add ''Original Format'' alternate link (always)
             raw_href = req.href.export(rev or repos.youngest_rev, 
-                                       reponame, node.path)
+                                       repos.reponame or None, node.path)
             add_link(req, 'alternate', raw_href, _('Original Format'),
                      mime_type)
 
@@ -784,34 +789,38 @@
                          if fnmatchcase(rdata[0], glob))
 
         if format == 'table':
-            data = self._render_repository_index(
-                    formatter.context, all_repos, order, desc)
+            repo = self._render_repository_index(formatter.context, all_repos,
+                                                 order, desc)
 
             add_stylesheet(formatter.req, 'common/css/browser.css')
+            data = {'repo': repo, 'desc': desc and 1 or None,
+                    'reponame': None, 'path': '/', 'stickyrev': None}
             from trac.web.chrome import Chrome
             return Chrome(self.env).render_template(
-                    formatter.req, 'repository_index.html', 
-                    {'repo': data}, None, fragment=True)
+                    formatter.req, 'repository_index.html', data, None,
+                    fragment=True)
 
-        def repolink(reponame):
+        def repolink(reponame, repos):
             label = reponame or _('(default)')
             return Markup(tag.a(label, 
                           title=_('View repository %(repo)s', repo=label),
-                          href=formatter.href.browser(reponame or None)))
+                          href=formatter.href.browser(repos.reponame or None)))
 
-        all_repos = sorted(
-            (reponame, info) for reponame, info in all_repos.iteritems()
-            if info.get('hidden') not in _TRUE_VALUE
-               and rm.get_repository(reponame).can_view(formatter.perm))
+        all_repos = dict((reponame, rm.get_repository(reponame))
+                         for reponame in all_repos)
+        all_repos = sorted((reponame, repos) for reponame, repos in all_repos
+                           if repos
+                           and repos.params.get('hidden') not in _TRUE_VALUE
+                           and repos.can_view(formatter.perm))
 
         if format == 'list':
             return tag.dl([
-                tag(tag.dt(repolink(reponame)),
-                    tag.dd(repoinfo.get('description')))
-                for reponame, repoinfo in all_repos])
+                tag(tag.dt(repolink(reponame, repos)),
+                    tag.dd(repos.params.get('description')))
+                for reponame, repos in all_repos])
         else: # compact
-            return Markup(', ').join([repolink(reponame)
-                                      for reponame, repoinfo in all_repos])
+            return Markup(', ').join([repolink(reponame, repos)
+                                      for reponame, repos in all_repos])
 
         
 
@@ -820,9 +829,8 @@
     def __init__(self, env, context):
         self.env = env
         self.context = context
-        self.reponame = context.resource.parent.id
+        self.repos = env.get_repository(context.resource.parent.id)
         self.path = context.resource.id
-        self.repos = env.get_repository(self.reponame)
         self.rev = context.resource.version
         # maintain state
         self.prev_chgset = None
@@ -876,9 +884,9 @@
         
         # -- compute anchor and style once per revision
         if rev not in self.chgset_data:
-            chgset_href = self.context.href.changeset(rev,
-                                                      self.reponame or None,
-                                                      path)
+            chgset_href = \
+                self.context.href.changeset(rev, self.repos.reponame or None,
+                                            path)
             short_author = chgset.author.split(' ', 1)[0]
             title = shorten_line('%s: %s' % (short_author, chgset.message))
             anchor = tag.a('[%s]' % self.repos.short_rev(rev), # shortname
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
@@ -34,7 +34,7 @@
 from trac.resource import Resource, ResourceNotFound
 from trac.search import ISearchSource, search_to_sql, shorten_result
 from trac.timeline.api import ITimelineEventProvider
-from trac.util import embedded_numbers, content_disposition
+from trac.util import content_disposition, embedded_numbers, pathjoin
 from trac.util.compat import any, set
 from trac.util.datefmt import pretty_timedelta, utc
 from trac.util.text import exception_to_unicode, to_unicode, \
@@ -252,8 +252,10 @@
         try:
             new_path = repos.normalize_path(new_path)
             new = repos.normalize_rev(new)
+            full_new_path = '/' + pathjoin(repos.reponame, new_path)
             old_path = repos.normalize_path(old_path or new_path)
             old = repos.normalize_rev(old or new)
+            full_old_path = '/' + pathjoin(repos.reponame, old_path)
         except NoSuchChangeset, e:
             raise ResourceNotFound(e.message, _('Invalid Changeset Number'))
 
@@ -269,16 +271,16 @@
         else:
             restricted = old_path == new_path # (same path or not)
 
-        # -- redirect if changing the diff options
-        if req.args.has_key('update'):
+        # -- redirect if changing the diff options or alias requested
+        if req.args.has_key('update') or reponame != repos.reponame:
+            reponame = repos.reponame or None
             if chgset:
                 if restricted:
-                    req.redirect(req.href.changeset(new, reponame or None,
-                                                    new_path))
+                    req.redirect(req.href.changeset(new, reponame, new_path))
                 else:
-                    req.redirect(req.href.changeset(new, reponame or None))
+                    req.redirect(req.href.changeset(new, reponame))
             else:
-                req.redirect(req.href.changeset(new, reponame or None,
+                req.redirect(req.href.changeset(new, reponame,
                                                 new_path, old=old,
                                                 old_path=full_old_path))
 
@@ -300,9 +302,9 @@
                 old_path = new_path
             data = {'old_path': old_path, 'old_rev': old,
                     'new_path': new_path, 'new_rev': new}
-        data['reponame'] = reponame or None
-        data['diff'] = diff_data
-        data['wiki_format_messages'] = self.wiki_format_messages
+        data.update({'repos': repos, 'reponame': repos.reponame or None,
+                     'diff': diff_data,
+                     'wiki_format_messages': self.wiki_format_messages})
 
         if chgset:
             chgset = repos.get_changeset(new)
@@ -340,19 +342,17 @@
                 self._render_zip(req, filename, repos, data)
 
         # -- HTML format
-        self._render_html(req, reponame, repos, chgset, restricted, xhr, data)
+        self._render_html(req, repos, chgset, restricted, xhr, data)
         
         if chgset:
             diff_params = 'new=%s' % new
         else:
-            diff_params = unicode_urlencode({'new_path': new_path,
-                                             'new': new,
-                                             'old_path': old_path,
-                                             'old': old})
-        add_link(req, 'alternate', '?format=diff&'+diff_params,
+            diff_params = unicode_urlencode({'new_path': new_path, 'new': new,
+                                             'old_path': old_path, 'old': old})
+        add_link(req, 'alternate', '?format=diff&' + diff_params,
                  _('Unified Diff'), 'text/plain', 'diff')
-        add_link(req, 'alternate', '?format=zip&'+diff_params, _('Zip Archive'),
-                 'application/zip', 'zip')
+        add_link(req, 'alternate', '?format=zip&' + diff_params,
+                 _('Zip Archive'), 'application/zip', 'zip')
         add_script(req, 'common/js/diff.js')
         add_stylesheet(req, 'common/css/changeset.css')
         add_stylesheet(req, 'common/css/diff.css')
@@ -371,10 +371,11 @@
 
     # Internal methods
 
-    def _render_html(self, req, reponame, repos, chgset, restricted, xhr, data):
+    def _render_html(self, req, repos, chgset, restricted, xhr, data):
         """HTML version"""
         data['restricted'] = restricted
         browser = BrowserModule(self.env)
+        reponame = repos.reponame or None
 
         if chgset: # Changeset Mode (possibly restricted on a path)
             path, rev = data['new_path'], data['new_rev']
@@ -405,9 +406,8 @@
             title = _changeset_title(rev)
 
             # Support for revision properties (#2545)
-            repos_resource = Resource('repository', reponame)
             context = Context.from_request(req, 'changeset', chgset.rev,
-                                           parent=repos_resource)
+                                           parent=repos.resource)
             data['context'] = context
             revprops = chgset.get_properties()
             data['properties'] = browser.render_properties('revprop', context,
@@ -419,20 +419,18 @@
                     if prev:
                         prev_path, prev_rev = prev[:2]
                         if prev_rev:
-                            prev_href = req.href.changeset(prev_rev,
-                                                           reponame or None,
+                            prev_href = req.href.changeset(prev_rev, reponame,
                                                            prev_path)
                     else:
                         prev_path = prev_rev = None
                 else:
                     add_link(req, 'first', 
-                             req.href.changeset(oldest_rev, reponame or None),
+                             req.href.changeset(oldest_rev, reponame),
                              _('Changeset %(id)s', id=oldest_rev))
                     prev_path = data['old_path']
                     prev_rev = repos.previous_rev(chgset.rev)
                     if prev_rev:
-                        prev_href = req.href.changeset(prev_rev,
-                                                       reponame or None)
+                        prev_href = req.href.changeset(prev_rev, reponame)
                 if prev_rev:
                     add_link(req, 'prev', prev_href, _changeset_title(prev_rev))
             youngest_rev = repos.youngest_rev
@@ -441,21 +439,17 @@
                     next_rev = repos.next_rev(chgset.rev, path)
                     if next_rev:
                         if repos.has_node(path, next_rev):
-                            next_href = req.href.changeset(next_rev,
-                                                           reponame or None,
+                            next_href = req.href.changeset(next_rev, reponame,
                                                            path)
                         else: # must be a 'D'elete or 'R'ename, show full cset
-                            next_href = req.href.changeset(next_rev,
-                                                           reponame or None)
+                            next_href = req.href.changeset(next_rev, reponame)
                 else:
                     add_link(req, 'last', 
-                             req.href.changeset(youngest_rev,
-                                                reponame or None),
+                             req.href.changeset(youngest_rev, reponame),
                              _('Changeset %(id)s', id=youngest_rev))
                     next_rev = repos.next_rev(chgset.rev)
                     if next_rev:
-                        next_href = req.href.changeset(next_rev,
-                                                       reponame or None)
+                        next_href = req.href.changeset(next_rev, reponame)
                 if next_rev:
                     add_link(req, 'next', next_href, _changeset_title(next_rev))
 
@@ -478,7 +472,7 @@
             return {'path': node.path,
                     'rev': node.rev,
                     'shortrev': repos.short_rev(node.rev),
-                    'href': req.href.browser(reponame or None,
+                    'href': req.href.browser(reponame,
                                              node.created_path,
                                              rev=node.created_rev,
                                              annotate=annotated and 'blame' or \
@@ -623,19 +617,18 @@
                 filestats[change] += 1
                 if change in Changeset.DIFF_CHANGES:
                     if chgset:
-                        href = req.href.changeset(new_node.rev,
-                                                  reponame or None, 
+                        href = req.href.changeset(new_node.rev, reponame,
                                                   new_node.path)
                         title = _('Show the changeset %(id)s restricted to '
                                   '%(path)s', id=new_node.rev,
                                   path=new_node.path)
                     else:
                         href = req.href.changeset(
-                            new_node.created_rev, reponame or None,
+                            new_node.created_rev, reponame,
                             new_node.created_path,
                             old=old_node.created_rev,
-                            old_path=posixpath.join(reponame, 
-                                                    old_node.created_path))
+                            old_path=pathjoin(repos.reponame, 
+                                              old_node.created_path))
                         title = _('Show the %(range)s differences restricted '
                                   'to %(path)s',
                                   range='r%s:%s' % (old_node.rev, new_node.rev),
@@ -713,8 +706,8 @@
             else:
                 old_node_path = repos.normalize_path(old_node.path)
                 diff_old_path = repos.normalize_path(data['old_path'])
-                new_path = posixpath.join(data['new_path'],
-                                          old_node_path[len(diff_old_path)+1:])
+                new_path = pathjoin(data['new_path'],
+                                    old_node_path[len(diff_old_path) + 1:])
 
             if old_content != new_content:
                 options = data['diff']['options']
@@ -845,27 +838,14 @@
             # only repository filter.
             filters = []
             rm = RepositoryManager(self.env)
-            repositories = rm.get_all_repositories()
+            repositories = rm.get_real_repositories()
             if len(repositories) > 1:
-                visible_repos = set(
-                    name for name, info in repositories.items()
-                    if info.get('hidden') not in _TRUE_VALUES
-                    and rm.get_repository(name).can_view(req.perm))
-                default_is_aliased = any(info.get('alias') == '' and
-                                         name in visible_repos
-                                         for name, info in repositories.items())
-                default_is_alias = repositories.get('', {}).get('alias') \
-                                   in visible_repos
-                for reponame in repositories.keys():
-                    if reponame:
-                        label = reponame
-                    elif default_is_aliased or default_is_alias:
-                        continue
-                    else:
-                        label = _('(default)')
-                    if reponame in visible_repos:
-                        filters.append(('repo-' + reponame,
-                                        u"\xa0\xa0-\xa0" + label))
+                filters = [
+                    ('repo-' + repos.reponame,
+                     u"\xa0\xa0-\xa0" + (repos.reponame or _('(default)')))
+                    for repos in repositories
+                    if repos.params.get('hidden') not in _TRUE_VALUES
+                    and repos.can_view(req.perm)]
                 filters.sort()
                 add_script(req, 'common/js/timeline_multirepos.js')
                 changeset_label = _('Changesets in all repositories')
@@ -893,21 +873,20 @@
                 collapse_changesets = lambda c: c.rev
                 
             uids_seen = {}
-            def generate_changesets(reponame, repos):
-                repos_resource = Resource('repository', reponame)
+            def generate_changesets(repos):
                 for _, changesets in groupby(repos.get_changesets(start, stop),
                                              key=collapse_changesets):
                     viewable_changesets = []
                     for cset in changesets:
                         cset_resource = Resource('changeset', cset.rev,
-                                                 parent=repos_resource)
+                                                 parent=repos.resource)
                         if cset.can_view(req.perm):
-                            repos_for_uid = [reponame]
+                            repos_for_uid = [repos.reponame]
                             uid = repos.get_changeset_uid(cset.rev)
                             if uid:
                                 # uid can be seen in multiple repositories
                                 if uid in uids_seen:
-                                    uids_seen[uid].append(reponame)
+                                    uids_seen[uid].append(repos.reponame)
                                     continue # already viewable, simply append
                                 uids_seen[uid] = repos_for_uid
                             viewable_changesets.append((cset, cset_resource,
@@ -919,16 +898,15 @@
                                 show_location, show_files))
 
             rm = RepositoryManager(self.env)
-            for reponame in rm.get_all_repositories():
-                if all_repos or ('repo-' + reponame) in repo_filters:
+            for repos in rm.get_real_repositories():
+                if all_repos or ('repo-' + repos.reponame) in repo_filters:
                     try:
-                        repos = rm.get_repository(reponame)
-                        for event in generate_changesets(reponame, repos):
+                        for event in generate_changesets(repos):
                             yield event
                     except TracError, e:
                         self.log.error("Timeline event provider for repository"
                                        " '%s' failed: %r", 
-                                       reponame, exception_to_unicode(e))
+                                       repos.reponame, exception_to_unicode(e))
 
     def render_timeline_event(self, context, field, event):
         changesets, show_location, show_files = event[3]
@@ -1062,7 +1040,8 @@
             try:
                 changeset = repos.get_changeset(rev)
                 if changeset.can_view(formatter.perm):
-                    href = formatter.href.changeset(rev, reponame or None,
+                    href = formatter.href.changeset(rev,
+                                                    repos.reponame or None,
                                                     path)
                     return tag.a(label, class_="changeset",
                                  title=shorten_line(changeset.message),
@@ -1119,19 +1098,21 @@
     def get_search_results(self, req, terms, filters):
         if not 'changeset' in filters:
             return
+        rm = RepositoryManager(self.env)
+        repositories = dict((repos.params['id'], repos)
+                            for repos in rm.get_real_repositories())
         db = self.env.get_db_cnx()
         sql, args = search_to_sql(db, ['rev', 'message', 'author'], terms)
         cursor = db.cursor()
         cursor.execute("SELECT repos,rev,time,author,message "
                        "FROM revision WHERE " + sql, args)
-        for reponame, rev, ts, author, log in cursor:
-            repos = self.env.get_repository(reponame)
+        for id, rev, ts, author, log in cursor:
+            repos = repositories.get(id)
             if not repos:
                 continue # revisions for a no longer active repository
-            cset = Resource('repository', repos.reponame).child('changeset',
-                                                                rev)
+            cset = repos.resource.child('changeset', rev)
             if 'CHANGESET_VIEW' in req.perm(cset):
-                yield (req.href.changeset(rev, reponame or None),
+                yield (req.href.changeset(rev, repos.reponame or None),
                        '[%s]: %s' % (rev, shorten_line(log)),
                        datetime.fromtimestamp(ts, utc), author,
                        shorten_result(log, terms))
@@ -1159,13 +1140,13 @@
 
             if repos:
                 entries = [(e.isdir, e.name, 
-                            '/' + posixpath.join(reponame, e.path))
+                            '/' + pathjoin(repos.reponame, e.path))
                            for e in repos.get_node(path).get_entries()
                            if e.can_view(req.perm)]
             if not reponame:
-                entries.extend((True, name, '/' + name)
-                               for name in rm.get_all_repositories()
-                               if rm.get_repository(name).can_view(req.perm))
+                entries.extend((True, repos.reponame, '/' + repos.reponame)
+                               for repos in rm.get_real_repositories()
+                               if repos.can_view(req.perm))
 
             elem = tag.ul(
                 [tag.li(isdir and tag.b(path) or path)
@@ -1192,9 +1173,9 @@
         old_rev = old_repos.normalize_rev(old_rev)
 
         # -- prepare rendering
-        data = {'new_path': posixpath.join(new_reponame, new_path),
+        data = {'new_path': '/' + pathjoin(new_repos.reponame, new_path),
                 'new_rev': new_rev,
-                'old_path': posixpath.join(old_reponame, old_path),
+                'old_path': '/' + pathjoin(old_repos.reponame, old_path),
                 'old_rev': old_rev}
 
         add_script(req, 'common/js/suggest.js')
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
@@ -25,7 +25,6 @@
 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.html import html
@@ -82,8 +81,17 @@
         verbose = req.args.get('verbose')
         limit = int(req.args.get('limit') or self.default_log_limit)
 
-        reponame, repos, path = RepositoryManager(self.env).\
-                get_repository_by_path(path)
+        rm = RepositoryManager(self.env)
+        reponame, repos, path = rm.get_repository_by_path(path)
+        
+        if not repos:
+            raise ResourceNotFound(_("No repository '%(repo)s' found",
+                                   repo=reponame))
+
+        if reponame != repos.reponame:  # Redirect alias
+            qs = req.query_string
+            req.redirect(req.href.log(repos.reponame or None, path)
+                         + (qs and '?' + qs or ''))
 
         normpath = repos.normalize_path(path)
         # if `revs` parameter is given, then we're restricted to the 
@@ -198,7 +206,7 @@
             params.update(args)
             if verbose:
                 params['verbose'] = verbose
-            return req.href.log(reponame or None, path, **params)
+            return req.href.log(repos.reponame or None, path, **params)
 
         if format in ('rss', 'changelog'):
             info = [i for i in info if i['change']] # drop separators
@@ -247,11 +255,10 @@
                 cs['actions'] = actions
                 extra_changes[rev] = cs
 
-        repos_resource = Resource('repository', reponame)
         data = {
             'context': Context.from_request(req, 'source', path,
-                                            parent=repos_resource),
-            'reponame': reponame or None, 'repos': repos,
+                                            parent=repos.resource),
+            'reponame': repos.reponame or None, 'repos': repos,
             'path': path, 'rev': rev, 'stop_rev': stop_rev, 
             'revranges': revranges,
             'mode': mode, 'verbose': verbose, 'limit' : limit,
@@ -265,7 +272,7 @@
             return 'revisionlog.txt', data, 'text/plain'
         elif req.args.get('format') == 'rss':
             data['context'] = Context.from_request(req, 'source', 
-                                                   path, parent=repos_resource,
+                                                   path, parent=repos.resource,
                                                    absurls=True)
             return 'revisionlog.rss', data, 'application/rss+xml'
 
@@ -286,7 +293,7 @@
         add_stylesheet(req, 'common/css/diff.css')
         add_stylesheet(req, 'common/css/browser.css')
 
-        path_links = get_path_links(req.href, reponame, path, rev)
+        path_links = get_path_links(req.href, repos.reponame, path, rev)
         if path_links:
             data['path_links'] = path_links
         if len(path_links) > 1:
@@ -301,7 +308,7 @@
         add_link(req, 'alternate', changelog_href, _('ChangeLog'), 'text/plain')
 
         add_ctxtnav(req, _('View Latest Revision'), 
-                    href=req.href.browser(reponame or None, path))
+                    href=req.href.browser(repos.reponame or None, path))
         if 'next' in req.chrome['links']:
             next = req.chrome['links']['next'][0]
             add_ctxtnav(req, tag.span(tag.a(_('Older Revisions'), 
@@ -368,14 +375,14 @@
             revs = None
         if 'LOG_VIEW' in formatter.perm:
             if revranges:
-                href = formatter.href.log(reponame or None, path or '/',
+                href = formatter.href.log(repos.reponame or None, path or '/',
                                           revs=str(revranges)) 
             else:
                 try:
                     rev = repos.normalize_rev(revs)
                 except NoSuchChangeset:
                     rev = None
-                href = formatter.href.log(reponame or None, path or '/',
+                href = formatter.href.log(repos.reponame or None, path or '/',
                                           rev=rev)
             if query and (revranges or revs):
                 query = '&' + query[1:]
diff --git a/trac/versioncontrol/web_ui/tests/wikisyntax.py b/trac/versioncontrol/web_ui/tests/wikisyntax.py
--- a/trac/versioncontrol/web_ui/tests/wikisyntax.py
+++ b/trac/versioncontrol/web_ui/tests/wikisyntax.py
@@ -3,10 +3,10 @@
 import unittest
 
 from trac.test import Mock
-from trac.wiki.tests import formatter
 from trac.versioncontrol import NoSuchChangeset
 from trac.versioncontrol.api import *
 from trac.versioncontrol.web_ui import *
+from trac.wiki.tests import formatter
 
 
 def _get_changeset(rev):
@@ -25,7 +25,8 @@
             raise NoSuchChangeset(rev)
     
 def _get_repository(reponame):
-    return Mock(get_changeset=_get_changeset, youngest_rev='200',
+    return Mock(reponame=reponame, youngest_rev='200',
+                get_changeset=_get_changeset,
                 normalize_rev=_normalize_rev)
 
 def repository_setup(tc):
diff --git a/trac/web/chrome.py b/trac/web/chrome.py
--- a/trac/web/chrome.py
+++ b/trac/web/chrome.py
@@ -58,7 +58,7 @@
 from trac.mimeview import get_mimetype, Context
 from trac.resource import *
 from trac.util import compat, get_reporter_id, presentation, get_pkginfo, \
-                      translation
+                      pathjoin, translation
 from trac.util.compat import any, partial
 from trac.util.html import escape, plaintext
 from trac.util.text import pretty_size, obfuscate_email_address, \
@@ -366,6 +366,7 @@
         'ngettext': translation.ngettext,
         'paginate': presentation.paginate,
         'partial': partial,
+        'pathjoin': pathjoin,
         'plaintext': plaintext,
         'pprint': pprint.pformat,
         'pretty_size': pretty_size,
diff --git a/tracopt/ticket/commit_updater.py b/tracopt/ticket/commit_updater.py
--- a/tracopt/ticket/commit_updater.py
+++ b/tracopt/ticket/commit_updater.py
@@ -43,7 +43,6 @@
 from trac.config import BoolOption, Option
 from trac.core import Component, implements
 from trac.perm import PermissionCache
-from trac.resource import Resource
 from trac.ticket import Ticket
 from trac.ticket.notification import TicketNotifyEmail
 from trac.ticket.web_ui import TicketModule
@@ -276,17 +275,21 @@
         reponame = args.get('repository')
         rev = args.get('revision')
         repos = RepositoryManager(self.env).get_repository(reponame)
-        changeset = repos.get_changeset(rev)
+        if repos:
+            changeset = repos.get_changeset(rev)
+            message = changeset.message
+            rev = changeset.rev
+        else:
+            message = content
         if formatter.context.resource.realm == 'ticket':
             ticket_re = CommitTicketUpdater.ticket_re
             if not any(int(tkt_id) == formatter.context.resource.id
-                       for tkt_id in ticket_re.findall(changeset.message)):
+                       for tkt_id in ticket_re.findall(message)):
                 return tag.p("(The changeset message doesn't reference this "
                              "ticket)", class_='hint')
         if ChangesetModule(self.env).wiki_format_messages:
             return tag.div(format_to_html(self.env,
-                formatter.context('changeset', changeset.rev,
-                                  parent=Resource('repository', reponame)),
-                changeset.message, escape_newlines=True), class_='message')
+                formatter.context('changeset', rev, parent=repos.resource),
+                message, escape_newlines=True), class_='message')
         else:
-            return tag.pre(changeset.message, class_='message')
+            return tag.pre(message, class_='message')

