Index: htdocs/css/browser.css
===================================================================
--- htdocs/css/browser.css	(revision 2744)
+++ htdocs/css/browser.css	(working copy)
@@ -45,10 +45,12 @@
  padding-left: 20px;
 }
 #dirlist td.name a.parent { background-image: url(../parent.png) }
-#dirlist td.name a.dir { background-image: url(../folder.png) }
+#dirlist td.name a.dir { display: inline; background-image: url(../folder.png) }
 #dirlist td.name span.dir { background-image: url(../folderdeny.png) }
-#dirlist td.name a.file { background-image: url(../file.png) }
+#dirlist td.name a.file { display: inline; background-image: url(../file.png) }
 #dirlist td.name span.file { background-image: url(../filedeny.png) }
+#dirlist td.name a.link { display: inline; background-image: url(../link.png) }
+#dirlist td.name span.link { background-image: url(../linkdeny.png) }
 #dirlist td.name a, #dirlist td.rev a { border-bottom: none; display: block }
 #dirlist td.rev { text-align: right }
 #dirlist td.change { font-size: 85%; vertical-align: middle; white-space: nowrap }
Index: trac/versioncontrol/api.py
===================================================================
--- trac/versioncontrol/api.py	(revision 2744)
+++ trac/versioncontrol/api.py	(working copy)
@@ -195,9 +195,10 @@
 
     DIRECTORY = "dir"
     FILE = "file"
+    LINK = "link"
 
     def __init__(self, path, rev, kind):
-        assert kind in (Node.DIRECTORY, Node.FILE), "Unknown node kind %s" % kind
+        assert kind in (Node.DIRECTORY, Node.FILE, Node.LINK), "Unknown node kind %s" % kind
         self.path = str(path)
         self.rev = rev
         self.kind = kind
@@ -251,8 +252,13 @@
         raise NotImplementedError
     last_modified = property(lambda x: x.get_last_modified())
 
+    def get_link_target(self):
+        raise NotImplementedError
+    link_target = property(lambda x: x.get_link_target())
+
     isdir = property(lambda x: x.kind == Node.DIRECTORY)
     isfile = property(lambda x: x.kind == Node.FILE)
+    islink = property(lambda x: x.kind == Node.LINK)
 
 
 class Changeset(object):
@@ -277,7 +283,8 @@
         Generator that produces a (path, kind, change, base_rev, base_path)
         tuple for every change in the changeset, where change can be one of
         Changeset.ADD, Changeset.COPY, Changeset.DELETE, Changeset.EDIT or
