Re: [viewvc-dev] Various patches for ViewVC

Виталий Филиппов <[email protected]> Thu, 21 May 2009 18:00:46 +0400
Newsgroups gmane.comp.version-control.cvs.viewcvs.devel
Organization CustIS
Message-ID <[email protected]>
> Decoding to ... Unicode?

Yeah

> Don't you have to read a block of file data to feed to libmagic?

Yes

> Do you do this for every directory listing and other codepath that wants to  
> populate the "mimetype" entry property?

No, only in annotate and checkout views

> I know it's a pain to keep these changes separable, but if you look at  

It's a... kind of very big pain :)
I've now tried to separate some of them, see the attaches.
But as the result, I think, they won't apply on a "trunk" ViewVC copy...

Also I think these patches are stupid rather than smart... but they work.
And when separated... They need much more debug. :)
I don't insist on accepting the "fat patch", I agree with your opinion. I'm just informing about "it". :)

-- 
With best regards,
  Vitaliy Filippov

------------------------------------------------------
http://viewvc.tigris.org/ds/viewMessage.do?dsForumId=4251&dsMessageId=2349779

To unsubscribe from this discussion, e-mail: [[email protected]].
libmagic-mime-type-guessing.diff (application/octet-stream, 12.3 KB)
Index: viewvc.org/trunk/lib/viewvc.py
===================================================================
--- viewvc.org/trunk/lib/viewvc.py	2008/12/09 18:44:05	54
+++ viewvc.org/trunk/lib/viewvc.py	2008/12/10 13:21:37	58
@@ -67,6 +67,8 @@
 viewcvs_mime_type = 'text/vnd.viewcvs-markup'
 alt_mime_type = 'text/x-cvsweb-markup'
 view_roots_magic = '*viewroots*'
+magic_buf_size = 4096
+default_mime_type = 'application/octet-stream'
 
 # Put here the variables we need in order to hold our state - they
 # will be added (with their current value) to (almost) any link/query
@@ -100,6 +102,7 @@
   def __init__(self, server, cfg):
     self.server = server
     self.cfg = cfg
+    self.cfg.options.binary_mime_re = re.compile(self.cfg.options.binary_mime_re)
 
     self.script_name = _normalize_path(server.getenv('SCRIPT_NAME', ''))
     self.browser = server.getenv('HTTP_USER_AGENT', 'unknown')
@@ -114,6 +117,10 @@
     # check for an authenticated username
     self.username = server.getenv('REMOTE_USER')
 
+    # construct MIME magic
+    self.ms = None
+    self.ms_fail = 0
+
     # if we allow compressed output, see if the client does too
     self.gzip_compress_level = 0
     if cfg.options.allow_compress:
@@ -989,6 +996,9 @@
 def is_viewable_image(mime_type):
   return mime_type and mime_type in ('image/gif', 'image/jpeg', 'image/png')
 
+def is_binary(cfg, mime_type):
+  return mime_type and re.match(cfg.options.binary_mime_re, mime_type)
+
 def is_text(mime_type):
   return not mime_type or mime_type[:5] == 'text/'
 
@@ -1335,6 +1345,7 @@
   # Reasons not to include a) being told not to by the configuration,
   # b) not being able to import the Pygments modules, and c) Pygments
   # not having a lexer for our file's format.
+
   blame_source = []
   if blame_data:
     for i in blame_data:
@@ -1376,9 +1387,10 @@
       lines = []
       line_no = 0
       while 1:
-        line = cvsdb.utf8string(fp.readline())
+        line = fp.readline()
         if not line:
           break
+        line = cvsdb.utf8string(line)
         line_no = line_no + 1
         item = vclib.Annotation(cgi.escape(line), line_no,
                                 None, None, None, None)
@@ -1490,18 +1502,41 @@
   revision = None
   mime_type = calculate_mime_type(request, path, rev)
 
-  # Is this a viewable image type?
-  if is_viewable_image(mime_type) \
-     and 'co' in cfg.options.allowed_views:
+  if not mime_type or mime_type == default_mime_type:
+    if request.ms is None and not request.ms_fail:
+      try:
+        import magic
+        request.ms = magic.open(magic.MAGIC_NONE | magic.MAGIC_MIME)
+        request.ms.load()
+      except:
+        request.ms_fail = 1
+    if request.ms:
+      try:
+        fp, revision = request.repos.openfile(path, rev)
+        buffer = fp.read(magic_buf_size)
+        fp.close()
+        mime_type = request.ms.buffer(buffer)
+      except:
+        pass
+
+  # Is this a binary type?
+  if is_binary(request.cfg, mime_type):
     fp, revision = request.repos.openfile(path, rev)
     fp.close()
     if check_freshness(request, None, revision, weak=1):
       return
     annotation = 'binary'
-    image_src_href = request.get_url(view_func=view_checkout,
-                                     params={'revision': rev}, escape=1)
+    if 'co' in cfg.options.allowed_views:
+      # Is this a viewable image type?
+      if is_viewable_image(mime_type) \
+         and 'co' in cfg.options.allowed_views:
+        image_src_href = request.get_url(view_func=view_checkout,
+                                         params={'revision': rev}, escape=1)
+      else:
+        download_href = request.get_url(view_func=view_checkout,
+                                        params={'revision': rev}, escape=1)
 
-  # Not a viewable image.
+  # Text type
   else:
     blame_source = None
     if is_annotate:
@@ -1584,9 +1619,9 @@
                                         pathtype=vclib.FILE,
                                         params={'pathrev': revision},
                                         escape=1)
-    
+
   generate_page(request, "file", data)
-  
+
 def view_markup(request):
   if 'markup' not in request.cfg.options.allowed_views:
     raise debug.ViewVCException('Markup view is disabled',
@@ -1748,7 +1783,7 @@
   rows = [ ]
   num_displayed = 0
   num_dead = 0
-  
+
   # set some values to be used inside loop
   where = request.where
   where_prefix = where and where + '/'
@@ -2557,11 +2592,11 @@
   def _format_text(self, text):
     text = string.expandtabs(string.rstrip(text))
     hr_breakable = self.cfg.options.hr_breakable
-    
+
     # in the code below, "\x01" will be our stand-in for "&". We don't want
     # to insert "&" because it would get escaped by htmlify().  Similarly,
     # we use "\x02" as a stand-in for "<br>"
-  
+
     if hr_breakable > 1 and len(text) > hr_breakable:
       text = re.sub('(' + ('.' * hr_breakable) + ')', '\\1\x02', text)
     if hr_breakable:
@@ -2574,7 +2609,7 @@
     text = string.replace(text, '\x02',
                           '<span style="color:red">\</span><br />')
     return text
-    
+
   def _get_row(self):
     if self.state[:5] == 'flush':
       item = self._flush_row()
@@ -2614,7 +2649,7 @@
                    line_info_left=match.group(1),
                    line_info_right=match.group(2),
                    line_info_extra=match.group(3))
-    
+
     if line[0] == '\\':
       # \ No newline at end of file
 
@@ -2626,7 +2661,7 @@
     diff_code = line[0]
     output = self._format_text(line[1:])
     output = cvsdb.utf8string(output)
-    
+
     if diff_code == '+':
       if self.state == 'dump':
         self.line_number = self.line_number + 1
@@ -2782,7 +2817,7 @@
     else:
       rev1 = r1[:idx]
       sym1 = r1[idx+1:]
