From 5204e266667931f759ca3da9c60619ca4c040439 Mon Sep 17 00:00:00 2001 From: rey4 Date: Thu, 3 Nov 2005 13:23:42 +0000 Subject: [PATCH] Change vclib interface to accept 'rev' parameters wherever it accepts path parameters so it is possible to use a single Repository object to request information about any path at any revision. Note: I wasn't able to test the svn_ra.get_location function because I only have subversion 1.2.3. * lib/vclib/__init__.py (Repository.itemtype, Repository.openfile, Repository.listdir, Repository.dirlogs, Repository.itemlog, Repository.annotate): add rev arguments, update docstrings * lib/vclib/bincvs/__init__.py (CVSRepository.itemtype, CVSRepository.listdir, BinCVSRepository.openfile, BinCVSRepository.dirlogs, BinCVSRepository.itemlog): same * lib/vclib/ccvs/__init__.py (CCVSRepository.dirlogs, CCVSRepository.itemlog): same * lib/vclib/svn/__init__.py (SubversionRepository.__init__): remove rev argument (get_location, created_rev, _get_history, get_revision_info, _fetch_log, _get_last_history_rev, get_logs, do_diff, SubversionRepository.itemtype, SubversionRepository.openfile, SubversionRepository.listdir, SubversionRepository.dirlogs, SubversionRepository.itemlog, SubversionRepository.annotate): add rev arguments, use new _getrev and _getroot functions to handle all revision string parsing and revision_root creation (SubversionRepository._getrev, SubversionRepository._getroot): new functions * lib/vclib/svn_ra/__init__.py (_rev2optrev): accept integers instead of strings, and eliminate head/unspecified revision handling which is already taken care of by _getrev (SubversionRepository.__init__): remove rev argument (get_location, created_rev, get_revision_info, get_logs, SubversionRepository.itemtype, SubversionRepository.openfile, SubversionRepository.listdir, SubversionRepository.dirlogs, SubversionRepository.itemlog, SubversionRepository.annotate, SubversionRepository.rawdiff, SubversionRepository._get_dirents): add rev arguments, use new _getrev function to handle all revision string parsing (SubversionRepository._getrev): new function * lib/blame.py (link_includes): update call to repos.itemtype * tools/cvsdbadmin (RecurseUpdate): update call to repos.listdir * lib/viewcvs.py (Request.run_viewcvs): update calls to SubversionRepository constructors and _strip_suffix (_strip_suffix): add rev parameter, remove redundant where parameter (_repos_pathtype, view_markup, search_files, _get_diff_path_parts, generate_tarball): update calls to vclib methods (nav_path, view_directory, view_log, setup_diff, download_tarball, view_revision): explicitly read 'rev' query param instead of relying on SubversionRepository.rev, and update vclib calls git-svn-id: http://viewvc.tigris.org/svn/viewvc/trunk@1144 8cb11bc2-c004-0410-86c3-e597b4017df7 --- lib/blame.py | 2 +- lib/vclib/__init__.py | 25 +++--- lib/vclib/bincvs/__init__.py | 22 ++--- lib/vclib/ccvs/__init__.py | 16 ++-- lib/vclib/svn/__init__.py | 150 ++++++++++++++++++++++------------- lib/vclib/svn_ra/__init__.py | 116 +++++++++++++-------------- lib/viewcvs.py | 125 ++++++++++++++--------------- tools/cvsdbadmin | 2 +- 8 files changed, 248 insertions(+), 210 deletions(-) diff --git a/lib/blame.py b/lib/blame.py index 184b9255..d9a62402 100644 --- a/lib/blame.py +++ b/lib/blame.py @@ -55,7 +55,7 @@ def link_includes(text, repos, path_parts, include_url): include_path = path_parts[:depth] + [incfile] try: # will throw if path doesn't exist - if repos.itemtype(include_path) == vclib.FILE: + if repos.itemtype(include_path, None) == vclib.FILE: break except vclib.ItemNotFound: pass diff --git a/lib/vclib/__init__.py b/lib/vclib/__init__.py index 1dfbf135..dba4b781 100644 --- a/lib/vclib/__init__.py +++ b/lib/vclib/__init__.py @@ -34,17 +34,19 @@ SIDE_BY_SIDE = 3 class Repository: """Abstract class representing a repository.""" - def itemtype(self, path_parts): - """Return the type of the item (file or dir) at the given path. + def itemtype(self, path_parts, rev): + """Return the type of the item (file or dir) at the given path and revision The result will be vclib.DIR or vclib.FILE The path is specified as a list of components, relative to the root of the repository. e.g. ["subdir1", "subdir2", "filename"] + + rev is the revision of the item to check """ pass - def openfile(self, path_parts, rev=None): + def openfile(self, path_parts, rev): """Open a file object to read file contents at a given path and revision. The return value is a 2-tuple of containg the file object and revision @@ -53,10 +55,10 @@ class Repository: The path is specified as a list of components, relative to the root of the repository. e.g. ["subdir1", "subdir2", "filename"] - The revision number can be None to access a default revision. + rev is the revision of the file to check out """ - def listdir(self, path_parts, options): + def listdir(self, path_parts, rev, options): """Return list of files in a directory The result is a list of DirEntry objects @@ -64,10 +66,12 @@ class Repository: The path is specified as a list of components, relative to the root of the repository. e.g. ["subdir1", "subdir2", "filename"] + rev is the revision of the directory to list + options is a dictionary of implementation specific options """ - def dirlogs(self, path_parts, entries, options): + def dirlogs(self, path_parts, rev, entries, options): """Augment directory entries with log information New properties will be set on all of the DirEntry objects in the entries @@ -78,6 +82,9 @@ class Repository: The path is specified as a list of components, relative to the root of the repository. e.g. ["subdir1", "subdir2", "filename"] + rev is the revision of the directory listing and will effect which log + messages are returned + entries is a list of DirEntry objects returned from a previous call to the listdir() method @@ -92,9 +99,7 @@ class Repository: The path is specified as a list of components, relative to the root of the repository. e.g. ["subdir1", "subdir2", "filename"] - The rev parameter can be set to only retrieve log information for a - specified revision, or it can be None to return information on all - file revisions. + rev is the revision of the item to return information about options is a dictionary of implementation specific options """ @@ -114,7 +119,7 @@ class Repository: Return value is a python file object """ - def annotate(self, path_parts, rev=None): + def annotate(self, path_parts, rev): """Return a list of annotate file content lines and a revision. The annotated lines are an collection of objects with the diff --git a/lib/vclib/bincvs/__init__.py b/lib/vclib/bincvs/__init__.py index 05f2315e..ab03a509 100644 --- a/lib/vclib/bincvs/__init__.py +++ b/lib/vclib/bincvs/__init__.py @@ -38,7 +38,7 @@ class CVSRepository(vclib.Repository): self.name = name self.rootpath = rootpath - def itemtype(self, path_parts): + def itemtype(self, path_parts, rev): basepath = self._getpath(path_parts) if os.path.isdir(basepath): return vclib.DIR @@ -49,7 +49,7 @@ class CVSRepository(vclib.Repository): return vclib.FILE raise vclib.ItemNotFound(path_parts) - def listdir(self, path_parts, options): + def listdir(self, path_parts, rev, options): # Only RCS files (*,v) and subdirs are returned. data = [ ] @@ -124,7 +124,7 @@ class BinCVSRepository(CVSRepository): return revs[-1] return None - def openfile(self, path_parts, rev=None): + def openfile(self, path_parts, rev): if not rev or rev == 'HEAD' or rev == 'MAIN': rev_flag = '-p' else: @@ -177,29 +177,27 @@ class BinCVSRepository(CVSRepository): return fp, revision - def dirlogs(self, path_parts, entries, options): + def dirlogs(self, path_parts, rev, entries, options): """see vclib.Repository.dirlogs docstring + rev can be a tag name or None. if set only information from revisions + matching the tag will be retrieved + Option values recognized by this implementation: cvs_subdirs boolean. true to fetch logs of the most recently modified file in each subdirectory - cvs_dir_tag - string set to a tag name. if set only logs from revisions matching the - tag will be retrieved - Option values returned by this implementation: cvs_tags, cvs_branches lists of tag and branch names encountered in the directory """ subdirs = options.get('cvs_subdirs', 0) - tag = options.get('cvs_dir_tag') dirpath = self._getpath(path_parts) - alltags = _get_logs(self, dirpath, entries, tag, subdirs) + alltags = _get_logs(self, dirpath, entries, rev, subdirs) branches = options['cvs_branches'] = [] tags = options['cvs_tags'] = [] @@ -212,7 +210,9 @@ class BinCVSRepository(CVSRepository): def itemlog(self, path_parts, rev, options): """see vclib.Repository.itemlog docstring - rev parameter can be a revision number, branch number or tag name + rev parameter can be a revision number, a branch number, a tag name, + or None. If None, will return information about all revisions, otherwise, + will only return information about the specified revision or branch. Option values recognized by this implementation: diff --git a/lib/vclib/ccvs/__init__.py b/lib/vclib/ccvs/__init__.py index de7d2137..346737bf 100644 --- a/lib/vclib/ccvs/__init__.py +++ b/lib/vclib/ccvs/__init__.py @@ -35,26 +35,24 @@ from vclib.bincvs import CVSRepository, Revision, Tag, \ _file_log, _log_path class CCVSRepository(CVSRepository): - def dirlogs(self, path_parts, entries, options): + def dirlogs(self, path_parts, rev, entries, options): """see vclib.Repository.dirlogs docstring + rev can be a tag name or None. if set only information from revisions + matching the tag will be retrieved + Option values recognized by this implementation: cvs_subdirs boolean. true to fetch logs of the most recently modified file in each subdirectory - cvs_dir_tag - string set to a tag name. if set only logs from revisions matching the - tag will be retrieved - Option values returned by this implementation: cvs_tags, cvs_branches lists of tag and branch names encountered in the directory """ subdirs = options.get('cvs_subdirs', 0) - tag = options.get('cvs_dir_tag') dirpath = self._getpath(path_parts) alltags = { # all the tags seen in the files of this dir @@ -67,7 +65,7 @@ class CCVSRepository(CVSRepository): path = _log_path(entry, dirpath, subdirs) if path: try: - rcsparse.Parser().parse(open(path, 'rb'), InfoSink(entry, tag, alltags)) + rcsparse.Parser().parse(open(path, 'rb'), InfoSink(entry, rev, alltags)) except IOError, e: entry.errors.append("rcsparse error: %s" % e) except RuntimeError, e: @@ -86,7 +84,9 @@ class CCVSRepository(CVSRepository): def itemlog(self, path_parts, rev, options): """see vclib.Repository.itemlog docstring - rev parameter can be a revision number, branch number or tag name + rev parameter can be a revision number, a branch number, a tag name, + or None. If None, will return information about all revisions, otherwise, + will only return information about the specified revision or branch. Option values returned by this implementation: diff --git a/lib/vclib/svn/__init__.py b/lib/vclib/svn/__init__.py index 7e7ed726..c9882c49 100644 --- a/lib/vclib/svn/__init__.py +++ b/lib/vclib/svn/__init__.py @@ -108,18 +108,27 @@ def date_from_rev(svnrepos, rev): return _datestr_to_date(datestr, svnrepos.pool) -def get_location(svnrepos, path, rev): +def get_location(svnrepos, path, rev, old_rev): try: results = repos.svn_repos_trace_node_locations(svnrepos.fs_ptr, path, - svnrepos.rev, [int(rev)], + rev, [old_rev], _allow_all, svnrepos.pool) - return results[int(rev)] - except: - raise vclib.ItemNotFound(filter(None, string.split(path, '/'))) - + except core.SubversionException, e: + if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: + raise vclib.ItemNotFound(path) + raise -def created_rev(svnrepos, full_name): - return fs.node_created_rev(svnrepos.fsroot, full_name, svnrepos.pool) + try: + old_path = results[old_rev] + except KeyError: + raise vclib.ItemNotFound(path) + + return _cleanup_path(old_path) + + +def created_rev(svnrepos, full_name, rev): + fsroot = svnrepos._getroot(rev) + return fs.node_created_rev(fsroot, full_name, svnrepos.pool) class Revision(vclib.Revision): @@ -171,11 +180,12 @@ class NodeHistory: self.histories[revision] = _cleanup_path(path) -def _get_history(svnrepos, full_name, options): +def _get_history(svnrepos, full_name, rev, options): + fsroot = svnrepos._getroot(rev) show_all_logs = options.get('svn_show_all_dir_logs', 0) if not show_all_logs: # See if the path is a file or directory. - kind = fs.check_path(svnrepos.fsroot, full_name, svnrepos.pool) + kind = fs.check_path(fsroot, full_name, svnrepos.pool) if kind is core.svn_node_file: show_all_logs = 1 @@ -187,7 +197,7 @@ def _get_history(svnrepos, full_name, options): # Get the history items for PATH. repos.svn_repos_history(svnrepos.fs_ptr, full_name, history.add_history, - 1, svnrepos.rev, cross_copies, svnrepos.pool) + 1, rev, cross_copies, svnrepos.pool) return history.histories @@ -244,15 +254,15 @@ class ChangedPathSet: return changes -def get_revision_info(svnrepos): - fsroot = fs.revision_root(svnrepos.fs_ptr, svnrepos.rev, svnrepos.pool) +def get_revision_info(svnrepos, rev): + fsroot = svnrepos._getroot(rev) # Get the changes for the revision cps = ChangedPathSet() editor = repos.ChangeCollector(svnrepos.fs_ptr, fsroot, svnrepos.pool, cps.add_change) e_ptr, e_baton = delta.make_editor(editor, svnrepos.pool) - repos.svn_repos_replay(svnrepos.fsroot, e_ptr, e_baton, svnrepos.pool) + repos.svn_repos_replay(fsroot, e_ptr, e_baton, svnrepos.pool) # Now get the revision property info props = editor.get_root_props() @@ -286,14 +296,12 @@ def _log_helper(svnrepos, rev, path, pool): def _fetch_log(svnrepos, full_name, which_rev, options, pool): revs = [] - if which_rev is not None: - if (which_rev < 0) or (which_rev > svnrepos.youngest): - raise vclib.InvalidRevision(which_rev) + if options.get('svn_latest_log', 0): rev = _log_helper(svnrepos, which_rev, full_name, pool) if rev: revs.append(rev) else: - history_set = _get_history(svnrepos, full_name, options) + history_set = _get_history(svnrepos, full_name, which_rev, options) history_revs = history_set.keys() history_revs.sort() history_revs.reverse() @@ -308,19 +316,20 @@ def _fetch_log(svnrepos, full_name, which_rev, options, pool): return revs -def _get_last_history_rev(svnrepos, path, pool): - history = fs.node_history(svnrepos.fsroot, path, pool) +def _get_last_history_rev(fsroot, path, pool): + history = fs.node_history(fsroot, path, pool) history = fs.history_prev(history, 0, pool) history_path, history_rev = fs.history_location(history, pool); return history_rev -def get_logs(svnrepos, full_name, files): +def get_logs(svnrepos, full_name, rev, files): + fsroot = svnrepos._getroot(rev) subpool = core.svn_pool_create(svnrepos.pool) for file in files: core.svn_pool_clear(subpool) path = _fs_path_join(full_name, file.name) - rev = _get_last_history_rev(svnrepos, path, subpool) + rev = _get_last_history_rev(fsroot, path, subpool) datestr, author, msg = _fs_rev_props(svnrepos.fs_ptr, rev, subpool) date = _datestr_to_date(datestr, subpool) file.rev = str(rev) @@ -328,7 +337,7 @@ def get_logs(svnrepos, full_name, files): file.author = author file.log = msg if file.kind == vclib.FILE: - file.size = fs.file_length(svnrepos.fsroot, path, subpool) + file.size = fs.file_length(fsroot, path, subpool) core.svn_pool_destroy(subpool) @@ -337,8 +346,8 @@ def get_youngest_revision(svnrepos): def do_diff(svnrepos, path1, rev1, path2, rev2, diffoptions): - root1 = fs.revision_root(svnrepos.fs_ptr, rev1, svnrepos.pool) - root2 = fs.revision_root(svnrepos.fs_ptr, rev2, svnrepos.pool) + root1 = svnrepos._getroot(rev1) + root2 = svnrepos._getroot(rev2) date1 = date_from_rev(svnrepos, rev1) date2 = date_from_rev(svnrepos, rev2) @@ -475,7 +484,7 @@ class BlameSequencingError(Exception): class SubversionRepository(vclib.Repository): - def __init__(self, name, rootpath, svn_path, rev=None): + def __init__(self, name, rootpath, svn_path): if not os.path.isdir(rootpath): raise vclib.ReposNotFound(name) @@ -484,7 +493,6 @@ class SubversionRepository(vclib.Repository): self.apr_init = 0 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 @@ -514,11 +522,7 @@ class SubversionRepository(vclib.Repository): self.repos = repos.svn_repos_open(rootpath, self.pool) self.fs_ptr = repos.svn_repos_fs(self.repos) self.youngest = fs.youngest_rev(self.fs_ptr, self.pool) - if self.rev is None: - self.rev = self.youngest - if (self.rev < 0) or (self.rev > self.youngest): - raise vclib.InvalidRevision(self.rev) - self.fsroot = fs.revision_root(self.fs_ptr, self.rev, self.pool) + self._fsroots = {} def __del__(self): self._close() @@ -533,10 +537,11 @@ class SubversionRepository(vclib.Repository): def _scratch_clear(self): core.svn_pool_clear(self.scratch_pool) - - def itemtype(self, path_parts): + + def itemtype(self, path_parts, rev): + rev = self._getrev(rev) basepath = self._getpath(path_parts) - kind = fs.check_path(self.fsroot, basepath, self.scratch_pool) + kind = fs.check_path(self._getroot(rev), basepath, self.scratch_pool) self._scratch_clear() if kind == core.svn_node_dir: return vclib.DIR @@ -544,20 +549,23 @@ class SubversionRepository(vclib.Repository): return vclib.FILE raise vclib.ItemNotFound(path_parts) - def openfile(self, path_parts, rev=None): - assert rev is None or int(rev) == self.rev + def openfile(self, path_parts, rev): path = self._getpath(path_parts) - revision = str(_get_last_history_rev(self, path, self.scratch_pool)) + rev = self._getrev(rev) + fsroot = self._getroot(rev) + revision = str(_get_last_history_rev(fsroot, path, self.scratch_pool)) self._scratch_clear() - fp = FileContentsPipe(self.fsroot, path, self.pool) + fp = FileContentsPipe(fsroot, path, self.pool) return fp, revision - def listdir(self, path_parts, options): + def listdir(self, path_parts, rev, options): basepath = self._getpath(path_parts) - if self.itemtype(path_parts) != vclib.DIR: + if self.itemtype(path_parts, rev) != vclib.DIR: raise vclib.Error("Path '%s' is not a directory." % basepath) - dirents = fs.dir_entries(self.fsroot, basepath, self.scratch_pool) + rev = self._getrev(rev) + fsroot = self._getroot(rev) + dirents = fs.dir_entries(fsroot, basepath, self.scratch_pool) entries = [ ] for entry in dirents.values(): if entry.kind == core.svn_node_dir: @@ -568,19 +576,30 @@ class SubversionRepository(vclib.Repository): self._scratch_clear() return entries - def dirlogs(self, path_parts, entries, options): - get_logs(self, self._getpath(path_parts), entries) + def dirlogs(self, path_parts, rev, entries, options): + get_logs(self, self._getpath(path_parts), self._getrev(rev), entries) def itemlog(self, path_parts, rev, options): - full_name = self._getpath(path_parts) + """see vclib.Repository.itemlog docstring - if rev is not None: - try: - rev = int(rev) - except ValueError: - vclib.InvalidRevision(rev) + Option values recognized by this implementation - revs = _fetch_log(self, full_name, rev, options, self.scratch_pool) + svn_show_all_dir_logs + boolean, default false. if set for a directory path, will include + revisions where files underneath the directory have changed + + svn_cross_copies + boolean, default false. if set for a path created by a copy, will + include revisions from before the copy + + svn_latest_log + boolean, default false. if set will return only newest single log + entry + """ + path = self._getpath(path_parts) + rev = self._getrev(rev) + + revs = _fetch_log(self, path, rev, options, self.scratch_pool) self._scratch_clear() revs.sort() @@ -591,11 +610,11 @@ class SubversionRepository(vclib.Repository): return revs - def annotate(self, path_parts, rev=None): - if not rev: - rev = self.rev + def annotate(self, path_parts, rev): path = self._getpath(path_parts) - revision = str(_get_last_history_rev(self, path, self.scratch_pool)) + rev = self._getrev(rev) + fsroot = self._getroot(rev) + revision = str(_get_last_history_rev(fsroot, path, self.scratch_pool)) ### Something's buggy in BlameSource, and the results are ### catastrophic for users of Mozilla and Firefox (it seems that @@ -615,13 +634,15 @@ class SubversionRepository(vclib.Repository): """ p1 = self._getpath(path_parts1) p2 = self._getpath(path_parts2) + r1 = self._getrev(rev1) + r2 = self._getrev(rev2) args = vclib._diff_args(type, options) # Need to keep a reference to the FileDiff object around long # enough to use. It destroys its underlying temporary files when # the class is destroyed. diffobj = options['diffobj'] = \ - do_diff(self, p1, int(rev1), p2, int(rev2), args) + do_diff(self, p1, r1, p2, r2, args) try: return diffobj.get_pipe() @@ -633,6 +654,23 @@ class SubversionRepository(vclib.Repository): def _getpath(self, path_parts): return string.join(path_parts, '/') + def _getrev(self, rev): + if rev is None or rev == 'HEAD': + return self.youngest + try: + rev = int(rev) + except ValueError: + raise vclib.InvalidRevision(rev) + if (rev < 0) or (rev > self.youngest): + raise vclib.InvalidRevision(rev) + return rev + + def _getroot(self, rev): + try: + return self._fsroots[rev] + except KeyError: + r = self._fsroots[rev] = fs.revision_root(self.fs_ptr, rev, self.pool) + return r class _item: def __init__(self, **kw): diff --git a/lib/vclib/svn_ra/__init__.py b/lib/vclib/svn_ra/__init__.py index f66ebee6..c23e0379 100644 --- a/lib/vclib/svn_ra/__init__.py +++ b/lib/vclib/svn_ra/__init__.py @@ -25,7 +25,7 @@ import re import tempfile import popen2 import time -from vclib.svn import Revision, ChangedPath, _datestr_to_date, _compare_paths +from vclib.svn import Revision, ChangedPath, _datestr_to_date, _compare_paths, _cleanup_path from svn import core, delta, client, wc, ra @@ -35,15 +35,10 @@ if (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_PATCH) < (1, 3, 0): def _rev2optrev(rev): + assert type(rev) is int rt = core.svn_opt_revision_t() - if rev is not None: - if str(rev) == 'HEAD': - rt.kind = core.svn_opt_revision_head - else: - rt.kind = core.svn_opt_revision_number - rt.value.number = rev - else: - rt.kind = core.svn_opt_revision_unspecified + rt.kind = core.svn_opt_revision_number + rt.value.number = rev return rt @@ -53,21 +48,29 @@ def date_from_rev(svnrepos, rev): return _datestr_to_date(datestr, svnrepos.pool) -def get_location(svnrepos, path, rev): +def get_location(svnrepos, path, rev, old_rev): try: - results = ra.get_locations(svnrepos.ra_session, path, svnrepos.rev, - [int(rev)], svnrepos.pool) - return results[int(rev)] - except: - raise vclib.ItemNotFound(filter(None, string.split(path, '/'))) + results = ra.get_locations(svnrepos.ra_session, path, rev, + [old_rev], svnrepos.pool) + except core.SubversionException, e: + if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: + raise vclib.ItemNotFound(path) + raise + + try: + old_path = results[old_rev] + except KeyError: + raise vclib.ItemNotFound(path) + + return _cleanup_path(old_path) -def created_rev(svnrepos, full_name): - kind = ra.svn_ra_check_path(svnrepos.ra_session, full_name, svnrepos.rev, +def created_rev(svnrepos, full_name, rev): + kind = ra.svn_ra_check_path(svnrepos.ra_session, full_name, rev, svnrepos.pool) if kind == core.svn_node_dir: props = ra.svn_ra_get_dir(svnrepos.ra_session, full_name, - svnrepos.rev, svnrepos.pool) + rev, svnrepos.pool) return int(props[core.SVN_PROP_ENTRY_COMMITTED_REV]) return core.SVN_INVALID_REVNUM @@ -122,9 +125,9 @@ def _get_rev_details(svnrepos, rev, pool): return lhc.get_history() -def get_revision_info(svnrepos): +def get_revision_info(svnrepos, rev): rev, author, date, log, changes = \ - _get_rev_details(svnrepos, svnrepos.rev, svnrepos.pool) + _get_rev_details(svnrepos, rev, svnrepos.pool) return _datestr_to_date(date, svnrepos.pool), author, log, changes @@ -166,9 +169,8 @@ class LogCollector: self.path = this_path -def get_logs(svnrepos, full_name, files): - parts = filter(None, string.split(full_name, '/')) - dirents = svnrepos.get_dirents(parts, svnrepos.rev) +def get_logs(svnrepos, full_name, rev, files): + dirents = svnrepos._get_dirents(full_name, rev) subpool = core.svn_pool_create(svnrepos.pool) rev_info_cache = { } for file in files: @@ -342,7 +344,7 @@ class SelfCleanFP: class SubversionRepository(vclib.Repository): - def __init__(self, name, rootpath, rev=None): + def __init__(self, name, rootpath): # Init the client app core.apr_initialize() pool = core.svn_pool_create(None) @@ -370,22 +372,18 @@ class SubversionRepository(vclib.Repository): self.ra_session = ra.svn_ra_open(self.rootpath, ra_callbacks, None, ctx.config, pool) self.youngest = ra.svn_ra_get_latest_revnum(self.ra_session, pool) - if rev is not None: - self.rev = rev - if self.rev > self.youngest: - raise vclib.InvalidRevision(self.rev) - else: - self.rev = self.youngest self._dirent_cache = { } def __del__(self): core.svn_pool_destroy(self.pool) core.apr_terminate() - def itemtype(self, path_parts): + def itemtype(self, path_parts, rev): + path = self._getpath(path_parts[:-1]) + rev = self._getrev(rev) if not len(path_parts): return vclib.DIR - dirents = self.get_dirents(path_parts[:-1], self.rev) + dirents = self._get_dirents(path, rev) try: entry = dirents[path_parts[-1]] if entry.kind == core.svn_node_dir: @@ -395,11 +393,8 @@ class SubversionRepository(vclib.Repository): except KeyError: raise vclib.ItemNotFound(path_parts) - def openfile(self, path_parts, rev=None): - if rev is None: - rev = self.rev - else: - rev = int(rev) + def openfile(self, path_parts, rev): + rev = self._getrev(rev) url = self.rootpath if len(path_parts): url = self.rootpath + '/' + self._getpath(path_parts) @@ -411,9 +406,11 @@ class SubversionRepository(vclib.Repository): core.svn_stream_close(stream) return SelfCleanFP(tmp_file), rev - def listdir(self, path_parts, options): + def listdir(self, path_parts, rev, options): + path = self._getpath(path_parts) + rev = self._getrev(rev) entries = [ ] - dirents = self.get_dirents(path_parts, self.rev) + dirents = self._get_dirents(path, rev) for name in dirents.keys(): entry = dirents[name] if entry.kind == core.svn_node_dir: @@ -423,17 +420,12 @@ class SubversionRepository(vclib.Repository): entries.append(vclib.DirEntry(name, kind)) return entries - def dirlogs(self, path_parts, entries, options): - get_logs(self, self._getpath(path_parts), entries) + def dirlogs(self, path_parts, rev, entries, options): + get_logs(self, self._getpath(path_parts), self._getrev(rev), entries) def itemlog(self, path_parts, rev, options): full_name = self._getpath(path_parts) - - if rev is not None: - try: - rev = int(rev) - except ValueError: - vclib.InvalidRevision(rev) + rev = self._getrev(rev) # It's okay if we're told to not show all logs on a file -- all # the revisions should match correctly anyway. @@ -443,7 +435,7 @@ class SubversionRepository(vclib.Repository): dir_url = dir_url + '/' + full_name cross_copies = options.get('svn_cross_copies', 0) - client.svn_client_log([dir_url], _rev2optrev(self.rev), _rev2optrev(1), + client.svn_client_log([dir_url], _rev2optrev(rev), _rev2optrev(1), 1, not cross_copies, lc.add_log, self.ctx, self.pool) revs = lc.logs @@ -455,13 +447,10 @@ class SubversionRepository(vclib.Repository): return revs - def annotate(self, path_parts, rev=None): + def annotate(self, path_parts, rev): path = self._getpath(path_parts) + rev = self._getrev(rev) url = self.rootpath + (path and '/' + path) - if rev is None: - rev = self.rev - else: - rev = int(rev) blame_data = [] @@ -487,13 +476,15 @@ class SubversionRepository(vclib.Repository): """ p1 = self._getpath(path_parts1) p2 = self._getpath(path_parts2) + r1 = self._getrev(rev1) + r2 = self._getrev(rev2) args = vclib._diff_args(type, options) # Need to keep a reference to the FileDiff object around long # enough to use. It destroys its underlying temporary files when # the class is destroyed. diffobj = options['diffobj'] = \ - do_diff(self, p1, int(rev1), p2, int(rev2), args) + do_diff(self, p1, r1, p2, r2, args) try: return diffobj.get_pipe() @@ -505,13 +496,22 @@ class SubversionRepository(vclib.Repository): def _getpath(self, path_parts): return string.join(path_parts, '/') - def get_dirents(self, path_parts, rev): - if len(path_parts): - path = self._getpath(path_parts) + def _getrev(self, rev): + if rev is None or rev == 'HEAD': + return self.youngest + try: + rev = int(rev) + except ValueError: + raise vclib.InvalidRevision(rev) + if (rev < 0) or (rev > self.youngest): + raise vclib.InvalidRevision(rev) + return rev + + def _get_dirents(self, path, rev): + if path: key = str(rev) + '/' + path dir_url = self.rootpath + '/' + path else: - path = None key = str(rev) dir_url = self.rootpath dirents = self._dirent_cache.get(key) diff --git a/lib/viewcvs.py b/lib/viewcvs.py index 24275e67..542f2719 100644 --- a/lib/viewcvs.py +++ b/lib/viewcvs.py @@ -248,25 +248,19 @@ 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) + self.rootpath) else: self.rootpath = os.path.normpath(self.rootpath) import vclib.svn self.repos = vclib.svn.SubversionRepository(self.rootname, self.rootpath, - cfg.general.svn_path, - rev) + cfg.general.svn_path) self.roottype = 'svn' except vclib.ReposNotFound: raise debug.ViewCVSException( @@ -283,18 +277,20 @@ class Request: % self.rootname, "404 Repository not found") # Make sure path exists - self.pathtype = _repos_pathtype(self.repos, self.path_parts) + rev = self.query_dict.get('rev') + self.pathtype = _repos_pathtype(self.repos, self.path_parts, rev) if self.pathtype is None: # path doesn't exist, try stripping known fake suffixes - result = _strip_suffix('.diff', self.where, self.path_parts, \ - vclib.FILE, self.repos, view_diff) or \ - _strip_suffix('.tar.gz', self.where, self.path_parts, \ - vclib.DIR, self.repos, download_tarball) or \ - _strip_suffix('root.tar.gz', self.where, self.path_parts, \ - vclib.DIR, self.repos, download_tarball) + result = _strip_suffix('.diff', path_parts, rev, vclib.FILE, \ + self.repos, view_diff) or \ + _strip_suffix('.tar.gz', path_parts, rev, vclib.DIR, \ + self.repos, download_tarball) or \ + _strip_suffix('root.tar.gz', path_parts, rev, vclib.DIR, \ + self.repos, download_tarball) if result: - self.where, self.path_parts, self.pathtype, self.view_func = result + self.path_parts, self.pathtype, self.view_func = result + self.where = _path_join(path_parts) else: raise debug.ViewCVSException('%s: unknown location' % self.where, '404 Not Found') @@ -647,30 +643,27 @@ _legal_params = { def _path_join(path_parts): return string.join(path_parts, '/') -def _strip_suffix(suffix, where, path_parts, pathtype, repos, view_func): +def _strip_suffix(suffix, path_parts, rev, pathtype, repos, view_func): """strip the suffix from a repository path if the resulting path is of the specified type, otherwise return None""" l = len(suffix) - if where[-l:] == suffix: + if path_parts[-1][-l:] == suffix: path_parts = path_parts[:] if len(path_parts[-1]) == l: del path_parts[-1] else: path_parts[-1] = path_parts[-1][:-l] - t = _repos_pathtype(repos, path_parts) + t = _repos_pathtype(repos, path_parts, rev) if pathtype == t: - return where[:-l], path_parts, t, view_func + return path_parts, t, view_func return None -def _repos_pathtype(repos, path_parts): - """return the type of a repository path, or None if the path - does not exist""" - type = None +def _repos_pathtype(repos, path_parts, rev): + """return the type of a repository path, or None if the path doesn't exist""" try: - type = repos.itemtype(path_parts) + return repos.itemtype(path_parts, rev) except vclib.ItemNotFound: - pass - return type + return None def check_freshness(request, mtime=None, etag=None, weak=0): # See if we are supposed to disable etags (for debugging, usually) @@ -757,8 +750,8 @@ def nav_path(request): # set convenient "rev" and "is_dir" values rev = None - if request.roottype == "svn" and request.query_dict.get('rev'): - rev = request.repos.rev + if request.roottype == 'svn': + rev = request.query_dict.get('rev') is_dir = request.pathtype == vclib.DIR # add root item @@ -1301,7 +1294,7 @@ def view_markup(request): }) if cfg.options.show_log_in_markup: - options = {} + options = {'svn_latest_log': 1} revs = request.repos.itemlog(request.path_parts, revision, options) entry = revs[-1] @@ -1429,34 +1422,38 @@ def view_roots(request): generate_page(request, "roots", data) def view_directory(request): + if request.roottype == 'svn': + rev = request.query_dict.get('rev') + elif request.roottype == 'cvs': + rev = view_tag = request.query_dict.get('only_with_tag') + # For Subversion repositories, the revision acts as a weak validator for # the directory listing (to take into account template changes or # revision property changes). if request.roottype == 'svn': - revision = str(vclib.svn.created_rev(request.repos, request.where)) + revision = str(vclib.svn.created_rev(request.repos, request.where, + request.repos._getrev(rev))) if check_freshness(request, None, revision, weak=1): return # List current directory options = {} if request.roottype == 'cvs': - view_tag = request.query_dict.get('only_with_tag') hideattic = int(request.query_dict.get('hideattic', cfg.options.hide_attic)) options["cvs_subdirs"] = (cfg.options.show_subdir_lastmod and cfg.options.show_logs) - options["cvs_dir_tag"] = view_tag - file_data = request.repos.listdir(request.path_parts, options) + file_data = request.repos.listdir(request.path_parts, rev, options) # Filter file list if a regex is specified search_re = request.query_dict.get('search', '') if cfg.options.use_re_search and search_re: - file_data = search_files(request.repos, request.path_parts, + file_data = search_files(request.repos, request.path_parts, rev, file_data, search_re) # Retrieve log messages, authors, revision numbers, timestamps - request.repos.dirlogs(request.path_parts, file_data, options) + request.repos.dirlogs(request.path_parts, rev, file_data, options) # sort with directories first, and using the "sortby" criteria sortby = request.query_dict.get('sortby', cfg.options.sort_by) or 'file' @@ -1473,7 +1470,7 @@ def view_directory(request): where = request.where where_prefix = where and where + '/' if request.roottype == 'svn': - dir_params = {'rev': request.query_dict.get('rev')} + dir_params = {'rev': rev} else: dir_params = {} @@ -1653,10 +1650,7 @@ def view_directory(request): data['youngest_rev_href'] = request.get_url(view_func=view_revision, params={}, escape=1) - if request.query_dict.has_key('rev'): - data['rev'] = request.query_dict['rev'] - else: - data['rev'] = str(request.repos.rev) + data['rev'] = str(request.repos._getrev(rev)) url, params = request.get_link(params={'rev': None}) data['jump_rev_action'] = urllib.quote(url, _URL_SAFE_CHARS) data['jump_rev_hidden_values'] = prepare_hidden_values(params) @@ -1664,8 +1658,8 @@ def view_directory(request): if is_query_supported(request): params = {} - if options.has_key('cvs_dir_tag'): - params['branch'] = options['cvs_dir_tag'] + if request.roottype == 'cvs' and view_tag: + params['branch'] = view_tag data['queryform_href'] = request.get_url(view_func=view_queryform, params=params, escape=1) @@ -1749,7 +1743,7 @@ def view_log(request): if request.roottype == 'cvs': rev = view_tag else: - rev = None + rev = request.query_dict.get('rev') show_revs = request.repos.itemlog(request.path_parts, rev, options) if logsort == 'date': @@ -1906,7 +1900,7 @@ def view_log(request): 'r2': entry.rev, 'diff_format': None}, escape=1) - # moves aren't handled here but they are only supported by CVS right now. + if entry.next_main: entry.diff_to_main_href = \ request.get_url(view_func=view_diff, @@ -2144,7 +2138,7 @@ def view_cvsgraph(request): request.server.header() generate_page(request, "graph", data) -def search_files(repos, path_parts, files, search_re): +def search_files(repos, path_parts, rev, files, search_re): """ Search files in a directory for a regular expression. Does a check-out of each file in the directory. Only checks for @@ -2179,9 +2173,8 @@ def search_files(repos, path_parts, files, search_re): # Only text files at this point - # process_checkout will checkout the head version out of the repository # Assign contents of checked out file to fp. - fp = repos.openfile(path_parts + [file.name])[0] + fp = repos.openfile(path_parts + [file.name], rev)[0] # Read in each line, use re.search to search line. # If successful, add file to new_file_list and break. @@ -2459,13 +2452,15 @@ def diff_parse_headers(fp, diff_type, rev1, rev2, sym1=None, sym2=None): return date1, date2, flag, string.join(header_lines, '') -def _get_diff_path_parts(request, query_key, rev): +def _get_diff_path_parts(request, query_key, rev, base_rev): if request.query_dict.has_key(query_key): parts = _path_parts(request.query_dict[query_key]) elif request.roottype == 'svn': try: - parts = _path_parts(vclib.svn.get_location(request.repos, - request.where, rev)) + repos = request.repos + parts = _path_parts(vclib.svn.get_location(repos, request.where, + repos._getrev(base_rev), + repos._getrev(rev))) except vclib.InvalidRevision: raise debug.ViewCVSException('Invalid path(s) or revision(s) passed ' 'to diff', '400 Bad Request') @@ -2482,6 +2477,7 @@ def setup_diff(request): rev1 = r1 = query_dict['r1'] rev2 = r2 = query_dict['r2'] + base_rev = query_dict.get('rev') sym1 = sym2 = None # hack on the diff revisions @@ -2512,8 +2508,8 @@ def setup_diff(request): rev2 = r2[:idx] sym2 = r2[idx+1:] - p1 = _get_diff_path_parts(request, 'p1', rev1) - p2 = _get_diff_path_parts(request, 'p2', rev2) + p1 = _get_diff_path_parts(request, 'p1', rev1, base_rev) + p2 = _get_diff_path_parts(request, 'p2', rev2, base_rev) try: if revcmp(rev1, rev2) > 0: @@ -2523,7 +2519,6 @@ def setup_diff(request): except ValueError: raise debug.ViewCVSException('Invalid revision(s) passed to diff', '400 Bad Request') - return p1, p2, rev1, rev2, sym1, sym2 @@ -2693,11 +2688,11 @@ def generate_tarball_header(out, name, size=0, mode=None, mtime=0, out.write(block) -def generate_tarball(out, request, options, reldir, stack): +def generate_tarball(out, request, rev, reldir, stack): # get directory info from repository rep_path = request.path_parts + reldir - entries = request.repos.listdir(rep_path, options) - request.repos.dirlogs(rep_path, entries, options) + entries = request.repos.listdir(rep_path, rev, {}) + request.repos.dirlogs(rep_path, rev, entries, {}) entries.sort(lambda a, b: cmp(a.name, b.name)) # figure out corresponding path in tar file. everything gets put underneath @@ -2764,7 +2759,7 @@ def generate_tarball(out, request, options, reldir, stack): or (cvs and cfg.options.hide_cvsroot and file.name == 'CVSROOT')): continue - generate_tarball(out, request, options, reldir + [file.name], stack) + generate_tarball(out, request, rev, reldir + [file.name], stack) # pop directory (if it's being pruned. otherwise stack is already empty) del stack[-1:] @@ -2774,10 +2769,10 @@ def download_tarball(request): raise debug.ViewCVSException('Tarball generation is disabled', '403 Forbidden') - options = {} - if request.roottype == 'cvs': - tag = request.query_dict.get('only_with_tag') - options['cvs_dir_tag'] = tag + if request.roottype == 'svn': + rev = request.query_dict.get('rev') + elif request.roottype == 'cvs': + rev = request.query_dict.get('only_with_tag') ### look for GZIP binary @@ -2785,7 +2780,7 @@ def download_tarball(request): sys.stdout.flush() fp = popen.pipe_cmds([('gzip', '-c', '-n')]) - generate_tarball(fp, request, options, [], []) + generate_tarball(fp, request, rev, [], []) fp.write('\0' * 1024) fp.close() @@ -2797,9 +2792,9 @@ def view_revision(request): data = common_template_data(request) query_dict = request.query_dict - date, author, msg, changes = vclib.svn.get_revision_info(request.repos) + rev = request.repos._getrev(query_dict.get('rev')) + date, author, msg, changes = vclib.svn.get_revision_info(request.repos, rev) date_str = make_time_string(date) - rev = request.repos.rev # The revision number acts as a weak validator. if check_freshness(request, None, str(rev), weak=1): diff --git a/tools/cvsdbadmin b/tools/cvsdbadmin index 8aa21176..553aaf35 100755 --- a/tools/cvsdbadmin +++ b/tools/cvsdbadmin @@ -71,7 +71,7 @@ def UpdateFile(db, repository, path, update): def RecurseUpdate(db, repository, directory, update): - for entry in repository.listdir(directory, {}): + for entry in repository.listdir(directory, None, {}): path = directory + [entry.name] if entry.errors: