diff --git a/lib/config.py b/lib/config.py index 55248cd4..60d01c82 100644 --- a/lib/config.py +++ b/lib/config.py @@ -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 = 'No admin address has been configured' self.general.forbidden = () diff --git a/lib/vclib/svn/__init__.py b/lib/vclib/svn/__init__.py index 31563668..199225a2 100644 --- a/lib/vclib/svn/__init__.py +++ b/lib/vclib/svn/__init__.py @@ -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) diff --git a/lib/viewcvs.py b/lib/viewcvs.py index 2210f0d2..dc4e9fd0 100644 --- a/lib/viewcvs.py +++ b/lib/viewcvs.py @@ -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( diff --git a/templates/log.ezt b/templates/log.ezt index ed2129b9..2c712ea5 100644 --- a/templates/log.ezt +++ b/templates/log.ezt @@ -34,7 +34,7 @@ (download) [if-any entries.download_text_href](as text)[end] [# if you don't want to allow annotation, then remove this line] - [is roottype "svn"][else](annotate)[end] + (annotate) [# if you don't want to allow select for diffs then remove this section] [is entries.rev rev_selected] diff --git a/templates/log_table.ezt b/templates/log_table.ezt index a5b22723..cb1a1cec 100644 --- a/templates/log_table.ezt +++ b/templates/log_table.ezt @@ -46,7 +46,7 @@ [if-any entries.download_text_href]As text
[end] [# if you don't want to allow annotation, then remove this line] - [is roottype "svn"][else]Annotate
[end] + Annotate
[is entries.state "dead"] diff --git a/viewcvs.conf.dist b/viewcvs.conf.dist index 86bd463b..27d747e2 100644 --- a/viewcvs.conf.dist +++ b/viewcvs.conf.dist @@ -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.