-      
+
   if r2 == 'text':
     rev2 = query_dict.get('tr2', None)
     if not rev2:
@@ -2804,7 +2839,7 @@
     except vclib.InvalidRevision:
       raise debug.ViewVCException('Invalid revision(s) passed to diff',
                                   '400 Bad Request')
-    
+
   p1 = _get_diff_path_parts(request, 'p1', rev1, request.pathrev)
   p2 = _get_diff_path_parts(request, 'p2', rev2, request.pathrev)
 
@@ -2836,7 +2871,7 @@
   else:
     raise debug.ViewVCException('Diff format %s not understood'
                                  % format, '400 Bad Request')
-  
+
   try:
     fp = request.repos.rawdiff(p1, rev1, p2, rev2, diff_type)
   except vclib.InvalidRevision:
@@ -2856,7 +2891,7 @@
   cfg = request.cfg
   query_dict = request.query_dict
   p1, p2, rev1, rev2, sym1, sym2 = setup_diff(request)
-  
+
   # since templates are in use and subversion allows changes to the dates,
   # we can't provide a strong etag
   if check_freshness(request, None, '%s-%s' % (rev1, rev2), weak=1):
@@ -2915,7 +2950,7 @@
       else:
         unified = idiff.unified(lines_left, lines_right,
                                 diff_options.get("context", 2))
