Expose annotation support for local Subversion repositories. Patch

largely by Stefan Haller <haller@ableton.com>, at least in the
approach used.

* viewcvs/viewcvs.conf.dist
  (svn_path): New configuration variable.

* viewcvs/lib/config.py
  (Config.set_defaults): Populate a new svn_path config member.

* viewcvs/lib/viewcvs.py
  (Request.__init__): Tweak the way Subversion's vclib.Repository
    objects are opened to have different codepaths for the 'svn' and
    'svn_ra' modules, and pass the new cfg.general.svn_path option to
    the 'svn' module's SubversionRepository.

* viewcvs/lib/vclib/svn/__init__.py
  (BlameSource, BlameSequencingError, _item): New.
  (SubversionRepository.__init__): Add 'svn_path' parameter.
    Calculate and store a path for the 'svn' command-line client binary.
  (SubversionRepository.annotate): Really implement annotation.

* viewcvs/templates/log.ezt,
* viewcvs/templates/log_table.ezt
  Enable annotate links for Subversion, too!


git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@1100 8cb11bc2-c004-0410-86c3-e597b4017df7
remotes/tags/1.0.0-rc1
cmpilato 2005-09-23 20:15:08 +00:00
parent a5ad1e5f41
commit ac88e349ca
6 changed files with 67 additions and 11 deletions

View File

@ -157,6 +157,7 @@ class Config:
else:
self.general.cvsnt_exe_path = None
self.general.use_rcsparse = 0
self.general.svn_path = ''
self.general.mime_types_file = ''
self.general.address = '<a href="mailto:user@insert.your.domain.here">No admin address has been configured</a>'
self.general.forbidden = ()

View File

@ -24,6 +24,7 @@ import string
import cStringIO
import signal
import time
import popen
from svn import fs, repos, core, delta
@ -346,9 +347,46 @@ class FileContentsPipe:
def eof(self):
return self._eof
class BlameSource:
def __init__(self, svn_client_path, rootpath, fs_path, rev):
self.idx = -1
self.line_number = 1
self.last = None
rootpath = os.path.abspath(rootpath)
url = 'file://' + string.join([rootpath, fs_path], "/")
self.fp = popen.popen(svn_client_path,
('blame', "%s@%d" % (url, int(rev))), 'rb', 1)
def __getitem__(self, idx):
if idx == self.idx:
return self.last
if self.fp.eof():
raise IndexError("No more annotations")
if idx != self.idx + 1:
raise BlameSequencingError()
line = self.fp.readline()
if not line:
raise IndexError("No more annotations")
rev, author = line[:17].split(None, 1)
text = line[18:]
rev = int(rev)
if rev > 1:
prev_rev = rev - 1
item = _item(text=text, line_number=idx+1, rev=rev,
prev_rev=prev_rev, author=author, date=None)
self.last = item
self.idx = idx
return item
class BlameSequencingError(Exception):
pass
class SubversionRepository(vclib.Repository):
def __init__(self, name, rootpath, rev=None):
def __init__(self, name, rootpath, svn_path, rev=None):
if not os.path.isdir(rootpath):
raise vclib.ReposNotFound(name)
@ -358,6 +396,7 @@ class SubversionRepository(vclib.Repository):
self.rootpath = rootpath
self.name = name
self.rev = rev
self.svn_client_path = os.path.normpath(os.path.join(svn_path, 'svn'))
# Register a handler for SIGTERM so we can have a chance to
# cleanup. If ViewCVS takes too long to start generating CGI
@ -464,8 +503,12 @@ class SubversionRepository(vclib.Repository):
return revs
def annotate(self, path_parts, rev=None):
raise NotImplementedError, \
"No support for Subversion annotation yet"
if not rev:
rev = self.rev
path = self._getpath(path_parts)
revision = str(_get_last_history_rev(self, path, self.scratch_pool))
source = BlameSource(self.svn_client_path, self.rootpath, path, rev)
return source, revision
def rawdiff(self, path1, rev1, path2, rev2, type, options={}):
"""see vclib.Repository.rawdiff docstring
@ -493,3 +536,7 @@ class SubversionRepository(vclib.Repository):
def _getpath(self, path_parts):
return string.join(path_parts, '/')
class _item:
def __init__(self, **kw):
vars(self).update(kw)

View File

@ -245,20 +245,25 @@ class Request:
elif cfg.general.svn_roots.has_key(self.rootname):
self.rootpath = cfg.general.svn_roots[self.rootname]
try:
rev = None
if self.query_dict.has_key('rev') \
and self.query_dict['rev'] != 'HEAD':
rev = int(self.query_dict['rev'])
if re.match(_re_rewrite_url, self.rootpath):
# If the rootpath is a URL, we'll use the svn_ra module, but
# lie about its name.
import vclib.svn_ra
vclib.svn = vclib.svn_ra
self.repos = vclib.svn.SubversionRepository(self.rootname,
self.rootpath,
rev)
else:
self.rootpath = os.path.normpath(self.rootpath)
import vclib.svn
rev = None
if self.query_dict.has_key('rev') \
and self.query_dict['rev'] != 'HEAD':
rev = int(self.query_dict['rev'])
self.repos = vclib.svn.SubversionRepository(self.rootname,
self.rootpath, rev)
self.repos = vclib.svn.SubversionRepository(self.rootname,
self.rootpath,
cfg.general.svn_path,
rev)
self.roottype = 'svn'
except vclib.ReposNotFound:
raise debug.ViewCVSException(

View File

@ -34,7 +34,7 @@
(<a href="[entries.download_href]">download</a>)
[if-any entries.download_text_href](<a href="[entries.download_text_href]">as text</a>)[end]
[# if you don't want to allow annotation, then remove this line]
[is roottype "svn"][else](<a href="[entries.annotate_href]">annotate</a>)[end]
(<a href="[entries.annotate_href]">annotate</a>)
[# if you don't want to allow select for diffs then remove this section]
[is entries.rev rev_selected]

View File

@ -46,7 +46,7 @@
[if-any entries.download_text_href]<a href="[entries.download_text_href]"><b>As text</b></a><br>[end]
[# if you don't want to allow annotation, then remove this line]
[is roottype "svn"][else]<a href="[entries.annotate_href]"><b>Annotate</b></a><br>[end]
<a href="[entries.annotate_href]"><b>Annotate</b></a><br>
</td>
<td>
[is entries.state "dead"]

View File

@ -124,6 +124,9 @@ default_root = cvs
# of invoking rcs utilities. this feature is experimental
use_rcsparse = 0
# uncomment if the svn command-line utilities are not on the standard path
#svn_path = /usr/bin/
#
# This is a pathname to a MIME types file to help viewcvs to guess the
# correct MIME type on checkout.