 from James Henstridge <james@jamesh.id.au>
 http://sourceforge.net/tracker/index.php?func=detail&aid=986023&group_id=18760&atid=318760

 lib/config.py    |    1 +
 lib/viewvc.py    |   21 ++++++++++++---------
 viewvc.conf.dist |   15 +++++++++++++++
 3 files changed, 28 insertions(+), 9 deletions(-)

--- a/lib/config.py
+++ b/lib/config.py
@@ -224,6 +224,7 @@ class Config:
     self.options.use_localtime = 0
     self.options.http_expiration_time = 600
     self.options.generate_etags = 1
+    self.options.buglink_base = None
 
   def is_forbidden(self, root, path_parts, pathtype):
     # If we don't have a root and path to check, get outta here.
--- a/lib/viewvc.py
+++ b/lib/viewvc.py
@@ -982,17 +982,20 @@ def get_file_view_info(request, where, r
 # addresses.  Note that the regexps assume the text is already HTML-encoded.
 _re_rewrite_url = re.compile('((http|https|ftp|file|svn|svn\+ssh)(://[-a-zA-Z0-9%.~:_/]+)((\?|\&amp;)([-a-zA-Z0-9%.~:_]+)=([-a-zA-Z0-9%.~:_])+)*(#([-a-zA-Z0-9%.~:_]+)?)?)')
 _re_rewrite_email = re.compile('([-a-zA-Z0-9_.\+]+)@(([-a-zA-Z0-9]+\.)+[A-Za-z]{2,4})')
-def htmlify(html):
+_re_rewrite_bug = re.compile(r'((?:\bbug[\s#+]|[^&]#|^#)\s*(\d\d+))', re.I)
+def htmlify(html, buglink = None):
   html = cgi.escape(html)
   html = re.sub(_re_rewrite_url, r'<a href="\1">\1</a>', html)
   html = re.sub(_re_rewrite_email, r'<a href="mailto:\1&#64;\2">\1&#64;\2</a>', html)
+  if buglink is not None:
+     html = re.sub(_re_rewrite_bug, r'<a href="%s\2">\1</a>' % buglink, html)
   return html
 
 def format_log(log, cfg, htmlize=1):
   if not log:
     return log
   if htmlize:
-    s = htmlify(log[:cfg.options.short_log_len])
+    s = htmlify(log[:cfg.options.short_log_len], cfg.options.buglink_base)
   else:
     s = cgi.escape(log[:cfg.options.short_log_len])
   if len(log) > cfg.options.short_log_len:
@@ -1412,7 +1415,7 @@ def view_markup(request):
         'date' : make_time_string(entry.date, cfg),
         'author' : entry.author,
         'changed' : entry.changed,
-        'log' : htmlify(entry.log),
+        'log' : htmlify(entry.log, cfg.options.buglink_base),
         'size' : entry.size,
         })
 
@@ -1673,7 +1676,7 @@ def view_directory(request):
     'sortby' : sortby,
     'sortdir' : sortdir,
     'tarball_href' : None,
-    'search_re' : search_re and htmlify(search_re) or None,
+    'search_re' : search_re and htmlify(search_re, cfg.options.buglink_base) or None,
     'dir_pagestart' : None,
     'sortby_file_href' :   request.get_url(params={'sortby': 'file',
                                                    'sortdir': None},
@@ -1921,7 +1924,7 @@ def view_log(request):
     entry.ago = None
     if rev.date is not None:
       entry.ago = html_time(request, rev.date, 1)
-    entry.log = htmlify(rev.log or "")
+    entry.log = htmlify(rev.log or "", cfg.options.buglink_base)
     entry.size = rev.size
     entry.branch_point = None
     entry.next_main = None
@@ -2395,7 +2398,7 @@ def spaced_html_text(text, cfg):
     text = string.replace(text, '  ', ' \x01nbsp;')
   else:
     text = string.replace(text, ' ', '\x01nbsp;')
-  text = htmlify(text)
+  text = htmlify(text, cfg.options.buglink_base)
   text = string.replace(text, '\x01', '&')
   text = string.replace(text, '\x02', '<span style="color:red">\</span><br />')
   return text
@@ -2815,7 +2818,7 @@ def view_diff(request):
       else:
         changes = DiffSource(fp, cfg)
     else:
-      raw_diff_fp = MarkupPipeWrapper(fp, htmlify(headers), None, 1)
+      raw_diff_fp = MarkupPipeWrapper(fp, htmlify(headers, cfg.options.buglink_base), None, 1)
 
   data.update({
     'date_left' : rcsdiff_date_reformat(date1, cfg),
@@ -3139,7 +3142,7 @@ def view_revision(request):
     'rev' : str(rev),
     'author' : author,
     'date' : date_str,
-    'log' : msg and htmlify(msg) or None,
+    'log' : msg and htmlify(msg, request.cfg.options.buglink_base) or None,
     'ago' : None,
     'changes' : changes,
     'prev_href' : prev_rev_href,
@@ -3401,7 +3404,7 @@ def build_commit(request, files, max_fil
   commit = _item(num_files=len(commit_files), files=commit_files,
                  plus=plus_count, minus=minus_count)
   commit.limited_files = ezt.boolean(num_allowed > len(commit_files))
-  commit.log = htmlify(desc)
+  commit.log = htmlify(desc, request.cfg.options.buglink_base)
   commit.short_log = format_log(desc, request.cfg, format != 'rss')
   commit.author = request.server.escape(author)
   commit.rss_date = make_rss_time_string(date, request.cfg)
--- a/viewvc.conf.dist
+++ b/viewvc.conf.dist
@@ -293,6 +293,21 @@ languages = en-us
 #---------------------------------------------------------------------------
 [options]
 
+# The 'buglink_base' value is a string that can be used to form a URL
+# by appending a bug number.  If viewvc sees something that looks
+# like a bug number in a log message (eg. "bug 12345" or "#12345"), it
+# will be displayed as a link to the bug in your bug tracking system.
+#
+# For a Bugzilla installation, you probably want to set this to
+# something like "http://hostname/show_bug.cgi?id=".  For the Debian
+# bug tracker, you might use
+# "http://hostname/cgi-bin/bugreport.cgi?bug=".
+#
+# If 'buglink_base' is not set, then bug tracker links won't be
+# generated.
+#
+# buglink_base = http://example.com/show_bug.cgi?id=
+
 # root_as_url_component: Interpret the first path component in the URL
 # after the script location as the root to use.  This is an
 # alternative to using the "root=" query key. If ViewVC is configured