-    else: 
+    else:
       fp = request.repos.rawdiff(p1, rev1, p2, rev2, diff_type, diff_options)
   except vclib.InvalidRevision:
     raise debug.ViewVCException('Invalid path(s) or revision(s) passed '
@@ -2949,13 +2984,13 @@
   left.view_href, left.download_href, left.download_text_href, \
     left.annotate_href, left.revision_href, left.prefer_markup \
     = get_file_view_info(request, path_left, rev1)
-  
+
   right = _item(date=rcsdiff_date_reformat(date2, cfg),
                 path=path_right, rev=rev2, tag=sym2)
   right.view_href, right.download_href, right.download_text_href, \
     right.annotate_href, right.revision_href, right.prefer_markup \
     = get_file_view_info(request, path_right, rev2)
-      
+
   data = common_template_data(request)
   data.update({
     'left' : left,
@@ -3053,7 +3088,7 @@
     tar_dir = tar_dir + _path_join(reldir) + '/'
 
   cvs = request.roottype == 'cvs'
-  
+
   # If our caller doesn't dictate a datestamp to use for the current
   # directory, its datestamps will be the youngest of the datestamps
   # of versioned items in that subdirectory.  We'll be ignoring dead
@@ -3128,14 +3163,14 @@
 
 def download_tarball(request):
   cfg = request.cfg
-  
+
   if 'tar' not in request.cfg.options.allowed_views:
     raise debug.ViewVCException('Tarball generation is disabled',
                                  '403 Forbidden')
 
   if debug.TARFILE_PATH:
     fp = open(debug.TARFILE_PATH, 'w')
-  else:    
+  else:
     tarfile = request.rootname
     if request.path_parts:
       tarfile = "%s-%s" % (tarfile, request.path_parts[-1])
@@ -3261,7 +3296,7 @@
                                                    'r2' : str(change.base_rev),
                                                    },
                                            escape=1)
-    
+
 
     # use same variable names as the log template
     change.path = _path_join(change.path_parts)
@@ -3382,7 +3417,7 @@
 
 def parse_date(datestr):
   """Parse a date string from the query form."""
-  
+
   match = re.match(r'^(\d\d\d\d)-(\d\d)-(\d\d)(?:\ +'
                    '(\d\d):(\d\d)(?::(\d\d))?)?$', datestr)
   if match:
@@ -3824,7 +3859,7 @@
       limited_files = 0
       current_desc = commit_desc
       current_rev = commit_rev
-      
+
     # we need to tack on our last commit grouping, if any
     commit_item = build_commit(request, files, limit_changes,
                                dir_strip, format)
@@ -3833,7 +3868,7 @@
       plus_count = plus_count + commit_item.plus
       minus_count = minus_count + commit_item.minus
       commits.append(commit_item)
-  
+
   # only show the branch column if we are querying all branches
   # or doing a non-exact branch match on a CVS repository.
   show_branch = ezt.boolean(request.roottype == 'cvs' and
@@ -3908,7 +3943,7 @@
 def list_roots(request):
   cfg = request.cfg
   allroots = { }
-  
+
   # Add the viewable Subversion roots
   for root in cfg.general.svn_roots.keys():
     auth = setup_authorizer(cfg, request.username, root)
@@ -3928,7 +3963,7 @@
     except vclib.ReposNotFound:
       continue
     allroots[root] = [cfg.general.cvs_roots[root], 'cvs']
-    
+
   return allroots
 
 def find_root_in_parents(cfg, rootname, roottype):
@@ -3999,7 +4034,7 @@
   # load mime types file
   if cfg.general.mime_types_file:
     mimetypes.init([cfg.general.mime_types_file])
-  
+
   debug.t_end('load-config')
   return cfg
 
@@ -4013,7 +4048,7 @@
     exc_dict['stacktrace'] = htmlify(exc_dict['stacktrace'],
                                      mangle_email_addrs=0)
   handled = 0
-  
+
   # use the configured error template if possible
   try:
     if cfg and not server.headerSent:
Index: viewvc.org/trunk/templates/file.ezt
===================================================================
--- viewvc.org/trunk/templates/file.ezt	2008/12/09 18:44:05	54
+++ viewvc.org/trunk/templates/file.ezt	2008/12/10 12:26:16	55
@@ -8,7 +8,7 @@
 <hr />
 <div class="vc_summary">
 Revision [if-any revision_href]<a href="[revision_href]"><strong>[rev]</strong></a>[else]<strong>[rev]</strong>[end] -
-([if-any annotation][is annotation "annotated"]<a href="[view_href]"><strong>hide annotations</strong></a>[end][else]<a href="[annotate_href]"><strong>show annotations</strong></a>[end])
+([if-any annotation][is annotation "annotated"]<a href="[view_href]"><strong>hide annotations</strong></a>[end][is annotation "binary"]binary[end][else]<a href="[annotate_href]"><strong>show annotations</strong></a>[end])
 [if-any download_href](<a href="[download_href]"><strong>download</strong></a>)[end]
 [if-any download_text_href](<a href="[download_text_href]"><strong>as text</strong></a>)[end]
 
@@ -45,7 +45,7 @@
 [end]
 [if-any annotation]
 [is annotation "binary"]
-  <br /><strong>Unable to calculate annotation data on binary file contents.</strong>
+  <br /><strong>Unable to calculate annotation data on binary file contents [if-any download_href](<a href="[download_href]">download</a>)[end].</strong>
 [end]
 [is annotation "error"]
   <br /><strong>Error occurred while calculating annotation data.</strong>
Index: viewvc.org/trunk/lib/config.py
===================================================================
--- viewvc.org/trunk/lib/config.py	2008/12/09 18:44:05	54
+++ viewvc.org/trunk/lib/config.py	2008/12/10 12:26:16	55
@@ -279,6 +279,7 @@
     self.options.use_pagesize = 0
     self.options.limit_changes = 100
     self.options.cvs_ondisk_charset = 'cp1251'
+    self.options.binary_mime_re = '^(?!text/|application/xml)'
 
     self.templates.diff = None
     self.templates.directory = None
patches-from-search-results.diff (application/octet-stream, 16.4 KB)
Index: viewvc.org/trunk/lib/vclib/__init__.py
===================================================================
--- viewvc.org/trunk/lib/vclib/__init__.py	2009/01/15 09:11:40	98
+++ viewvc.org/trunk/lib/vclib/__init__.py	2009/01/15 10:00:38	99
@@ -381,13 +381,13 @@
         self.fp = None
     finally:
       try:
-        if self.temp1:
+        if self.temp1 and self.temp1 != '/dev/null':
           os.remove(self.temp1)
-          self.temp1 = None
+        self.temp1 = None
       finally:
-        if self.temp2:
+        if self.temp2 and self.temp2 != '/dev/null':
           os.remove(self.temp2)
-          self.temp2 = None
+        self.temp2 = None
 
   def __del__(self):
     self.close()
Index: viewvc.org/trunk/lib/vclib/ccvs/bincvs.py
===================================================================
--- viewvc.org/trunk/lib/vclib/ccvs/bincvs.py	2009/01/15 09:11:40	98
+++ viewvc.org/trunk/lib/vclib/ccvs/bincvs.py	2009/01/15 10:00:38	99
@@ -338,13 +338,19 @@
 
       ignore_keyword_subst - boolean, ignore keyword substitution
     """
+    if not path_parts1:
+      path_parts1 = path_parts2
+      rev1 = '1.0'
+    if not path_parts2:
+      path_parts2 = path_parts1
+      rev2 = '1.0'
     if self.itemtype(path_parts1, rev1) != vclib.FILE:  # does auth-check
       raise vclib.Error("Path '%s' is not a file."
                         % (string.join(path_parts1, "/")))
     if self.itemtype(path_parts2, rev2) != vclib.FILE:  # does auth-check
       raise vclib.Error("Path '%s' is not a file."
                         % (string.join(path_parts2, "/")))
-    
+
     args = vclib._diff_args(type, options)
     if options.get('ignore_keyword_subst', 0):
       args.append('-kk')
@@ -352,7 +358,7 @@
     rcsfile = self.rcsfile(path_parts1, 1)
     if path_parts1 != path_parts2:
       raise NotImplementedError, "cannot diff across paths in cvs"
-    args.extend(['-r' + rev1, '-r' + rev2, rcsfile])
+    args.extend(['-N', '-r' + rev1, '-r' + rev2, rcsfile])
     fp = self.rcs_popen('rcsdiff', args, 'rt')
 
     # Eat up the non-GNU-diff-y headers.
@@ -361,7 +367,6 @@
       if not line or line[0:5] == 'diff ':
         break
     return fp
-  
 
 class CVSDirEntry(vclib.DirEntry):
   def __init__(self, name, kind, errors, in_attic, absent=0):
Index: viewvc.org/trunk/templates/query_results.ezt
===================================================================
--- viewvc.org/trunk/templates/query_results.ezt	2009/01/13 15:23:11	96
+++ viewvc.org/trunk/templates/query_results.ezt	2009/01/14 14:07:41	97
@@ -9,6 +9,10 @@
 [# <!-- {sql} --> ]
 <p><a href="[queryform_href]">Modify query</a></p>
 <p><a href="[backout_href]">Show commands which could be used to back out these changes</a></p>
+<p>
+    <a href="[patch_href]">Show a patch built from these changes</a>
+    [if-any patch_unsecure]<br /><b>CAUTION: selected changes are not contiguous, patch may include differences from other commits.</b>[end]
+</p>
 
 <p><strong>+[plus_count]/-[minus_count]</strong> changed lines total.</p>
 
Index: viewvc.org/trunk/lib/viewvc.py
===================================================================
--- viewvc.org/trunk/lib/viewvc.py	2009/01/13 15:23:11	96
+++ viewvc.org/trunk/lib/viewvc.py	2009/01/27 11:27:05	108
@@ -36,6 +36,7 @@
 import time
 import types
 import urllib
+import datetime
 
 # These modules come from our library (the stub has set up the path)
 import accept
@@ -163,6 +164,7 @@
         'rootname' : rootname,
         'auth'     : authorizer,
         'rootpath' : rootpath,
+        'roottype' : roottype,
       }
 
     return None
@@ -1320,13 +1322,16 @@
     return chunk
   
 def copy_stream(src, dst, cfg, htmlize=0):
+  nch = 0
   while 1:
     chunk = retry_read(src)
     if not chunk:
       break
+    nch = nch+1
     if htmlize:
       chunk = htmlify(chunk, mangle_email_addrs=0)
     dst.write(chunk)
+  return nch
 
 class MarkupPipeWrapper:
   """An EZT callback that outputs a filepointer, plus some optional
@@ -3518,6 +3523,16 @@
     r = r[:-2]
   return string.join(r, '.')
 
+def rev_cmp(rev1, rev2):
+  """Compares two revision numbers rev1 and rev2"""
+  r1 = string.split(str(rev1), '.')
+  r2 = string.split(str(rev2), '.')
+  l = max(map(lambda s: len(s), r1 + r2))
+  f = '%%0%ds' % (l)
+  rev1 = '.'.join(map(lambda s: f % (s), r1))
+  rev2 = '.'.join(map(lambda s: f % (s), r2))
+  return cmp(rev1, rev2)
+
 def build_commit(request, files, max_files, dir_strip, format):
   """Return a commit object build from the information in FILES, or
   None if no allowed files are present in the set.  DIR_STRIP is the
@@ -3655,7 +3670,8 @@
                               view_href=view_href,
                               download_href=download_href,
                               prefer_markup=prefer_markup,
-                              diff_href=diff_href))
+                              diff_href=diff_href,
+                              root=my_repos))
 
   # No files survived authz checks?  Let's just pretend this
   # little commit didn't happen, shall we?
@@ -3717,6 +3733,109 @@
               % (fileinfo.rev, prev_rev(fileinfo.rev),
                  fileinfo.dir, fileinfo.file)
 
+def query_is_unsecure_patch(request, commits):
+  if not commits:
+    return None
+  mr = {}
+  lr = {}
+  for commit in commits:
+    for fileinfo in commit.files:
+      fn = _path_join([fileinfo.dir, fileinfo.file])
+      if mr.get(fn, ''):
+        pr = mr[fn]
+        if fileinfo.root['roottype'] == 'svn':
+          pr = lr[fileinfo.root['rootname']]
+        pr = prev_rev(pr)
+        if rev_cmp(pr, fileinfo.rev) > 0:
+          return True
+      if fileinfo.root['roottype'] == 'svn':
+        lr[fileinfo.root['rootname']] = fileinfo.rev
+      mr[fn] = fileinfo.rev
+  return None
+
+def query_patch(request, commits):
+  request.server.header('text/x-diff')
+  if not commits:
+    print '# No changes were selected by the query.'
+    print '# There is nothing to show in the patch.'
+    return
+  files = {}
+  rev = ''
+  for commit in commits:
+    for fileinfo in commit.files:
+      fn = _path_join([fileinfo.dir, fileinfo.file])
+      rev = files.get(fn, '')
+      if not rev:
+        files[fn] = [ fileinfo.rev, fileinfo.rev, fileinfo.root ]
+      elif rev_cmp(rev[0], fileinfo.rev) > 0:
+        files[fn] = [ fileinfo.rev, rev[1], rev[2] ]
+  server_fp = get_writeready_server_file(request, 'text/plain')
+  for file in files.keys():
+    rev1 = prev_rev(files[file][0])
+    rev2 = files[file][1]
+    repos = files[file][2]['repos']
+    roottype = files[file][2]['roottype']
+    if roottype == 'svn':
+      try:
+        rev1 = repos._getrev(rev1)
+        rev2 = repos._getrev(rev2)
+      except vclib.InvalidRevision:
+        raise debug.ViewVCException('Invalid revision(s) passed to diff',
+                                    '400 Bad Request')
+    server_fp.write('Index: %s\n===================================================================\n' % (file))
+    try:
+      rdate1, _, _, _ = repos.revinfo(rev1)
+      rdate2, _, _, _ = repos.revinfo(rev2)
+      rdate1 = datetime.date.fromtimestamp(rdate1).strftime(' %Y/%m/%d %H:%M:%S')
+      rdate2 = datetime.date.fromtimestamp(rdate2).strftime(' %Y/%m/%d %H:%M:%S')
+    except vclib.UnsupportedFeature:
+      rdate1 = ''
+      rdate2 = ''
+    pr = rev2
+    try:
+      if roottype == 'svn':
+        p2 = _path_parts(repos.get_location(file, rev2, rev2))
+      else:
+        p2 = _path_parts(file)
+        fd, fr = repos.openfile(p2, rev2)
+        if not fd or rev2 != fr:
+          raise vclib.ItemNotFound(p2)
+        if fd:
+          fd.close()
+    except vclib.ItemNotFound:
+      # file removed at rev2
+      p2 = None
+      pr = prev_rev(str(rev2))
+    try:
+      if roottype == 'svn':
+        trev1, p1 = repos.last_rev(file, pr, rev1)
+        if trev1 != rev1:
+          p1 = None
+        else:
+          p1 = _path_parts(repos.get_location(p1, rev1, rev1))
+      else:
+        p1 = _path_parts(file)
+        fd, fr = repos.openfile(p1, rev1)
+        if fd:
+          fd.close()
+          rev1 = fr
+        else:
+          rev1 = '0'
+      if rev_cmp(rev1, rev2) >= 0:
+        # file added at rev2
+        p1 = None
+    except:
+      p1 = None
+    try:
+      fp = repos.rawdiff(p1, rev1, p2, rev2, vclib.UNIFIED)
+      nc = copy_stream(fp, server_fp, request.cfg)
+      if not nc and p1 and p2 and _path_join(p1) != _path_join(p2):
+        server_fp.write('--- %s%s\t%s\n' % (_path_join(p1), rdate1, rev1))
+        server_fp.write('+++ %s%s\t%s\n' % (_path_join(p2), rdate2, rev2))
+      fp.close()
+    except:
+      pass
+
 def view_query(request):
   if not is_query_supported(request):
     raise debug.ViewVCException('Can not query project root "%s" at "%s".'
@@ -3890,6 +4009,12 @@
   backout_href = request.get_url(params=params,
                                  escape=1)
 
+  # patch link
+  params = request.query_dict.copy()
+  params['format'] = 'patch'
+  patch_href = request.get_url(params=params,
+                                 escape=1)
+
   # link to zero limit_changes value
   params = request.query_dict.copy()
   params['limit_changes'] = 0
@@ -3904,12 +4029,18 @@
     query_backout(request, commits)
     return
 
+  if format == 'patch':
+    query_patch(request, commits)
+    return
+
   data = common_template_data(request)
   data.update({
     'sql': sql,
     'english_query': english_query(request),
     'queryform_href': request.get_url(view_func=view_queryform, escape=1),
     'backout_href': backout_href,
+    'patch_href' : patch_href,
+    'patch_unsecure' : ezt.boolean(query_is_unsecure_patch(request, commits)),
     'plus_count': plus_count,
     'minus_count': minus_count,
     'show_branch': show_branch,
Index: viewvc.org/trunk/lib/vclib/svn/svn_repos.py
===================================================================
--- viewvc.org/trunk/lib/vclib/svn/svn_repos.py	2009/01/15 09:11:40	98
+++ viewvc.org/trunk/lib/vclib/svn/svn_repos.py	2009/01/15 10:18:33	100
@@ -649,15 +649,24 @@
     return cached_info[0], cached_info[1], cached_info[2], cached_info[3]
   
   def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
-    p1 = self._getpath(path_parts1)
-    p2 = self._getpath(path_parts2)
-    r1 = self._getrev(rev1)
-    r2 = self._getrev(rev2)
-    if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
-      raise vclib.ItemNotFound(path_parts1)
-    if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
-      raise vclib.ItemNotFound(path_parts2)
-    
+    if path_parts1:
+      p1 = self._getpath(path_parts1)
+      r1 = self._getrev(rev1)
+      if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
+        raise vclib.ItemNotFound(path_parts1)
+    else:
+      p1 = None
+
+    if path_parts2:
+      p2 = self._getpath(path_parts2)
+      r2 = self._getrev(rev2)
+      if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
+        raise vclib.ItemNotFound(path_parts2)
+    else:
+      if not p1:
+        raise vclib.ItemNotFound(path_parts2)
+      p2 = None
+
     args = vclib._diff_args(type, options)
 
     def _date_from_rev(rev):
@@ -665,10 +674,18 @@
       return date
 
     try:
-      temp1 = temp_checkout(self, p1, r1)
-      temp2 = temp_checkout(self, p2, r2)
-      info1 = p1, _date_from_rev(r1), r1
-      info2 = p2, _date_from_rev(r2), r2
+      if p1:
+        temp1 = temp_checkout(self, p1, r1)
+        info1 = p1, _date_from_rev(r1), r1
+      else:
+        temp1 = '/dev/null'
+        info1 = '/dev/null', _date_from_rev(rev1), rev1
+      if p2:
+        temp2 = temp_checkout(self, p2, r2)
+        info2 = p2, _date_from_rev(r2), r2
+      else:
+        temp2 = '/dev/null'
+        info2 = '/dev/null', _date_from_rev(rev2), rev2
       return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args)
     except core.SubversionException, e:
       if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
Index: viewvc.org/trunk/lib/vclib/svn/svn_ra.py
===================================================================
--- viewvc.org/trunk/lib/vclib/svn/svn_ra.py	2009/01/15 09:11:40	98
+++ viewvc.org/trunk/lib/vclib/svn/svn_ra.py	2009/01/15 10:00:38	99
@@ -340,32 +340,47 @@
     return cached_info[0], cached_info[1], cached_info[2], cached_info[3]
     
   def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
-    p1 = self._getpath(path_parts1)
-    p2 = self._getpath(path_parts2)
-    r1 = self._getrev(rev1)
-    r2 = self._getrev(rev2)
-    if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
-      raise vclib.ItemNotFound(path_parts1)
-    if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
-      raise vclib.ItemNotFound(path_parts2)
+
+    if path_parts1 is not None:
+      p1 = self._getpath(path_parts1)
+      r1 = self._getrev(rev1)
+      if not vclib.check_path_access(self, path_parts1, vclib.FILE, rev1):
+        raise vclib.ItemNotFound(path_parts1)
+    else:
+      p1 = None
+
+    if path_parts2 is not None:
+      if not p1:
+        raise vclib.ItemNotFound(parh_parts2)
+      p2 = self._getpath(path_parts2)
+      r2 = self._getrev(rev2)
+      if not vclib.check_path_access(self, path_parts2, vclib.FILE, rev2):
+        raise vclib.ItemNotFound(path_parts2)
+    else:
+      p2 = None
 
     args = vclib._diff_args(type, options)
 
     def _date_from_rev(rev):
       date, author, msg, changes = self.revinfo(rev)
       return date
-    
+
     try:
-      temp1 = temp_checkout(self, p1, r1)
-      temp2 = temp_checkout(self, p2, r2)
       info1 = p1, _date_from_rev(r1), r1
       info2 = p2, _date_from_rev(r2), r2
+      if p1:
+        temp1 = temp_checkout(self, p1, r1)
+      else:
+        temp1 = '/dev/null'
+      if p2:
+        temp2 = temp_checkout(self, p2, r2)
+      else:
+        temp2 = '/dev/null'
       return vclib._diff_fp(temp1, temp2, info1, info2, self.diff_cmd, args)
     except core.SubversionException, e:
-      if e.apr_err == vclib.svn.core.SVN_ERR_FS_NOT_FOUND:
+      if e.apr_err == core.SVN_ERR_FS_NOT_FOUND:
         raise vclib.InvalidRevision
       raise
-
   def isexecutable(self, path_parts, rev):
     props = self.itemprops(path_parts, rev) # does authz-check
     return props.has_key(core.SVN_PROP_EXECUTABLE)
Index: viewvc.org/trunk/lib/vclib/ccvs/ccvs.py
===================================================================
--- viewvc.org/trunk/lib/vclib/ccvs/ccvs.py	2009/01/15 09:11:40	98
+++ viewvc.org/trunk/lib/vclib/ccvs/ccvs.py	2009/01/15 10:00:38	99
@@ -121,23 +121,32 @@
     return filtered_revs
 
   def rawdiff(self, path_parts1, rev1, path_parts2, rev2, type, options={}):
-    if self.itemtype(path_parts1, rev1) != vclib.FILE:  # does auth-check
+    if path_parts1 and self.itemtype(path_parts1, rev1) != vclib.FILE:  # does auth-check
       raise vclib.Error("Path '%s' is not a file."
                         % (string.join(path_parts1, "/")))
-    if self.itemtype(path_parts2, rev2) != vclib.FILE:  # does auth-check
+    if path_parts2 and self.itemtype(path_parts2, rev2) != vclib.FILE:  # does auth-check
       raise vclib.Error("Path '%s' is not a file."
                         % (string.join(path_parts2, "/")))
-    
-    temp1 = tempfile.mktemp()
-    open(temp1, 'wb').write(self.openfile(path_parts1, rev1)[0].getvalue())
-    temp2 = tempfile.mktemp()
-    open(temp2, 'wb').write(self.openfile(path_parts2, rev2)[0].getvalue())
+    if not path_parts1 and not path_parts2:
+      raise vclib.Error("Nothing to diff.")
 
-    r1 = self.itemlog(path_parts1, rev1, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
-    r2 = self.itemlog(path_parts2, rev2, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
+    if path_parts1:
+      temp1 = tempfile.mktemp()
+      open(temp1, 'wb').write(self.openfile(path_parts1, rev1)[0].getvalue())
+      r1 = self.itemlog(path_parts1, rev1, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
+      info1 = (self.rcsfile(path_parts1, root=1, v=0), r1.date, r1.string)
+    else:
+      temp1 = '/dev/null'
+      info1 = ('/dev/null', '', '')
 
-    info1 = (self.rcsfile(path_parts1, root=1, v=0), r1.date, r1.string)
-    info2 = (self.rcsfile(path_parts2, root=1, v=0), r2.date, r2.string)
+    if path_parts2:
+      temp2 = tempfile.mktemp()
+      open(temp2, 'wb').write(self.openfile(path_parts2, rev2)[0].getvalue())
+      r2 = self.itemlog(path_parts2, rev2, vclib.SORTBY_DEFAULT, 0, 0, {})[-1]
+      info2 = (self.rcsfile(path_parts2, root=1, v=0), r2.date, r2.string)
+    else:
+      temp2 = '/dev/null'
+      info2 = ('/dev/null', '', '')
 
     diff_args = vclib._diff_args(type, options)
repository-type-selection.diff (application/octet-stream, 5.3 KB)
Index: viewvc.org/trunk/templates/query_results.ezt
===================================================================
--- viewvc.org/trunk/templates/query_results.ezt	2009/02/03 12:03:05	120
+++ viewvc.org/trunk/templates/query_results.ezt	2009/02/03 12:19:33	121
@@ -7,7 +7,14 @@
 
 <p><strong>[english_query]</strong></p>
 [# <!-- {sql} --> ]
-<p><a href="[queryform_href]">Modify query</a></p>
+<p><a href="[queryform_href]">Modify query</a>[if-any repos_root] [else]
+    [if-any repos_type]
+    [is repos_type "cvs"]| <a href="[querysvn_href]">Look only in SVN</a> | <a href="[queryall_href]">Look in all repos</a> [end]
+    [is repos_type "svn"]| <a href="[querycvs_href]">Look only in CVS</a> | <a href="[queryall_href]">Look in all repos</a> [end]
+    [else]
+    | <a href="[querysvn_href]">Look only in SVN</a> | <a href="[querycvs_href]">Look only in CVS</a>
+    [end]
+[end]</p>
 <p><a href="[backout_href]">Show commands which could be used to back out these changes</a></p>
 <p>
     <a href="[patch_href]">Show a patch built from these changes</a>
Index: viewvc.org/trunk/lib/viewvc.py
===================================================================
--- viewvc.org/trunk/lib/viewvc.py	2009/02/02 12:39:26	118
+++ viewvc.org/trunk/lib/viewvc.py	2009/02/03 12:19:33	121
@@ -711,6 +711,7 @@
   # for query
   'repos'         : _validate_regex,
   'repos_match'   : _re_validate_alpha,
+  'repos_type'    : None,
   'branch'        : _validate_regex,
   'branch_match'  : _re_validate_alpha,
   'dir'           : None,
@@ -3400,6 +3401,7 @@
   # default values ...
   data['repos'] = request.query_dict.get('repos', '')
   data['repos_match'] = request.query_dict.get('repos_match', 'exact')
+  data['repos_type'] = request.query_dict.get('repos_type', '')
   if not data['repos']:
     data['repos'] = request.rootpath
     data['repos_match'] = 'exact'
@@ -3846,7 +3848,9 @@
 
   # get form data
   repos_root = request.query_dict.get('repos', '')
+  repos_root_t = repos_root
   repos_match = request.query_dict.get('repos_match', 'exact')
+  repos_type = request.query_dict.get('repos_type', '')
   branch = request.query_dict.get('branch', '')
   branch_match = request.query_dict.get('branch_match', 'exact')
   dir = request.query_dict.get('dir', '')
@@ -3893,6 +3897,15 @@
   query = cvsdb.CreateCheckinQuery()
   if repos_root:
     query.SetRepository(repos_root, repos_match)
+  elif repos_type == 'cvs' or repos_type == 'svn':
+    # select only CVS/SVN repositories
+    all = list_roots(request)
+    re = []
+    for r in all.keys():
+      if all[r][1] == repos_type:
+        re.append('^'+all[r][0]+'$')
+    re = '|'.join(re)
+    query.SetRepository(re, 'regex')
   # treat "HEAD" specially ...
   if branch_match == 'exact' and branch == 'HEAD':
     query.SetBranch('')
@@ -4006,14 +4019,21 @@
   # backout link
   params = request.query_dict.copy()
   params['format'] = 'backout'
-  backout_href = request.get_url(params=params,
-                                 escape=1)
+  backout_href = request.get_url(params=params, escape=1)
 
   # patch link
   params = request.query_dict.copy()
   params['format'] = 'patch'
-  patch_href = request.get_url(params=params,
-                                 escape=1)
+  patch_href = request.get_url(params=params, escape=1)
+
+  # look only in... links
+  params = request.query_dict.copy()
+  params['repos_type'] = 'cvs'
+  lookcvs_href = request.get_url(params=params, escape=1)
+  params['repos_type'] = 'svn'
+  looksvn_href = request.get_url(params=params, escape=1)
+  params['repos_type'] = ''
+  lookall_href = request.get_url(params=params, escape=1)
 
   # link to zero limit_changes value
   params = request.query_dict.copy()
@@ -4036,11 +4056,16 @@
   data = common_template_data(request)
   data.update({
     'sql': sql,
+    'repos_root': repos_root_t,
+    'repos_type': repos_type,
     'english_query': english_query(request),
     'queryform_href': request.get_url(view_func=view_queryform, escape=1),
+    'querycvs_href': lookcvs_href,
+    'querysvn_href': looksvn_href,
+    'queryall_href': lookall_href,
     'backout_href': backout_href,
-    'patch_href' : patch_href,
-    'patch_unsecure' : ezt.boolean(query_is_unsecure_patch(request, commits)),
+    'patch_href': patch_href,
+    'patch_unsecure': ezt.boolean(query_is_unsecure_patch(request, commits)),
     'plus_count': plus_count,
     'minus_count': minus_count,
     'show_branch': show_branch,
Index: viewvc.org/trunk/templates/query_form.ezt
===================================================================
--- viewvc.org/trunk/templates/query_form.ezt	2009/02/02 12:39:26	118
+++ viewvc.org/trunk/templates/query_form.ezt	2009/02/02 17:20:11	119
@@ -17,7 +17,13 @@
   <tr>
     <th style="text-align:right;vertical-align:top;">Repository:</th>
     <td>
-      <input type="text" name="repos" value="[repos]" /><br />
+      <input type="text" name="repos" value="[repos]" /> &nbsp; <b>Repository type:</b> &nbsp;
+      <select name="repos_type">
+        <option value="">Any</option>
+        <option value="cvs" [is repos_type "cvs"]selected[end]>CVS</option>
+        <option value="svn" [is repos_type "svn"]selected[end]>Subversion</option>
+      </select>
+      <br />
       <label for="repos_match_exact">
         <input type="radio" name="repos_match" id="repos_match_exact"
            value="exact" [is repos_match "exact"]checked="checked"[end] />
search-results-check-commit-access.diff (application/octet-stream, 5.8 KB)
Index: viewvc.org/trunk/lib/cvsdb.py
===================================================================
--- viewvc.org/trunk/lib/cvsdb.py	2008/11/17 17:04:16	26
+++ viewvc.org/trunk/lib/cvsdb.py	2008/11/18 13:36:33	27
@@ -30,7 +30,7 @@
 ## complient database interface
 
 class CheckinDatabase:
-    def __init__(self, host, port, socket, user, passwd, database, row_limit, min_relevance):
+    def __init__(self, host, port, socket, user, passwd, database, row_limit, min_relevance, authorizer = None):
         self._host = host
         self._port = port
         self._socket = socket
@@ -39,7 +39,7 @@
         self._database = database
         self._row_limit = row_limit
         self._min_relevance = min_relevance
-        self.text_query = ""
+        self.authorizer = authorizer
 
         ## database lookup caches
         self._get_cache = {}
@@ -341,8 +341,16 @@
         return "(%s)" % (string.join(sqlList, " OR "))
 
     def CreateSQLQueryString(self, query):
-        fields = ["checkins.*"]
-        tableList = [("checkins", None)]
+        fields = [
+            "checkins.*",
+            "repositories.repository AS repository_name",
+            "dirs.dir AS dir_name",
+            "files.file AS file_name"]
+        tableList = [
+            ("checkins", None),
+            ("repositories","(checkins.repositoryid=repositories.id)"),
+            ("dirs", "(checkins.dirid=dirs.id)"),
+            ("files", "(checkins.fileid=files.id)")]
         condList = []
         
         if len(query.text_query):
@@ -354,8 +362,6 @@
             fields.append("'' AS relevance")
 
         if len(query.repository_list):
-            tableList.append(("repositories",
-                              "(checkins.repositoryid=repositories.id)"))
             temp = self.SQLQueryListString("repositories.repository",
                                            query.repository_list)
             condList.append(temp)
@@ -367,7 +373,6 @@
             condList.append(temp)
 
         if len(query.directory_list):
-            tableList.append(("dirs", "(checkins.dirid=dirs.id)"))
             temp = self.SQLQueryListString("dirs.dir", query.directory_list)
             condList.append(temp)
             
@@ -434,6 +439,15 @@
             fields, tables, conditions, order_by, limit)
 
         return sql
+    
+    def check_commit_access(self, repos, dir, file, rev):
+        if self.authorizer:
+            rootname = repos.split('/')
+            rootname = rootname.pop()
+            path_parts = dir.split('/')
+            path_parts.append(file)
+            return self.authorizer.check_path_access(rootname, path_parts, vclib.FILE, rev)
+        return True
 
     def RunQuery(self, query):
         sql = self.CreateSQLQueryString(query)
@@ -447,7 +461,11 @@
 
             (dbType, dbCI_When, dbAuthorID, dbRepositoryID, dbDirID,
              dbFileID, dbRevision, dbStickyTag, dbBranchID, dbAddedLines,
-             dbRemovedLines, dbDescID, dbRelevance) = row
+             dbRemovedLines, dbDescID, dbRepositoryName, dbDirName,
+             dbFileName, dbRelevance) = row
+
+            if not self.check_commit_access(dbRepositoryName, dbDirName, dbFileName, dbRevision):
+                continue
 
             commit = LazyCommit(self)
             if dbType == 'Add':
@@ -456,6 +474,7 @@
                 commit.SetTypeRemove()
             else:
                 commit.SetTypeChange()
+
             commit.SetTime(dbi.TicksFromDateTime(dbCI_When))
             commit.SetFileID(dbFileID)
             commit.SetDirectoryID(dbDirID)
@@ -806,7 +825,7 @@
 def CreateCheckinQuery():
     return CheckinDatabaseQuery()
 
-def ConnectDatabase(cfg, readonly=0):
+def ConnectDatabase(cfg, authorizer=None, readonly=0):
     if readonly:
         user = cfg.cvsdb.readonly_user
         passwd = cfg.cvsdb.readonly_passwd
@@ -814,12 +833,13 @@
         user = cfg.cvsdb.user
         passwd = cfg.cvsdb.passwd
     db = CheckinDatabase(cfg.cvsdb.host, cfg.cvsdb.port, cfg.cvsdb.socket, user, passwd,
-                         cfg.cvsdb.database_name, cfg.cvsdb.row_limit, cfg.cvsdb.fulltext_min_relevance)
+                         cfg.cvsdb.database_name, cfg.cvsdb.row_limit, cfg.cvsdb.fulltext_min_relevance,
+                         authorizer)
     db.Connect()
     return db
 
-def ConnectDatabaseReadOnly(cfg):
-    return ConnectDatabase(cfg, 1)
+def ConnectDatabaseReadOnly(cfg, authorizer):
+    return ConnectDatabase(cfg, authorizer, 1)
 
 def GetCommitListFromRCSFile(repository, path_parts, revision=None):
     commit_list = []
Index: viewvc.org/trunk/lib/viewvc.py
===================================================================
--- viewvc.org/trunk/lib/viewvc.py	2008/11/17 17:04:16	26
+++ viewvc.org/trunk/lib/viewvc.py	2008/11/18 13:36:33	27
@@ -3312,7 +3312,7 @@
     if request.cfg.cvsdb.check_database_for_root:
       global cvsdb
       import cvsdb
-      db = cvsdb.ConnectDatabaseReadOnly(request.cfg)
+      db = cvsdb.ConnectDatabaseReadOnly(request.cfg, request.auth)
       repos_root, repos_dir = cvsdb.FindRepository(db, request.rootpath)
       if repos_root:
         return 1
@@ -3684,7 +3684,7 @@
   global cvsdb
   import cvsdb
 
-  db = cvsdb.ConnectDatabaseReadOnly(cfg)
+  db = cvsdb.ConnectDatabaseReadOnly(cfg, request.auth)
   repos_root, repos_dir = cvsdb.FindRepository(db, request.rootpath)
   if not repos_root:
     raise debug.ViewVCException(
Index: viewvc.org/trunk/lib/query.py
===================================================================
--- viewvc.org/trunk/lib/query.py	2008/11/18 13:36:33	27
+++ viewvc.org/trunk/lib/query.py	2008/11/18 13:49:12	28
@@ -422,7 +422,7 @@
     form = server.FieldStorage()
     form_data = FormData(form)
 
-    db = cvsdb.ConnectDatabaseReadOnly(cfg)
+    db = cvsdb.ConnectDatabaseReadOnly(cfg, None)
     if form_data.valid:
         commits = run_query(server, cfg, db, form_data, viewvc_link)
         query = None
utf8-encoding-guess.diff (application/octet-stream, 10.9 KB)
Index: viewvc.org/trunk/lib/cvsdb.py
===================================================================
--- viewvc.org/trunk/lib/cvsdb.py	2008/11/13 14:56:58	20
+++ viewvc.org/trunk/lib/cvsdb.py	2009/02/11 12:57:01	126
@@ -29,14 +29,29 @@
 ## defined to actually be complete; it should run well off of any DBI 2.0
 ## complient database interface
 
+encs = [ "utf-8", "cp1251", "iso-8859-1" ]
+
+def utf8string(value):
+    for e in encs:
+        try:
+            value = value.decode(e)
+            break
+        except: pass
+    return value.encode("utf-8")
+
+def setencs(e):
+    global encs
+    encs = e
+
 class CheckinDatabase:
-    def __init__(self, host, port, user, passwd, database, row_limit):
+    def __init__(self, host, port, socket, user, passwd, database, row_limit):
         self._host = host
         self._port = port
+        self._socket = socket
         self._user = user
         self._passwd = passwd
         self._database = database
         self._row_limit = row_limit
 
         ## database lookup caches
         self._get_cache = {}
@@ -45,14 +62,16 @@
 
     def Connect(self):
         self.db = dbi.connect(
-            self._host, self._port, self._user, self._passwd, self._database)
+            self._host, self._port, self._socket, self._user, self._passwd, self._database)
         cursor = self.db.cursor()
         cursor.execute("SET AUTOCOMMIT=1")
 
     def sql_get_id(self, table, column, value, auto_set):
+        value = utf8string(value)
+
         sql = "SELECT id FROM %s WHERE %s=%%s" % (table, column)
         sql_args = (value, )
         
         cursor = self.db.cursor()
         cursor.execute(sql, sql_args)
         try:
@@ -53,9 +67,11 @@
         cursor.execute("SET AUTOCOMMIT=1")
 
     def sql_get_id(self, table, column, value, auto_set):
+        value = utf8string(value)
+
         sql = "SELECT id FROM %s WHERE %s=%%s" % (table, column)
         sql_args = (value, )
         
         cursor = self.db.cursor()
         cursor.execute(sql, sql_args)
         try:
@@ -184,12 +200,13 @@
         return self.get_list("repositories", repository)
 
     def SQLGetDescriptionID(self, description, auto_set = 1):
+        description = utf8string(description)
         ## lame string hash, blame Netscape -JMP
         hash = len(description)
 
         sql = "SELECT id FROM descs WHERE hash=%s AND description=%s"
         sql_args = (hash, description)
         
         cursor = self.db.cursor()
         cursor.execute(sql, sql_args)
         try:
Index: viewvc.org/trunk/lib/vclib/ccvs/bincvs.py
===================================================================
--- viewvc.org/trunk/lib/vclib/ccvs/bincvs.py	2008/12/02 14:55:27	40
+++ viewvc.org/trunk/lib/vclib/ccvs/bincvs.py	2008/12/12 12:36:39	61
@@ -21,6 +21,7 @@
 import string
 import re
 import time
+import cvsdb
 
 # ViewVC libs
 import compat
@@ -322,7 +323,7 @@
     if self.itemtype(path_parts, rev) != vclib.FILE:  # does auth-check
       raise vclib.Error("Path '%s' is not a file."
                         % (string.join(path_parts, "/")))
-                        
+
     from vclib.ccvs import blame
     source = blame.BlameSource(self.rcsfile(path_parts, 1), rev)
     return source, source.revision
@@ -833,6 +834,8 @@
       raise ValueError, 'invalid year'
   date = compat.timegm(tm)
 
+  log = cvsdb.utf8string(log)
+
   return Revision(rev, date,
                   # author, state, lines changed
                   match.group(2), match.group(3) == "dead", match.group(5),
Index: viewvc.org/trunk/lib/vclib/ccvs/blame.py
===================================================================
--- viewvc.org/trunk/lib/vclib/ccvs/blame.py	2008/12/16 12:11:16	67
+++ viewvc.org/trunk/lib/vclib/ccvs/blame.py	2008/12/16 14:29:30	68
@@ -32,6 +32,7 @@
 import math
 import rcsparse
 import vclib
+import cvsdb
 
 class CVSParser(rcsparse.Sink):
   # Precompiled regular expressions
@@ -446,7 +447,7 @@
     prev_rev = self.parser.prev_revision.get(rev)
     line_number = idx + 1
     author = self.parser.revision_author[rev]
-    thisline = self.lines[idx]
+    thisline = cvsdb.utf8string(self.lines[idx])
     ### TODO:  Put a real date in here.
     item = vclib.Annotation(thisline, line_number, rev, prev_rev, author, None)
     self.last = item
Index: viewvc.org/trunk/lib/config.py
===================================================================
--- viewvc.org/trunk/lib/config.py	2008/11/27 15:20:35	34
+++ viewvc.org/trunk/lib/config.py	2009/02/11 12:57:01	126
@@ -22,6 +22,7 @@
 import vclib
 import vclib.ccvs
 import vclib.svn
+import cvsdb
 
 #########################################################################
 #
@@ -64,6 +65,7 @@
     if rootname:
       self._process_root_options(self.parser, rootname)
     self.expand_root_parents()
+    cvsdb.setencs(self.options.encodings.split(':'))
 
   def expand_root_parents(self):
     """Expand the configured root parents into individual roots."""
@@ -278,6 +280,9 @@
     self.options.use_re_search = 0
     self.options.use_pagesize = 0
     self.options.limit_changes = 100
+    self.options.cvs_ondisk_charset = 'cp1251'
+    self.options.binary_mime_re = '^(?!text/|.*\Wxml)'
+    self.options.encodings = 'utf-8:cp1251:iso-8859-1'
 
     self.templates.diff = None
     self.templates.directory = None
Index: viewvc.org/trunk/lib/viewvc.py
===================================================================
--- viewvc.org/trunk/lib/viewvc.py	2008/11/27 15:20:35	34
+++ viewvc.org/trunk/lib/viewvc.py	2008/12/12 16:19:41	62
@@ -41,6 +41,7 @@
 import accept
 import compat
 import config
+import cvsdb
 import ezt
 import popen
 import sapi
@@ -935,7 +936,10 @@
     path_parts.append(part)
     is_last = len(path_parts) == len(request.path_parts)
 
-    item = _item(name=part, href=None)
+    if request.roottype == 'cvs':
+      item = _item(name=cvsdb.utf8string(part), href=None)
+    else:
+      item = _item(name=part, href=None)
 
     if not is_last or (is_dir and request.view_func is not view_directory):
       item.href = request.get_url(view_func=view_directory,
@@ -1158,6 +1188,10 @@
 
 def common_template_data(request, revision=None, mime_type=None):
   cfg = request.cfg
+  where = request.where
+  if request.roottype == 'cvs':
+    where = cvsdb.utf8string(where)
+  where = request.server.escape(where)
   data = {
     'cfg' : cfg,
     'vsn' : __version__,
@@ -1166,7 +1200,7 @@
                 and request.script_name + '/' + docroot_magic_path \
                 or cfg.options.docroot,
     'username' : request.username,
-    'where' : request.server.escape(request.where),
+    'where'    : where,
     'roottype' : request.roottype,
     'rootname' : request.rootname \
                  and request.server.escape(request.rootname) or None,
@@ -1362,6 +1397,7 @@
         line = fp.readline()
         if not line:
           break
+        line = cvsdb.utf8string(line)
         line_no = line_no + 1
         item = vclib.Annotation(cgi.escape(line), line_no,
                                 None, None, None, None)
@@ -1756,8 +1815,8 @@
       row.short_log = format_log(file.log, cfg)
       row.log = htmlify(file.log, cfg.options.mangle_email_addresses)
     row.lockinfo = file.lockinfo
-    row.anchor = request.server.escape(file.name)
-    row.name = request.server.escape(file.name)
+    row.name = request.server.escape(cvsdb.utf8string(file.name))
+    row.anchor = row.name
     row.pathtype = (file.kind == vclib.FILE and 'file') or \
                    (file.kind == vclib.DIR and 'dir')
     row.errors = file.errors
@@ -2106,7 +2165,10 @@
     entry.ago = None
     if rev.date is not None:
       entry.ago = html_time(request, rev.date, 1)
-    entry.log = htmlify(rev.log or "", cfg.options.mangle_email_addresses)
+    entry.log = rev.log or ""
+    if cvs:
+      entry.log = cvsdb.utf8string(entry.log)
+    entry.log = htmlify(entry.log, cfg.options.mangle_email_addresses)
     entry.size = rev.size
     entry.lockinfo = rev.lockinfo
     entry.branch_point = None
@@ -2605,7 +2667,8 @@
 
     diff_code = line[0]
     output = self._format_text(line[1:])
-    
+    output = cvsdb.utf8string(output)
+
     if diff_code == '+':
       if self.state == 'dump':
         self.line_number = self.line_number + 1
@@ -3493,14 +3562,19 @@
 
     # Check path access (since the commits database logic bypasses the
     # vclib layer and, thus, the vcauth stuff that layer uses).
+    if request.roottype == 'cvs':
+      try: where = unicode(where,'utf-8')
+      except: pass
+      try: where = where.encode(cfg.options.cvs_ondisk_charset)
+      except: pass
     path_parts = _path_parts(where)
     if path_parts:
       # Skip files in CVSROOT if asked to hide such.
       if cfg.options.hide_cvsroot \
          and is_cvsroot_path(request.roottype, path_parts):
         found_unreadable = 1
         continue
       
       # We have to do a rare authz check here because this data comes
       # from the CVSdb, not from the vclib providers.
       #
Index: viewvc.org/trunk/lib/vclib/ccvs/ccvs.py
===================================================================
--- viewvc.org/trunk/lib/vclib/ccvs/ccvs.py	2008/12/11 16:02:02	60
+++ viewvc.org/trunk/lib/vclib/ccvs/ccvs.py	2008/12/12 12:36:39	61
@@ -19,6 +19,7 @@
 import vclib
 import rcsparse
 import blame
+import cvsdb
 
 ### The functionality shared with bincvs should probably be moved to a
 ### separate module
@@ -66,6 +67,7 @@
         entry.path = path
         try:
           rcsparse.parse(open(path, 'rb'), InfoSink(entry, rev, alltags))
+          entry.log = cvsdb.utf8string(entry.log)
         except IOError, e:
           entry.errors.append("rcsparse error: %s" % e)
         except RuntimeError, e:
Index: viewvc.org/trunk/lib/dbi.py
===================================================================
--- viewvc.org/trunk/lib/dbi.py	2008/11/13 16:51:57	21
+++ viewvc.org/trunk/lib/dbi.py	2008/11/14 14:53:48	22
@@ -59,5 +59,13 @@
   else:
     return time.mktime(t[:8] + (-1,))
     
-def connect(host, port, socket, user, passwd, db):
-    return MySQLdb.connect(host=host, port=port, unix_socket=socket, user=user, passwd=passwd, db=db)
+def connect(host, port, socket, user, passwd, db, charset = 'utf8'):
+    return MySQLdb.connect(
+        host = host,
+        port = port,
+        unix_socket = socket,
+        user = user,
+        passwd = passwd,
+        db = db,
+        charset = charset,
+        use_unicode = charset == 'utf8')
Index: viewvc.org/trunk/lib/ezt.py
===================================================================
--- viewvc.org/trunk/lib/ezt.py	2008/11/13 16:51:57	21
+++ viewvc.org/trunk/lib/ezt.py	2008/12/11 10:46:42	59
@@ -790,12 +790,18 @@
   """The format specifier is an unknown value."""
 
 def _raw_printer(ctx, s):
+  try: s = s.encode('utf-8')
+  except: pass
   ctx.fp.write(s)
-  
+
 def _html_printer(ctx, s):
+  try: s = s.encode('utf-8')
+  except: pass
   ctx.fp.write(cgi.escape(s))
 
 def _uri_printer(ctx, s):
+  try: s = s.encode('utf-8')
+  except: pass
   ctx.fp.write(urllib.quote(s))
 
 _printers = {