You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by gs...@locus.apache.org on 2000/05/12 14:01:50 UTC

cvs commit: apache-site/websrc viewcvs.cgi viewcvs.conf

gstein      00/05/12 05:01:50

  Modified:    websrc   viewcvs.cgi
  Removed:     websrc   viewcvs.conf
  Log:
  upgrade to ViewCVS 0.5. plenty o' fixes. annotation support. better
      colorization. initial work on Bonsai-like query support.
  ViewCVS has turned into more than a single CGI, so it is now installed
      outside the CVS repository. (my home dir pending a shift to /usr/local)
      This CGI script may disappear, too.
  
  Revision  Changes    Path
  1.2       +215 -311  apache-site/websrc/viewcvs.cgi
  
  Index: viewcvs.cgi
  ===================================================================
  RCS file: /home/cvs/apache-site/websrc/viewcvs.cgi,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- viewcvs.cgi	2000/03/24 11:40:00	1.1
  +++ viewcvs.cgi	2000/05/12 12:01:49	1.2
  @@ -1,40 +1,19 @@
   #!/usr/local/bin/python
   # -*-python-*-
   #
  -# viewcvs: View CVS repositories via a web browser
  -#
  -# Copyright (C) 1999-2000 Greg Stein. All Rights Reserved.
  -#
  -# By using this file, you agree to the terms and conditions set forth below:
  +# Copyright (C) 1999-2000 The ViewCVS Group. All Rights Reserved.
   #
  -# Redistribution and use in source and binary forms, with or without
  -# modification, are permitted provided that the following conditions
  -# are met:
  -# 1. Redistributions of source code must retain the above copyright
  -#    notice, this list of conditions and the following disclaimer.
  -# 2. Redistributions in binary form must reproduce the above copyright
  -#    notice, this list of conditions and the following disclaimer in the
  -#    documentation and/or other materials provided with the distribution.
  +# By using this file, you agree to the terms and conditions set forth in
  +# the LICENSE.html file which can be found at the top level of the ViewCVS
  +# distribution or at http://www.lyra.org/viewcvs/license-1.html.
  +#
  +# Contact information:
  +#   Greg Stein, PO Box 760, Palo Alto, CA, 94302
  +#   gstein@lyra.org, http://www.lyra.org/viewcvs/
   #
  -# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
  -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  -# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
  -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  -# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  -# SUCH DAMAGE.
  -#
   # -----------------------------------------------------------------------
  -#
  -# This module is maintained by Greg and is available at:
  -#    http://www.lyra.org/greg/python/viewcvs/
   #
  -# For tracking purposes, this software is identified by:
  -#   viewcvs.cgi,v 1.30 2000/03/24 11:09:14 gstein Exp
  +# viewcvs: View CVS repositories via a web browser
   #
   # -----------------------------------------------------------------------
   #
  @@ -47,205 +26,19 @@
   # -----------------------------------------------------------------------
   #
   
  -__version__ = '0.4'
  +__version__ = '0.5'
   
   #########################################################################
  -#
  -# CONFIGURATION
   #
  -# There are three forms of configuration:
  +# INSTALL-TIME CONFIGURATION
   #
  -#       1) copy viewcvs.conf.dist to viewcvs.conf and edit
  -#       2) as (1), but delete all unchanged entries from viewcvs.conf
  -#       3) do not use viewcvs.conf and just edit the defaults in this file
  +# These values will be set during the installation process. During
  +# development, they will remain None.
   #
  -# Most users will want to use (1), but there are slight speed advantages
  -# to the other two options. Note that viewcvs.conf values are a bit easier
  -# to work with since it is raw text, rather than python literal values.
  -#
  -
  -class Config:
  -  _sections = ('general', 'images', 'options', 'colors', 'text')
  -  _force_multi_value = ('cvs_roots', 'forbidden')
  -
  -  def __init__(self):
  -    for section in self._sections:
  -      setattr(self, section, _sub_config())
  -
  -  def load_config(self, fname):
  -    this_dir = os.path.dirname(sys.argv[0])
  -    pathname = os.path.join(this_dir, fname)
  -    parser = ConfigParser.ConfigParser()
  -    parser.read(pathname)
  -
  -    for section in self._sections:
  -      if not parser.has_section(section):
  -        continue
  -
  -      sc = getattr(self, section)
  -
  -      for opt in parser.options(section):
  -        value = parser.get(section, opt)
  -        if (section != 'text' and ',' in value) or \
  -           opt in self._force_multi_value:
  -          value = map(string.strip, string.split(value, ','))
  -        else:
  -          try:
  -            value = int(value)
  -          except ValueError:
  -            pass
  -
  -        if opt == 'cvs_roots':
  -          roots = { }
  -          for root in value:
  -            name, path = map(string.strip, string.split(root, ':'))
  -            roots[name] = path
  -          value = roots
  -        setattr(sc, opt, value)
  -
  -class _sub_config:
  -  def get_image(self, which):
  -    text = '[%s]' % string.upper(which)
  -    path, width, height = getattr(self, which)
  -    if path:
  -      return '<img src="%s" alt="%s" border=0 width=%s height=%s>' % \
  -             (path, text, width, height)
  -    return text
   
  -cfg = Config()
  +LIBRARY_DIR = "/home/gstein/viewcvs/lib"
  +CONF_PATHNAME = "/home/gstein/viewcvs/viewcvs.conf"
   
  -cfg.general.cvs_roots = {
  -  # user-visible-name : path
  -  "Development" : "/home/cvsroot",
  -  }
  -cfg.general.default_root = "Development"
  -cfg.general.rcs_path = ''
  -cfg.general.mime_types_file = ''
  -cfg.general.address = '<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>'
  -cfg.general.main_title = 'CVS Repository'
  -cfg.general.forbidden = ()
  -
  -cfg.images.logo = "/icons/apache_pb.gif", 259, 32
  -cfg.images.back_icon = "/icons/small/back.gif", 16, 16
  -cfg.images.dir_icon = "/icons/small/dir.gif",  16, 16
  -cfg.images.file_icon = "/icons/small/text.gif", 16, 16
  -
  -cfg.colors.markup_log = "#ffffff"
  -
  -cfg.colors.diff_heading = "#99cccc"
  -cfg.colors.diff_empty = "#cccccc"
  -cfg.colors.diff_remove = "#ff9999"
  -cfg.colors.diff_change = "#99ff99"
  -cfg.colors.diff_add = "#ccccff"
  -cfg.colors.diff_dark_change = "#99cc99"
  -
  -cfg.colors.even_odd = ("#ccccee", "#ffffff")
  -
  -cfg.colors.nav_header = "#9999ee"
  -
  -cfg.colors.text = "#000000"
  -cfg.colors.background = "#ffffff"
  -cfg.colors.alt_background = "#eeeeee"
  -
  -cfg.colors.column_header_normal = "#cccccc"
  -cfg.colors.column_header_sorted = "#88ff88"
  -
  -cfg.colors.table_border = None	# no border
  -
  -cfg.options.sort_by = 'file'
  -cfg.options.hide_attic = 1
  -cfg.options.log_sort = 'date'
  -cfg.options.diff_format = 'h'
  -cfg.options.hide_cvsroot = 1
  -cfg.options.hide_non_readable = 1
  -cfg.options.show_author = 1
  -cfg.options.hr_breakable = 1
  -cfg.options.hr_funout = 1
  -cfg.options.hr_ignore_white = 1
  -cfg.options.hr_ignore_keyword_subst = 1
  -cfg.options.allow_annotate = 0	### doesn't work yet!
  -cfg.options.allow_markup = 1
  -cfg.options.allow_compress = 1
  -cfg.options.use_java_script = 1
  -cfg.options.open_extern_window = 1
  -cfg.options.extern_window_width = 600
  -cfg.options.extern_window_height = 440
  -cfg.options.checkout_magic = 1
  -cfg.options.show_subdir_lastmod = 0
  -cfg.options.show_logs = 1
  -cfg.options.show_log_in_markup = 1
  -cfg.options.allow_version_select = 1
  -cfg.options.py2html_path = '.'
  -cfg.options.short_log_len = 80
  -cfg.options.table_padding = 2
  -cfg.options.diff_font_face = 'Helvetica,Arial'
  -cfg.options.diff_font_size = -1
  -cfg.options.input_text_size = 12
  -
  -cfg.text.long_intro = """\
  -<p>
  -This is a WWW interface for CVS Repositories.
  -You can browse the file hierarchy by picking directories
  -(which have slashes after them, <i>e.g.</i>, <b>src/</b>).
  -If you pick a file, you will see the revision history
  -for that file.
  -Selecting a revision number will download that revision of
  -the file.  There is a link at each revision to display
  -diffs between that revision and the previous one, and
  -a form at the bottom of the page that allows you to
  -display diffs between arbitrary revisions.
  -</p>
  -<p>
  -This script
  -(<a href="http://www.lyra.org/greg/python/viewcvs/">ViewCVS</a>)
  -has been written by Greg Stein
  -&lt;<a href="mailto:gstein@lyra.org">gstein@lyra.org</a>&gt;
  -based on the
  -<a href="http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi">cvsweb</a>
  -script by Henner Zeller
  -&lt;<a href="mailto:zeller@think.de">zeller@think.de</a>&gt;;
  -it is covered by the
  -<a href="http://www.opensource.org/licenses/bsd-license.html">BSD-Licence</a>.
  -If you would like to use this CGI script on your own web server and
  -CVS tree, see Greg's
  -<a href="http://www.lyra.org/greg/python/viewcvs/">ViewCVS distribution
  -site</a>.
  -Please send any suggestions, comments, etc. to
  -<a href="mailto:gstein@lyra.org">Greg Stein</a>.
  -</p>
  -"""
  -# ' stupid emacs...
  -
  -cfg.text.doc_info = """
  -<h3>CVS Documentation</h3>
  -<blockquote>
  -<p>
  -  <a href="http://www.loria.fr/~molli/cvs/doc/cvs_toc.html">CVS
  -  User's Guide</a><br>
  -  <a href="http://www.arc.unm.edu/~rsahu/cvs.html">CVS Tutorial</a><br>
  -  <a href="http://cellworks.washington.edu/pub/docs/cvs/tutorial/cvs_tutorial_1.html">Another CVS tutorial</a><br>
  -  <a href="http://www.csc.calpoly.edu/~dbutler/tutorials/winter96/cvs/">Yet another CVS tutorial (a little old, but nice)</a><br>
  -  <a href="http://www.cs.utah.edu/dept/old/texinfo/cvs/FAQ.txt">An old but very useful FAQ about CVS</a>
  -</p>
  -</blockquote>
  -"""
  -
  -# Fill in stuff on (say) anonymous pserver access here. For example, what
  -# access mechanism, login, path, etc should be used.
  -cfg.text.repository_info = """
  -<!-- insert repository access instructions here -->
  -"""
  -
  -cfg.text.short_intro = """\
  -<p>
  -Click on a directory to enter that directory. Click on a file to display
  -its revision history and to get a chance to display diffs between revisions. 
  -</p>
  -"""
  -
  -#
  -# CONFIGURATION END
  -#
   #########################################################################
   
   import sys
  @@ -257,9 +50,25 @@
   import time
   import re
   import stat
  -import ConfigParser
  +
  +#########################################################################
  +#
  +# Adjust sys.path to include our library directory
  +#
  +
  +if LIBRARY_DIR:
  +  sys.path.insert(0, LIBRARY_DIR)
  +else:
  +  sys.path[:0] = ['../lib']	# any other places to look?
  +
  +# time for our imports now
  +import compat
  +import config
  +import popen
   
   
  +#########################################################################
  +
   checkout_magic_path = '~checkout~/'
   viewcvs_mime_type = 'text/vnd.viewcvs-markup'
   
  @@ -284,12 +93,19 @@
   _FILE_HAD_ERROR = 'could not read file'
   
   header_comment = '''\
  -<!-- ViewCVS       -- http://www.lyra.org/greg/python/viewcvs/
  +<!-- ViewCVS       -- http://www.lyra.org/viewcvs/
        by Greg Stein -- mailto:gstein@lyra.org
     -->
   '''
   
  +# for reading/writing between a couple descriptors
  +CHUNK_SIZE = 8192
  +
  +# if your rlog doesn't use 77 '=' characters, then this must change
  +LOG_END_MARKER = '=' * 77 + '\n'
  +ENTRY_END_MARKER = '-' * 28 + '\n'
   
  +
   class Request:
     def __init__(self):
       where = os.environ.get('PATH_INFO', '')
  @@ -393,34 +209,6 @@
       self.log = log
   
   
  -#
  -# Compatibility stuff for pre-1.5.2 versions of Python
  -#
  -# Two items: urllib.urlencode and time.strptime
  -#
  -try:
  -  my_urlencode = urllib.urlencode
  -except AttributeError:
  -  def my_urlencode(dict):
  -    if not dict:
  -      return ''
  -    quote = urllib.quote_plus
  -    keyvalue = [ ]
  -    for key, value in dict.items():
  -      keyvalue.append(quote(key) + '=' + quote(str(value)))
  -    return '?' + string.join(keyvalue, '&')
  -
  -if hasattr(time, 'strptime'):
  -  def my_strptime(timestr):
  -    return time.strptime(timestr, '%Y/%m/%d %H:%M:%S')
  -else:
  -  _re_rev_date = re.compile('([0-9]{4})/([0-9][0-9])/([0-9][0-9]) '
  -                            '([0-9][0-9]):([0-9][0-9]):([0-9][0-9])')
  -  def my_strptime(timestr):
  -    matches = _re_rev_date.match(timestr).groups()
  -    return tuple(map(int, matches)) + (0, 1, -1)
  -
  -
   def redirect(location):
     print 'Status: 301 Moved'
     print 'Location:', location
  @@ -462,7 +250,7 @@
   def html_footer():
     print '<hr noshade><table width="100&#37;" border=0 cellpadding=0 cellspacing=0><tr>'
     print '<td align=left><address>%s</address></td>' % cfg.general.address
  -  print '<td align=right><a href="http://www.lyra.org/greg/python/viewcvs/">ViewCVS %s</a><br>' % __version__
  +  print '<td align=right><a href="http://www.lyra.org/viewcvs/">ViewCVS %s</a><br>' % __version__
     print 'by <a href="mailto:gstein@lyra.org">Greg Stein</a>'
     print '</td></tr></table>'
     print '</body></html>'
  @@ -473,7 +261,7 @@
       value = dict.get(varname)
       if value is not None and value != default_settings.get(varname, ''):
         sticky_dict[varname] = value
  -  return my_urlencode(sticky_dict)
  +  return compat.urlencode(sticky_dict)
   
   def toggle_query(query_dict, which, value=None):
     dict = query_dict.copy()
  @@ -571,7 +359,7 @@
       print ' target="cvs_checkout"'
       if cfg.options.use_java_script:
         print " onClick=\"window.open('%s','cvs_checkout'," \
  -            "'resizeable,scrollbars" % full_url,
  +            "'resizeable=1,scrollbars=1" % full_url,
         if mime_type == 'text/html':
           print ',status,toolbar',
         print "');\""
  @@ -667,7 +455,7 @@
     while 1:
       ### technically, the htmlify() could fail if something falls across
       ### the chunk boundary. TFB.
  -    chunk = fp.read(8192)
  +    chunk = fp.read(CHUNK_SIZE)
       if not chunk:
         break
       sys.stdout.write(htmlify(chunk))
  @@ -694,8 +482,83 @@
       html = re.sub(_re_rewrite_email, r'<a href="mailto:\1">\1</a>', html)
       sys.stdout.write(html)
   
  +def markup_stream_enscript(lang, fp):
  +  sys.stdout.flush()
  +  cmd = "%senscript --color -W html -E%s -o - - 2> /dev/null " \
  +        "| sed -n '/^<PRE>$/,/<\\/PRE>$/p'" % \
  +        (cfg.options.enscript_path, lang,)
  +  enscript = os.popen(cmd, "w")
  +  while 1:
  +    chunk = fp.read(CHUNK_SIZE)
  +    if not chunk:
  +      break
  +    enscript.write(chunk)
  +  enscript.close()
  +
   markup_streamers = {
  -  '.py' : markup_stream_python,
  +#  '.py' : markup_stream_python,
  +  }
  +
  +### this sucks... we have to duplicate the extensions defined by enscript
  +enscript_extensions = {
  +  '.c' : 'c',
  +  '.h' : 'c',
  +  '.c++' : 'cpp',
  +  '.C' : 'cpp',
  +  '.H' : 'cpp',
  +  '.cpp' : 'cpp',
  +  '.cc' : 'cpp',
  +  '.cxx' : 'cpp',
  +  '.m' : 'objc',
  +  '.scm' : 'scheme',
  +  '.scheme' : 'scheme',
  +  '.el' : 'elisp',
  +  '.ada' : 'ada',
  +  '.adb' : 'ada',
  +  '.ads' : 'ada',
  +  '.s' : 'asm',
  +  '.S' : 'asm',
  +  '.st' : 'states',
  +  '.tcl' : 'tcl',
  +  '.v' : 'verilog',
  +  '.vh' : 'verilog',
  +  '.htm' : 'html',
  +  '.html' : 'html',
  +  '.shtml' : 'html',
  +  '.vhd' : 'vhdl',
  +  '.vhdl' : 'vhdl',
  +  '.scr' : 'synopsys',
  +  '.syn' : 'synopsys',
  +  '.synth' : 'synopsys',
  +  '.idl' : 'idl',
  +  '.hs' : 'haskell',
  +  '.lhs' : 'haskell',
  +  '.gs' : 'haskell',
  +  '.lgs' : 'haskell',
  +  '.pm' : 'perl',
  +  '.pl' : 'perl',
  +  '.eps' : 'postscript',
  +  '.EPS' : 'postscript',
  +  '.ps' : 'postscript',
  +  '.PS' : 'postscript',
  +  '.js' : 'javascript',
  +  '.java' : 'java',
  +  '.pas' : 'pascal',
  +  '.pp' : 'pascal',
  +  '.p' : 'pascal',
  +  '.f' : 'fortran',
  +  '.F' : 'fortran',
  +  '.awk' : 'awk',
  +  '.sh' : 'sh',
  +  '.vba' : 'vba',
  +
  +  ### use enscript or py2html?
  +  '.py' : 'python',
  +  }
  +enscript_filenames = {
  +  '.emacs' : 'elisp',
  +  'Makefile' : 'makefile',
  +  'makefile' : 'makefile',
     }
   
   def markup_stream(request, fp, revision, mime_type):
  @@ -730,14 +593,26 @@
         print 'Tag: <b>%s</b><br>' % tag
     print '</td></tr></table>'
   
  -  url = download_url(request, file_url, revision, mime_type)
     print '<hr noshade>'
     if mime_type[:6] == 'image/':
  +    url = download_url(request, file_url, revision, mime_type)
       print '<img src="%s%s"><br>' % (url, request.amp_query)
     else:
       basename, ext = os.path.splitext(filename)
  -    streamer = markup_streamers.get(ext, markup_stream_default)
  -    streamer(fp)
  +    streamer = markup_streamers.get(ext)
  +    if streamer:
  +      streamer(fp)
  +    elif not cfg.options.use_enscript:
  +      markup_stream_default(fp)
  +    else:
  +      lang = enscript_extensions.get(ext)
  +      if not lang:
  +        lang = enscript_filenames.get(basename)
  +      if lang and lang not in cfg.options.disable_enscript_lang:
  +        markup_stream_enscript(lang, fp)
  +      else:
  +        markup_stream_default(fp)
  +  html_footer()
   
   def get_file_data(full_name):
     """Return a sequence of tuples containing various data about the files.
  @@ -836,10 +711,10 @@
         elif line[:14] == 'symbolic names':
           # start parsing the tag information
           parsing_tags = 1
  -      elif line == '----------------------------\n':
  +      elif line == ENTRY_END_MARKER:
           # end of the headers
           break
  -      elif line[:10] == '==========':
  +      elif line == LOG_END_MARKER:
           # end of this file's log information
           eof = _EOF_FILE
           break
  @@ -899,9 +774,9 @@
         break
       if line[:9] == 'branches:':
         continue
  -    if line == '----------------------------\n':
  +    if line == ENTRY_END_MARKER:
         break
  -    if line[:10] == '==========':
  +    if line == LOG_END_MARKER:
         # end of this file's log information
         eof = _EOF_FILE
         break
  @@ -912,7 +787,7 @@
       # there was a parsing error
       return None, eof
   
  -  date = int(time.mktime(my_strptime(match.group(1)))) - time.timezone
  +  date = int(time.mktime(compat.cvs_strptime(match.group(1)))) - time.timezone
   
     return LogEntry(rev, date,
                     # author, state, lines changed
  @@ -925,7 +800,7 @@
       line = fp.readline()
       if not line:
         break
  -    if line[:10] == '==========':
  +    if line == LOG_END_MARKER:
         break
   
   def process_rlog_output(rlog, full_name, view_tag, fileinfo, alltags):
  @@ -1062,18 +937,18 @@
       chunk = files[:chunk_size]
       del files[:chunk_size]
   
  -    arglist = string.join(chunk, "' '" + full_name + '/')
  +    # prepend the full pathname for each file
  +    for i in range(len(chunk)):
  +      chunk[i] = full_name + '/' + chunk[i]
  +
       if view_tag:
         # NOTE: can't pass tag on command line since a tag may contain "-"
         #       we'll search the output for the appropriate revision
  -      rlog = os.popen("%srlog '%s/%s' 2>&1" %
  -                      (cfg.general.rcs_path, full_name, arglist),
  -                      "r")
  +      rlog = popen.popen(cfg.general.rcs_path + 'rlog', chunk, 'r')
       else:
         # fetch the latest revision on the default branch
  -      rlog = os.popen("%srlog -r '%s/%s' 2>&1" %
  -                      (cfg.general.rcs_path, full_name, arglist),
  -                      "r")
  +      chunk = ('-r',) + tuple(chunk)
  +      rlog = popen.popen(cfg.general.rcs_path + 'rlog', chunk, 'r')
   
       process_rlog_output(rlog, full_name, view_tag, fileinfo, alltags)
   
  @@ -1086,6 +961,10 @@
       ###
       ### more work for later...
   
  +    if rlog.close():
  +      ### what to do?
  +      pass
  +
     return fileinfo, alltags.keys()
   
   def revcmp(rev1, rev2):
  @@ -1277,7 +1156,7 @@
       if isdir:
         if not hideattic and file == 'Attic':
           continue
  -      if where == '' and (file == 'CVSROOT' or file in cfg.general.forbidden):
  +      if where == '' and (file == 'CVSROOT' or cfg.is_forbidden(file)):
           continue
   
         print '<tr bgcolor="%s"><td>' % cfg.colors.even_odd[cur_row % 2]
  @@ -1412,12 +1291,10 @@
   
   def fetch_log(full_name, which_rev=None):
     if which_rev:
  -    rev_flag = '-r' + which_rev
  +    args = ('-r' + which_rev, full_name)
     else:
  -    rev_flag = ''
  -  rlog = os.popen("%srlog %s '%s' 2>&1" %
  -                  (cfg.general.rcs_path, rev_flag, full_name),
  -                  "r")
  +    args = (full_name,)
  +  rlog = popen.popen(cfg.general.rcs_path + 'rlog', args, 'r')
   
     header, eof = parse_log_header(rlog)
     filename = header.filename
  @@ -1905,18 +1782,14 @@
       ### validate it?
       pass
     else:
  -    mime_type, encoding = mimetypes.guess_type(where)
  -    if not mime_type:
  -      mime_type = 'text/plain'
  +    mime_type = request.mime_type
   
     if rev:
       rev_flag = '-p' + rev
     else:
       rev_flag = '-p'
   
  -  fp = os.popen("%sco '%s' '%s' 2>&1" %
  -                (cfg.general.rcs_path, rev_flag, full_name),
  -                'r')
  +  fp = popen.popen(cfg.general.rcs_path + 'co', (rev_flag, full_name), 'r')
   
     # header from co:
   
  @@ -1945,7 +1818,8 @@
             (header, filename, where))
   
     if mime_type == viewcvs_mime_type:
  -    markup_stream(request, fp, revision, mime_type)
  +    # use the "real" MIME type
  +    markup_stream(request, fp, revision, request.mime_type)
     else:
       http_header(mime_type)
       while 1:
  @@ -1955,14 +1829,22 @@
         sys.stdout.write(chunk)
   
   def view_annotate(request):
  -  ### dunno what this is for... check against cvsweb
  -  some_value = request.query_dict['annotate']
  +  rev = request.query_dict['annotate']
   
  -  ### testing
  -  html_header('annotate')
  -  print "annotate"
  +  pathname, filename = os.path.split(request.where)
  +  if pathname[-6:] == '/Attic':
  +    pathname = pathname[:-6]
  +
  +  http_header()
  +  navigate_header(request, request.url, pathname, filename, rev, 'view')
  +  print '<hr noshade>'
  +
  +  import blame
  +  blame.make_html(request.cvsroot, request.where + ',v', rev)
  +
     html_footer()
   
  +
   _re_extract_rev = re.compile(r'^[-+]+ [^\t]+\t([^\t]+)\t((\d+\.)+\d+)$')
   _re_extract_info = re.compile(r'@@ \-([0-9]+).*\+([0-9]+).*@@(.*)')
   _re_extract_diff = re.compile(r'^([-+ ])(.*)')
  @@ -2026,11 +1908,17 @@
          (cfg.options.diff_font_face, cfg.options.diff_font_size)
     left_row = right_row = 0
   
  +  # this will be set to true if any changes are found
  +  changes_seen = 0
  +
     while 1:
       line = fp.readline()
       if not line:
         break
   
  +    # we've seen some kind of change
  +    changes_seen = 1
  +
       if line[:2] == '@@':
         match = _re_extract_info.match(line)
         print '<tr bgcolor="%s"><td width="50&#37;">' % cfg.colors.diff_heading
  @@ -2046,6 +1934,11 @@
         state = 'dump'
         left_col = [ ]
         right_col = [ ]
  +    elif line[0] == '\\':
  +      # \ No newline at end of file
  +      flush_diff_rows(state, left_col, right_col)
  +      left_col = [ ]
  +      right_col = [ ]
       else:
         match = _re_extract_diff.match(line)
         line = spaced_html_text(match.group(2))
  @@ -2072,10 +1965,11 @@
           left_col = [ ]
           right_col = [ ]
   
  -  flush_diff_rows(state, left_col, right_col)
  -  if not state:
  +  if changes_seen:
  +    flush_diff_rows(state, left_col, right_col)
  +  else:
       print '<tr><td colspan=2>&nbsp;</td></tr>'
  -    print '<tr bgcolor="%s"><td colspan=2 align=center><b>- No viewable change -</b></td></tr>' % (cfg.colors.diff_empty)
  +    print '<tr bgcolor="%s"><td colspan=2 align=center><br><b>- No changes -</b><br>&nbsp;</td></tr>' % (cfg.colors.diff_empty)
   
     print '</table><br><hr noshade width="100&#37;">'
     print '<table border=0 cellpadding=10><tr><td>'
  @@ -2168,45 +2062,51 @@
         rev2 = r2[:idx]
         sym2 = r2[idx+1:]
   
  -  ### check rev1, rev2 for well-formed-ness (security reasons)
  -
     if revcmp(rev1, rev2) > 0:
       rev1, rev2 = rev2, rev1
       sym1, sym2 = sym2, sym1
   
     human_readable = 0
  +  unified = 0
  +
  +  args = [ ]
  +
     format = query_dict['diff_format']
     if format == 'c':
  -    diff_type = '-c'
  +    args.append('-c')
       diff_name = 'Context diff'
     elif format == 's':
  -    diff_type = '--side-by-side --width=164'
  +    args.append('--side-by-side')
  +    args.append('--width=164')
       diff_name = 'Side by Side'
     elif format == 'H':
  -    diff_type = '--unified=15'
  +    args.append('--unified=15')
       diff_name = 'Long Human readable'
       human_readable = 1
  +    unified = 1
     elif format == 'h':
  -    diff_type = '-u'
  +    args.append('-u')
       diff_name = 'Human readable'
       human_readable = 1
  +    unified = 1
     elif format == 'u':
  -    diff_type = '-u'
  +    args.append('-u')
       diff_name = 'Unidiff'
  +    unified = 1
     else:
       error('Diff format %s not understood' % format, '400 Bad arguments')
   
     if human_readable:
       if cfg.options.hr_funout:
  -      diff_type = diff_type + ' -p'
  +      args.append('-p')
       if cfg.options.hr_ignore_white:
  -      diff_type = diff_type + ' -w'
  +      args.append('-w')
       if cfg.options.hr_ignore_keyword_subst:
  -      diff_type = diff_type + ' -kk'
  +      args.append('-kk')
   
  -  fp = os.popen("%srcsdiff %s '-r%s' '-r%s' '%s' 2>&1" %
  -                (cfg.general.rcs_path, diff_type, rev1, rev2, cvs_filename),
  -                'r')
  +  args[len(args):] = ['-r' + rev1, '-r' + rev2, cvs_filename]
  +  fp = popen.popen(cfg.general.rcs_path + 'rcsdiff', args, 'r')
  +
     if human_readable:
       http_header()
       human_readable_diff(request, fp, rev1, rev2, sym1, sym2)
  @@ -2214,7 +2114,7 @@
   
     http_header('text/plain')
   
  -  if diff_type == '-u':
  +  if unified:
       f1 = '--- ' + cvsroot
       f2 = '+++ ' + cvsroot
     else:
  @@ -2238,9 +2138,13 @@
       print line[:-1]
   
   def handle_config():
  +  global cfg
  +  cfg = config.Config()
  +  cfg.set_defaults()
  +
     # load in configuration information from the config file
  -  ### allow changes and paths here...??
  -  cfg.load_config('viewcvs.conf')
  +  pathname = CONF_PATHNAME or 'viewcvs.conf'
  +  cfg.load_config(pathname, os.environ.get('HTTP_HOST'))
   
     global default_settings
     default_settings = {
  @@ -2280,7 +2184,7 @@
       redirect(url + '/' + request.qmark_query)
   
     # check the forbidden list
  -  if request.module in cfg.general.forbidden:
  +  if cfg.is_forbidden(request.module):
       error('Access to "%s" is forbidden.' % request.module, '403 Forbidden')
   
     if isdir: