diff --git a/trac/htdocs/css/browser.css b/trac/htdocs/css/browser.css
--- a/trac/htdocs/css/browser.css
+++ b/trac/htdocs/css/browser.css
@@ -174,7 +174,7 @@
  margin: 0 0 .4em 1.6em;
  padding: 0;
 }
-#info .props li { padding: 0; overflow: auto; }
+#info .props > li { padding: 2px 0; overflow: auto; }
 
 /* Styles for the HTML preview */
 #preview { background: #fff; clear: both; margin: 0 }
diff --git a/trac/util/__init__.py b/trac/util/__init__.py
--- a/trac/util/__init__.py
+++ b/trac/util/__init__.py
@@ -542,6 +542,33 @@
         else:
             return 0
 
+def to_ranges(revs):
+    """Converts a list of revisions to a minimal set of ranges.
+    
+    >>> to_ranges([2, 12, 3, 6, 9, 1, 5, 11])
+    '1-3,5-6,9,11-12'
+    >>> to_ranges([])
+    ''
+    """
+    ranges = []
+    begin = end = None
+    def store():
+        if end == begin:
+            ranges.append(str(begin))
+        else:
+            ranges.append('%d-%d' % (begin, end))
+    for rev in sorted(revs):
+        if begin is None:
+            begin = end = rev
+        elif rev == end + 1:
+            end = rev
+        else:
+            store()
+            begin = end = rev
+    if begin is not None:
+        store()
+    return ','.join(ranges)
+
 def content_disposition(type, filename=None):
     """Generate a properly escaped Content-Disposition header"""
     if filename is not None:
diff --git a/trac/versioncontrol/svn_fs.py b/trac/versioncontrol/svn_fs.py
--- a/trac/versioncontrol/svn_fs.py
+++ b/trac/versioncontrol/svn_fs.py
@@ -60,7 +60,7 @@
 from trac.versioncontrol.cache import CachedRepository
 from trac.versioncontrol.svn_authz import SubversionAuthorizer
 from trac.versioncontrol.web_ui.browser import IPropertyRenderer
-from trac.util import embedded_numbers
+from trac.util import Ranges, embedded_numbers, to_ranges
 from trac.util.text import exception_to_unicode, to_unicode
 from trac.util.translation import _
 from trac.util.datefmt import utc
@@ -323,7 +323,7 @@
         if name == 'svn:externals':
             return self._render_externals(props[name])
         elif name == 'svn:mergeinfo' or name.startswith('svnmerge-'):
-            return self._render_mergeinfo(props[name])
+            return self._render_mergeinfo(name, mode, context, props)
         elif name == 'svn:needs-lock':
             return self._render_needslock(context)
 
@@ -389,12 +389,79 @@
         return tag.ul([tag.li(tag.a(label, href=href, title=title))
                        for label, href, title in externals_data])
 
-    def _render_mergeinfo(self, prop):
-        prop = prop.rsplit(':', 1)
-        if len(prop) == 2:
-            prop[1] = prop[1].replace(',', u',\u200b')
-        return ':'.join(prop)
-
+    def _get_blocked_revs(self, props, name, path):
+        """Return the revisions blocked from merging for the given property
+        name and path.
+        """
+        if name == 'svnmerge-integrated':
+            prop = props['svnmerge-blocked']
+        else:
+            return ""
+        for line in prop.splitlines():
+            try:
+                p, revs = line.split(':', 1)
+                if p.strip('/') == path:
+                    return revs
+            except Exception:
+                pass
+        return ""
+    
+    def _render_mergeinfo(self, name, mode, context, props):
+        """Parse svn:mergeinfo and svnmerge-* properties, converting branch
+        names to links and providing links to the revision log for merged
+        and eligible revisions.
+        """
+        has_eligible = name in ('svnmerge-integrated', 'svn:mergeinfo')
+        rows = []
+        for line in props[name].splitlines():
+            try:
+                path, revs = line.split(':', 1)
+                spath = path.strip('/')
+                revs = revs.strip()
+                if 'LOG_VIEW' in context.perm('source', spath):
+                    row = [tag.a(path, title=_('View dir'),
+                                 href=context.href.browser(spath,
+                                                rev=context.resource.version))]
+                    if revs:
+                        log_href = context.href.log(spath, revs=revs)
+                        row.append(tag.a(_('revisions'),
+                                         title=revs.replace(',', ', '),
+                                         href=log_href))
+                    else:
+                        row.append(tag.span(_('revisions'),
+                                            title=_('No revisions')))
+                    if has_eligible:
+                        repos = self.env.get_repository()
+                        node = repos.get_node(spath, context.resource.version)
+                        eligible = set()
+                        for (p, rev, chg) in node.get_history():
+                            if p != spath:
+                                break
+                            eligible.add(rev)
+                        eligible -= set(Ranges(revs))
+                        blocked = self._get_blocked_revs(props, name, spath)
+                        eligible -= set(Ranges(blocked))
+                        if eligible:
+                            eligible = to_ranges(eligible)
+                            row.append(tag.a(_('eligible'),
+                                       title=eligible.replace(',', ', '),
+                                       href=context.href.log(spath,
+                                                             revs=eligible)))
+                        else:
+                            row.append(tag.span(_('eligible'),
+                                       title=_('No eligible revisions')))
+                    rows.append(tag.td(each) for each in row)
+                else:
+                    revs = revs.replace(',', u',\u200b')
+                    rows.append(tag.td(path),
+                                tag.td(revs,
+                                       colspan=has_eligible and 2 or None))
+            except Exception, e:
+                self.log.warning('Rendering of property %s failed: %s', name,
+                                 exception_to_unicode(e))
+                rows.append(tag.td(line, colspan=has_eligible and 3 or 2))
+        return tag.div(tag.table(tag.tbody(tag.tr(each) for each in rows)))
+    
     def _render_needslock(self, context):
         return tag.img(src=context.href.chrome('common/lock-locked.png'),
                        alt="needs lock", title="needs lock")