-        Changeset.MOVE, and kind is one of Node.FILE or Node.DIRECTORY.
+        Changeset.MOVE, and kind is one of Node.FILE, Node.DIRECTORY or
+        Node.LINK.
         """
         raise NotImplementedError
 
Index: trac/versioncontrol/svn_fs.py
===================================================================
--- trac/versioncontrol/svn_fs.py	(revision 2744)
+++ trac/versioncontrol/svn_fs.py	(working copy)
@@ -408,9 +408,17 @@
         # 'created_path' differs from 'path' if the last operation is a copy,
         # and furthermore, 'path' might not exist at 'create_rev'
         self.rev = self.created_rev
-        
+
         Node.__init__(self, path, self.rev, _kindmap[node_type])
 
+        # Determine if this 'file' is a link
+        if self.kind == Node.FILE and 'svn:special' in self.get_properties():
+            for line in self.get_content().read().splitlines():
+                if line.startswith('link'):
+                    self._link_target = line[5:]
+                    self.kind = Node.LINK
+                    break
+
     def get_content(self):
         if self.isdir:
             return None
@@ -422,7 +430,7 @@
         return s
 
     def get_entries(self):
-        if self.isfile:
+        if not self.isdir:
             return
         pool = Pool(self.pool)
         entries = fs.dir_entries(self.root, self.scoped_path, pool())
@@ -472,6 +480,10 @@
                                 core.SVN_PROP_REVISION_DATE, self.pool())
         return core.svn_time_from_cstring(date, self.pool()) / 1000000
 
+    def get_link_target(self):
+        assert self.islink
+        return self._link_target
+
     def _get_prop(self, name):
         return fs.node_prop(self.root, self.scoped_path, name, self.pool())
 
Index: trac/versioncontrol/cache.py
===================================================================
--- trac/versioncontrol/cache.py	(revision 2744)
+++ trac/versioncontrol/cache.py	(working copy)
@@ -18,7 +18,7 @@
 from trac.versioncontrol import Changeset, Node, Repository, Authorizer
 
 
-_kindmap = {'D': Node.DIRECTORY, 'F': Node.FILE}
+_kindmap = {'D': Node.DIRECTORY, 'F': Node.FILE, 'L': Node.LINK}
 _actionmap = {'A': Changeset.ADD, 'C': Changeset.COPY,
               'D': Changeset.DELETE, 'E': Changeset.EDIT,
               'M': Changeset.MOVE}
Index: trac/versioncontrol/web_ui/browser.py
===================================================================
--- trac/versioncontrol/web_ui/browser.py	(revision 2744)
+++ trac/versioncontrol/web_ui/browser.py	(working copy)
@@ -15,6 +15,7 @@
 # Author: Jonas Borgström <jonas@edgewall.com>
 
 import re
+import posixpath
 import urllib
 
 from trac import util
@@ -122,6 +123,25 @@
 
     # Internal methods
 
+    def _entry_info(self, entry, rev, link = 0, name = ''):
+        path = entry.path or '/'
+        name = name or entry.name
+        return {
+            'name': name or '/',
+            'fullpath': path,
+            'is_dir': int(entry.isdir),
+            'is_link': int(entry.islink),
+            'is_file': int(entry.isfile),
+            'link_target': link,
+            'content_length': entry.content_length,
+            'size': util.pretty_size(entry.content_length),
+            'rev': entry.rev,
+            'permission': 1, # FIXME
+            'log_href': self.env.href.log(path, rev=rev),
+            'browser_href': self.env.href.browser(path,
+                                                  rev=rev)
+        }
+
     def _render_directory(self, req, repos, node, rev=None):
         req.perm.assert_permission('BROWSER_VIEW')
 
@@ -132,18 +152,18 @@
 
         info = []
         for entry in node.get_entries():
-            info.append({
-                'name': entry.name,
-                'fullpath': entry.path,
-                'is_dir': int(entry.isdir),
-                'content_length': entry.content_length,
-                'size': util.pretty_size(entry.content_length),
-                'rev': entry.rev,
-                'permission': 1, # FIXME
-                'log_href': self.env.href.log(entry.path, rev=rev),
-                'browser_href': self.env.href.browser(entry.path,
-                                                      rev=rev)
-            })
+            link = 0
+
+            # Handle SVN links
+            if entry.islink:
+                try:
+                    path = posixpath.normpath(posixpath.join(node.path or '/', entry.link_target))
+                    linknode = get_existing_node(self.env, repos, path, rev)
+                    link = self._entry_info(linknode, rev, name = entry.link_target)
+                except TracError:
+                    link = entry.link_target
+
+            info.append(self._entry_info(entry, rev, link))
         changes = get_changes(self.env, repos, [i['rev'] for i in info])
 
         def cmp_func(a, b):
Index: templates/browser.cs
===================================================================
--- templates/browser.cs	(revision 2744)
+++ templates/browser.cs	(working copy)
@@ -5,8 +5,53 @@
  <ul>
   <li class="last"><a href="<?cs var:browser.log_href ?>">Revision Log</a></li>
  </ul>
-</div>
+</div> 
 
+<?cs
+def:browser_directory_entry(item)?><?cs
+if:item.is_dir ?><?cs
+	if:item.permission ?>
+		<a class="dir" title="Browse Directory" href="<?cs
+				var:item.browser_href ?>"><?cs var:item.name ?></a><?cs
+	else ?>
+		<span class="dir" title="Access Denied"><?cs
+				var:item.name ?></span><?cs
+	/if ?><?cs
+elif:item.is_file ?><?cs
+	if:item.permission != '' ?>
+		<a class="file" title="View File" href="<?cs
+				var:item.browser_href ?>"><?cs var:item.name ?></a><?cs
+	else ?>
+		<span class="file" title="Access Denied"><?cs
+				var:item.name ?></span><?cs
+	/if ?><?cs
+else ?><?cs
+	if:item.link_target.is_dir ?><?cs
+		if:item.permission ?>
+			<a class="dir" title="Directory Link" href="<?cs
+					var:item.link_target.browser_href ?>"><?cs var:item.name ?></a> &rarr; <?cs
+					call:browser_directory_entry(item.link_target) ?><?cs
+		else ?>
+			<span class="dir" title="Access Denied"><?cs
+					var:item.name ?></span><?cs
+		/if ?><?cs
+	elif:item.link_target.is_file ?><?cs
+		if:item.permission != '' ?>
+			<a class="file" title="File Link" href="<?cs
+					var:item.link_target.browser_href ?>"><?cs var:item.name ?></a> &rarr; <?cs
+					call:browser_directory_entry(item.link_target) ?><?cs
+		else ?>
+			<span class="file" title="Access Denied"><?cs
+					var:item.name ?></span><?cs
+		/if ?><?cs
+	else ?>
+			<span class="file" title="Link"><?cs
+					var:item.name ?></span> &rarr; <span class="file" title="Link Target"><?cs
+					var:item.link_target ?></span><?cs
+	/if ?><?cs
+/if ?><?cs
+/def ?>
+
 <div id="content" class="browser">
  <h1><?cs call:browser_path_links(browser.path, browser) ?></h1>
 
@@ -42,24 +87,7 @@
      <?cs set:change = browser.changes[item.rev] ?>
      <tr class="<?cs if:name(item) % #2 ?>even<?cs else ?>odd<?cs /if ?>">
       <td class="name"><?cs
-       if:item.is_dir ?><?cs
-        if:item.permission ?>
-         <a class="dir" title="Browse Directory" href="<?cs
-           var:item.browser_href ?>"><?cs var:item.name ?></a><?cs
-        else ?>
-         <span class="dir" title="Access Denied" href=""><?cs
-           var:item.name ?></span><?cs
-        /if ?><?cs
-       else ?><?cs
-        if:item.permission != '' ?>
-         <a class="file" title="View File" href="<?cs
-           var:item.browser_href ?>"><?cs var:item.name ?></a><?cs
-        else ?>
-         <span class="file" title="Access Denied" href=""><?cs
-           var:item.name ?></span><?cs
-        /if ?><?cs
-       /if ?>
-      </td>
+       call:browser_directory_entry(item) ?></td>
       <td class="size"><?cs var:item.size ?></td>
       <td class="rev"><?cs if:item.permission != '' ?><a title="View Revision Log" href="<?cs
         var:item.log_href ?>"><?cs var:item.rev ?></a><?cs else ?><?cs var:item.rev ?><?cs /if ?></td>
